From 76c9b05404d6de95f2157ef6220ca8d908f73d86 Mon Sep 17 00:00:00 2001 From: linju Date: Fri, 16 Sep 2022 20:41:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 16 +- README.md | 348 +++++- changelog.md | 2 +- common/appInit.js | 87 +- package.json | 2 +- pages.json | 21 +- pages/grid/grid.vue | 3 +- uni-starter.config.js | 4 +- uni_modules/uni-badge/changelog.md | 54 +- .../components/uni-badge/uni-badge.vue | 20 +- uni_modules/uni-badge/package.json | 171 ++- uni_modules/uni-data-checkbox/changelog.md | 4 + .../uni-data-checkbox/uni-data-checkbox.vue | 138 +-- uni_modules/uni-data-checkbox/package.json | 11 +- uni_modules/uni-easyinput/changelog.md | 10 + .../uni-easyinput/uni-easyinput.vue | 324 ++++-- uni_modules/uni-easyinput/package.json | 2 +- uni_modules/uni-forms/changelog.md | 30 + .../uni-forms-item/uni-forms-item.vue | 1030 +++++++++-------- .../components/uni-forms/uni-forms.vue | 463 ++++---- uni_modules/uni-forms/package.json | 14 +- uni_modules/uni-id-pages/changelog.md | 7 + uni_modules/uni-id-pages/common/common.js | 5 +- .../uni-id-pages/common/login-page.mixin.js | 48 +- .../uni-id-pages/common/loginSuccess.js | 40 +- .../components/cloud-image/cloud-image.vue | 4 +- uni_modules/uni-id-pages/config.js | 19 +- uni_modules/uni-id-pages/package.json | 4 +- .../pages/login/login-withpwd.vue | 307 ++--- .../pages/register/register-admin.vue | 179 +++ .../uni-id-pages/pages/userinfo/userinfo.vue | 4 +- .../uni-id-co/common/constants.js | 13 +- .../cloudfunctions/uni-id-co/index.obj.js | 38 +- .../uni-id-co/module/account/index.js | 1 + .../module/account/reset-pwd-by-email.js | 119 ++ .../uni-id-co/module/register/index.js | 3 +- .../module/register/register-admin.js | 3 +- .../module/register/register-user-by-email.js | 87 ++ .../module/verify/send-email-code.js | 52 +- .../cloudfunctions/uni-id-co/package.json | 4 +- .../database/opendb-device.schema.json | 2 +- .../database/uni-id-users.schema.json | 2 +- .../cloudfunctions/common/uni-id/LICENSE.md | 201 ---- .../uni-open-bridge-common/changelog.md | 4 + .../uni-open-bridge-common/package.json | 2 +- .../uni-open-bridge-common/bridge-error.js | 8 +- .../common/uni-open-bridge-common/config.js | 188 +-- .../common/uni-open-bridge-common/consts.js | 50 +- .../common/uni-open-bridge-common/index.js | 436 +++---- .../common/uni-open-bridge-common/storage.js | 232 ++-- .../uni-open-bridge-common/uni-cloud-cache.js | 646 +++++------ .../uni-open-bridge-common/validator.js | 60 +- .../uni-open-bridge-common/weixin-server.js | 320 ++--- .../database/opendb-open-data.schema.json | 19 + uni_modules/uni-upgrade-center/changelog.md | 49 + .../js_sdk/validator/opendb-app-list.js | 44 + .../js_sdk/validator/opendb-app-versions.js | 151 +++ uni_modules/uni-upgrade-center/menu.json | 10 + uni_modules/uni-upgrade-center/package.json | 94 ++ .../pages/components/show-info.vue | 56 + .../pages/mixin/version_add_detail_mixin.js | 170 +++ uni_modules/uni-upgrade-center/pages/utils.js | 26 + .../uni-upgrade-center/pages/version/add.vue | 418 +++++++ .../pages/version/detail.vue | 316 +++++ .../uni-upgrade-center/pages/version/list.vue | 365 ++++++ uni_modules/uni-upgrade-center/readme.md | 225 ++++ .../cloudfunctions/upgrade-center/index.js | 19 + 67 files changed, 5295 insertions(+), 2509 deletions(-) create mode 100644 uni_modules/uni-id-pages/pages/register/register-admin.vue create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js delete mode 100644 uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md create mode 100644 uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json create mode 100644 uni_modules/uni-upgrade-center/changelog.md create mode 100644 uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-list.js create mode 100644 uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js create mode 100644 uni_modules/uni-upgrade-center/menu.json create mode 100644 uni_modules/uni-upgrade-center/package.json create mode 100644 uni_modules/uni-upgrade-center/pages/components/show-info.vue create mode 100644 uni_modules/uni-upgrade-center/pages/mixin/version_add_detail_mixin.js create mode 100644 uni_modules/uni-upgrade-center/pages/utils.js create mode 100644 uni_modules/uni-upgrade-center/pages/version/add.vue create mode 100644 uni_modules/uni-upgrade-center/pages/version/detail.vue create mode 100644 uni_modules/uni-upgrade-center/pages/version/list.vue create mode 100644 uni_modules/uni-upgrade-center/readme.md create mode 100644 uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center/index.js diff --git a/App.vue b/App.vue index 0682c7d7..bc02d158 100644 --- a/App.vue +++ b/App.vue @@ -31,7 +31,7 @@ // #ifdef APP-PLUS //idfa有需要的用户在应用首次启动时自己获取存储到storage中 - var idfa = ''; + /*var idfa = ''; var manager = plus.ios.invoke('ASIdentifierManager', 'sharedManager'); if(plus.ios.invoke(manager, 'isAdvertisingTrackingEnabled')){ var identifier = plus.ios.invoke(manager, 'advertisingIdentifier'); @@ -39,19 +39,7 @@ plus.ios.deleteObject(identifier); } plus.ios.deleteObject(manager); - console.log('idfa = '+idfa); - - //https://ask.dcloud.net.cn/article/36107 - /*if(~plus.storage.getItem('idfa')){ - plus.device.getInfo({//需要勾选IDFA - success:function(e){ - console.log('idfa = '+JSON.stringify(e.idfa)); - }, - fail:function(e){ - console.log('getDeviceInfo failed: '+JSON.stringify(e)); - } - }); - }*/ + console.log('idfa = '+idfa);*/ // #endif }, onShow: function() { diff --git a/README.md b/README.md index faccd69a..7242191a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,349 @@

文档已移至 uni-starter文档 -

\ No newline at end of file + + +> 以下为uni-starter v2的文档,新项目推荐直接使用。如果你的老项目使用的是uni-starter v1版,不想升级请查看:[uni-starter v1 文档](https://gitcode.net/dcloud/uni-starter/-/blob/v1/README.md) + +## 简介 +uni-starter是集成商用项目常见功能的、云端一体应用快速开发项目模版。 + +一个应用有很多通用的功能,比如登录注册、个人中心、设置、权限管理、拦截器、banner... uni-starter将这些功能都已经集成好。 + +直接在`HBuilderx`新建项目选择`uni-starter`模板,即可在此基础上快速开发自己的特色业务。 + +有了`uni-starter`,再加上`schema2code`生成前端页面,一个简单应用就可以快速完成。 + +如果说[uni-admin](https://uniapp.dcloud.io/uniCloud/admin)是管理端项目的基本项目模版,那么uni-starter则是用户端、尤其是移动端的基础项目模板。 + +`uni-starter` + `uni-admin` 提供了用户端和管理端的全套模版,开箱即用,应用开发从未如此简单快捷! + +演示项目:[https://uni-starter.dcloud.net.cn](https://uni-starter.dcloud.net.cn) + +扫码体验: + +下载地址:[https://ext.dcloud.net.cn/plugin?id=5057](https://ext.dcloud.net.cn/plugin?id=5057) + +GitCode 仓库:[https://gitcode.net/dcloud/uni-starter](https://gitcode.net/dcloud/uni-starter) + +## uni-starter集成包括: +1. 用户管理: + uni-starter的用户管理本质是集成了[uni-id-pages](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html),它包括众多功能: + - 登录注册(用户名密码登录、手机号验证码登录、APP一键登录、微信登录、Apple登录、微信小程序登录、手机微信扫码登录、微信公众号内登录) + - 修改密码、忘记密码、头像更换(集成图片裁剪)、昵称修改、积分查看、退出登录、账号注销 +2. 系统设置: + - 内置[App升级中心](https://uniapp.dcloud.net.cn/uniCloud/upgrade-center.html)(整包升级、wgt升级、强制升级,后台搭配uni-admin的升级中心插件管理) + - 推送开关(app)、清除缓存(app) + - 指纹解锁(app)、人脸解锁(app) + - 多语言切换 +3. 隐私权限:内置Android先弹出隐私协议对话框,然后再向用户申请设备权限 +4. 权限引导:当应用拒绝授权某些权限,但在后续使用中又需要这个权限;此时实现:引导用户可“一键跳转至系统设置”中开启。 + - 而不是报错让用户自己去找解决方案(更好的用户体验)。 + - 采用高内聚低耦合的设计结构,直接在应用启动时,应用拦截器中实现。免去在每个业务代码中处理这类问题,更优雅更方便。 + - 已实现项目:摄像头、相册、获取GPS定位、网络2/3/4/5G和Wi-Fi。你可以参考这些实现,处理更多该类场景的处理。uni-starter也会持续更新完善。 +5. 实用功能 + - 问题与反馈、关于、隐私政策、用户服务协议 + - banner(后台搭配uni-admin的banner插件管理) + - 新闻的搜索、列表、详情、分享。通过clientDB实现,开发者直接修改定义的表名等参数,即可轻松改为自己的业务 + - 可覆盖原生层的分享菜单 + - h5版在页面顶部(全局悬浮)引导用户点击下载App + - 营销裂变:点击“分销推荐”,生成带用户inviteCode参数的应用下载页(H5),一键分享到微信或微信朋友圈等。被邀请人打开下载页面点击下载,设备剪贴板的内容会被自动设置为邀请者的inviteCode。被邀请人下载app之后通过任何方式登录(含:注册并登录),uni-starter框架会自动获取设备剪切板中的inviteCode提交到服务端绑定关联关系。 +6. 更好的性能:首页采用nvue,fast编译模式,加快App端启动速度 +7. 内置拦截器: + - 页面路由拦截,配置需强制登录的页面;打开时自动检测`token`若无效就自动跳转到登录页 + - 调用云函数(callFunction)拦截器,自动携带必要参数、自动处理响应体。详见8.自动完成1-2 +8. 自动完成: + - 分析uniCloud.callFunction和clientDB操作的响应体,判断code执行对应的操作如跳转到登录页,自动续期token + - 操作注册/登录操作自动获取客户端设备:push_clientid、imei、oaid、idfa新增/更新到数据表uni-id-device + - 异常恢复处理:断网恢复后自动重连“因网络错误导致的”网络请求 + - 为迎合苹果App Store的规则,登录与分享功能项显示之前自动检测是否安装了对应客户端。比如:设备未安装微信则不显示微信快捷登录和微信分享选项 + +## 快速体验部署流程 +#### 1. 开通uniCloud +- 开通`uniCloud`:本项目是云端一体的,它的云端代码需要部署在uniCloud云服务空间里,需要开通uniCloud。在[https://unicloud.dcloud.net.cn/](https://unicloud.dcloud.net.cn/)登录,按云厂商要求进行实名认证。 +- 在uniCloud认证通过后,创建一个服务空间给本项目使用。选择阿里云或腾讯云均可,两种服务空间差异[详情](https://uniapp.dcloud.net.cn/uniCloud/price) + +#### 2. 运行云服务空间初始化向导 + + + + +## 功能模块介绍 +### 1.账户管理 +uni-starter 使用 `uni-id-pages`实现:登录注册账户管理相关功能 [详情查看](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html) +### 2.路由管理 +大多数应用,都会指定某些页面需要登录才能访问。以往开发者需要写不少代码。现在,只需在项目的pages.json内配置登录页路径、需要登录才能访问的页面等信息,uni-app框架的路由跳转,会自动在需要登录且客户端登录状态过期或未登录时跳转到登录页面。详情查看:[uniIdRouter文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#uni-id-router) +### 3.h5版在页面顶部引导用户`点击下载App` +把h5端用户引流到APP端,是一个非常实用的功能。相对于h5,APP端有更高的用户留存和更好的产品体验。 +uni-starter集成了这个功能,你只需直接在`项目根目录/uni-starter.config.js`的"h5"->"openApp"中配置相关内容,即可开启全局悬浮的下载引导。 +这也是一个演示开发者如何在h5端做全局悬浮块的例子。你也可以在`/common/openApp.js`中修改他的样式等代码等,注意它只支持原生js语法。 + +### 4.分享模块 +一个可覆盖原生层分享模块 +- 应用配置:`manifest.json` App模块配置 --> Share --> 勾选并配置你所需要的模块 +- 分享功能配置参数,随着应用的业务场景决定,在各场景调用的时候配置。参考uni-starter的`/pages/list/detail.vue`的`methods -> shareClick` +- 更多`uni-share`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4860) + +### 5.升级中心相关 +为了解决开发者维护多个 App 升级繁琐,重复逻辑过多,管理不便的问题,升级中心`uni-upgrade-center`应运而生。 +提供了简单、易用、统一的 App 管理、App 版本管理、安装包发布管理,升级检测更新管理。 +- 升级中心分为两个部分:`uni-upgrade-center` 前台检测更新和`uni-upgrade-center-Admin`后台管理系统。 +- `uni-upgrade-center`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4542) +- `uni-upgrade-center-Admin`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4470) + +### 6.意见反馈 +- 客户端[详情](https://ext.dcloud.net.cn/plugin?id=50) +- admin端[详情](https://ext.dcloud.net.cn/plugin?id=4992) + +### 7.指纹识别模块 +- `manifest.json` App模块配置 --> `Fingerprint`指纹识别 + +### 8.消息推送模块 +- `manifest.json` App模块配置 --> `push`消息推送 + +### 9.隐私政策弹框 +根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件: +- 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据 +- 应用不能强制要求用户授予权限,即不能“不给权限不让用” ++ 如不希望应用启动时申请“读写手机存储”和“访问设备信息”权限,请参考:[https://ask.dcloud.net.cn/article/36549](https://ask.dcloud.net.cn/article/36549) + +配置弹出“隐私协议和政策”打开项目的manifest.json文件,切换到“源码视图”项 +在`manifest.json` -> `app-plus` -> `privacy` 节点下添加 prompt节点 +```js +"privacy" : { + "prompt" : "template", + "template" : { + "title" : "服务协议和隐私政策", + "message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。
  你可阅读《服务协议》《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", + "buttonAccept" : "同意", + "buttonRefuse" : "暂不同意" + } +} +``` +- prompt + 字符串类型,必填,隐私政策提示框配置策略,可取值template、custom、none,默认值为none + + template + 使用原生提示框模板,可自定义标题、内容已经按钮上的文本 + + custom + 自定义隐私政策提示框,uni-app项目中推荐使用nvue页面进行自定义,5+ APP使用html页面进行自定义 + + none + 不弹出隐私政策提示框 +- template + json格式,可选,模板提示框上显示的内容 + + title + 模板提示框上的标题,默认为“服务协议和隐私政策” + + message + 模板提示框上的内容,richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。 + **注意:务必配置此提示内容,或参考上面示例内容并修改《服务协议》和《隐私政策》链接地址** + + buttonAccept + 模板提示框上接受按钮的文本,默认值为“我知道了” + + buttonRefuse + 模板提示框上拒绝按钮的文本,默认不显示此按钮 + + second + HBuilderX3.1.12+版本新增支持隐私提示框二次确认提示,用于配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框 + + title 二次确认提示框上的标题 + + message 二次确认提示框上的内容,支持richtext类型字符串 + + buttonAccept 二次确认提示框上接受按钮的文本 + + buttonRefuse 二次确认提示框上拒绝按钮的文本 + +> 更多Android平台隐私与政策提示框配置方法,[详情](https://ask.dcloud.net.cn/article/36937) + +##### 注意: +1. 最新的华为应用市场要求,隐私政策提示框上接受按钮的文本,必须为“同意”而不能是其他有歧义的文字。 +2. 配置后提交云端打包后生效。理论上绝大部分和`manifest.json`生效相关的配置均需要提交云打包后生效 + +### 10.云对象拦截器应用 +云对象拦截器文档[详情查看](https://uniapp.dcloud.net.cn/uniCloud/client-sdk.html#add-interceptor) + +1. 控制调试模式 +配置路径:`uni-starter/uni-starter.config.js` +云对象请求fail时,开启调试模式将以`showModal`的模式弹出真实错误信息。关闭调试模式,则以`showToast`的模式模糊提示(弹出系统错误请稍后再试!) + +2. 裂变营销功能原理 +当用户请求云对象`uni-id-co`的任何方式登录(含:注册并登录)功能时, +云对象拦截器逻辑内部:判断用户的剪切板是否包含`uniInvitationCode:`开头的邀请码,如果存在则在请求时带上此邀请码;实现裂变营销功能的用户关系绑定。 + +### 12.关于升级 +- 项目升级 +uni-starter遵循uni-app的插件模块化规范,即:[uni_modules](https://uniapp.dcloud.io/uni_modules) 。它是个项目类型的插件。在项目的根目录下有一个符合uni_modules规范的package.json文件,在这个文件右键-从插件市场更新即可更新该插件。 + +- 插件升级 +非项目类型的`uni_modules`插件在项目根目录下的`uni_modules`目录下。以插件ID为插件文件夹命名,在该目录右键也会看到“从插件市场更新”选项,点击即可更新该插件。 + +uni-starter内集成的uni-id-pages、uni-upgrade-center等插件都可以独立升级。 + +### 13.多语言国际化 +uni-starter支持多语言国际化,默认关闭,可以在`uni-starter.config.js`->`i18n`->`enable`中配置。 + +如果你启用了多语言国际化需要先阅读:[uni-app多语言国际化](https://uniapp.dcloud.io/collocation/i18n?id=%e6%a1%86%e6%9e%b6%e5%86%85%e7%bd%ae%e7%bb%84%e4%bb%b6%e5%92%8capi%e5%9b%bd%e9%99%85%e5%8c%96) + +### 14.微信登录自动获取头像 +当用户首次在微信小程序中通过微信登录应用。uni-starter将获取用户的微信头像,设置为当前账号头像。 + +**注意:** 保存头像的过程是:先将微信头像的图片下载,再上传到uniCloud云存储。而小程序平台要求在管理后台配置小程序应用的联网服务器域名,否则无法联网。请确认已正确配置download、uploadFile合法域名[详情查看](https://uniapp.dcloud.io/uniCloud/publish.html#useinmp) + +## initApp()做了什么 +1. 读取uni-starter.config并挂载到globalData的config下 +2. 读取应用版本号,并存到globalData下 +3. 检查是否有可更新的应用版本,决定是否启动在线更新版本 +4. 监听设备的网络变化并以uni.showToast APi的方式提醒用户 +5. 使用[拦截器](https://uniapp.dcloud.io/api/interceptor?id=addinterceptor) 实现:自动引导打开`选择图片`所需要的权限。当调用`uni.chooseImage`时检测到无权限则自动开启引导。并不是在每次调用接口时处理这类问题,你可以参考该例子做更多该类场景的处理。uni-starter也会持续完善。 + +## 配置文件 +uni-starter提供了`uni-starter.config.js`,可配置选择登录注册方式及优先级等,可指定该应用是否强制登录才能进入某个页面。配置项内容如下: +```js +module.exports = { + "h5": { + "url": "https://static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com", // 前端网页托管的域名 + // 在h5端全局悬浮引导用户下载app的功能 更多自定义要求在/common/openApp.js中修改 + "openApp": { + //点击悬浮下载栏后打开的网页链接 + "openUrl": 'https://sj.qq.com/myapp/detail.htm?apkName=com.tencent.android.qqdownloader&info=6646FD239A6EBA9E2DEE5DFC7E18D867', + //左侧显示的应用名称 + "appname": 'uni-starter', + //应用的图标 + "logo": './static/logo.png', + } + }, + "mp": { + "weixin": { + //微信小程序原始id,微信小程序分享时 + "id": "gh_132465798" + } + }, + //关于应用 + "about": { + //应用名称 + "appName": "uni-starter", + //应用logo + "logo": "/static/logo.png", + //公司名称 + "company": "数字天堂(北京)网络技术有限公司", + //口号 + "slogan": "为开发而生", + //应用的链接,用于分享到第三方平台和生成关于我们页的二维码 + "download": "https://m3w.cn/uniapp", + //应用版本号,用于非app端显示,app端自动获取 + "version":"1.0.0" + }, + //用于打开应用市场评分界面 + "marketId":{ + "ios":"", + "android":"" + }, + //配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称 + "i18n":{ + "enable":false //默认启用,国际化。如果你不想使用国际化相关功能,请改为false + } +} +``` + +## 目录结构@catalogue +
+
+uni-starter
+├─uniCloud-aliyun	
+│	├─cloudfunctions 云函数目录
+│	|	├─common 公共模块
+│	│	|	├─uni-config-center			uni-starter的服务端配置中心,项目所有云函数的配置在这里填写 详情
+│	│	|	|	├─index.js				config-center入口文件
+│	│	|	|	└─uni-id				uni-id模块配置目录
+│	│	|	|		├─config.json		uni-id对应的配置数据:微信登录、一键登录、短信验证码登录等key都在这里填写详情
+│	│	|	|		└─file.cert			uni-id依赖的配置文件,假如你使用微信发红包功能,需要的证书文件就是放到这里
+│	|	|	└───uni-id					uni-id用户体系 详情
+│	|	├─check-version					检查更新云函数 详情
+│	|	├─rewarded-video-ad-notify-url	签到插件广告回调 详情
+│	|	├─uni-analyse-searchhot			云端一体搜索模板依赖的云函数 详情
+│	|	├─uni-captcha-co				云端一体图形验证码组件云对象 详情
+│	|	├─uni-clientDB-actions			客户端直接操作数据库拦截逻辑 详情
+│	|	├─uni-open-bridge				统一接管微信等三方平台认证的开源库 详情
+│	|	├─upgrade-center				云端一体uni升级中心的云函数 详情
+│	|	└─uni-id-co						用户中心云函数,实现用户注册、修改密码、发送验证码、快捷登录(微信、短信、账户、一键登录) 详情
+│	└──database							云数据目录
+│		├─db_init.json					db_init.json初始化数据库文件,其中不再包含schema 详情
+│		├─opendb-app-versions.schema.json		应用版本,表结构文件
+│		├─opendb-banner.schema.json	        	横幅数据表,表结构文件
+│		├─opendb-feedback.schema.json	        意见反馈表,表结构文件
+│		├─opendb-news-articles.schema.json	    新闻文章表,表结构文件
+│		├─opendb-news-categories.schema.json	新闻分类表,表结构文件
+│		├─opendb-news-comments.schema.json		新闻评论表,表结构文件
+│		├─opendb-news-favorite.schema.json		新闻收藏表,表结构文件
+│		├─opendb-search-hot.schema.json			热门搜索表,表结构文件
+│		├─opendb-search-log.schema.json			搜索记录表,表结构文件
+│		├─opendb-verify-codes.schema.json		验证码表,表结构文件
+│		├─uni-id-log.schema.json	        	登录日志表,表结构文件
+│		├─uni-id-scores.schema.json	        	用户积分表,表结构文件
+│		└─uni-id-users.schema.json	        	用户表,表结构文件
+├─pages										业务页面文件存放的目录
+│	├─common						
+│	│	└─webview							webview目录
+│	│		└─webview.vue					webview页面	用于实现跨端的web页面浏览
+│	├─grid
+│	│	└─grid.vue	 						带宫格和banner的示例页面
+│	├─list
+│	│	├─list.vue	 						新闻列表
+│	│	├─search
+│	│	│	└─search						云端一体搜索插件
+│	│	└─detail.vue						新闻详情
+│	├─ucenter
+│	│	├─about								关于我们
+│	│	│	└─about
+│	│	├─login-page						登录模块详情参考
+│	│	├─read-news-log						新闻阅读记录
+│	│	│	└─read-news-log
+│	│	├─invite							带用户inviteCode参数的应用下载页
+│	│	│	└─invite
+│	│	├─settings						
+│	│	│	├─dc-push
+│	│	│	│	└─push.js					push权限操作SDK
+│	│	│	└─settings.vue					app设置
+│	│	├─userinfo							用户个人信息
+│	│	│	├─bind-mobile
+│	│	│	│	└─bind-mobile.vue			绑定手机号码
+│	│	│	├─limeClipper					图片裁剪插件,来源limeClipper @作者: 陌上华年
+│	│	│	│	├─images
+│	│	│	│	│	├─photo.svg
+│	│	│	│	│	└─rotate.svg
+│	│	│	│	├─index.css
+│	│	│	│	├─limeClipper.vue
+│	│	│	│	├─README.md
+│	│	│	│	└─utils.js
+│	│	│	├─main.js
+│	│	│	├─cropImage.vue		引用limeClipper的图片裁剪模块,为了方便二开可能会出现兼容`vue`与`nvue`,所以做成了`页面`而不是`组件`
+│	│	│	└─userinfo.vue
+│	|	└─ucenter.vue						用户中心
+│	|
+├─static	 							存放应用引用的本地静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
+├─uni_modules						存放uni_modules规范的插件。
+├─uni_modules_tools				uni_modules插件上传辅助脚本 详情。
+├─main.js							Vue初始化入口文件
+├─App.vue							应用配置,用来配置App全局样式以及监听 应用生命周期
+├─uni-starter.config				uni-starter的前端的配置文件,项目所有模块的配置在这里填写。详见该文件的代码注释。
+├─manifest.json	 				配置应用名称、appid、logo、版本等打包信息,详见
+└─pages.json						配置页面路由、导航条、选项卡等页面类信息,详见
+
+
+完整的uni-app目录结构[详情](https://uniapp.dcloud.io/frame?id=%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84) + +## 常见API示范 +1. 判断当前用户是否拥有某角色`uniIDHasRole` 演示页面:`/pages/grid/grid` [API文档详情](https://uniapp.dcloud.io/api/global?id=uniidhasrole) +2. 指纹解锁、人脸解锁 演示页面:`/pages/ucenter/settings/settings` [API文档详情](https://uniapp.dcloud.io/api/system/authentication) + +## 注意事项 +1. 真机运行需要制作自定义基座,制作后选择运行到自定义基座 +2. 苹果登录的图标,需要满足苹果应用市场的审核规范请勿随便修改;如需修改请先阅读:[Sign in with Apple Button](https://appleid.apple.com/signinwithapple/button) +3. 应用登录功能,默认不勾选同意隐私权限是响应安卓应用市场的规范;请勿修改该逻辑。 + +## FAQ:常见问题 +1. 提示“公共模块uni-id缺少配置信息”解决方案:在cloudfunctions右键‘上传所有云函数、公共模块及actions’之后,需要在cloudfunctions -> common -> uni-config-center 目录单独上传一次,右键‘上传公共模块’。 +2. 本项目代码可以商用,无需为DCloud付费。但不能把本项目的代码改造用于非uni-app和uniCloud的技术体系。即,不能将后台改成php、java等其他后台,这将违反使用许可协议。 + +## 相关案例 +[ + ![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-f184e7c3-1912-41b2-b81f-435d1b37c7b4/dd4c366f-6165-46c0-8500-5a679d7e5463.jpg) +](https://ext.dcloud.net.cn/search?q=uni-starter) +(点击跳转到案例列表) + + +## 第三方插件(感谢插件作者,排名不分前后): +1. 图片裁剪 [limeClipper](https://ext.dcloud.net.cn/plugin?id=3594) @作者: 陌上华年 +2. 二维码生成 [Sansnn-uQRCode](https://ext.dcloud.net.cn/plugin?id=1287) @作者: 3snn \ No newline at end of file diff --git a/changelog.md b/changelog.md index 52f753f6..bda0c331 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,5 @@ ## 2.0.0(2022-08-10) -应用`uni-id-pages`、`uniIdRouter` +- 重要:应用`uni-id-pages`、`uniIdRouter` ## 1.2.7(2022-08-10) - 修复微信小程序绑定手机号失败的问题 ## 1.2.6(2022-06-29) diff --git a/common/appInit.js b/common/appInit.js index 013a3ade..90dae494 100644 --- a/common/appInit.js +++ b/common/appInit.js @@ -4,7 +4,7 @@ import uniStarterConfig from '@/uni-starter.config.js'; import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update'; import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'; -// 实现,路由拦截。当应用无访问摄像头/相册权限,引导跳到设置界面 +// 实现,路由拦截。当应用无访问摄像头/相册权限,引导跳到设置界面 https://ext.dcloud.net.cn/plugin?id=5095 import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/js_sdk/main.js'; interceptorChooseImage() @@ -82,7 +82,7 @@ export default async function() { } }); }) - console.log(7897897897899,params); + // console.log(params); } console.log(params); }, @@ -93,12 +93,17 @@ export default async function() { }, fail(e){ + console.error(e); if (debug) { uni.showModal({ content: JSON.stringify(e), showCancel: false }); - console.error(e); + }else{ + uni.showToast({ + title: '系统错误请稍后再试', + icon:'error' + }); } } }) @@ -155,78 +160,4 @@ function initAppVersion() { }); // 检查更新 // #endif -} - -async function getDeviceInfo() { - let deviceInfo = { - "uuid": '', - "vendor": '', - "push_clientid": '', - "imei": '', - "oaid": '', - "idfa": '', - "model": '', - "platform": '', - } - const { - model, - platform, - } = uni.getSystemInfoSync(); - Object.assign(deviceInfo, { - model, - platform - }); - - // #ifdef APP-PLUS - const oaid = await new Promise((callBack, fail) => { - if (deviceInfo.platform == "android") { - plus.device.getOAID({ - success: function(e) { - callBack(e.oaid) - // console.log('getOAID success: '+JSON.stringify(e)); - }, - fail: function(e) { - callBack() - console.log('getOAID failed: ' + JSON.stringify(e)); - } - }); - } else { - callBack() - } - }), - { - imei, - uuid - } = await new Promise((callBack, fail) => { - plus.device.getInfo({ - success: function(e) { - callBack(e) - // console.log('getOAID success: '+JSON.stringify(e)); - }, - fail: function(e) { - callBack() - console.log('getOAID failed: ' + JSON.stringify(e)); - } - }); - }), - idfa = plus.storage.getItem('idfa') || '', //idfa有需要的用户在应用首次启动时自己获取存储到storage中 - vendor = plus.device.vendor; - try { - deviceInfo.push_clientid = plus.push.getClientInfo().clientid - } catch (e) { - uni.showModal({ - content: '获取推送标识失败。如果你的应用不需要推送功能,请注释掉本代码块', - showCancel: false, - confirmText: "好的" - }); - console.log(e) - } - Object.assign(deviceInfo, { - imei, - uuid, - idfa, - vendor - }); - // #endif - return deviceInfo -} +} \ No newline at end of file diff --git a/package.json b/package.json index b19828b0..a6ea39bd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "id": "uni-starter", "displayName": "uni-starter", - "version": "1.2.7", + "version": "2.0.0", "description": "云端一体应用快速开发基本项目模版", "keywords": [ "login", diff --git a/pages.json b/pages.json index 658c3418..b3e1f58d 100644 --- a/pages.json +++ b/pages.json @@ -176,7 +176,26 @@ "navigationBarTitleText": "修改密码" } } - ], + ,{ + "path": "uni_modules/uni-id-pages/pages/register/register-by-email", + "style": { + "navigationBarTitleText": "邮箱验证码注册" + } +} +,{ + "path": "uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email", + "style": { + "navigationBarTitleText": "通过邮箱重置密码" + } +} +,{ + "path": "uni_modules/uni-id-pages/pages/register/register-admin", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "注册管理员账号" + } +} +], "globalStyle": { // #ifdef H5 "h5": { diff --git a/pages/grid/grid.vue b/pages/grid/grid.vue index 61d26108..a7638068 100644 --- a/pages/grid/grid.vue +++ b/pages/grid/grid.vue @@ -20,8 +20,7 @@ - - + diff --git a/uni-starter.config.js b/uni-starter.config.js index ca6ad87b..7b3ed9e3 100644 --- a/uni-starter.config.js +++ b/uni-starter.config.js @@ -41,8 +41,8 @@ export default { }, //用于打开应用市场评分界面 "marketId":{ - "ios":"id1417078253", - "android":"123456" + "ios":"", + "android":"" }, //配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称 "i18n":{ diff --git a/uni_modules/uni-badge/changelog.md b/uni_modules/uni-badge/changelog.md index 1aecf782..56581c4e 100644 --- a/uni_modules/uni-badge/changelog.md +++ b/uni_modules/uni-badge/changelog.md @@ -1,29 +1,31 @@ +## 1.2.1(2022-09-05) +- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473) ## 1.2.0(2021-11-19) - 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) - 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge) -## 1.1.7(2021-11-08) -- 优化 升级ui -- 修改 size 属性默认值调整为 small -- 修改 type 属性,默认值调整为 error,info 替换 default -## 1.1.6(2021-09-22) -- 修复 在字节小程序上样式不生效的 bug -## 1.1.5(2021-07-30) -- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) -## 1.1.4(2021-07-29) -- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性 -## 1.1.3(2021-06-24) -- 优化 示例项目 -## 1.1.1(2021-05-12) -- 新增 组件示例地址 -## 1.1.0(2021-05-12) -- 新增 uni-badge 的 absolute 属性,支持定位 -- 新增 uni-badge 的 offset 属性,支持定位偏移 -- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点 -- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+ -- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式 -## 1.0.7(2021-05-07) -- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug -- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug -- 新增 uni-badge 属性 custom-style, 支持自定义样式 -## 1.0.6(2021-02-04) -- 调整为uni_modules目录规范 +## 1.1.7(2021-11-08) +- 优化 升级ui +- 修改 size 属性默认值调整为 small +- 修改 type 属性,默认值调整为 error,info 替换 default +## 1.1.6(2021-09-22) +- 修复 在字节小程序上样式不生效的 bug +## 1.1.5(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.4(2021-07-29) +- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性 +## 1.1.3(2021-06-24) +- 优化 示例项目 +## 1.1.1(2021-05-12) +- 新增 组件示例地址 +## 1.1.0(2021-05-12) +- 新增 uni-badge 的 absolute 属性,支持定位 +- 新增 uni-badge 的 offset 属性,支持定位偏移 +- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点 +- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+ +- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式 +## 1.0.7(2021-05-07) +- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug +- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug +- 新增 uni-badge 属性 custom-style, 支持自定义样式 +## 1.0.6(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-badge/components/uni-badge/uni-badge.vue b/uni_modules/uni-badge/components/uni-badge/uni-badge.vue index 5e11cc69..664dc370 100644 --- a/uni_modules/uni-badge/components/uni-badge/uni-badge.vue +++ b/uni_modules/uni-badge/components/uni-badge/uni-badge.vue @@ -1,7 +1,7 @@ @@ -21,11 +21,11 @@ * @value error 红色 * @property {String} inverted = [true|false] 是否无需背景颜色 * @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+ - * @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上 + * @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上 * @value rightTop 右上 * @value rightBottom 右下 * @value leftTop 左上 - * @value leftBottom 左下 + * @value leftBottom 左下 * @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px * @property {String} isDot = [true|false] 是否显示为一个小点 * @event {Function} click 点击 Badge 触发事件 @@ -130,16 +130,13 @@ const match = whiteList[this.absolute] return match ? match : whiteList['rightTop'] }, - badgeWidth() { - return { - width: `${this.width}px` - } - }, dotStyle() { if (!this.isDot) return {} return { width: '10px', + minWidth: '0', height: '10px', + padding: '0', borderRadius: '10px' } }, @@ -160,7 +157,7 @@ }; - + diff --git a/uni_modules/uni-badge/package.json b/uni_modules/uni-badge/package.json index 52bd1c93..7a34d9c3 100644 --- a/uni_modules/uni-badge/package.json +++ b/uni_modules/uni-badge/package.json @@ -1,88 +1,85 @@ -{ - "id": "uni-badge", - "displayName": "uni-badge 数字角标", - "version": "1.2.0", - "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。", - "keywords": [ - "", - "badge", - "uni-ui", - "uniui", - "数字角标", - "徽章" -], - "repository": "https://github.com/dcloudio/uni-ui", - "engines": { - "HBuilderX": "" - }, - "directories": { - "example": "../../temps/example_temps" - }, - "dcloudext": { - "category": [ - "前端组件", - "通用组件" - ], - "sale": { - "regular": { - "price": "0.00" - }, - "sourcecode": { - "price": "0.00" - } - }, - "contact": { - "qq": "" - }, - "declaration": { - "ads": "无", - "data": "无", - "permissions": "无" - }, - "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" - }, - "uni_modules": { - "dependencies": [], - "encrypt": [], - "platforms": { - "cloud": { - "tcb": "y", - "aliyun": "y" - }, - "client": { - "App": { - "app-vue": "y", - "app-nvue": "y" - }, - "H5-mobile": { - "Safari": "y", - "Android Browser": "y", - "微信浏览器(Android)": "y", - "QQ浏览器(Android)": "y" - }, - "H5-pc": { - "Chrome": "y", - "IE": "y", - "Edge": "y", - "Firefox": "y", - "Safari": "y" - }, - "小程序": { - "微信": "y", - "阿里": "y", - "百度": "y", - "字节跳动": "y", - "QQ": "y" - }, - "快应用": { - "华为": "y", - "联盟": "y" - }, - "Vue": { - "vue2": "y", - "vue3": "y" - } - } - } - } +{ + "id": "uni-badge", + "displayName": "uni-badge 数字角标", + "version": "1.2.1", + "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。", + "keywords": [ + "", + "badge", + "uni-ui", + "uniui", + "数字角标", + "徽章" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } } \ No newline at end of file diff --git a/uni_modules/uni-data-checkbox/changelog.md b/uni_modules/uni-data-checkbox/changelog.md index d8f420b9..c7a468a2 100644 --- a/uni_modules/uni-data-checkbox/changelog.md +++ b/uni_modules/uni-data-checkbox/changelog.md @@ -1,3 +1,7 @@ +## 1.0.3(2022-09-16) +- 可以使用 uni-scss 控制主题色 +## 1.0.2(2022-06-30) +- 优化 在 uni-forms 中的依赖注入方式 ## 1.0.1(2022-02-07) - 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug ## 1.0.0(2021-11-19) diff --git a/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue b/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue index 300cb5fe..341a4af4 100644 --- a/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue +++ b/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue @@ -155,17 +155,17 @@ value(newVal) { this.dataList = this.getDataList(newVal) // fix by mehaotian is_reset 在 uni-forms 中定义 - if(!this.is_reset){ - this.is_reset = false - this.formItem && this.formItem.setValue(newVal) - } + // if(!this.is_reset){ + // this.is_reset = false + // this.formItem && this.formItem.setValue(newVal) + // } }, modelValue(newVal) { this.dataList = this.getDataList(newVal); - if(!this.is_reset){ - this.is_reset = false - this.formItem && this.formItem.setValue(newVal) - } + // if(!this.is_reset){ + // this.is_reset = false + // this.formItem && this.formItem.setValue(newVal) + // } } }, data() { @@ -193,22 +193,22 @@ } }, created() { - this.form = this.getForm('uniForms') - this.formItem = this.getForm('uniFormsItem') + // this.form = this.getForm('uniForms') + // this.formItem = this.getForm('uniFormsItem') // this.formItem && this.formItem.setValue(this.value) - if (this.formItem) { - this.isTop = 6 - if (this.formItem.name) { - // 如果存在name添加默认值,否则formData 中不存在这个字段不校验 - if(!this.is_reset){ - this.is_reset = false - this.formItem.setValue(this.dataValue) - } - this.rename = this.formItem.name - this.form.inputChildrens.push(this) - } - } + // if (this.formItem) { + // this.isTop = 6 + // if (this.formItem.name) { + // // 如果存在name添加默认值,否则formData 中不存在这个字段不校验 + // if(!this.is_reset){ + // this.is_reset = false + // this.formItem.setValue(this.dataValue) + // } + // this.rename = this.formItem.name + // this.form.inputChildrens.push(this) + // } + // } if (this.localdata && this.localdata.length !== 0) { this.isLocal = true @@ -273,7 +273,7 @@ } } } - this.formItem && this.formItem.setValue(detail.value) + // this.formItem && this.formItem.setValue(detail.value) // TODO 兼容 vue2 this.$emit('input', detail.value); // // TOTO 兼容 vue3 @@ -375,7 +375,7 @@ selectedArr.push(item[this.map.value]) } }) - return this.dataValue && this.dataValue.length > 0 ? this.dataValue : selectedArr + return this.dataValue.length > 0 ? this.dataValue : selectedArr }, /** @@ -383,12 +383,14 @@ */ setStyleBackgroud(item) { let styles = {} - let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' - if (this.mode !== 'list') { - styles['border-color'] = item.selected?selectedColor:'#DCDFE6' - } - if (this.mode === 'tag') { - styles['background-color'] = item.selected? selectedColor:'#f5f5f5' + let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' + if (this.selectedColor) { + if (this.mode !== 'list') { + styles['border-color'] = item.selected?selectedColor:'#DCDFE6' + } + if (this.mode === 'tag') { + styles['background-color'] = item.selected? selectedColor:'#f5f5f5' + } } let classles = '' for (let i in styles) { @@ -398,16 +400,17 @@ }, setStyleIcon(item) { let styles = {} - let classles = '' - let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' - styles['background-color'] = item.selected?selectedColor:'#fff' - styles['border-color'] = item.selected?selectedColor:'#DCDFE6' - - if(!item.selected && item.disabled){ - styles['background-color'] = '#F2F6FC' - styles['border-color'] = item.selected?selectedColor:'#DCDFE6' + let classles = '' + if (this.selectedColor) { + let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' + styles['background-color'] = item.selected?selectedColor:'#fff' + styles['border-color'] = item.selected?selectedColor:'#DCDFE6' + + if(!item.selected && item.disabled){ + styles['background-color'] = '#F2F6FC' + styles['border-color'] = item.selected?selectedColor:'#DCDFE6' + } } - for (let i in styles) { classles += `${i}:${styles[i]};` } @@ -415,17 +418,18 @@ }, setStyleIconText(item) { let styles = {} - let classles = '' - let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' - if (this.mode === 'tag') { - styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666' - } else { - styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666' - } - if(!item.selected && item.disabled){ - styles.color = '#999' + let classles = '' + if (this.selectedColor) { + let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' + if (this.mode === 'tag') { + styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666' + } else { + styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666' + } + if(!item.selected && item.disabled){ + styles.color = '#999' + } } - for (let i in styles) { classles += `${i}:${styles[i]};` } @@ -448,7 +452,7 @@ + diff --git a/uni_modules/uni-easyinput/package.json b/uni_modules/uni-easyinput/package.json index 67e4e04c..3cc793e6 100644 --- a/uni_modules/uni-easyinput/package.json +++ b/uni_modules/uni-easyinput/package.json @@ -1,7 +1,7 @@ { "id": "uni-easyinput", "displayName": "uni-easyinput 增强输入框", - "version": "1.0.3", + "version": "1.1.0", "description": "Easyinput 组件是对原生input组件的增强", "keywords": [ "uni-ui", diff --git a/uni_modules/uni-forms/changelog.md b/uni_modules/uni-forms/changelog.md index 26e120b5..c358a21a 100644 --- a/uni_modules/uni-forms/changelog.md +++ b/uni_modules/uni-forms/changelog.md @@ -1,3 +1,33 @@ +## 1.4.8(2022-08-23) +- 优化 根据 rules 自动添加 required 的问题 +## 1.4.7(2022-08-22) +- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +## 1.4.6(2022-07-13) +- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug +## 1.4.5(2022-07-05) +- 新增 更多表单示例 +- 优化 子表单组件过期提示的问题 +- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +## 1.4.4(2022-07-04) +- 更新 删除组件日志 +## 1.4.3(2022-07-04) +- 修复 由 1.4.0 引发的 label 插槽不生效的bug +## 1.4.2(2022-07-04) +- 修复 子组件找不到 setValue 报错的bug +## 1.4.1(2022-07-04) +- 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +## 1.4.0(2022-06-30) +- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题 +- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- 优化 动态表单校验方式,废弃拼接name的方式 +## 1.3.3(2022-06-22) +- 修复 表单校验顺序无序问题 ## 1.3.2(2021-12-09) - ## 1.3.1(2021-11-19) diff --git a/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue index 679b12f9..349ee701 100644 --- a/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue +++ b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -1,213 +1,325 @@ - diff --git a/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue index dff823bb..21aee1a4 100644 --- a/uni_modules/uni-forms/components/uni-forms/uni-forms.vue +++ b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -1,13 +1,27 @@ - + diff --git a/uni_modules/uni-forms/package.json b/uni_modules/uni-forms/package.json index dfa7af4c..e3736c45 100644 --- a/uni_modules/uni-forms/package.json +++ b/uni_modules/uni-forms/package.json @@ -1,7 +1,7 @@ { "id": "uni-forms", "displayName": "uni-forms 表单", - "version": "1.3.2", + "version": "1.4.8", "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", "keywords": [ "uni-ui", @@ -17,11 +17,7 @@ "directories": { "example": "../../temps/example_temps" }, - "dcloudext": { - "category": [ - "前端组件", - "通用组件" - ], +"dcloudext": { "sale": { "regular": { "price": "0.00" @@ -38,7 +34,8 @@ "data": "无", "permissions": "无" }, - "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" }, "uni_modules": { "dependencies": [ @@ -74,7 +71,8 @@ "阿里": "y", "百度": "y", "字节跳动": "y", - "QQ": "y" + "QQ": "y", + "京东": "u" }, "快应用": { "华为": "u", diff --git a/uni_modules/uni-id-pages/changelog.md b/uni_modules/uni-id-pages/changelog.md index 6e2e4718..0695fdbd 100644 --- a/uni_modules/uni-id-pages/changelog.md +++ b/uni_modules/uni-id-pages/changelog.md @@ -1,3 +1,10 @@ +## 1.0.14(2022-09-16) +- 修改 配置项`isAdmin`默认值为`false` +## 1.0.13(2022-09-16) +- 新增 管理员注册页面 +- 新增 配置项`isAdmin`区分是否为管理端 +- 新增 登录成功后自动跳转;跳转优先级:路由携带(`uniIdRedirectUrl`参数) > 返回上一路由 > 跳转首页 +- uni-id-co 优化 注册管理员时管理员存在提示文案 ## 1.0.12(2022-09-07) - 修复 getSupportedLoginType判断是否支持微信公众号、PC网页微信扫码登录方式报错的Bug - 优化 适配pc端样式 diff --git a/uni_modules/uni-id-pages/common/common.js b/uni_modules/uni-id-pages/common/common.js index 6ba8e235..16fb4682 100644 --- a/uni_modules/uni-id-pages/common/common.js +++ b/uni_modules/uni-id-pages/common/common.js @@ -1,3 +1,4 @@ +import config from '../config' const uniIdCo = uniCloud.importObject("uni-id-co") export default { async logout() { @@ -5,8 +6,8 @@ export default { uni.removeStorageSync('uni_id_token'); uni.setStorageSync('uni_id_token_expired', 0) uni.redirectTo({ - url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd', + url: config.isAdmin ? '/uni_modules/uni-id-pages/pages/login/login-withpwd': '/uni_modules/uni-id-pages/pages/login/login-withoutpwd', }); uni.$emit('uni-id-pages-logout') }, -} \ No newline at end of file +} diff --git a/uni_modules/uni-id-pages/common/login-page.mixin.js b/uni_modules/uni-id-pages/common/login-page.mixin.js index ed99d419..130647f8 100644 --- a/uni_modules/uni-id-pages/common/login-page.mixin.js +++ b/uni_modules/uni-id-pages/common/login-page.mixin.js @@ -4,40 +4,45 @@ let mixin = { data() { return { config, + uniIdRedirectUrl: '', isMounted: false } }, onUnload() { // #ifdef H5 - document.onkeydown = false + document.onkeydown = false // #endif }, mounted() { - this.isMounted = true; + this.isMounted = true; }, onLoad(e) { - if (e.is_weixin_redirect) { - uni.showLoading({ - mask: true - }) - - if( window.location.href.includes('#') ){ - // 将url通过 ? 分割获取后面的参数字符串 再通过 & 将每一个参数单独分割出来 - let paramsArr = window.location.href.split('?')[1].split('&') - paramsArr.forEach(item=>{ - let arr = item.split('=') - if(arr[0] == 'code'){ - e.code = arr[1] - } - }) - } + if (e.is_weixin_redirect) { + uni.showLoading({ + mask: true + }) + + if( window.location.href.includes('#') ){ + // 将url通过 ? 分割获取后面的参数字符串 再通过 & 将每一个参数单独分割出来 + let paramsArr = window.location.href.split('?')[1].split('&') + paramsArr.forEach(item=>{ + let arr = item.split('=') + if(arr[0] == 'code'){ + e.code = arr[1] + } + }) + } this.$nextTick(n => { - console.log(this.$refs.uniFabLogin); + console.log(this.$refs.uniFabLogin); this.$refs.uniFabLogin.login({ code:e.code }, 'weixin') }) } + + if (e.uniIdRedirectUrl) { + this.uniIdRedirectUrl = decodeURIComponent(e.uniIdRedirectUrl) + } }, computed: { needAgreements() { @@ -70,8 +75,11 @@ let mixin = { }, methods: { loginSuccess(e) { - loginSuccess(e) + loginSuccess({ + ...e, + uniIdRedirectUrl: this.uniIdRedirectUrl + }) } } } -export default mixin +export default mixin diff --git a/uni_modules/uni-id-pages/common/loginSuccess.js b/uni_modules/uni-id-pages/common/loginSuccess.js index 17df9752..9f356cfe 100644 --- a/uni_modules/uni-id-pages/common/loginSuccess.js +++ b/uni_modules/uni-id-pages/common/loginSuccess.js @@ -1,6 +1,8 @@ +import pagesJson from '@/pages.json' + export default function(e = {}) { const { - showToast = true, toastText = '登录成功', autoBack = true + showToast = true, toastText = '登录成功', autoBack = true, uniIdRedirectUrl = '' } = e console.log({ toastText, @@ -14,24 +16,36 @@ export default function(e = {}) { } if (autoBack) { let delta = 0; //判断需要返回几层 - let pages = getCurrentPages(); + let pages = getCurrentPages(); uni.$emit('uni-id-pages-login-success',pages) - console.log(pages); + console.log(pages); pages.forEach((page, index) => { if (pages[pages.length - index - 1].route.split('/')[3] == 'login') { delta++ } - }) - console.log('判断需要返回几层:',pages, delta); - // #ifdef H5 - if(e.loginType == 'weixin'){ - console.log('window.history',window.history); - return window.history.go(-3) - } - // #endif - + }) + console.log('判断需要返回几层:',pages, delta); + if (uniIdRedirectUrl) { + return uni.reLaunch({ + url: uniIdRedirectUrl + }) + } + // #ifdef H5 + if(e.loginType == 'weixin'){ + console.log('window.history',window.history); + return window.history.go(-3) + } + // #endif + + if (delta) { + const page = pagesJson.pages[0] + return uni.reLaunch({ + url: `/${page.path}` + }) + } + uni.navigateBack({ delta }) } -} +} diff --git a/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue b/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue index 89a3cb13..f36bb91a 100644 --- a/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue +++ b/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue @@ -46,8 +46,8 @@ watch: { src:{ handler(src) { - console.log(src); - console.log(src.substring(0, 8)); + // console.log(src); + // console.log(src.substring(0, 8)); if (src&&src.substring(0, 8) == "cloud://") { uniCloud.getTempFileURL({ fileList: [src] diff --git a/uni_modules/uni-id-pages/config.js b/uni_modules/uni-id-pages/config.js index 09fc92ba..dcf9194e 100644 --- a/uni_modules/uni-id-pages/config.js +++ b/uni_modules/uni-id-pages/config.js @@ -5,6 +5,7 @@ export default { 登录类型 未列举到的或运行环境不支持的,将被自动隐藏。 如果需要在不同平台有不同的配置,直接用条件编译即可 */ + "isAdmin": false, // 区分管理端与用户端 "loginTypes": [ // "qq", // "xiaomi", @@ -43,13 +44,13 @@ export default { "web": "xxxxxx" } }, - /** - * 密码强度 - * super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间) - * strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间) - * medium (中:密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间) - * weak(弱:密码必须包含字母和数字,长度范围:6-16位之间) - * 为空或false则不验证密码强度 - */ + /** + * 密码强度 + * super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间) + * strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间) + * medium (中:密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间) + * weak(弱:密码必须包含字母和数字,长度范围:6-16位之间) + * 为空或false则不验证密码强度 + */ "passwordStrength":"medium" -} +} diff --git a/uni_modules/uni-id-pages/package.json b/uni_modules/uni-id-pages/package.json index 154021b6..79447288 100644 --- a/uni_modules/uni-id-pages/package.json +++ b/uni_modules/uni-id-pages/package.json @@ -1,7 +1,7 @@ { "id": "uni-id-pages", "displayName": "uni-id-pages", - "version": "1.0.10", + "version": "1.0.14", "description": "云端一体简单、统一、可扩展的用户中心页面模版", "keywords": [ "用户管理", @@ -48,7 +48,7 @@ "uni-popup", "uni-scss", "uni-transition", - "uni-open-bridge-common" + "uni-open-bridge-common" ], "encrypt": [], "platforms": { diff --git a/uni_modules/uni-id-pages/pages/login/login-withpwd.vue b/uni_modules/uni-id-pages/pages/login/login-withpwd.vue index b26a81b6..b7cfae0f 100644 --- a/uni_modules/uni-id-pages/pages/login/login-withpwd.vue +++ b/uni_modules/uni-id-pages/pages/login/login-withpwd.vue @@ -1,164 +1,167 @@ diff --git a/uni_modules/uni-id-pages/pages/register/register-admin.vue b/uni_modules/uni-id-pages/pages/register/register-admin.vue new file mode 100644 index 00000000..71690d95 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/register/register-admin.vue @@ -0,0 +1,179 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue index c59c9c69..cdc7a3fe 100644 --- a/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue +++ b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue @@ -9,6 +9,8 @@ + + @@ -87,7 +89,7 @@ uni.showLoading({ mask: true }); - usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname').get().then(res => { + usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname,email').get().then(res => { console.log({res}); this.userInfo = res.result.data[0] console.log('this.userInfo', this.userInfo); diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js index 519eff90..0322db6a 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js @@ -36,7 +36,9 @@ const CAPTCHA_SCENE = { LOGIN_BY_PWD: 'login-by-pwd', LOGIN_BY_SMS: 'login-by-sms', RESET_PWD_BY_SMS: 'reset-pwd-by-sms', + RESET_PWD_BY_EMAIL: 'reset-pwd-by-email', SEND_SMS_CODE: 'send-sms-code', + SEND_EMAIL_CODE: 'send-email-code', BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms' } @@ -45,6 +47,7 @@ const LOG_TYPE = { LOGIN: 'login', REGISTER: 'register', RESET_PWD_BY_SMS: 'reset-pwd', + RESET_PWD_BY_EMAIL: 'reset-pwd', BIND_MOBILE: 'bind-mobile', BIND_WEIXIN: 'bind-weixin', BIND_QQ: 'bind-qq', @@ -58,6 +61,13 @@ const SMS_SCENE = { BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms' } +const EMAIL_SCENE = { + REGISTER: 'register', + LOGIN_BY_EMAIL: 'login-by-email', + RESET_PWD_BY_EMAIL: 'reset-pwd-by-email', + BIND_EMAIL: 'bind-email' +} + module.exports = { db, dbCmd, @@ -68,5 +78,6 @@ module.exports = { USER_STATUS, CAPTCHA_SCENE, LOG_TYPE, - SMS_SCENE + SMS_SCENE, + EMAIL_SCENE } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js index 2fe72784..c8a3f112 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js @@ -15,7 +15,8 @@ const middleware = require('./middleware/index') const { registerAdmin, - registerUser + registerUser, + registerUserByEmail } = require('./module/register/index') const { addUser, @@ -45,13 +46,15 @@ const { const { updatePwd, resetPwdBySms, + resetPwdByEmail, closeAccount, getAccountInfo } = require('./module/account/index') const { createCaptcha, refreshCaptcha, - sendSmsCode + sendSmsCode, + sendEmailCode } = require('./module/verify/index') const { refreshToken, @@ -273,6 +276,17 @@ module.exports = { * @returns */ registerUser, + /** + * 通过邮箱+验证码注册用户 + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {String} params.code 邮箱验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + registerUserByEmail, /** * 用户名密码登录 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login @@ -433,6 +447,16 @@ module.exports = { * @returns {object} */ resetPwdBySms, + /** + * 通过邮箱验证码重置密码 + * @param {object} params + * @param {string} params.email 邮箱 + * @param {string} params.code 邮箱验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ + resetPwdByEmail, /** * 注销账户 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account @@ -470,6 +494,16 @@ module.exports = { * @returns */ sendSmsCode, + /** + * 发送邮箱验证码 + * @tutorial 需自行实现功能 + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.captcha 图形验证码 + * @param {String} params.scene 短信验证码使用场景 + * @returns + */ + sendEmailCode, /** * 刷新token * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js index 6b743753..f43cd273 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js @@ -1,6 +1,7 @@ module.exports = { updatePwd: require('./update-pwd'), resetPwdBySms: require('./reset-pwd-by-sms'), + resetPwdByEmail: require('./reset-pwd-by-email'), closeAccount: require('./close-account'), getAccountInfo: require('./get-account-info') } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js new file mode 100644 index 00000000..914a05ee --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js @@ -0,0 +1,119 @@ +const { + ERROR +} = require('../../common/error') +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + verifyEmailCode +} = require('../../lib/utils/verify-code') +const { + userCollection, + EMAIL_SCENE, + CAPTCHA_SCENE, + LOG_TYPE +} = require('../../common/constants') +const { + findUser +} = require('../../lib/utils/account') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 通过邮箱验证码重置密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-email + * @param {object} params + * @param {string} params.email 邮箱 + * @param {string} params.code 邮箱验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + email: 'email', + code: 'string', + password: 'password', + captcha: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + email, + code, + password, + captcha + } = params + + const needCaptcha = await getNeedCaptcha.call(this, { + email, + type: LOG_TYPE.RESET_PWD_BY_EMAIL + }) + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.RESET_PWD_BY_EMAIL + }) + } + try { + // 验证手机号验证码,验证不通过时写入失败日志 + await verifyEmailCode({ + email, + code, + scene: EMAIL_SCENE.RESET_PWD_BY_EMAIL + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.RESET_PWD_BY_EMAIL, + success: false + }) + throw error + } + // 根据手机号查找匹配的用户 + const userMatched = await findUser.call(this, { + userQuery: { + email + }, + authorizedApp: [this.getClientInfo().appId] + }) + if (userMatched.length === 0) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } else if (userMatched.length > 1) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } + const { _id: uid } = userMatched[0] + const { + passwordHash, + version + } = new PasswordUtils({ + passwordSecret: this.config.passwordSecret + }).generatePasswordHash({ + password + }) + // 更新用户密码 + await userCollection.doc(uid).update({ + password: passwordHash, + password_secret_version: version, + valid_token_date: Date.now() + }) + + // 写入成功日志 + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.RESET_PWD_BY_SMS + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js index d3a7b142..64ff603c 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js @@ -1,4 +1,5 @@ module.exports = { registerUser: require('./register-user'), - registerAdmin: require('./register-admin') + registerAdmin: require('./register-admin'), + registerUserByEmail: require('./register-user-by-email') } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js index a5535f68..d9b8f330 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js @@ -38,7 +38,8 @@ module.exports = async function (params = {}) { }).limit(1).get() if (getAdminRes.data.length > 0) { return { - errCode: ERROR.ADMIN_EXISTS + errCode: ERROR.ADMIN_EXISTS, + errMsg: this.t('uni-id-admin-exists') } } const { diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js new file mode 100644 index 00000000..b52c1d25 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js @@ -0,0 +1,87 @@ +const { + postRegister, + preRegisterWithPassword +} = require('../../lib/utils/register') +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + CAPTCHA_SCENE, + EMAIL_SCENE, + LOG_TYPE +} = require('../../common/constants') +const { + verifyEmailCode +} = require('../../lib/utils/verify-code') + +/** + * 通过邮箱+验证码注册普通用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user-by-email + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {String} params.code 邮箱验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + email: 'email', + password: 'password', + nickname: { + required: false, + type: 'nickname' + }, + code: 'string', + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + email, + password, + nickname, + code, + inviteCode + } = params + + try { + // 验证邮箱验证码,验证不通过时写入失败日志 + await verifyEmailCode({ + email, + code, + scene: EMAIL_SCENE.REGISTER + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.REGISTER, + success: false + }) + throw error + } + + const { + user, + extraData + } = await preRegisterWithPassword.call(this, { + user: { + email + }, + password + }) + return postRegister.call(this, { + user, + extraData: { + ...extraData, + nickname, + email_confirmed: 1 + }, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js index 6f2a5fdb..5ed867c7 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js @@ -1,12 +1,60 @@ +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + EMAIL_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') /** * 发送邮箱验证码,可用于登录、注册、绑定邮箱、修改密码等操作 * @tutorial * @param {Object} params * @param {String} params.email 邮箱 + * @param {String} params.captcha 图形验证码 * @param {String} params.scene 使用场景 * @returns */ module.exports = async function (params = {}) { - // 此接口暂未实现,欢迎向我们提交pr - throw new Error('api[sendEmailCode] is not yet implemented') + const schema = { + email: 'email', + captcha: 'string', + scene: 'string' + } + this.middleware.validate(params, schema) + + const { + email, + captcha, + scene + } = params + + if (!(Object.values(EMAIL_SCENE).includes(scene))) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + + await verifyCaptcha.call(this, { + scene: 'send-email-code', + captcha + }) + + // -- 测试代码 + require('../../lib/utils/verify-code') + .setEmailVerifyCode.call(this, { + email, + code: '123456', + expiresIn: 180, + scene + }) + return { + errCode: 'uni-id-invalid-mail-template', + errMsg: `已启动测试模式,直接使用:123456作为邮箱验证码即可。\n如果是正式项目,需自行实现发送邮件的相关功能` + } + // -- 测试代码 + + + //发送邮件--需自行实现 } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json index 9f49a134..30d5f360 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json @@ -1,6 +1,6 @@ { "name": "uni-id-co", - "version": "1.0.12", + "version": "1.0.13", "description": "", "main": "index.js", "keywords": [], @@ -14,4 +14,4 @@ "uni-open-bridge-common": "file:../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common" }, "extensions": {} -} \ No newline at end of file +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json b/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json index 529b672c..c3591cc1 100644 --- a/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json +++ b/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json @@ -77,7 +77,7 @@ }, "network_model": { "bsonType": "string", - "description": "设备网络型号wifi/3G/4G/" + "description": "设备网络型号wifi\/3G\/4G\/" }, "window_width": { "bsonType": "string", diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json index 106d79fd..31c4e892 100644 --- a/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json @@ -2,7 +2,7 @@ "bsonType": "object", "permission": { "update": "doc._id == auth.uid", - "read": true + "read": "doc._id == auth.uid" }, "properties": { "_id": { diff --git a/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md deleted file mode 100644 index 29f81d81..00000000 --- a/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/uni_modules/uni-open-bridge-common/changelog.md b/uni_modules/uni-open-bridge-common/changelog.md index 88198145..01489ce3 100644 --- a/uni_modules/uni-open-bridge-common/changelog.md +++ b/uni_modules/uni-open-bridge-common/changelog.md @@ -1,2 +1,6 @@ +## 1.0.3(2022-09-06) +- 修复 过期时间问题,容错 AccessToken 默认 fallback 逻辑,当微信服务器没有返回过期时间时设置为2小时后过期 +## 1.0.2(2022-09-02) +- 新增 依赖数据表schema opendb-open-data ## 1.0.0(2022-08-22) - 首次发布 diff --git a/uni_modules/uni-open-bridge-common/package.json b/uni_modules/uni-open-bridge-common/package.json index 7c9aa6b7..2a3f9b62 100644 --- a/uni_modules/uni-open-bridge-common/package.json +++ b/uni_modules/uni-open-bridge-common/package.json @@ -1,7 +1,7 @@ { "id": "uni-open-bridge-common", "displayName": "uni-open-bridge-common", - "version": "1.0.0", + "version": "1.0.3", "description": "统一接管微信等三方平台认证凭据", "keywords": [ "uni-open-bridge-common", diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js index 95160a43..b6cfe661 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict'; class BridgeError extends Error { @@ -20,7 +20,7 @@ class BridgeError extends Error { return this.message } } - -module.exports = { - BridgeError + +module.exports = { + BridgeError } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js index 2fb7d7b0..a08f9973 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js @@ -1,95 +1,95 @@ -'use strict'; - -const { - PlatformType -} = require('./consts.js') - -const configCenter = require('uni-config-center') - -const OauthConfig = { - 'weixin-mp': ['mp-weixin', 'oauth', 'weixin'], - 'weixin-h5': ['web', 'oauth', 'weixin-h5'] -} - -class ConfigBase { - - constructor() { - this._ready = false - this._uniId = null - - const uniIdConfig = configCenter({ - pluginId: 'uni-id' - }) - - this._uniId = uniIdConfig.config() - - this._ready = true - } - - getAppConfig(appid) { - if (Array.isArray(this._uniId)) { - return this._uniId.find((item) => { - return (item.dcloudAppid === appid) - }) - } - return this._uniId - } - - get ready() { - return this._ready - } -} - -class AppConfig extends ConfigBase { - - constructor() { - super() - } - - get(appid, platform) { - if (!this.isSupport(platform)) { - return null - } - - let appConfig = this.getAppConfig(appid) - if (!appConfig) { - return null - } - - return this.getOauthConfig(appConfig, platform) - } - - isSupport(platformName) { - return (AppConfig.Support_Platforms.indexOf(platformName) >= 0) - } - - getOauthConfig(appConfig, platformName) { - let tree = OauthConfig[platformName] - let node = appConfig - for (let i = 0; i < tree.length; i++) { - let nodeName = tree[i] - if (node[nodeName]) { - node = node[nodeName] - } else { - node = null - break - } - } - - if (node && node.appid && node.appsecret) { - return { - appid: node.appid, - secret: node.appsecret - } - } - - return null - } -} - -AppConfig.Support_Platforms = [PlatformType.WEIXIN_MP, PlatformType.WEIXIN_H5] - - -module.exports = { - AppConfig +'use strict'; + +const { + PlatformType +} = require('./consts.js') + +const configCenter = require('uni-config-center') + +const OauthConfig = { + 'weixin-mp': ['mp-weixin', 'oauth', 'weixin'], + 'weixin-h5': ['web', 'oauth', 'weixin-h5'] +} + +class ConfigBase { + + constructor() { + this._ready = false + this._uniId = null + + const uniIdConfig = configCenter({ + pluginId: 'uni-id' + }) + + this._uniId = uniIdConfig.config() + + this._ready = true + } + + getAppConfig(appid) { + if (Array.isArray(this._uniId)) { + return this._uniId.find((item) => { + return (item.dcloudAppid === appid) + }) + } + return this._uniId + } + + get ready() { + return this._ready + } +} + +class AppConfig extends ConfigBase { + + constructor() { + super() + } + + get(appid, platform) { + if (!this.isSupport(platform)) { + return null + } + + let appConfig = this.getAppConfig(appid) + if (!appConfig) { + return null + } + + return this.getOauthConfig(appConfig, platform) + } + + isSupport(platformName) { + return (AppConfig.Support_Platforms.indexOf(platformName) >= 0) + } + + getOauthConfig(appConfig, platformName) { + let tree = OauthConfig[platformName] + let node = appConfig + for (let i = 0; i < tree.length; i++) { + let nodeName = tree[i] + if (node[nodeName]) { + node = node[nodeName] + } else { + node = null + break + } + } + + if (node && node.appid && node.appsecret) { + return { + appid: node.appid, + secret: node.appsecret + } + } + + return null + } +} + +AppConfig.Support_Platforms = [PlatformType.WEIXIN_MP, PlatformType.WEIXIN_H5] + + +module.exports = { + AppConfig }; diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js index 6da817b9..943de125 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js @@ -1,26 +1,26 @@ -'use strict'; - -const TAG = "UNI_OPEN_BRIDGE" - -const HTTP_STATUS = { - SUCCESS: 200 -} - -const PlatformType = { - WEIXIN_MP: 'weixin-mp', - WEIXIN_H5: 'weixin-h5', - WEIXIN_APP: 'weixin-app', - WEIXIN_WEB: 'weixin-web', - QQ_MP: 'qq-mp', - QQ_APP: 'qq-app' -} - -const ErrorCodeType = { - SYSTEM_ERROR: TAG + "_SYSTEM_ERROR" -} - -module.exports = { - HTTP_STATUS, - PlatformType, - ErrorCodeType +'use strict'; + +const TAG = "UNI_OPEN_BRIDGE" + +const HTTP_STATUS = { + SUCCESS: 200 +} + +const PlatformType = { + WEIXIN_MP: 'weixin-mp', + WEIXIN_H5: 'weixin-h5', + WEIXIN_APP: 'weixin-app', + WEIXIN_WEB: 'weixin-web', + QQ_MP: 'qq-mp', + QQ_APP: 'qq-app' +} + +const ErrorCodeType = { + SYSTEM_ERROR: TAG + "_SYSTEM_ERROR" +} + +module.exports = { + HTTP_STATUS, + PlatformType, + ErrorCodeType } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js index c6b3daff..caf2a496 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js @@ -1,219 +1,219 @@ -'use strict'; - -const { - PlatformType, - ErrorCodeType -} = require('./consts.js') - -const { - AppConfig -} = require('./config.js') - -const { - Storage, - Factory -} = require('./storage.js') - -const { - BridgeError -} = require('./bridge-error.js') - -const { - WeixinServer -} = require('./weixin-server.js') - -const appConfig = new AppConfig() - -class AccessToken extends Storage { - - constructor() { - super('access-token', ['dcloudAppid', 'platform']) - } - - async fallback(parameters) { - const oauthConfig = appConfig.get(parameters.dcloudAppid, parameters.platform) - let methodName - if (parameters.platform === PlatformType.WEIXIN_MP) { - methodName = 'GetMPAccessTokenData' - } else if (parameters.platform === PlatformType.WEIXIN_H5) { - methodName = 'GetH5AccessTokenData' - } else { - throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "platform invalid") - } - - const responseData = await WeixinServer[methodName](oauthConfig) - - const duration = responseData.expires_in - delete responseData.expires_in - - return { - value: responseData, - duration - } - } -} - -class UserAccessToken extends Storage { - - constructor() { - super('user-access-token', ['dcloudAppid', 'platform', 'openid']) - } -} - -class SessionKey extends Storage { - - constructor() { - super('session-key', ['dcloudAppid', 'platform', 'openid']) - } -} - -class Encryptkey extends Storage { - - constructor() { - super('encrypt-key', ['dcloudAppid', 'platform', 'openid']) - } - - getKeyString(key) { - return `${super.getKeyString(key)}-${key.version}` - } - - getExpiresIn(value) { - if (value <= 0) { - return 60 - } - return value - } - - async fallback(parameters) { - const accessToken = await Factory.Get(AccessToken, parameters) - const userSession = await Factory.Get(SessionKey, parameters) - - const responseData = await WeixinServer.GetUserEncryptKeyData({ - openid: parameters.openid, - access_token: accessToken.access_token, - session_key: userSession.session_key - }) - - const keyInfo = responseData.key_info_list.find((item) => { - return item.version = parameters.version - }) - - const value = { - encrypt_key: keyInfo.encrypt_key, - iv: keyInfo.iv - } - - return { - value, - duration: keyInfo.expire_in - } - } -} - -class Ticket extends Storage { - - constructor() { - super('ticket', ['dcloudAppid', 'platform']) - } - - async fallback(parameters) { - const accessToken = await Factory.Get(AccessToken, { - dcloudAppid: parameters.dcloudAppid, - platform: PlatformType.WEIXIN_H5 - }) - - const responseData = await WeixinServer.GetH5TicketData(accessToken) - - const duration = responseData.expires_in - delete responseData.expires_in - - return { - value: responseData, - duration - } - } -} - - -// exports - -async function getAccessToken(key, fallback) { - return await Factory.Get(AccessToken, key, fallback) -} - -async function setAccessToken(key, value, expiresIn) { - await Factory.Set(AccessToken, key, value, expiresIn) -} - -async function removeAccessToken(key) { - await Factory.Remove(AccessToken, key) -} - -async function getUserAccessToken(key, fallback) { - return await Factory.Get(UserAccessToken, key, fallback) -} - -async function setUserAccessToken(key, value, expiresIn) { - await Factory.Set(UserAccessToken, key, value, expiresIn) -} - -async function removeUserAccessToken(key) { - await Factory.Remove(UserAccessToken, key) -} - -async function getSessionKey(key, fallback) { - return await Factory.Get(SessionKey, key, fallback) -} - -async function setSessionKey(key, value, expiresIn) { - await Factory.Set(SessionKey, key, value, expiresIn) -} - -async function removeSessionKey(key) { - await Factory.Remove(SessionKey, key) -} - -async function getEncryptKey(key, fallback) { - return await Factory.Get(Encryptkey, key, fallback) -} - -async function setEncryptKey(key, value, expiresIn) { - await Factory.Set(Encryptkey, key, value, expiresIn) -} - -async function removeEncryptKey(key) { - await Factory.Remove(Encryptkey, key) -} - -async function getTicket(key, fallback) { - return await Factory.Get(Ticket, key, fallback) -} - -async function setTicket(key, value, expiresIn) { - await Factory.Set(Ticket, key, value, expiresIn) -} - -async function removeTicket(key) { - await Factory.Remove(Ticket, key) -} - -module.exports = { - getAccessToken, - setAccessToken, - removeAccessToken, - getUserAccessToken, - setUserAccessToken, - removeUserAccessToken, - getSessionKey, - setSessionKey, - removeSessionKey, - getEncryptKey, - setEncryptKey, - removeEncryptKey, - getTicket, - setTicket, - removeTicket, - PlatformType, - WeixinServer, - ErrorCodeType +'use strict'; + +const { + PlatformType, + ErrorCodeType +} = require('./consts.js') + +const { + AppConfig +} = require('./config.js') + +const { + Storage, + Factory +} = require('./storage.js') + +const { + BridgeError +} = require('./bridge-error.js') + +const { + WeixinServer +} = require('./weixin-server.js') + +const appConfig = new AppConfig() + +class AccessToken extends Storage { + + constructor() { + super('access-token', ['dcloudAppid', 'platform']) + } + + async fallback(parameters) { + const oauthConfig = appConfig.get(parameters.dcloudAppid, parameters.platform) + let methodName + if (parameters.platform === PlatformType.WEIXIN_MP) { + methodName = 'GetMPAccessTokenData' + } else if (parameters.platform === PlatformType.WEIXIN_H5) { + methodName = 'GetH5AccessTokenData' + } else { + throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "platform invalid") + } + + const responseData = await WeixinServer[methodName](oauthConfig) + + const duration = responseData.expires_in || (60 * 60 * 2) + delete responseData.expires_in + + return { + value: responseData, + duration + } + } +} + +class UserAccessToken extends Storage { + + constructor() { + super('user-access-token', ['dcloudAppid', 'platform', 'openid']) + } +} + +class SessionKey extends Storage { + + constructor() { + super('session-key', ['dcloudAppid', 'platform', 'openid']) + } +} + +class Encryptkey extends Storage { + + constructor() { + super('encrypt-key', ['dcloudAppid', 'platform', 'openid']) + } + + getKeyString(key) { + return `${super.getKeyString(key)}-${key.version}` + } + + getExpiresIn(value) { + if (value <= 0) { + return 60 + } + return value + } + + async fallback(parameters) { + const accessToken = await Factory.Get(AccessToken, parameters) + const userSession = await Factory.Get(SessionKey, parameters) + + const responseData = await WeixinServer.GetUserEncryptKeyData({ + openid: parameters.openid, + access_token: accessToken.access_token, + session_key: userSession.session_key + }) + + const keyInfo = responseData.key_info_list.find((item) => { + return item.version = parameters.version + }) + + const value = { + encrypt_key: keyInfo.encrypt_key, + iv: keyInfo.iv + } + + return { + value, + duration: keyInfo.expire_in + } + } +} + +class Ticket extends Storage { + + constructor() { + super('ticket', ['dcloudAppid', 'platform']) + } + + async fallback(parameters) { + const accessToken = await Factory.Get(AccessToken, { + dcloudAppid: parameters.dcloudAppid, + platform: PlatformType.WEIXIN_H5 + }) + + const responseData = await WeixinServer.GetH5TicketData(accessToken) + + const duration = responseData.expires_in + delete responseData.expires_in + + return { + value: responseData, + duration + } + } +} + + +// exports + +async function getAccessToken(key, fallback) { + return await Factory.Get(AccessToken, key, fallback) +} + +async function setAccessToken(key, value, expiresIn) { + await Factory.Set(AccessToken, key, value, expiresIn) +} + +async function removeAccessToken(key) { + await Factory.Remove(AccessToken, key) +} + +async function getUserAccessToken(key, fallback) { + return await Factory.Get(UserAccessToken, key, fallback) +} + +async function setUserAccessToken(key, value, expiresIn) { + await Factory.Set(UserAccessToken, key, value, expiresIn) +} + +async function removeUserAccessToken(key) { + await Factory.Remove(UserAccessToken, key) +} + +async function getSessionKey(key, fallback) { + return await Factory.Get(SessionKey, key, fallback) +} + +async function setSessionKey(key, value, expiresIn) { + await Factory.Set(SessionKey, key, value, expiresIn) +} + +async function removeSessionKey(key) { + await Factory.Remove(SessionKey, key) +} + +async function getEncryptKey(key, fallback) { + return await Factory.Get(Encryptkey, key, fallback) +} + +async function setEncryptKey(key, value, expiresIn) { + await Factory.Set(Encryptkey, key, value, expiresIn) +} + +async function removeEncryptKey(key) { + await Factory.Remove(Encryptkey, key) +} + +async function getTicket(key, fallback) { + return await Factory.Get(Ticket, key, fallback) +} + +async function setTicket(key, value, expiresIn) { + await Factory.Set(Ticket, key, value, expiresIn) +} + +async function removeTicket(key) { + await Factory.Remove(Ticket, key) +} + +module.exports = { + getAccessToken, + setAccessToken, + removeAccessToken, + getUserAccessToken, + setUserAccessToken, + removeUserAccessToken, + getSessionKey, + setSessionKey, + removeSessionKey, + getEncryptKey, + setEncryptKey, + removeEncryptKey, + getTicket, + setTicket, + removeTicket, + PlatformType, + WeixinServer, + ErrorCodeType } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js index b7395602..8345be39 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js @@ -1,117 +1,117 @@ -'use strict'; - -const { - Validator -} = require('./validator.js') - -const { - CacheKeyCascade -} = require('./uni-cloud-cache.js') - -class Storage { - - constructor(type, keys) { - this._type = type || null - this._keys = keys || [] - } - - async get(key, fallback) { - this.validateKey(key) - const result = await this.create(key, fallback).get() - return result.value - } - - async set(key, value, expiresIn) { - this.validateKey(key) - this.validateValue(value) - const expires_in = this.getExpiresIn(expiresIn) - if (expires_in !== 0) { - await this.create(key).set(this.getValue(value), expires_in) - } - } - - async remove(key) { - this.validateKey(key) - await this.create(key).remove() - } - - async ttl(key) { - this.validateKey(key) - // 后续考虑支持 - } - - getKeyString(key) { - const keyArray = [Storage.Prefix] - this._keys.forEach((name) => { - keyArray.push(key[name]) - }) - keyArray.push(this._type) - return keyArray.join(':') - } - - getValue(value) { - return value - } - - getExpiresIn(value) { - if (value !== undefined) { - return value - } - return -1 - } - - validateKey(key) { - Validator.Key(this._keys, key) - } - - validateValue(value) { - Validator.Value(value) - } - - create(key, fallback) { - const keyString = this.getKeyString(key) - const options = { - layers: [{ - type: 'database', - key: keyString - }, { - type: 'redis', - key: keyString - }] - } - if (fallback !== null) { - const fallbackFunction = fallback || this.fallback - if (fallbackFunction) { - options.fallback = async () => { - return await fallbackFunction(key) - } - } - } - return new CacheKeyCascade(options) - } -} -Storage.Prefix = "uni-id" - -const Factory = { - - async Get(T, key, fallback) { - return await Factory.MakeUnique(T).get(key, fallback) - }, - - async Set(T, key, value, expiresIn) { - await Factory.MakeUnique(T).set(key, value, expiresIn) - }, - - async Remove(T, key) { - await Factory.MakeUnique(T).remove(key) - }, - - MakeUnique(T) { - return new T() - } -} - -module.exports = { - Storage, - Factory +'use strict'; + +const { + Validator +} = require('./validator.js') + +const { + CacheKeyCascade +} = require('./uni-cloud-cache.js') + +class Storage { + + constructor(type, keys) { + this._type = type || null + this._keys = keys || [] + } + + async get(key, fallback) { + this.validateKey(key) + const result = await this.create(key, fallback).get() + return result.value + } + + async set(key, value, expiresIn) { + this.validateKey(key) + this.validateValue(value) + const expires_in = this.getExpiresIn(expiresIn) + if (expires_in !== 0) { + await this.create(key).set(this.getValue(value), expires_in) + } + } + + async remove(key) { + this.validateKey(key) + await this.create(key).remove() + } + + async ttl(key) { + this.validateKey(key) + // 后续考虑支持 + } + + getKeyString(key) { + const keyArray = [Storage.Prefix] + this._keys.forEach((name) => { + keyArray.push(key[name]) + }) + keyArray.push(this._type) + return keyArray.join(':') + } + + getValue(value) { + return value + } + + getExpiresIn(value) { + if (value !== undefined) { + return value + } + return -1 + } + + validateKey(key) { + Validator.Key(this._keys, key) + } + + validateValue(value) { + Validator.Value(value) + } + + create(key, fallback) { + const keyString = this.getKeyString(key) + const options = { + layers: [{ + type: 'database', + key: keyString + }, { + type: 'redis', + key: keyString + }] + } + if (fallback !== null) { + const fallbackFunction = fallback || this.fallback + if (fallbackFunction) { + options.fallback = async () => { + return await fallbackFunction(key) + } + } + } + return new CacheKeyCascade(options) + } +} +Storage.Prefix = "uni-id" + +const Factory = { + + async Get(T, key, fallback) { + return await Factory.MakeUnique(T).get(key, fallback) + }, + + async Set(T, key, value, expiresIn) { + await Factory.MakeUnique(T).set(key, value, expiresIn) + }, + + async Remove(T, key) { + await Factory.MakeUnique(T).remove(key) + }, + + MakeUnique(T) { + return new T() + } +} + +module.exports = { + Storage, + Factory }; diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js index 07509327..bde572ea 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js @@ -1,324 +1,324 @@ -const db = uniCloud.database() - -function getType(value) { - return Object.prototype.toString.call(value).slice(8, -1).toLowerCase() -} - -const validator = { - key: function(value) { - const err = new Error('Invalid key') - if (typeof value !== 'string') { - throw err - } - const valueTrim = value.trim() - if (!valueTrim || valueTrim !== value) { - throw err - } - }, - value: function(value) { - // 仅作简单校验 - const type = getType(value) - const validValueType = ['null', 'number', 'string', 'array', 'object'] - if (validValueType.indexOf(type) === -1) { - throw new Error('Invalid value type') - } - }, - duration: function(value) { - const err = new Error('Invalid duration') - if (value === undefined) { - return - } - if (typeof value !== 'number' || value === 0) { - throw err - } - } -} - -/** - * 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示 - * 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在 - */ -class DatabaseCache { - constructor({ - collection = 'opendb-open-data' - } = {}) { - this.type = 'db' - this.collection = db.collection(collection) - } - - _serializeValue(value) { - return value === undefined ? null : JSON.stringify(value) - } - - _deserializeValue(value) { - return value ? JSON.parse(value) : value - } - - async set(key, value, duration) { - validator.key(key) - validator.value(value) - validator.duration(duration) - value = this._serializeValue(value) - await this.collection.doc(key).set({ - value, - expired: duration && duration !== -1 ? Date.now() + duration : -1 - }) - } - - async _getWithDuration(key) { - const getKeyRes = await this.collection.doc(key).get() - const record = getKeyRes.data[0] - if (!record) { - return { - value: null, - duration: -2 - } - } - const value = this._deserializeValue(record.value) - const expired = record.expired - if (expired === -1) { - return { - value, - duration: -1 - } - } - const duration = expired - Date.now() - if (duration <= 0) { - await this.remove(key) - return { - value: null, - duration: -2 - } - } - return { - value, - duration: Math.floor(duration / 1000) - } - } - - async get(key, { - withDuration = true - } = {}) { - const result = await this._getWithDuration(key) - if (!withDuration) { - delete result.duration - } - return result - } - - async remove(key) { - await this.collection.doc(key).remove() - } -} - -class RedisCache { - constructor() { - this.type = 'redis' - this.redis = uniCloud.redis() - } - - _serializeValue(value) { - return value === undefined ? null : JSON.stringify(value) - } - - _deserializeValue(value) { - return value ? JSON.parse(value) : value - } - - async set(key, value, duration) { - validator.key(key) - validator.value(value) - validator.duration(duration) - value = this._serializeValue(value) - if (!duration || duration === -1) { - await this.redis.set(key, value) - } else { - await this.redis.set(key, value, 'EX', duration) - } - } - - async get(key, { - withDuration = false - } = {}) { - let value = await this.redis.get(key) - value = this._deserializeValue(value) - if (!withDuration) { - return { - value - } - } - const durationSecond = await this.redis.ttl(key) - let duration - switch (durationSecond) { - case -1: - duration = -1 - break - case -2: - duration = -2 - break - default: - duration = durationSecond - break - } - return { - value, - duration - } - } - - async remove(key) { - await this.redis.del(key) - } -} - -class Cache { - constructor({ - type, - collection - } = {}) { - if (type === 'database') { - return new DatabaseCache({ - collection - }) - } else if (type === 'redis') { - return new RedisCache() - } else { - throw new Error('Invalid cache type') - } - } -} - -class CacheKey { - constructor({ - type, - collection, - cache, - key, - fallback - } = {}) { - this.cache = cache || new Cache({ - type, - collection - }) - this.key = key - this.fallback = fallback - } - - async set(value, duration) { - await this.cache.set(this.key, value, duration) - } - - async setWithSync(value, duration, syncMethod) { - await Promise.all([ - this.set(this.key, value, duration), - syncMethod(value, duration) - ]) - } - - async get() { - let { - value, - duration - } = await this.cache.get(this.key) - if (value !== null && value !== undefined) { - return { - value, - duration - } - } - if (!this.fallback) { - return { - value: null, - duration: -2 - } - } - const fallbackResult = await this.fallback() - value = fallbackResult.value - duration = fallbackResult.duration - if (value !== null && duration !== undefined) { - await this.cache.set(this.key, value, duration) - } - return { - value, - duration - } - } - - async remove() { - await this.cache.remove(this.key) - } -} - -class CacheKeyCascade { - constructor({ - layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey] - fallback - } = {}) { - this.layers = layers - this.cacheLayers = [] - let lastCacheKey - for (let i = 0; i < layers.length; i++) { - const { - type, - cache, - collection, - key - } = layers[i] - const lastCacheKeyTemp = lastCacheKey - try { - const currentCacheKey = new CacheKey({ - type, - collection, - cache, - key, - fallback: i === 0 ? fallback : function() { - return lastCacheKeyTemp.get() - } - }) - this.cacheLayers.push(currentCacheKey) - lastCacheKey = currentCacheKey - } catch (e) {} - } - this.highLevelCache = lastCacheKey - } - - async set(value, duration) { - return Promise.all( - this.cacheLayers.map(item => { - return item.set(value, duration) - }) - ) - } - - async setWithSync(value, duration, syncMethod) { - const setPromise = this.cacheLayers.map(item => { - return item.set(value, duration) - }) - return Promise.all( - [ - ...setPromise, - syncMethod(value, duration) - ] - ) - } - - async get() { - return this.highLevelCache.get() - } - - async remove() { - await Promise.all( - this.cacheLayers.map(cacheKeyItem => { - return cacheKeyItem.remove() - }) - ) - } -} - -module.exports = { - Cache, - DatabaseCache, - RedisCache, - CacheKey, - CacheKeyCascade +const db = uniCloud.database() + +function getType(value) { + return Object.prototype.toString.call(value).slice(8, -1).toLowerCase() +} + +const validator = { + key: function(value) { + const err = new Error('Invalid key') + if (typeof value !== 'string') { + throw err + } + const valueTrim = value.trim() + if (!valueTrim || valueTrim !== value) { + throw err + } + }, + value: function(value) { + // 仅作简单校验 + const type = getType(value) + const validValueType = ['null', 'number', 'string', 'array', 'object'] + if (validValueType.indexOf(type) === -1) { + throw new Error('Invalid value type') + } + }, + duration: function(value) { + const err = new Error('Invalid duration') + if (value === undefined) { + return + } + if (typeof value !== 'number' || value === 0) { + throw err + } + } +} + +/** + * 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示 + * 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在 + */ +class DatabaseCache { + constructor({ + collection = 'opendb-open-data' + } = {}) { + this.type = 'db' + this.collection = db.collection(collection) + } + + _serializeValue(value) { + return value === undefined ? null : JSON.stringify(value) + } + + _deserializeValue(value) { + return value ? JSON.parse(value) : value + } + + async set(key, value, duration) { + validator.key(key) + validator.value(value) + validator.duration(duration) + value = this._serializeValue(value) + await this.collection.doc(key).set({ + value, + expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1 + }) + } + + async _getWithDuration(key) { + const getKeyRes = await this.collection.doc(key).get() + const record = getKeyRes.data[0] + if (!record) { + return { + value: null, + duration: -2 + } + } + const value = this._deserializeValue(record.value) + const expired = record.expired + if (expired === -1) { + return { + value, + duration: -1 + } + } + const duration = expired - Date.now() + if (duration <= 0) { + await this.remove(key) + return { + value: null, + duration: -2 + } + } + return { + value, + duration: Math.floor(duration / 1000) + } + } + + async get(key, { + withDuration = true + } = {}) { + const result = await this._getWithDuration(key) + if (!withDuration) { + delete result.duration + } + return result + } + + async remove(key) { + await this.collection.doc(key).remove() + } +} + +class RedisCache { + constructor() { + this.type = 'redis' + this.redis = uniCloud.redis() + } + + _serializeValue(value) { + return value === undefined ? null : JSON.stringify(value) + } + + _deserializeValue(value) { + return value ? JSON.parse(value) : value + } + + async set(key, value, duration) { + validator.key(key) + validator.value(value) + validator.duration(duration) + value = this._serializeValue(value) + if (!duration || duration === -1) { + await this.redis.set(key, value) + } else { + await this.redis.set(key, value, 'EX', duration) + } + } + + async get(key, { + withDuration = false + } = {}) { + let value = await this.redis.get(key) + value = this._deserializeValue(value) + if (!withDuration) { + return { + value + } + } + const durationSecond = await this.redis.ttl(key) + let duration + switch (durationSecond) { + case -1: + duration = -1 + break + case -2: + duration = -2 + break + default: + duration = durationSecond + break + } + return { + value, + duration + } + } + + async remove(key) { + await this.redis.del(key) + } +} + +class Cache { + constructor({ + type, + collection + } = {}) { + if (type === 'database') { + return new DatabaseCache({ + collection + }) + } else if (type === 'redis') { + return new RedisCache() + } else { + throw new Error('Invalid cache type') + } + } +} + +class CacheKey { + constructor({ + type, + collection, + cache, + key, + fallback + } = {}) { + this.cache = cache || new Cache({ + type, + collection + }) + this.key = key + this.fallback = fallback + } + + async set(value, duration) { + await this.cache.set(this.key, value, duration) + } + + async setWithSync(value, duration, syncMethod) { + await Promise.all([ + this.set(this.key, value, duration), + syncMethod(value, duration) + ]) + } + + async get() { + let { + value, + duration + } = await this.cache.get(this.key) + if (value !== null && value !== undefined) { + return { + value, + duration + } + } + if (!this.fallback) { + return { + value: null, + duration: -2 + } + } + const fallbackResult = await this.fallback() + value = fallbackResult.value + duration = fallbackResult.duration + if (value !== null && duration !== undefined) { + await this.cache.set(this.key, value, duration) + } + return { + value, + duration + } + } + + async remove() { + await this.cache.remove(this.key) + } +} + +class CacheKeyCascade { + constructor({ + layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey] + fallback + } = {}) { + this.layers = layers + this.cacheLayers = [] + let lastCacheKey + for (let i = 0; i < layers.length; i++) { + const { + type, + cache, + collection, + key + } = layers[i] + const lastCacheKeyTemp = lastCacheKey + try { + const currentCacheKey = new CacheKey({ + type, + collection, + cache, + key, + fallback: i === 0 ? fallback : function() { + return lastCacheKeyTemp.get() + } + }) + this.cacheLayers.push(currentCacheKey) + lastCacheKey = currentCacheKey + } catch (e) {} + } + this.highLevelCache = lastCacheKey + } + + async set(value, duration) { + return Promise.all( + this.cacheLayers.map(item => { + return item.set(value, duration) + }) + ) + } + + async setWithSync(value, duration, syncMethod) { + const setPromise = this.cacheLayers.map(item => { + return item.set(value, duration) + }) + return Promise.all( + [ + ...setPromise, + syncMethod(value, duration) + ] + ) + } + + async get() { + return this.highLevelCache.get() + } + + async remove() { + await Promise.all( + this.cacheLayers.map(cacheKeyItem => { + return cacheKeyItem.remove() + }) + ) + } +} + +module.exports = { + Cache, + DatabaseCache, + RedisCache, + CacheKey, + CacheKeyCascade } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js index 47a455b1..231dc8b1 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js @@ -1,31 +1,31 @@ -const Validator = { - - Key(keyArray, parameters) { - for (let i = 0; i < keyArray.length; i++) { - const keyName = keyArray[i] - if (typeof parameters[keyName] !== 'string') { - Validator.ThrowNewError(`Invalid ${keyName}`) - } - if (parameters[keyName].length < 1) { - Validator.ThrowNewError(`Invalid ${keyName}`) - } - } - }, - - Value(value) { - if (value === undefined) { - Validator.ThrowNewError('Invalid Value') - } - if (typeof value !== 'object') { - Validator.ThrowNewError('Invalid Value Type') - } - }, - - ThrowNewError(message) { - throw new Error(message) - } -} - -module.exports = { - Validator +const Validator = { + + Key(keyArray, parameters) { + for (let i = 0; i < keyArray.length; i++) { + const keyName = keyArray[i] + if (typeof parameters[keyName] !== 'string') { + Validator.ThrowNewError(`Invalid ${keyName}`) + } + if (parameters[keyName].length < 1) { + Validator.ThrowNewError(`Invalid ${keyName}`) + } + } + }, + + Value(value) { + if (value === undefined) { + Validator.ThrowNewError('Invalid Value') + } + if (typeof value !== 'object') { + Validator.ThrowNewError('Invalid Value Type') + } + }, + + ThrowNewError(message) { + throw new Error(message) + } +} + +module.exports = { + Validator } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js index f405bc2c..28d695d7 100644 --- a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js @@ -1,161 +1,161 @@ -'use strict'; - -const crypto = require('crypto') - -const { - HTTP_STATUS -} = require('./consts.js') - -const { - BridgeError -} = require('./bridge-error.js') - -class WeixinServer { - - constructor(options = {}) { - this._appid = options.appid - this._secret = options.secret - } - - getAccessToken() { - return uniCloud.httpclient.request(WeixinServer.AccessToken_Url, { - dataType: 'json', - method: 'POST', - data: { - appid: this._appid, - secret: this._secret, - grant_type: "client_credential" - } - }) - } - - // 使用客户端获取的 code 从微信服务器换取 openid,code 仅可使用一次 - codeToSession(code) { - return uniCloud.httpclient.request(WeixinServer.Code2Session_Url, { - dataType: 'json', - data: { - appid: this._appid, - secret: this._secret, - js_code: code, - grant_type: 'authorization_code' - } - }) - } - - getUserEncryptKey({ - access_token, - openid, - session_key - }) { - console.log(access_token, openid, session_key); - const signature = crypto.createHmac('sha256', session_key).update('').digest('hex') - return uniCloud.httpclient.request(WeixinServer.User_Encrypt_Key_Url, { - dataType: 'json', - method: 'POST', - dataAsQueryString: true, - data: { - access_token, - openid: openid, - signature: signature, - sig_method: 'hmac_sha256' - } - }) - } - - getAccessTokenH5() { - return uniCloud.httpclient.request(WeixinServer.AccessToken_H5_Url, { - dataType: 'json', - method: 'GET', - data: { - appid: this._appid, - secret: this._secret, - grant_type: "client_credential" - } - }) - } - - getTicket(access_token) { - return uniCloud.httpclient.request(WeixinServer.Ticket_Url, { - dataType: 'json', - dataAsQueryString: true, - method: 'POST', - data: { - access_token - } - }) - } -} - -WeixinServer.AccessToken_Url = 'https://api.weixin.qq.com/cgi-bin/token' -WeixinServer.Code2Session_Url = 'https://api.weixin.qq.com/sns/jscode2session' -WeixinServer.User_Encrypt_Key_Url = 'https://api.weixin.qq.com/wxa/business/getuserencryptkey' -WeixinServer.AccessToken_H5_Url = 'https://api.weixin.qq.com/cgi-bin/token' -WeixinServer.Ticket_Url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi' - -WeixinServer.GetMPAccessToken = function(options) { - return new WeixinServer(options).getAccessToken() -} - -WeixinServer.GetCodeToSession = function(options) { - return new WeixinServer(options).codeToSession(options.code) -} - -WeixinServer.GetUserEncryptKey = function(options) { - return new WeixinServer(options).getUserEncryptKey(options) -} - -WeixinServer.GetH5AccessToken = function(options) { - return new WeixinServer(options).getAccessTokenH5() -} - -WeixinServer.GetH5Ticket = function(options) { - return new WeixinServer(options).getTicket(options.access_token) -} - -//////////////////////////////////////////////////////////////// - -WeixinServer.GetResponseData = function(response) { - console.log("WeixinServer::response", response) - - if (response.status !== HTTP_STATUS.SUCCESS) { - throw new BridgeError(response.status, response.status) - } - - const responseData = response.data - - if (responseData.errcode !== undefined && responseData.errcode !== 0) { - throw new BridgeError(responseData.errcode, responseData.errmsg) - } - - return responseData -} - -WeixinServer.GetMPAccessTokenData = async function(options) { - const response = await new WeixinServer(options).getAccessToken() - return WeixinServer.GetResponseData(response) -} - -WeixinServer.GetCodeToSessionData = async function(options) { - const response = await new WeixinServer(options).codeToSession(options.code) - return WeixinServer.GetResponseData(response) -} - -WeixinServer.GetUserEncryptKeyData = async function(options) { - const response = await new WeixinServer(options).getUserEncryptKey(options) - return WeixinServer.GetResponseData(response) -} - -WeixinServer.GetH5AccessTokenData = async function(options) { - const response = await new WeixinServer(options).getAccessTokenH5() - return WeixinServer.GetResponseData(response) -} - -WeixinServer.GetH5TicketData = async function(options) { - const response = await new WeixinServer(options).getTicket(options.access_token) - return WeixinServer.GetResponseData(response) -} - - -module.exports = { - WeixinServer +'use strict'; + +const crypto = require('crypto') + +const { + HTTP_STATUS +} = require('./consts.js') + +const { + BridgeError +} = require('./bridge-error.js') + +class WeixinServer { + + constructor(options = {}) { + this._appid = options.appid + this._secret = options.secret + } + + getAccessToken() { + return uniCloud.httpclient.request(WeixinServer.AccessToken_Url, { + dataType: 'json', + method: 'POST', + data: { + appid: this._appid, + secret: this._secret, + grant_type: "client_credential" + } + }) + } + + // 使用客户端获取的 code 从微信服务器换取 openid,code 仅可使用一次 + codeToSession(code) { + return uniCloud.httpclient.request(WeixinServer.Code2Session_Url, { + dataType: 'json', + data: { + appid: this._appid, + secret: this._secret, + js_code: code, + grant_type: 'authorization_code' + } + }) + } + + getUserEncryptKey({ + access_token, + openid, + session_key + }) { + console.log(access_token, openid, session_key); + const signature = crypto.createHmac('sha256', session_key).update('').digest('hex') + return uniCloud.httpclient.request(WeixinServer.User_Encrypt_Key_Url, { + dataType: 'json', + method: 'POST', + dataAsQueryString: true, + data: { + access_token, + openid: openid, + signature: signature, + sig_method: 'hmac_sha256' + } + }) + } + + getAccessTokenH5() { + return uniCloud.httpclient.request(WeixinServer.AccessToken_H5_Url, { + dataType: 'json', + method: 'GET', + data: { + appid: this._appid, + secret: this._secret, + grant_type: "client_credential" + } + }) + } + + getTicket(access_token) { + return uniCloud.httpclient.request(WeixinServer.Ticket_Url, { + dataType: 'json', + dataAsQueryString: true, + method: 'POST', + data: { + access_token + } + }) + } +} + +WeixinServer.AccessToken_Url = 'https://api.weixin.qq.com/cgi-bin/token' +WeixinServer.Code2Session_Url = 'https://api.weixin.qq.com/sns/jscode2session' +WeixinServer.User_Encrypt_Key_Url = 'https://api.weixin.qq.com/wxa/business/getuserencryptkey' +WeixinServer.AccessToken_H5_Url = 'https://api.weixin.qq.com/cgi-bin/token' +WeixinServer.Ticket_Url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi' + +WeixinServer.GetMPAccessToken = function(options) { + return new WeixinServer(options).getAccessToken() +} + +WeixinServer.GetCodeToSession = function(options) { + return new WeixinServer(options).codeToSession(options.code) +} + +WeixinServer.GetUserEncryptKey = function(options) { + return new WeixinServer(options).getUserEncryptKey(options) +} + +WeixinServer.GetH5AccessToken = function(options) { + return new WeixinServer(options).getAccessTokenH5() +} + +WeixinServer.GetH5Ticket = function(options) { + return new WeixinServer(options).getTicket(options.access_token) +} + +//////////////////////////////////////////////////////////////// + +WeixinServer.GetResponseData = function(response) { + console.log("WeixinServer::response", response) + + if (response.status !== HTTP_STATUS.SUCCESS) { + throw new BridgeError(response.status, response.status) + } + + const responseData = response.data + + if (responseData.errcode !== undefined && responseData.errcode !== 0) { + throw new BridgeError(responseData.errcode, responseData.errmsg) + } + + return responseData +} + +WeixinServer.GetMPAccessTokenData = async function(options) { + const response = await new WeixinServer(options).getAccessToken() + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetCodeToSessionData = async function(options) { + const response = await new WeixinServer(options).codeToSession(options.code) + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetUserEncryptKeyData = async function(options) { + const response = await new WeixinServer(options).getUserEncryptKey(options) + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetH5AccessTokenData = async function(options) { + const response = await new WeixinServer(options).getAccessTokenH5() + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetH5TicketData = async function(options) { + const response = await new WeixinServer(options).getTicket(options.access_token) + return WeixinServer.GetResponseData(response) +} + + +module.exports = { + WeixinServer } diff --git a/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json b/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json new file mode 100644 index 00000000..68c0ebbe --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json @@ -0,0 +1,19 @@ +// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema +{ + "bsonType": "object", + "required": ["_id", "value"], + "properties": { + "_id": { + "bsonType": "string", + "description": "key,格式:uni-id:[dcloudAppid]:[platform]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]" + }, + "value": { + "bsonType": "object", + "description": "字段_id对应的值" + }, + "expired": { + "bsonType": "date", + "description": "过期时间" + } + } +} diff --git a/uni_modules/uni-upgrade-center/changelog.md b/uni_modules/uni-upgrade-center/changelog.md new file mode 100644 index 00000000..506bdc02 --- /dev/null +++ b/uni_modules/uni-upgrade-center/changelog.md @@ -0,0 +1,49 @@ +## 0.5.1(2022-07-06) +- 修复 上版带出云函数不存在的Bug +- 升级 uni-admin 大于等于 1.9.0 务必更新至此版本。uni-admin 版本小于 1.9.0 请不要更新,历史版本在 Gitee 有发行版。后续 uni-admin 会集成升级中心 +## 0.5.0(2022-07-05) +- 修复 版本列表默认显示全部版本的Bug +- 升级 uni-admin 1.9.0 务必更新至此版本。uni-admin 版本小于 1.9.0 请不要更新,后续 uni-admin 会集成升级中心 + +## 0.4.2(2021-12-07) +- 更新 优化 list 页面显示,修复 list 页面报错 +## 0.4.1(2021-12-01) +- 修复 0.4.0版本带出来,发布新版时 appid、name 不会自动填充的Bug +## 0.4.0(2021-11-26) +- 更新 升级中心移除应用管理,现在由uni-admin接管。旧版本若没有应用管理请做升级处理 +## 0.3.0(2021-11-18) +- 兼容 uni-admin 新版内置 $request 函数改动 +## 0.2.2(2021-09-06) +- 解决 opendb-app-list表对应的schema名称冲突的问题 +## 0.2.1(2021-09-03) +- 修复 一个在添加菜单时报错,createdate不与默认值匹配 的Bug +## 0.2.0(2021-08-25) +- 兼容vue3.0 +## 0.1.9(2021-08-13) +- 更新 uni-forms使用validate校验字段 +- 修复 报错dirty_data、create_date在数据库中并不存在 +## 0.1.8(2021-08-09) +- 修复 默认配置项配置错误 +## 0.1.7(2021-08-09) +- 移除测试时配置项 +## 0.1.6(2021-08-09) +- 修复 修改版本信息时,上传时间丢失问题 +## 0.1.5(2021-07-21) +- 更新 :value.sync 改为 :value 和 @update:value +## 0.1.4(2021-07-13) +- 修复 uni-easyinput去除输入字符长度限制 +- 更新文档 关于 uni-id缺少配置信息 错误。请查看安装指引第13条 +## 0.1.3(2021-06-15) +- 修复 wgt更新某些情况下获取数据错误 +## 0.1.2(2021-06-04) +- 修复 上传包时根据平台筛选文件 +- 更新 文档 +## 0.1.1(2021-05-18) +- 更新uni-table中uni-tr组件的selectable属性为disabled +## 0.1.0(2021-04-07) +- 更新版本对比函数 compare +## 0.0.6(2021-04-01) +- 调整db_init.json +## 0.0.5(2021-03-25) +- 调整为uni_modules目录 +- 升级中心后台管理系统拆分为 Admin 后台管理 和 前台检查更新(uni-upgrade-center-app) diff --git a/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-list.js b/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-list.js new file mode 100644 index 00000000..791a1c3c --- /dev/null +++ b/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-list.js @@ -0,0 +1,44 @@ + +// 表单校验规则由 schema2code 生成,不建议直接修改校验规则,而建议通过 schema2code 生成, 详情: https://uniapp.dcloud.net.cn/uniCloud/schema + + + +const validator = { + "appid": { + "rules": [ + { + "required": true + }, + { + "format": "string" + } + ], + "label": "AppID" + }, + "name": { + "rules": [ + { + "required": true + }, + { + "format": "string" + } + ], + "label": "应用名称" + }, + "description": { + "rules": [ + { + "required": true + }, + { + "format": "string" + } + ], + "label": "应用描述" + } +} + +const enumConverter = {} + +export { validator, enumConverter } diff --git a/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js b/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js new file mode 100644 index 00000000..7edec81e --- /dev/null +++ b/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js @@ -0,0 +1,151 @@ +// 表单校验规则由 schema2code 生成,不建议直接修改校验规则,而建议通过 schema2code 生成, 详情: https://uniapp.dcloud.net.cn/uniCloud/schema + + + +const validator = { + "appid": { + "rules": [{ + "required": true + }, + { + "format": "string" + } + ], + "label": "AppID" + }, + "name": { + "rules": [{ + "format": "string" + }], + "label": "应用名称" + }, + "title": { + "rules": [{ + "format": "string" + }], + "label": "更新标题" + }, + "contents": { + "rules": [{ + "required": true + }, + { + "format": "string" + } + ], + "label": "更新内容" + }, + "platform": { + "rules": [{ + "required": true + }, + /* 此处不校验数据类型,因为platform在发布app端是单选,在发布wgt时可能是多选 + { + "format": "array" + }, */ + { + "range": [{ + "value": "Android", + "text": "安卓" + }, + { + "value": "iOS", + "text": "苹果" + } + ] + } + ], + "label": "平台" + }, + "type": { + "rules": [{ + "required": true + }, { + "format": "string" + }, + { + "range": [{ + "value": "native_app", + "text": "原生App安装包" + }, + { + "value": "wgt", + "text": "wgt资源包" + } + ] + } + ], + "label": "安装包类型" + }, + "version": { + "rules": [{ + "required": true + }, + { + "format": "string" + } + ], + "label": "版本号" + }, + "min_uni_version": { + "rules": [{ + "format": "string" + }], + "label": "原生App最低版本" + }, + "url": { + "rules": [{ + "required": true + }, { + "format": "string" + }], + "label": "包地址" + }, + "stable_publish": { + "rules": [{ + "format": "bool" + }], + "label": "上线发行" + }, + "create_date": { + "rules": [{ + "format": "timestamp" + }], + "label": "上传时间" + }, + "is_silently": { + "rules": [{ + "format": "bool" + }], + "label": "静默更新", + "defaultValue": false + }, + "is_mandatory": { + "rules": [{ + "format": "bool" + }], + "label": "强制更新", + "defaultValue": false + } +} + +const enumConverter = { + "platform_valuetotext": [{ + "value": "Android", + "text": "安卓" + }, + { + "value": "iOS", + "text": "苹果" + } + ], + "type_valuetotext": { + "native_app": "原生App安装包", + "wgt": "wgt资源包" + } +} + +export { + validator, + enumConverter +} diff --git a/uni_modules/uni-upgrade-center/menu.json b/uni_modules/uni-upgrade-center/menu.json new file mode 100644 index 00000000..f4bd3165 --- /dev/null +++ b/uni_modules/uni-upgrade-center/menu.json @@ -0,0 +1,10 @@ +[{ + "menu_id": "system_update", + "name": "升级中心", + "icon": "uni-icons-cloud-upload", + "url": "uni_modules/uni-upgrade-center/pages/version/list", + "sort": 1050, + "parent_id": "system_management", + "permission": [], + "enable": true +}] diff --git a/uni_modules/uni-upgrade-center/package.json b/uni_modules/uni-upgrade-center/package.json new file mode 100644 index 00000000..a7f0d8e5 --- /dev/null +++ b/uni_modules/uni-upgrade-center/package.json @@ -0,0 +1,94 @@ +{ + "id": "uni-upgrade-center", + "displayName": "升级中心 uni-upgrade-center - Admin", + "version": "0.5.1", + "description": "uni升级中心 - 后台管理系统", + "keywords": [ + "uniCloud", + "admin", + "update", + "升级", + "wgt" +], + "repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "Admin插件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "插件不采集任何数据", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [ + "uni-data-checkbox", + "uni-data-picker", + "uni-dateformat", + "uni-easyinput", + "uni-file-picker", + "uni-forms", + "uni-icons", + "uni-pagination", + "uni-table" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-upgrade-center/pages/components/show-info.vue b/uni_modules/uni-upgrade-center/pages/components/show-info.vue new file mode 100644 index 00000000..58128889 --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/components/show-info.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/uni_modules/uni-upgrade-center/pages/mixin/version_add_detail_mixin.js b/uni_modules/uni-upgrade-center/pages/mixin/version_add_detail_mixin.js new file mode 100644 index 00000000..a428659d --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/mixin/version_add_detail_mixin.js @@ -0,0 +1,170 @@ +import { + validator, + enumConverter +} from '@/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js'; + +const platform_iOS = 'iOS'; +const platform_Android = 'Android'; + +function getValidator(fields) { + let reuslt = {} + for (let key in validator) { + if (fields.includes(key)) { + reuslt[key] = validator[key] + } + } + return reuslt +} + +export const fields = + 'appid,name,title,contents,platform,type,version,min_uni_version,url,stable_publish,is_silently,is_mandatory,create_date' + +export default { + data() { + return { + labelWidth: '80px', + enableiOSWgt: true, // 是否开启iOS的wgt更新 + silentlyContent: '静默更新:App升级时会在后台下载wgt包并自行安装。新功能在下次启动App时生效', + mandatoryContent: '强制更新:App升级弹出框不可取消', + stablePublishContent: '同时只可有一个线上发行版,线上发行不可更设为下线。\n未上线可以设为上线发行并自动替换当前线上发行版', + stablePublishContent2: '使用本包替换当前线上发行版', + uploadFileContent: '可下载安装包地址。上传文件到云存储自动填写,也可以手动填写', + minUniVersionContent: '上次使用新Api或打包新模块的App版本', + priorityContent: '检查更新时,按照优先级从大到小依次尝试跳转商店。如果都跳转失败,则会打开浏览器使用下载链接下载apk安装包', + latestStableData: [], // 库中最新已上线版 + appFileList: null, // 上传包 + type_valuetotext: enumConverter.type_valuetotext, + preUrl: '', + formData: { + "appid": "", + "name": "", + "title": "", + "contents": "", + "platform": [], + "type": "", + "version": "", + "min_uni_version": "", + "url": "", + "stable_publish": false, + "create_date": null + }, + formOptions: { + "platform_localdata": [{ + "value": "Android", + "text": "安卓" + }, + { + "value": "iOS", + "text": "苹果" + } + ], + "type_localdata": [{ + "value": "native_app", + "text": "原生App安装包" + }, + { + "value": "wgt", + "text": "App资源包" + } + ] + }, + rules: { + ...getValidator(["appid", "contents", "platform", "type", "version", "min_uni_version", "url", + "stable_publish", + "title", "name", "is_silently", "is_mandatory" + ]) + } + } + }, + onReady() { + this.$refs.form.setRules(this.rules) + }, + computed: { + isWGT() { + return this.formData.type === 'wgt' + }, + isiOS() { + return !this.isWGT ? this.formData.platform.includes(platform_iOS) : false; + }, + hasPackage() { + return this.appFileList && !!Object.keys(this.appFileList).length + }, + fileExtname() { + return this.isWGT ? ['wgt'] : ['apk'] + }, + platformLocaldata() { + return !this.isWGT ? this.formOptions.platform_localdata : this.enableiOSWgt ? this.formOptions + .platform_localdata : [this.formOptions.platform_localdata[0]] + }, + uni_platform() { + return (this.isiOS ? platform_iOS : platform_Android).toLocaleLowerCase() + } + }, + methods: { + packageUploadSuccess(res) { + uni.showToast({ + icon: 'success', + title: '上传成功', + duration: 800 + }) + this.preUrl = this.formData.url + this.formData.url = res.tempFilePaths[0] + }, + async packageDelete(res) { + if (!this.hasPackage) return; + let [deleteRes] = await this.$request('deleteFile', { + fileList: [res.tempFilePath] + }, { + functionName: 'upgrade-center' + }) + if (deleteRes.success) { + uni.showToast({ + icon: 'success', + title: '删除成功', + duration: 800 + }) + this.formData.url = this.preUrl + this.$refs.form.clearValidate('url') + } + }, + selectFile() { + if (this.hasPackage) { + uni.showToast({ + icon: 'none', + title: '只可上传一个文件,请删除已上传后重试', + duration: 1000 + }); + } + }, + createCenterRecord(value) { + return { + ...value, + uni_platform: this.uni_platform, + create_env: 'upgrade-center' + } + }, + createCenterQuery({ + appid + }) { + return { + appid, + create_env: 'upgrade-center' + } + }, + createStatQuery({ + appid, + type, + version, + uni_platform + }) { + return { + appid, + type, + version, + uni_platform: uni_platform ? uni_platform : this.uni_platform, + create_env: 'uni-stat', + stable_publish: false + } + } + } +} diff --git a/uni_modules/uni-upgrade-center/pages/utils.js b/uni_modules/uni-upgrade-center/pages/utils.js new file mode 100644 index 00000000..07126cd0 --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/utils.js @@ -0,0 +1,26 @@ +// 判断arr是否为一个数组,返回一个bool值 +function isArray(arr) { + return Object.prototype.toString.call(arr) === '[object Array]'; +} + +// 深度克隆 +export function deepClone(obj) { + // 对常见的“非”值,直接返回原来值 + if ([null, undefined, NaN, false].includes(obj)) return obj; + if (typeof obj !== "object" && typeof obj !== 'function') { + //原始类型直接返回 + return obj; + } + var o = isArray(obj) ? [] : {}; + for (let i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i]; + } + } + return o; +} + +export const appListDbName = 'opendb-app-list' +export const appVersionListDbName = 'opendb-app-versions' +// 版本列表默认显示应用Appid +export const defaultDisplayApp = '' diff --git a/uni_modules/uni-upgrade-center/pages/version/add.vue b/uni_modules/uni-upgrade-center/pages/version/add.vue new file mode 100644 index 00000000..9258c36d --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/version/add.vue @@ -0,0 +1,418 @@ + + + + + diff --git a/uni_modules/uni-upgrade-center/pages/version/detail.vue b/uni_modules/uni-upgrade-center/pages/version/detail.vue new file mode 100644 index 00000000..6c641ef8 --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/version/detail.vue @@ -0,0 +1,316 @@ + + + + diff --git a/uni_modules/uni-upgrade-center/pages/version/list.vue b/uni_modules/uni-upgrade-center/pages/version/list.vue new file mode 100644 index 00000000..d438264d --- /dev/null +++ b/uni_modules/uni-upgrade-center/pages/version/list.vue @@ -0,0 +1,365 @@ + + + + diff --git a/uni_modules/uni-upgrade-center/readme.md b/uni_modules/uni-upgrade-center/readme.md new file mode 100644 index 00000000..c7999a4d --- /dev/null +++ b/uni_modules/uni-upgrade-center/readme.md @@ -0,0 +1,225 @@ +# uni-upgrade-center - Admin + +### 概述 + +> 统一管理App及App在`Android`、`iOS`平台上`App安装包`和`wgt资源包`的发布升级 + +> 本插件为升级中心Admin后台管理系统,前台检查更新函数请点击查看 [uni-upgrade-center-app](https://ext.dcloud.net.cn/plugin?id=4542) + +### 基于uniCloud的App升级中心,本插件具有如下特征: + - 云端基于uniCloud云函数实现 + - 数据库遵循opendb规范 + - 遵循uniCloud Admin框架规范,可直接导入Admin项目中 + - 支持App整包升级及wgt资源包升级 + +## 什么是 uniCloud + +uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台,更多请参考[uniCloud 文档](https://uniapp.dcloud.io/uniCloud)。 + +## 升级中心解决了什么问题? + +升级中心是一款uniCloud admin插件,负责App版本更新业务。包含后台管理界面、更新检查逻辑,App内只要调用弹出提示即可。 + +升级中心有以下功能点: + +- 应用管理,对App的信息记录和应用版本管理 +- 版本管理,可以发布新版,也可方便直观的对当前App历史版本以及线上发行版本进行查看、编辑和删除操作 +- 版本发布信息管理,包括 更新标题,更新内容,版本号,静默更新,强制更新,灵活上线发行 的设置和修改 +- 原生App安装包,发布Apk更新,用于App的整包更新,可设置是否强制更新 +- wgt资源包,发布wgt更新,用于App的热更新,可设置是否强制更新,静默更新 +- App管理列表及App版本记录列表搜索 + +只需导入插件,初始化数据库即可拥有上述功能。 + +您也可以自己修改逻辑自定义数据库字段,和随意定制 UI 样式。 + +## 安装指引 + +1. 使用`HBuilderX 3.1.0+`,因为要使用到`uni_modules` + +2. 使用已有`uniCloud-admin`项目或新建项目:`打开HBuilderX` -> `文件` -> `新建` -> `项目` -> `uni-app` 选择 `uniCloud admin`模板,键入一个名字,确定 + +3. 鼠标右键选择`关联云服务空间`和`运行云服务空间初始化向导` + +3. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`,选择 `uniCloud admin` 项目点击确定 + +4. 等待下载安装完毕。由于本插件依赖一些uni-ui插件,下载完成后会显示合并插件页面,自行选择即可 + +5. 找到`/uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center`,右键上传部署 + +6. 找到`/uni_modules/uni-upgrade-center/uniCloud/database/db_init.json`,右键初始化数据库 + +7. 在`pages.json`中添加页面路径 +```json +//此结构与uniCloud admin中的pages.json结构一致 +{ + "pages": [ + // ……其他页面配置 + { + "path": "uni_modules/uni-upgrade-center/pages/version/list", + "style": { + "navigationBarTitleText": "版本列表" + } + }, { + "path": "uni_modules/uni-upgrade-center/pages/version/add", + "style": { + "navigationBarTitleText": "新版发布" + } + }, { + "path": "uni_modules/uni-upgrade-center/pages/version/detail", + "style": { + "navigationBarTitleText": "版本信息查看" + } + } + ] +} +``` + +8. 在`manifest.json -> 源码视图`中添加以下配置: + ```js + "networkTimeout":{ + "uploadFile":1200000 //ms, 如果不配置,上传大文件可能会超时 + } + ``` + +9. 运行项目到`Chrome` + +10. 运行起来uniCloud admin,菜单管理模块会自动读取`/uni_modules/uni-upgrade-center/menu.json`文件中的菜单配置,生成【待添加菜单】,选中升级中心,点击`添加选中的菜单`即可 + +
+ +
+ +11. 添加成功后,就可以在左侧的菜单栏中找到`升级中心`菜单 + +
+ +
+ +12. 在进入`升级中心`之前: + 1. 需要到`uni-admin`的`应用管理`中添加一个应用,才可以在`升级中心`中发布对应应用的版本。 + 2. 当你有多个应用时,可以在`/uni_modules/uni-upgrade-center/pages/utils.js`中修改`defaultDisplayApp`字段来设置默认显示应用的`appid`。 + 3. 如果不设置或设置应用不存在则默认从数据库中查出来的第一个应用。 + +13. 由于插件依赖的uni-ui的一些组件,建议右键`/uni_modules/uni-upgrade-center`安装一下第三方依赖,否则可能会出现一些问题 + +14. 运行在`uniCloud`,由于本插件使用了`clientDB`,因此可能需要配置一下`uni-config-center插件`关于`uni-id`的配置信息。如提示`公用模块uni-id缺少配置信息`请这样做: + 1. 点击[uni-config-center](https://ext.dcloud.net.cn/plugin?id=4425)导入插件 + 2. 在`/uniCloud/cloudfunctions/common/uni-config-center/`下创建`uni-id`文件夹,文件夹内创建`config.json`文件。 + 3. 点击[config.json默认配置](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=start)。将内容拷贝至`config.json`中。**注:一定要把注释去除!** + +## 使用指南 + +### 升级中心 + +#### 应用列表 + +1. 点击菜单 `应用管理`,这里展示你所添加的 App,点击右上角 `新增` 可以新增一个 App + +
+ +
+ +2. 将App的信息都填写完善后,你可以在列表的操作列进行`修改`应用信息或者`删除`该应用。 + +**Tips** +- 删除应用会把该应用的所有版本记录同时删除 + +#### 版本管理 +1. 在版本管理list的右上角点击`发布新版`,可以发布`原生App安装包`和`wgt资源包`。在左上角点击`下拉列表`,可以切换展示应用。 + +
+ +
+ +- #### 发布原生App安装包 + 1. 在上传安装包界面填写此次发版信息 + +
+ +
+ + 2. `包地址` + - 可以选择手动上传一个文件到 `云存储`,会自动将地址填入该项 + + - 也可以手动填写一个地址,就可以不用再上传文件 + + - 如果是发布`苹果`版本,包地址则为 应用在`AppStore的链接` + + 3. `强制更新` + - 如果使用强制更新,App端接收到该字段后,App升级弹出框不可取消 + + 4. `上线发行` + - 可设置当前包是否上线发行,只有已上线才会进行更新检测 + + - 同时只可有一个线上发行版,线上发行不可更设为下线。未上线可以设为上线发行并自动替换当前线上发行版 + + - 修改当前包为上线发行,自动替换当前线上发行版 + +- #### 发布wgt资源包 + 1. 大部分配置与发布 `原生App安装包` 一致 + +
+ +
+ + 2. `原生App最低版本` + - 上次使用新Api或打包新模块的App版本 + + - 如果此次打包wgt使用了`新的api`或者打包了`新的模块`,则在发布 `wgt资源包` 的时候,将此版本更新为本次版本 + + - 如果已有正式版`wgt资源包`,则本次新增会自动带出 + + 2. `静默更新` + - App升级时会在后台下载wgt包并自行安装。新功能在下次启动App时生效 + +- #### 发布完成页面 + +
+ +
+ +**Tips** + +1. `pages/system/upgradecenter/version/add.vue`中有版本对比函数(compare)。 + - 使用多段式版本格式(如:"3.0.0.0.0.1.0.1", "3.0.0.0.0.1")。如果不满足对比规则,请自行修改。 + +## 项目代码说明 + +### uniCloud 数据表 + +数据表基于 [openDB](https://gitee.com/dcloud/opendb/tree/master) 规范,它约定了一个标准用户表的表名和字段定义,并且基于 nosql 的特性,可以由开发者自行扩展字段。 + +本项目用到了 2 个表: + +- opendb-app-list:app管理列表。记录应用的 appid、name、description 用于展示。[详见](https://gitee.com/dcloud/opendb/tree/master/collection/opendb-app-list) +- opendb-app-versions:应用版本管理表。记录管理应用的版本信息。[详见](https://gitee.com/dcloud/opendb/tree/master/collection/opendb-app-versions) + +### 前端页面 + +点击`升级中心`,会进入应用管理列表,在这里你可以新增应用,或者在`应用详情`中查看、修改或删除一个已经录入的应用。 + +在应用管理列表中点击某个应用的`版本管理`,进入该应用的所有版本记录。列表排序为:先排序已上线版本,剩下已下线版本根据创建时间排列。 + +在应用版本列表中点击`详情`,即可进入该版本的信息详情中查看、修改或删除该记录。 + +**Tips** +- 升级中心设计之初就支持iOS的wgt更新 +- iOS的wgt更新肯定是违反apple政策的,注意事项: + - 审核期间请不要弹窗升级 + - 升级完后尽量不要自行重启 + - 尽量使用静默更新 +- 可以通过以下修改支持iOS的wgt更新: + > \uni_modules\uni-upgrade-center\pages\mixin\version_add_detail_mixin.js + > + > 将 `data` 中的 `enableiOSWgt: false` 中 改为 `enableiOSWgt: true` + +**常见问题** +- 以下问题可以通过升级插件版本解决: + - createdate不与默认值匹配 + - ["create_date"]在数据库中并不存在 + - 提交的字段["dirty_data"]在数据库中并不存在 + - 集合[opendb-app-list]对应的schema内存在错误,详细信息:opendb-app-list表对应的schema名称冲突,这是什么意思呢 + +- 没有/找不到 [opendb-app-list] 集合/表。**解决方案:**升级 admin 至 1.6.0+ 即可 +- 测试时发布了高版本的包,测试完了发布包提示需要大于版本号 (x.x.x)。**解决方案:**直接在控制台修改数据库 \ No newline at end of file diff --git a/uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center/index.js b/uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center/index.js new file mode 100644 index 00000000..f27a8857 --- /dev/null +++ b/uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center/index.js @@ -0,0 +1,19 @@ +'use strict'; +exports.main = async (event, context) => { + //event为客户端上传的参数 + console.log('event : ', event) + + let res = {}; + let params = event.data || event.params; + + switch (event.action) { + case 'deleteFile': + res = await uniCloud.deleteFile({ + fileList: params.fileList + }) + break; + } + + //返回数据给客户端 + return res +}; -- GitLab