# uni-id **Repository Path**: goolinlee/uni-id ## Basic Information - **Project Name**: uni-id - **Description**: 简单、统一、可扩展的用户中心 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 60 - **Created**: 2020-08-27 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 需求背景 99%的应用,都要开发用户注册、登录、发送短信验证码、密码加密保存、修改密码、token管理等功能,从前端到后端都需要。 为什么不能有一个开源的通用项目,避免大家的重复开发呢? `uni-id`应需而生。 `uni-id`为`uniCloud`开发者提供了简单、统一、可扩展的用户管理能力封装。 ## 组成部分 `uni-id`包括如下组成部分: 1. 云数据库 主表为 `uni-id-users` 表,保存用户的基本信息。 扩展字段有很多,如实名认证数据、工作履历数据,开发者可以自由扩展。 2. 云函数 提供一个名为`uni-id`的公共模块,该模块封装了一系列API,包括注册、登录、修改密码、设置头像等。 示例工程中还提供了一个`user-center`的云函数,演示在云函数中如何调用`uni-id`公共模块。[示例工程地址](https://ext.dcloud.net.cn/plugin?id=2116) 3. 前端调用 前端示例通过callfunction调用云函数`user-center`,在注册和登录时保存token。 uniCloud框架底层,会自动在callfunction时传递`uni-id`的token(uni-app 2.7.13+版本)。在云函数的event中可直接拿到`uni-id`的token。也就是说开发者无需自己管理token了。 **2.7.14版本使用的时候token持久化存储时需要存储为uniIdToken,之后的版本虽然也兼容uniIdToken,但是推荐存储为uni_id_token** ## uni-id 对开发者的价值 1. 节省了大量重复劳动 2. 降低门槛,前端开发者无需纠结怎样设计数据库设计才更合理 3. 多系统打通用户和上下游协同 关于第三点,着重强调下。 一个应用,往往需要集成多个功能模块。比如一个电商应用,需要一个基本电商模板,还需要客服聊天模板,甚至还需要用户交流社区。 在插件市场,每类模板插件都能找到,但他们如果不是基于同一套用户体系设计,就很难整合。 DCloud推荐所有uniCloud的应用,都基于`uni-id`来做。 有了统一的账户规范,并且围绕这套账户规范,有各种各样插件,那么开发者可以随意整合这些插件,让数据连同。 规范,还可以让上下游充分协同。插件市场会出现各种数据迁移插件,比如把从discuz里把用户迁移到`uni-id`中的插件,相信围绕这套规范的产业链会非常活跃。 ## 现状和未来 `uni-id`已完整的内容: - 注册、登录、发送短信验证码、密码加密保存、修改密码、token管理(短信验证码功能需要HBuilderX 2.8.3+) - 三方登录:App中的微信登录、微信小程序中的微信登录、支付宝小程序中的支付宝账户登录 关于还缺少的部分,哪些DCloud在完善,哪些希望开发者给共同完善开源项目,计划与边界公布如下: - 部分社交账户登录 DCloud暂无计划开发百度、头条、QQ等小程序的登录,以及Apple ID、微博、QQ等App端的登录。欢迎其他开发者在开源项目上提交pr,共同完善`uni-id`。 - 邮箱验证和手机号一键认证sdk集成 手机号一键认证sdk,目前插件市场里已经有不少相关插件,未来DCloud会整合到`uni-id`中。邮箱验证,DCloud暂无计划开发,有需求的开发者欢迎提供pr。 - 实名认证、活体检测 目前插件市场里已经有不少相关插件,未来DCloud会整合到`uni-id`中。 - 权限管理ACL 这部分欢迎开发者参与完善。 其他方面,各种常见开源项目如discuz、wordPress、ecshop的用户导入插件,不属于`uni-id`主工程,欢迎开发者单独提交插件到插件市场。 对于`uni-id`还未封装的能力,欢迎大家在开源项目上提交 pr,共同完善这个开源项目。 ## 使用方式 uni-id 作为公用模块导入 common 目录,然后在云函数中调用,示例代码: ```js 'use strict'; const uniID = require('uni-id') exports.main = async (event) => { let params = event.params let res = {} let payload = {} // 不需要uid的方法 let noTokenActions = ['register', 'login', 'loginByWeixin'] if(!!event.uniIdToken && noTokenActions.indexOf(event.action) >= 0){ payload = await uniID.checkToken(event.uniIdToken) if (payload.code && payload.code > 0) { return payload } if(!!params){ params.uid = payload.uid } } switch (event.action) { case 'register': res = await uniID.register(event.params); break; case 'login': res = await uniID.login(event.params); break; case 'loginByWeixin': res = await uniID.loginByWeixin(event.code); break; case 'logout': res = await uniID.logout(payload.uid); break; case 'updatePassword': res = await uniID.updatePwd(params); break; case 'resetPwd': res = await uniID.resetPwd(params); break; case 'setAvatar': res = await uniID.setAvatar(params); break; case 'bindMobile': res = await uniID.bindMobile(params); break; case 'bindEmail': res = await uniID.bindEmail(params); break; case 'bindWeixin': res = await uniID.bindWeixin(payload.uid, event.code); break; case 'unbindWeixin': res = await uniID.unbindWeixin(payload.uid); break; case 'bindWeixin': res = await uniID.bindEmail(params); break; case 'updateUser': res = await uniID.updateUser(params); break; case 'setVerifyCode': //mobile email提供一个即可。验证码需要调用方发送给用户 let code = '1234'; //send verify code res = await uniID.setVerifyCode({ mobile: params.mobile, email: params.email, code, expiresIn: params.expiresIn, type: params.type }); break; case 'loginBySms': // 需要先调用sendSmsCode发送验证码或者setVerifyCode设置验证码 res = await uniID.loginBySms(params); break; default: res = { code: 403, msg: '非法访问' } break; } //返回数据给客户端 return res }; ``` ## 数据表设计 ### 用户表 表名:uni-id-users | 字段 | 类型 | 必填 | 描述 | | ---------------- | --------- | ---- | ------------------------------------------- | | \_id | Object ID | 是 | 存储文档 ID(用户 ID),系统自动生成 | | username | String | 是 | 用户名,不允许重复 | | password | String | 否 | 密码,加密存储 | | nickname | String | 否 | 用户昵称 | | gender | Integer | 否 | 用户性别:0 未知 1 男性 2 女性 | | status | Integer | 是 | 用户状态:0 正常 1 禁用 2 审核中 3 审核拒绝 | | mobile | String | 否 | 手机号码 | | mobile_confirmed | Integer | 否 | 手机号验证状态:0 未验证 1 已验证 | | email | String | 否 | 邮箱地址 | | email_confirmed | Integer | 否 | 邮箱验证状态:0 未验证 1 已验证 | | avatar | String | 否 | 头像地址 | | wx_unionid | String | 否 | 微信unionid | | wx_openid | Object | 否 | 微信各个平台openid | | ali_openid | String | 否 | 支付宝平台openid | | comment | String | 否 | 备注 | | realname_auth | Object | 否 | 实名认证信息 | | register_date | Timestamp | 否 | 注册时间 | | register_ip | String | 否 | 注册时 IP 地址 | | last_login_date | Timestamp | 否 | 最后登录时间 | | last_login_ip | String | 否 | 最后登录时 IP 地址 | | login_ip_limit | Array | 否 | 登录 IP 限制 | | inviter_uid | Array | 否 | 邀请人uid,按层级从下往上排列的uid数组,即第一个是直接上级 | | my_invite_code | String | 否 | 用户自己的邀请码 | | score | Integer | 否 | 用户积分 | **realname_auth字段定义** | 字段 | 类型 | 必填 | 描述 | | --------------- | --------- | ---- | --------------------------------------------------- | | type | Integer | 是 | 用户类型:0 个人用户 1 企业用户 | | auth_status | Integer | 是 | 认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败 | | auth_date | Timestamp | 否 | 认证通过时间 | | real_name | String | 否 | 真实姓名/企业名称 | | identity | String | 否 | 身份证号码/营业执照号码 | | id_card_front | String | 否 | 身份证正面照 URL | | id_card_back | String | 否 | 身份证反面照 URL | | id_card_in_hand | String | 否 | 手持身份证照片 URL | | license | String | 否 | 营业执照 URL | | contact_person | String | 否 | 联系人姓名 | | contact_mobile | String | 否 | 联系人手机号码 | | contact_email | String | 否 | 联系人邮箱 | **login_ip_limit字段定义** | 字段 | 类型 | 必填 | 描述 | | --------------- | --------- | ---- | ---------------- | | ip | String | 是 | ip地址 | | error_times | Integer | 是 | 密码错误次数 | | last_error_time | Timestamp | 是 | 上次密码错误时间 | **wx_openid字段定义** | 字段 | 类型 | 必填 | 描述 | | --------- | ------ | ---- | -------------------- | | app-plus | String | 否 | app平台微信openid | | mp-weixin | String | 否 | 微信小程序平台openid | **job 字段定义** | 字段 | 类型 | 必填 | 描述 | | ------- | ------ | ---- | -------- | | company | String | 否 | 公司名称 | | title | String | 否 | 职位 | 用户集合示例: ``` { "_id": "f2a60d815ee1da3900823d45541bb162", "username": "姓名" "password": "503005d4dd16dd7771b2d0a47aaef927e9dba89e", "status":0,//用户状态:0正常 1禁用 2审核中 3审核拒绝 "mobile":"", "mobile_confirmed":0, //手机号是否验证,0为未验证,1为已验证 "email":"amdin@domain.com", "email_confirmed":0, //邮箱是否验证,0为未验证,1为已验证 "avatar":"https://cdn.domain.com/avatar.png" "register_ip": "123.120.11.128", //注册IP "last_login_ip": "123.120.11.128", //最后登录IP } ``` ### 验证码 表名:`uni-verify` | 字段 | 类型 | 必填 | 描述 | | ---------- | --------- | ---- | -------------------------------------------- | | \_id | Object ID | 是 | 存储文档 ID(验证码 ID),系统自动生成 | | mobile | String | 是 | 手机号,和邮箱二选一 | | email | String | 是 | 邮箱,和手机号二选一 | | code | String | 是 | 验证码 | | type | String | 是 | 验证类型:login, register, bind, unbind, pay | | state | Integer | 是 | 验证状态:0 未验证 1 已验证 2 已作废 | | ip | String | 否 | 请求时 IP 地址 | | created_at | Timestamp | 否 | 创建时间 | | expired_at | Timestamp | 否 | 验证码过期时间 | ## 代码规范 - 存储到数据库时使用蛇形参数 - 代码内参数名使用驼峰形式 - 确定只有一个参数且以后几乎不会变更的时候使用`api(xxx)`形式,否则使用`api({xxx:xxx,xxx:xxx})`形式方便后续扩充 ## 错误码规范 |模块 |模块码 |错误代码 |错误信息 | |:-: |:-: |:-: |:-: | |登录通用模块 |100 |01 |账号已禁用 | |账号、邮箱、手机+密码登录 |101 |01 |用户不存在 | | | |02 |密码错误 | | | |03 |密码错误次数过多 | |手机号验证码登录/注册 |102 |01 |手机号已存在(传入type='register'且手机号已注册时触发)| | |102 |02 |此手机号尚未注册(传入type='login'且手机号未注册时触发) | | |102 |03 |邀请码无效(邀请码存在且唯一时才算有效) | |邮箱验证码登录/注册 |103 |01 |此邮箱已注册(传入type='register'且邮箱已注册时触发)| | |103 |02 |此邮箱尚未注册(传入type='login'且邮箱未注册时触发) | |微信登录/注册 |104 |01 |获取openid失败 | |支付宝登录/注册 |105 |01 |获取openid失败 | |注册通用模块 |200 |- |- | |账号、邮箱、手机+密码注册 |201 |01 |用户名、邮箱、手机号必填一项 | | | |02 |用户名、邮箱、手机号冲突 | |Token类 |300 |- |- | |生成Token |301 |- |- | |验证Token |302 |01 |设备特征校验未通过 | | | |02 |云端以不包含此token | | | |03 |token已过期 | | | |04 |token校验未通过 | |账号安全类 |400 |- |- | |登出 |401 |- |- | |修改密码 |402 |01 |用户不存在 | | | |02 |旧密码错误 | |重置密码 |403 |- |- | |验证类 |500 |- |- | |设置验证码 |501 |01 |参数错误 | |校验验证码 |502 |01 |参数错误 | | | |02 |验证码错误或已失效 | |发送短信验证码 |503 |01 |验证码发送失败,一般msg内有描述 | |绑定账号 |600 |- |- | |绑定手机号 |601 |01 |此手机号已被绑定 | |绑定邮箱 |602 |01 |此邮箱已被绑定 | |绑定微信 |603 |01 |获取openid失败 | | | |02 |此账号已被绑定 | |绑定支付宝 |604 |01 |获取openid失败 | | | |02 |此账号已被绑定 | |解绑账号 |700 |- |- | |解绑手机号 |701 |01 |解绑失败,可能已经解绑或者账号不匹配 | |解绑邮箱 |702 |01 |解绑失败,可能已经解绑或者账号不匹配 | |解绑微信 |703 |01 |解绑失败,可能已经解绑 | |解绑支付宝 |704 |01 |解绑失败,可能已经解绑 | |基础功能 |800 |- |- | |更新用户信息 |801 |01 |参数错误 | |设置头像 |802 |- |- | |获取用户信息 |803 |01 |未查询到用户信息 | |设置用户自己的邀请码 |804 |01 |邀请码设置失败,验证码重复或自动设置重试多次依然重复 | | | |02 |邀请码重试多次依然重复 | |填写邀请人邀请码 |805 |01 |邀请码无效(邀请码存在且唯一时才算有效) | | | |02 |uid错误,用户不存在 | | | |03 |邀请码不可修改 | |公用码 |900 |01 |数据库读写异常 | ## 遗留问题 1. [x] 补充使用文档,上线插件市场 2. 补充实名认证接口 3. token 刷新机制 4. [x] 用户表的索引创建 5. 权限管理实现(ACL or RBAC) 6. 更多用户信息,比如: - 个人信息:生日、身高、体重、血型、政治面貌、婚否 - 兴趣爱好、擅长领域 - 籍贯信息:省市区联动选择保存 - 教育经历 - 从业经历 - 行业、常住地、标签等 7. [x] 重置密码接口 8. 全国省市区数据表 9. 注销用户,建立归档表 10. 全国省市区数据表 11. HTTP 访问的接口文档 12. [x] 错误代码规范化