uni-starter214(九)uni-id-co

服务端代码:

说明

const uniIdCommon = require('uni-id-common')
const uniCaptcha = require('uni-captcha')
const {getType,checkIdCard} = require('./common/utils')
const {checkClientInfo,Validator} = require('./common/validator')
const ConfigUtils = require('./lib/utils/config')
const {isUniIdError,ERROR} = require('./common/error')
const middleware = require('./middleware/index')
const universal = require('./common/universal')

const {registerAdmin,registerUser,registerUserByEmail} = require('./module/register/index')
const {addUser,updateUser} = require('./module/admin/index')
const {login,loginBySms,loginByUniverify,loginByWeixin,loginByAlipay,loginByQQ,loginByApple,loginByWeixinMobile} = require('./module/login/index')
const {logout} = require('./module/logout/index')
const {bindMobileBySms,bindMobileByUniverify,bindMobileByMpWeixin,bindAlipay,bindApple,bindQQ,bindWeixin,
  unbindWeixin,unbindAlipay,unbindQQ,unbindApple} = require('./module/relate/index')
const {setPwd,updatePwd,resetPwdBySms,resetPwdByEmail,closeAccount,getAccountInfo,getRealNameInfo} = require('./module/account/index')
const {createCaptcha,refreshCaptcha,sendSmsCode,sendEmailCode} = require('./module/verify/index')
const {refreshToken,setPushCid,secureNetworkHandshakeByWeixin} = require('./module/utils/index')
const {getInvitedUser,acceptInvite} = require('./module/fission')
const {authorizeAppLogin,removeAuthorizedApp,setAuthorizedApp} = require('./module/multi-end')
const {getSupportedLoginType} = require('./module/dev/index')
const {externalRegister,externalLogin,updateUserInfoByExternal} = require('./module/external')
const {getFrvCertifyId,getFrvAuthResult} = require('./module/facial-recognition-verify')

module.exports = {
  async _before () {
    // 支持 callFunction 与 URL化
    universal.call(this)

    const clientInfo = this.getUniversalClientInfo()
    /**
     * 检查clientInfo,无appId和uniPlatform时本云对象无法正常运行
     * 此外需要保证用到的clientInfo字段均经过类型检查
     * clientInfo由客户端上传并非完全可信,clientInfo内除clientIP、userAgent、source外均为客户端上传参数
     * 否则可能会出现一些意料外的情况
     */
    checkClientInfo(clientInfo)
    let clientPlatform = clientInfo.uniPlatform
    // 统一platform名称
    switch (clientPlatform) {
      case 'app':
      case 'app-plus':
        clientPlatform = 'app'
        break
      case 'web':
      case 'h5':
        clientPlatform = 'web'
        break
      default:
        break
    }

    this.clientPlatform = clientPlatform

    // 挂载uni-id实例到this上,方便后续调用
    this.uniIdCommon = uniIdCommon.createInstance({
      clientInfo
    })

    // 包含uni-id配置合并等功能的工具集
    this.configUtils = new ConfigUtils({
      context: this
    })
    this.config = this.configUtils.getPlatformConfig()
    this.hooks = this.configUtils.getHooks()

    this.validator = new Validator({
      passwordStrength: this.config.passwordStrength
    })

    // 扩展 validator 增加 验证身份证号码合法性
    this.validator.mixin('idCard', function (idCard) {
      if (!checkIdCard(idCard)) {
        return {
          errCode: ERROR.INVALID_ID_CARD
        }
      }
    })
    this.validator.mixin('realName', function (realName) {
      if (
        typeof realName !== 'string' ||
        realName.length < 2 ||
        !/^[\u4e00-\u9fa5]{1,10}(·?[\u4e00-\u9fa5]{1,10}){0,5}$/.test(realName)
      ) {
        return {
          errCode: ERROR.INVALID_REAL_NAME
        }
      }
    })
  

    // 挂载uni-captcha到this上,方便后续调用
    this.uniCaptcha = uniCaptcha
    Object.defineProperty(this, 'uniOpenBridge', {
      get () {
        return require('uni-open-bridge-common')
      }
    })

    // 挂载中间件
    this.middleware = {}
    for (const mwName in middleware) {
      this.middleware[mwName] = middleware[mwName].bind(this)
    }

    // 国际化
    const messages = require('./lang/index')
    const fallbackLocale = 'zh-Hans'
    const i18n = uniCloud.initI18n({
      locale: clientInfo.locale,
      fallbackLocale,
      messages: JSON.parse(JSON.stringify(messages))
    })
    if (!messages[i18n.locale]) {
      i18n.setLocale(fallbackLocale)  
    }
    this.t = i18n.t.bind(i18n)

    this.response = {}

    // 请求鉴权验证
    await this.middleware.verifyRequestSign()

    // 通用权限校验模块
    await this.middleware.accessControl()
  },
  _after (error, result) {
    if (error) {
      // 处理中间件内抛出的标准响应对象
      if (error.errCode && getType(error) === 'object') {
        const errCode = error.errCode
        if (!isUniIdError(errCode)) {
          return error
        }
        return {
          errCode,
          errMsg: error.errMsg || this.t(errCode, error.errMsgValue)
        }
      }
      throw error
    }
    return Object.assign(this.response, result)
  },
  /**
   * 注册管理员
   * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-admin
   * @param {Object} params
   * @param {String} params.username   用户名
   * @param {String} params.password   密码
   * @param {String} params.nickname   昵称
   * @returns
   */
  registerAdmin,
...

end

1、registerAdmin-注册管理员

A、服务端代码

/module/register/index.js

module.exports = {
  registerUser: require('./register-user'),
  registerAdmin: require('./register-admin'),
  registerUserByEmail: require('./register-user-by-email')
}

register-admin

const {userCollection} = require('../../common/constants')
const {ERROR} = require('../../common/error')
const {preRegisterWithPassword,postRegister} = require('../../lib/utils/register')

/**
 * 注册管理员
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-admin
 * @param {Object} params
 * @param {String} params.username   用户名
 * @param {String} params.password   密码
 * @param {String} params.nickname   昵称
 * @returns
 */
module.exports = async function (params = {}) {
  //超级用户数据结构
  const schema = {
    username: 'username',
    password: 'password',
    nickname: {type: 'nickname',required: false}
  }
  //字段验证
  this.middleware.validate(params, schema)
  //取出参数
  const {username,password,nickname} = params
  //取一个管理员
  const getAdminRes = await userCollection.where({role: 'admin'}).limit(1).get()

  if (getAdminRes.data.length > 0) {//已经有数据了
    const [admin] = getAdminRes.data  //get出的是数组,admin是对象
    const appId = this.getUniversalClientInfo().appId  //这是什么东东?

    if (!admin.dcloud_appid || (admin.dcloud_appid && admin.dcloud_appid.includes(appId))) {
      return {errCode: ERROR.ADMIN_EXISTS,errMsg: this.t('uni-id-admin-exists')}  //多语言返回,这个this.t是如何加上的?
    } else {
      return {errCode: ERROR.ADMIN_EXISTS,errMsg: this.t('uni-id-admin-exist-in-other-apps')}
    }
  }
  const {user,extraData} = await preRegisterWithPassword.call(this, {user: {username},password})  //????
  return postRegister.call(this, {user,extraData: {
      ...extraData,nickname,role: ['admin']}
  })
}

preRegisterWithPassword.call:

/uni-id-co/lib/utils/register.js

const {userCollection,LOG_TYPE} = require('../../common/constants')
const {ERROR} = require('../../common/error')
const {findUser} = require('./account')
const {getValidInviteCode,generateInviteInfo} = require('./fission')
const {logout} = require('./logout')
const PasswordUtils = require('./password')
const merge = require('lodash.merge')

async function realPreRegister (params = {}) {
  const {user} = params
  const {userMatched} = await findUser({userQuery: user,authorizedApp: this.getUniversalClientInfo().appId})
  if (userMatched.length > 0) {
    throw {errCode: ERROR.ACCOUNT_EXISTS}
  }
}

async function preRegister (params = {}) {
  try {
    await realPreRegister.call(this, params)
  } catch (error) {
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.REGISTER})
    throw error
  }
}

async function preRegisterWithPassword (params = {}) {
  const {user,password} = params
  await preRegister.call(this, {user})
  const passwordUtils = new PasswordUtils({clientInfo: this.getUniversalClientInfo(),passwordSecret: this.config.passwordSecret})
  
  const {passwordHash,version} = passwordUtils.generatePasswordHash({password})
  const extraData = {password: passwordHash,password_secret_version: version}
  return {user,extraData}
}

async function thirdPartyRegister ({user = {}} = {}) {
  return {
    mobileConfirmed: !!(user.mobile && user.mobile_confirmed) || false,
    emailConfirmed: !!(user.email && user.email_confirmed) || false
  }
}

async function postRegister (params = {}) {
  const {user,extraData = {},isThirdParty = false,inviteCode} = params
  const {appId,appName,appVersion,appVersionCode,channel,scene,clientIP,osName} = this.getUniversalClientInfo()
  const uniIdToken = this.getUniversalUniIdToken()

  merge(user, extraData)

  const registerChannel = channel || scene
  user.register_env = {
    appid: appId || '',
    uni_platform: this.clientPlatform || '',
    os_name: osName || '',
    app_name: appName || '',
    app_version: appVersion || '',
    app_version_code: appVersionCode || '',
    channel: registerChannel ? registerChannel + '' : '', // channel可能为数字,统一存为字符串
    client_ip: clientIP || ''
  }

  user.register_date = Date.now()
  user.dcloud_appid = [appId]

  if (user.username) {user.username = user.username.toLowerCase()}
  if (user.email) {user.email = user.email.toLowerCase()}

  const {
    autoSetInviteCode, // 注册时自动设置邀请码
    forceInviteCode, // 必须有邀请码才允许注册,注意此逻辑不可对admin生效
    userRegisterDefaultRole // 用户注册时配置的默认角色
  } = this.config
  if (autoSetInviteCode) {
    user.my_invite_code = await getValidInviteCode()
  }

  // 如果用户注册默认角色配置存在且不为空数组
  if (userRegisterDefaultRole && userRegisterDefaultRole.length) {
    // 将用户已有的角色和配置的默认角色合并成一个数组,并去重
    user.role = Array.from(new Set([...(user.role || []), ...userRegisterDefaultRole]))
  }

  const isAdmin = user.role && user.role.includes('admin')

  if (forceInviteCode && !isAdmin && !inviteCode) {
    throw {
      errCode: ERROR.INVALID_INVITE_CODE
    }
  }

  if (inviteCode) {
    const {inviterUid,inviteTime} = await generateInviteInfo({inviteCode})
    user.inviter_uid = inviterUid
    user.invite_time = inviteTime
  }

  if (uniIdToken) {
    try {
      await logout.call(this)
    } catch (error) { }
  }

  const beforeRegister = this.hooks.beforeRegister
  let userRecord = user
  if (beforeRegister) {
    userRecord = await beforeRegister({userRecord,clientInfo: this.getUniversalClientInfo()})
  }

  const {id: uid} = await userCollection.add(userRecord)

  const createTokenRes = await this.uniIdCommon.createToken({uid})

  const {errCode,token,tokenExpired} = createTokenRes

  if (errCode) {throw createTokenRes}

  await this.middleware.uniIdLog({data: {user_id: uid},type: LOG_TYPE.REGISTER})

  return {	errCode: 0,uid,newToken: {token,tokenExpired},
			...(isThirdParty ? thirdPartyRegister({user: {...userRecord,_id: uid}}) : {} ),
			passwordConfirmed: !!userRecord.password
		}
}

module.exports = {
  preRegister,
  preRegisterWithPassword,
  postRegister
}

B、问题

this.getUniversalClientInfo(),不知何义?

this.t如何引入?

call是什么意思?

C、客户端调用
await uniIdCo.registerAdmin({
	username,
	password,
	nickname
})

参数说明

参数名类型必填说明
usernamestring超级管理员用户名
passwordstring超级管理员密码
nicknamestring超级管理员昵称

返回值

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
|-tokenstringtoken
|-tokenExpiredstringtoken过期时间

注意:

系统中仅可存在一个超级管理员

D、实例

/uni_modules/uni-id-pages/register/register-admin.vue

...
	const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true})
	export default {
		mixins: [mixin],
		data() {
			return {
				formData: {
					username: "",
					nickname: "",
					password: "",
					password2: "",
					captcha: ""
				},
...
			submitForm(params) {
				uniIdCo.registerAdmin(this.formData).then(e => {
					uni.navigateBack()
				})
				.catch(e => {
					//更好的体验:登录错误,直接刷新验证码
					this.$refs.captcha.getImageCaptcha()
					uni.showModal({
						title: '提示',
						content: e.errMsg || `创建失败: ${e.errCode}`,
						showCancel: false
					})
				})
			},

2、addUser-管理接口

A、服务器代码

/uni-id-co/module/admin/add-user.js

const {findUser} = require('../../lib/utils/account')
const {ERROR} = require('../../common/error')
const {userCollection} = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password')

/**
 * 新增用户
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user
 * @param {Object}  params
 * @param {String}  params.username       用户名
 * @param {String}  params.password       密码
 * @param {String}  params.nickname       昵称
 * @param {Array}   params.authorizedApp  允许登录的AppID列表
 * @param {Array}   params.role           用户角色列表
 * @param {String}  params.mobile         手机号
 * @param {String}  params.email          邮箱
 * @param {Array}   params.tags           用户标签
 * @param {Number}  params.status         用户状态
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    username: 'username',
    password: 'password',
    authorizedApp: 	{required: false,type: 'array<string>'}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
    nickname: 		{required: false,type: 'nickname'},
    role: 		{required: false,type: 'array<string>'},
    mobile: 		{required: false,type: 'mobile'},
    email: 		{required: false,type: 'email'},
    tags: 		{required: false,type: 'array<string>'},
    status: 		{required: false,type: 'number'}
  }
  this.middleware.validate(params, schema)
  
  const {username,password,authorizedApp,nickname,role,mobile,email,tags,status} = params
  
  const {userMatched} = await findUser({ userQuery: {username,mobile,email},authorizedApp })
  if (userMatched.length) {
    throw {errCode: ERROR.ACCOUNT_EXISTS}
  }
  
  const passwordUtils = new PasswordUtils({
    clientInfo: this.getUniversalClientInfo(),
    passwordSecret: this.config.passwordSecret
  })
  
  const {passwordHash,version} = passwordUtils.generatePasswordHash({password})
  
  const data = {username,password: passwordHash,password_secret_version: version,
    dcloud_appid: authorizedApp || [],nickname,
    role: role || [],mobile,email,
    tags: tags || [],status
  }
  
  if (email)  {data.email_confirmed = 1}
  if (mobile) {data.mobile_confirmed = 1}

  // 触发 beforeRegister 钩子
  const beforeRegister = this.hooks.beforeRegister
  let userRecord = data
  if (beforeRegister) {
    userRecord = await beforeRegister({ userRecord,clientInfo: this.getUniversalClientInfo() })
  }

  await userCollection.add(userRecord)
  return {errCode: 0,errMsg: ''}
}

B、问题
  • this.middleware.validate 是不是中间层验证?
  • this.hooks.beforeRegister 这个钩子是什么?
C、客户端
await uniIdCo.addUser({
	username,
	password,
	authorizedApp,
	nickname,
	role
})

参数说明

参数名类型必填说明usernamestring是用户名passwordstring是密码authorizedAppArray <string>否允许登录nicknamestring否昵称roleArray <string>否用户角色mobilestring否手机号emailstring否邮箱tagsarray否用户标签statusnumber否用户状态,0:正常状态,1:封禁状态
2:审核中,3:审核失败,4:注销状态

返回值

参数名类型说明
errCodestring错误码
errMsgstring错误信息

没用调用过未知

3、updateUser-修改用户

A、服务端代码
module.exports = {
  addUser: require('./add-user'),
  updateUser: require('./update-user')
}

/module/admin/update-user.js

const {findUser} = require('../../lib/utils/account')
const {ERROR} = require('../../common/error')
const {userCollection} = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password')

/**
 * 修改用户
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user
 * @param {Object}  params
 * @param {String}  params.uid            要更新的用户id
 * @param {String}  params.username       用户名
 * @param {String}  params.password       密码
 * @param {String}  params.nickname       昵称
 * @param {Array}   params.authorizedApp  允许登录的AppID列表
 * @param {Array}   params.role           用户角色列表
 * @param {String}  params.mobile         手机号
 * @param {String}  params.email          邮箱
 * @param {Array}   params.tags           用户标签
 * @param {Number}  params.status         用户状态
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    uid: 'string',
    username: 'username',
    password: 		{required: false,type: 'password'},
    authorizedApp: 	{required: false,type: 'array<string>'}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
    nickname: 		{required: false,type: 'nickname'},
    role: 		{required: false,type: 'array<string>'},
    mobile: 		{required: false,type: 'mobile'},
    email: 		{required: false,type: 'email'},
    tags: 		{required: false,type: 'array<string>'},
    status: 		{required: false,type: 'number'}
  }

  this.middleware.validate(params, schema)

  const {uid,username,password,authorizedApp,nickname,role,mobile,email,tags,status} = params

  // 更新的用户数据字段
  const data = {username,dcloud_appid: authorizedApp,nickname,role,mobile,email,tags,status}

  const realData = Object.keys(data).reduce((res, key) => {
    const item = data[key]
    if (item !== undefined) {res[key] = item}
    return res
  }, {})

  // 更新用户名时验证用户名是否重新
  if (username) {
    const {userMatched} = await findUser({userQuery: {username},authorizedApp})
    if (userMatched.filter(user => user._id !== uid).length) {
      throw {errCode: ERROR.ACCOUNT_EXISTS}
    }
  }
  if (password) {
    const passwordUtils = new PasswordUtils({
      clientInfo: this.getUniversalClientInfo(),
      passwordSecret: this.config.passwordSecret
    })
    const {passwordHash,version} = passwordUtils.generatePasswordHash({password})

    realData.password = passwordHash
    realData.password_secret_version = version
  }

  await userCollection.doc(uid).update(realData)

  return {errCode: 0}
}

B、问题

这是个什么?

C、客户端
// 云函数代码
const uniID = require('uni-id')
exports.main = async function(event,context) {
  payload = await uniID.checkToken(event.uniIdToken)
  if (payload.code) {
  	return payload
  }
	const res = await uniID.updateUser({
    uid: payload.uid,
    nickname: 'user nickname'
  })
	return res
}

更新用户信息

参数说明

字段类型必填说明
uidString用户Id,可以通过checkToken返回
其余参数Any要设置的用户信息

响应参数

字段类型必填说明
codeNumber错误码,0表示成功
messageString详细信息

4、authorizeAppLogin-授权用户登录指定客户端

A、服务端代码

/uni-id-co/module/multi-end/index.js

module.exports = {
  authorizeAppLogin: require('./authorize-app-login'),
  removeAuthorizedApp: require('./remove-authorized-app'),
  setAuthorizedApp: require('./set-authorized-app')
}

authorize-app-login.js

const {isAuthorizeApproved} = require('./utils')
const {dbCmd,userCollection} = require('../../common/constants')

/**
 * 授权用户登录应用
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login
 * @param {Object} params
 * @param {String} params.uid   用户id
 * @param {String} params.appId 授权的应用的AppId
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {uid: 'string',appId: 'string'}
  this.middleware.validate(params, schema)
  const {uid,appId} = params
  await isAuthorizeApproved({uid,appIdList: [appId]})
  
  await userCollection.doc(uid).update({dcloud_appid: dbCmd.push(appId)})
  
  return {errCode: 0}
}

utils

const {userCollection} = require('../../common/constants')
const {ERROR} = require('../../common/error')
const {findUser} = require('../../lib/utils/account')

async function isAuthorizeApproved ({uid,appIdList} = {}) {
  const getUserRes = await userCollection.doc(uid).get()
  const userRecord = getUserRes.data[0]
  if (!userRecord) {
    throw {errCode: ERROR.ACCOUNT_NOT_EXISTS}
  }
  const {userMatched} = await findUser({userQuery: userRecord,authorizedApp: appIdList})

  if (userMatched.some(item => item._id !== uid)) {
    throw {errCode: ERROR.ACCOUNT_CONFLICT}
  }
}

module.exports = {
  isAuthorizeApproved
}

/common/constants.js

const db = uniCloud.database()
const dbCmd = db.command
const userCollectionName = 'uni-id-users'
const userCollection = db.collection(userCollectionName)
const verifyCollectionName = 'opendb-verify-codes'
const verifyCollection = db.collection(verifyCollectionName)
const deviceCollectionName = 'uni-id-device'
const deviceCollection = db.collection(deviceCollectionName)
const openDataCollectionName = 'opendb-open-data'
const openDataCollection = db.collection(openDataCollectionName)
const frvLogsCollectionName = 'opendb-frv-logs'
const frvLogsCollection = db.collection(frvLogsCollectionName)

const USER_IDENTIFIER = {
  _id: 'uid',
  username: 'username',
  mobile: 'mobile',
  email: 'email',
  wx_unionid: 'wechat-account',
  'wx_openid.app': 'wechat-account',
  'wx_openid.mp': 'wechat-account',
  'wx_openid.h5': 'wechat-account',
  'wx_openid.web': 'wechat-account',
  qq_unionid: 'qq-account',
  'qq_openid.app': 'qq-account',
  'qq_openid.mp': 'qq-account',
  ali_openid: 'alipay-account',
  apple_openid: 'alipay-account',
  identities: 'idp'
}

const USER_STATUS = {
  NORMAL: 0,
  BANNED: 1,
  AUDITING: 2,
  AUDIT_FAILED: 3,
  CLOSED: 4
}

const CAPTCHA_SCENE = {
  REGISTER: 'register',
  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',
  SET_PWD_BY_SMS: 'set-pwd-by-sms'
}

const LOG_TYPE = {
  LOGOUT: 'logout',
  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',
  BIND_APPLE: 'bind-apple',
  BIND_ALIPAY: 'bind-alipay',
  UNBIND_WEIXIN: 'unbind-weixin',
  UNBIND_QQ: 'unbind-qq',
  UNBIND_ALIPAY: 'unbind-alipay',
  UNBIND_APPLE: 'unbind-apple'
}

const SMS_SCENE = {
  LOGIN_BY_SMS: 'login-by-sms',
  RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
  BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms',
  SET_PWD_BY_SMS: 'set-pwd-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'
}

const REAL_NAME_STATUS = {
  NOT_CERTIFIED: 0,
  WAITING_CERTIFIED: 1,
  CERTIFIED: 2,
  CERTIFY_FAILED: 3
}

const EXTERNAL_DIRECT_CONNECT_PROVIDER = 'externalDirectConnect'

module.exports = {
  db,
  dbCmd,
  userCollection,
  verifyCollection,
  deviceCollection,
  openDataCollection,
  frvLogsCollection,
  USER_IDENTIFIER,
  USER_STATUS,
  CAPTCHA_SCENE,
  LOG_TYPE,
  SMS_SCENE,
  EMAIL_SCENE,
  REAL_NAME_STATUS,
  EXTERNAL_DIRECT_CONNECT_PROVIDER
}

B、问题
C、客户端

授权用户登录指定客户端

await uniIdCo.authorizeAppLogin({
	uid,
	appId
})

参数说明

参数名类型必填说明
uidstring用户id
appIdstring允许登录应用的DCloud AppId

返回值

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
- tokenstringtoken
- tokenExpiredstringtoken过期时间

没有实例

5、removeAuthorizedApp-移除用户登录授权

A、服务器端代码

/module/multi-end/index.js

module.exports = {
  authorizeAppLogin: require('./authorize-app-login'),
  removeAuthorizedApp: require('./remove-authorized-app'),
  setAuthorizedApp: require('./set-authorized-app')
}

remove-authorized-app.js

const {dbCmd,userCollection} = require('../../common/constants')

/**
 * 移除用户登录授权
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#remove-authorized-app
 * @param {Object} params
 * @param {String} params.uid   用户id
 * @param {String} params.appId 取消授权的应用的AppId
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {uid: 'string',appId: 'string'}
  this.middleware.validate(params, schema)
  const {uid,appId} = params
  await userCollection.doc(uid).update({
    dcloud_appid: dbCmd.pull(appId)
  })
  return {errCode: 0}
}

B、问题
C、客户端

接口形式:

await uniIdCo.removeAuthorizedApp({
	uid,
	appId
})

没有例子

注意

  • 此接口为管理端接口
  • 仅在用户token即将过期时返回新newToken

6、setAuthorizedApp-设置允许登录的应用列表

A、服务器代码

/module/multi-end/set-authorized-app.js

const {isAuthorizeApproved} = require('./utils')
const {userCollection} = require('../../common/constants')

/**
 * 设置用户允许登录的应用列表
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-authorized-app
 * @param {Object} params
 * @param {String} params.uid       用户id
 * @param {Array} params.appIdList 允许登录的应用AppId列表
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {uid: 'string',appIdList: 'array<string>'}
  this.middleware.validate(params, schema)
  const {uid,appIdList} = params
  await isAuthorizeApproved({uid,appIdList})
  await userCollection.doc(uid).update({dcloud_appid: appIdList})
  
  return {errCode: 0}
}

B、问题
C、客户端

接口形式

await uniIdCo.setAuthorizedApp({
	uid,
	appIdList
})

参数说明

参数名类型必填说明
uidstring用户id
appIdListArray <String>允许登录的应用对应的DCloud AppId列表

没有demo

7、registerUser-用户名密码注册用户

A、服务器代码

const {postRegister,preRegisterWithPassword} = require('../../lib/utils/register')
const {verifyCaptcha} = require('../../lib/utils/captcha')
const {CAPTCHA_SCENE} = require('../../common/constants')

/**
 * 注册普通用户
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user
 * @param {Object} params
 * @param {String} params.username    用户名
 * @param {String} params.password    密码
 * @param {String} params.captcha     图形验证码
 * @param {String} params.nickname    昵称
 * @param {String} params.inviteCode  邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {username: 'username',password: 'password',captcha: 'string',
    nickname: {required: false,type: 'nickname'},
    inviteCode: {required: false,type: 'string'}
  }
  
  this.middleware.validate(params, schema)
  
  const {username,password,nickname,captcha,inviteCode} = params

  await verifyCaptcha.call(this, {captcha,scene: CAPTCHA_SCENE.REGISTER})

  const {user,extraData} = await preRegisterWithPassword.call(this, {
    user: {username},password})

  return postRegister.call(this, {user,extraData: {...extraData,nickname},inviteCode})
}

B、问题
C、客户端
await uniIdCo.registerUser({
	username,
	password,
	captcha,
	nickname,
	inviteCode
})

参数说明

参数名类型必填说明
usernamestring用户名
passwordstring密码
captchastring图形验证码
nicknamestring呢称
inviteCodestring邀请码

返回值

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
–tokenstringtoken
–tokenExpiredstringtoken过期时间

8、registerUserByEmail

功能:通过邮箱+验证码注册用户

A、调用端

接口形式:

await uniIdCo.registerUserByEmail({
	email,
	password,
	code,
	nickname,
	inviteCode
})

参数说明:

参数名类型必填说明
emailstring邮箱
passwordstring密码
codestring邮箱验证码
nicknamestring呢称
inviteCodestring邀请码

返回值:

参数名类型说明
errCodestring/number错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例(无)


B、服务端代码

/uni-id-co/module/register/register-user-by-email.js

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},
	)
}

C、问题

暂无

9、login

功能:密码登录

A、调用端

接口形式:

await uniIdCo.login({
	username,
	password,
	captcha
})

参数说明:

参数名类型必填说明
usernamestring和mobile、email三选一用户名
mobilestring和username、email三选一手机号
emailstring和username、mobile三选一邮箱
passwordstring密码
captchastring图形验证码

返回值:

参数名类型说明
同上

实例()


B、服务端代码

/uni-id-co/module/login/login.js

const {preLoginWithPassword,postLogin} = require('../../lib/utils/login')
const {getNeedCaptcha,verifyCaptcha} = require('../../lib/utils/captcha')
const {CAPTCHA_SCENE} = require('../../common/constants')
const {ERROR} = require('../../common/error')

/**
 * 用户名密码登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login
 * @param {Object} params
 * @param {String} params.username  用户名
 * @param {String} params.mobile    手机号
 * @param {String} params.email     邮箱
 * @param {String} params.password  密码
 * @param {String} params.captcha   图形验证码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    username: 	{required: false,type: 'username'},
    mobile: 	{required: false,type: 'mobile'},
    email: 	{required: false,type: 'email'},
    password: 	'password',
    captcha: 	{required: false,type: 'string'}
  }
  this.middleware.validate(params, schema)
  
  const {username,mobile,email,password,captcha} = params
  if (!username && !mobile && !email) {
    throw {errCode: ERROR.INVALID_USERNAME}
  } else if (
    (username && email) ||
    (username && mobile) ||
    (email && mobile)
  ) {
    throw {errCode: ERROR.INVALID_PARAM}
  }
  const needCaptcha = await getNeedCaptcha.call(this, {username,mobile,email},)
  if (needCaptcha) {
    await verifyCaptcha.call(this, {captcha,scene: CAPTCHA_SCENE.LOGIN_BY_PWD})
  }
  const {user,extraData} = await preLoginWithPassword.call(this, {
    user: {username,mobile,email},password})

  return postLogin.call(this, {user,extraData})
}

getNeedCaptcha

const {ERROR} = require('../../common/error')

async function getNeedCaptcha ({uid,username,mobile,email,type = 'login',
  limitDuration = 7200000, // 两小时
  limitTimes = 3 // 记录次数
} = {}) {
  const db = uniCloud.database()
  const dbCmd = db.command
  // 当用户最近“2小时内(limitDuration)”登录失败达到3次(limitTimes)时。要求用户提交验证码
  const now = Date.now()
  const uniIdLogCollection = db.collection('uni-id-log')
  const userIdentifier = {user_id: uid,username,mobile,email}

  let totalKey = 0; let deleteKey = 0
  for (const key in userIdentifier) {
    totalKey++
    if (!userIdentifier[key] || typeof userIdentifier[key] !== 'string') {
      deleteKey++
      delete userIdentifier[key]
    }
  }

  if (deleteKey === totalKey) {
    throw new Error('System error') // 正常情况下不会进入此条件,但是考虑到后续会有其他开发者修改此云对象,在此处做一个判断
  }

  const {data: recentRecord} = await uniIdLogCollection.where({
    ip: this.getUniversalClientInfo().clientIP,...userIdentifier,type,
    create_date: dbCmd.gt(now - limitDuration)
  })
    .orderBy('create_date', 'desc')
    .limit(limitTimes)
    .get()
  return recentRecord.length === limitTimes && recentRecord.every(item => item.state === 0)
}

async function verifyCaptcha (params = {}) {
  const {captcha,scene} = params
  if (!captcha) {
    throw {errCode: ERROR.CAPTCHA_REQUIRED}
  }
  const payload = await this.uniCaptcha.verify({deviceId: this.getUniversalClientInfo().deviceId,captcha,scene})
  if (payload.errCode) {
    throw payload
  }
}

module.exports = {
  getNeedCaptcha,
  verifyCaptcha
}

C、问题

暂无

10、loginBySms

功能:手机号已存在时登录,否则注册

A、调用端

接口形式:

await uniIdCo.loginBySms({
	mobile,
	code,
	captcha,
	inviteCode
})

参数说明:

参数名类型必填说明
mobilestring手机号
codestring短信验证码
captchastring图形验证码
inviteCodestring邀请码,仅注册时生效

两小时内登录失败3次的用户必填图形验证码,如果客户端没有使用uni-id-pages,可以参考uni-id-pages验证码登录页面的相关逻辑。

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

地址:/uni-id-co/module/login/login-by-sms.js

const {getNeedCaptcha,verifyCaptcha} = require('../../lib/utils/captcha')
const {verifyMobileCode} = require('../../lib/utils/verify-code')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {CAPTCHA_SCENE,SMS_SCENE,LOG_TYPE} = require('../../common/constants')

/**
 * 短信验证码登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-sms
 * @param {Object} params
 * @param {String} params.mobile      手机号
 * @param {String} params.code        短信验证码
 * @param {String} params.captcha     图形验证码
 * @param {String} params.inviteCode  邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema ={ mobile: 'mobile',code: 'string',
		  captcha: 	{required: false,type: 'string'},
		  inviteCode: 	{required: false,type: 'string'}
		}
  this.middleware.validate(params, schema)
  
  const {mobile,code,captcha,inviteCode} = params

  const needCaptcha = await getNeedCaptcha.call(this, {mobile})

  if (needCaptcha) {
    await verifyCaptcha.call(this, {captcha,scene: CAPTCHA_SCENE.LOGIN_BY_SMS})
  }

  try {
    await verifyMobileCode({mobile,code,scene: SMS_SCENE.LOGIN_BY_SMS})
  } catch (error) {
    console.log(error, {mobile,code,type: SMS_SCENE.LOGIN_BY_SMS})
    await this.middleware.uniIdLog({success: false,data: {mobile},type: LOG_TYPE.LOGIN})
    throw error
  }

  const {type,user} = await preUnifiedLogin.call(this, {user: {mobile}})
  return postUnifiedLogin.call(this, 
	{user,extraData: {mobile_confirmed: 1},isThirdParty: false,type,inviteCode},
	)
}

C、问题

暂无

11、loginByUniverify

功能:一键登录

A、调用端

接口形式:

await uniIdCo.loginByUniverify({
	access_token,
	openid,
	inviteCode
})

参数说明:

参数名类型必填说明
access_tokenstring一键登录客户端返回的access_token
openidstring一键登录客户端返回的openid
inviteCodestring邀请码,仅注册时生效

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期

实例()


B、服务端代码

/uni-id-co/module/login/login-by-univerify.js

const {getPhoneNumber} = require('../../lib/utils/univerify')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {LOG_TYPE} = require('../../common/constants')

/**
 * App端一键登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-univerify
 * @param {Object} params
 * @param {String} params.access_token  APP端一键登录返回的access_token
 * @param {String} params.openid        APP端一键登录返回的openid
 * @param {String} params.inviteCode    邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {access_token: 'string',openid: 'string',
	inviteCode: {required: false,type: 'string'}
  }
  this.middleware.validate(params, schema)
  const {access_token,openid,inviteCode} = params

  let mobile
  try {
    const phoneInfo = await getPhoneNumber.call(this, {access_token,openid})
    mobile = phoneInfo.phoneNumber
  } catch (error) {
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.LOGIN})
    throw error
  }
  
  const {user,type} = await preUnifiedLogin.call(this, {user: {mobile}})
  return postUnifiedLogin.call(this, {user,extraData: {mobile_confirmed: 1},type,inviteCode})
}

C、问题

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

var name="小王",age=17;
var obj={
	name:"小张",
	objAge:this.age,
	myFun:function(fm,t){
		console.log(this.name+"年龄"+this.age,"来自"+fm+"去往"+t);
		}
	}
var db={name:"德玛",age:99}
obj.myFun.call(db,'成都','上海');	//德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);	//德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,'成都','上海')();      //德玛 年龄 99  来自 成都去往上海

12、loginByWeixin

功能:微信账号已存在时登录,否则注册

A、调用端

接口形式:

await uniIdCo.loginByWeixin({
	code,
	inviteCode
})

参数说明:

参数名类型必填说明
codestringuni.login接口返回的code参数
inviteCodestring邀请码,仅注册时生效

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/login/login-by-weixin.js

const {initWeixin} = require('../../lib/third-party/index')
const {ERROR} = require('../../common/error')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {generateWeixinCache,getWeixinPlatform,
  saveWeixinUserKey,saveSecureNetworkCache} = require('../../lib/utils/weixin')
const {LOG_TYPE} = require('../../common/constants')
const url = require('url')

/**
 * 微信登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin
 * @param {Object} params
 * @param {String} params.code          微信登录返回的code
 * @param {String} params.inviteCode    邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {code: 'string',inviteCode: {type: 'string',required: false}}
  this.middleware.validate(params, schema)

  const {code,inviteCode,secureNetworkCache = false} = params
  const {appId} = this.getUniversalClientInfo()
  const weixinApi = initWeixin.call(this)
  const weixinPlatform = getWeixinPlatform.call(this)
  let apiName
  switch (weixinPlatform) {
    case 'mp':
      apiName = 'code2Session'
      break
    case 'app':
    case 'h5':
    case 'web':
      apiName = 'getOauthAccessToken'
      break
    default:
      throw new Error('Unsupported weixin platform')
  }
  let getWeixinAccountResult
  try {
    getWeixinAccountResult = await weixinApi[apiName](code)
  } catch (error) {
    console.error(error)
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.LOGIN})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }

  const {openid,unionid,
    // 保存下面四个字段
    sessionKey, // 微信小程序用户sessionKey
    accessToken, // App端微信用户accessToken
    refreshToken, // App端微信用户refreshToken
    expired: accessTokenExpired // App端微信用户accessToken过期时间
  } = getWeixinAccountResult

  if (secureNetworkCache) {
    if (weixinPlatform !== 'mp') {
      throw new Error('Unsupported weixin platform, expect mp-weixin')
    }
    await saveSecureNetworkCache.call(this, {code,openid,unionid,sessionKey})
  }

  const {type,user} = await preUnifiedLogin.call(this, {
    user: {wx_openid: {[weixinPlatform]: openid},wx_unionid: unionid}
  })
  const extraData = {
    wx_openid: {[`${weixinPlatform}_${appId}`]: openid},
    wx_unionid: unionid
  }
  if (type === 'register' && weixinPlatform !== 'mp') {
    const {nickname,avatar} = await weixinApi.getUserInfo({accessToken,openid})

    if (avatar) {
      // eslint-disable-next-line n/no-deprecated-api
      const avatarPath = url.parse(avatar).pathname
      const extName = avatarPath.indexOf('.') > -1 ? url.parse(avatar).pathname.split('.').pop() : 'jpg'
      const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
      const getAvatarRes = await uniCloud.httpclient.request(avatar)
      if (getAvatarRes.status >= 400) {
        throw {errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED}
      }

      const {fileID} = await uniCloud.uploadFile({cloudPath,fileContent: getAvatarRes.data})

      extraData.avatar_file = {name: cloudPath,extname: extName,url: fileID}
    }

    extraData.nickname = nickname
  }
  await saveWeixinUserKey.call(this, {openid,sessionKey,accessToken,refreshToken,accessTokenExpired})
  
  return postUnifiedLogin.call(this, {user,
    extraData: {...extraData,
      ...generateWeixinCache.call(this, {openid,
        sessionKey, // 微信小程序用户sessionKey
        accessToken, // App端微信用户accessToken
        refreshToken, // App端微信用户refreshToken
        accessTokenExpired // App端微信用户accessToken过期时间
      })
    },
    isThirdParty: true,type,inviteCode
  })
}

C、问题

暂无

13、loginByAlipay

功能:支付宝账号已存在时登录,否则注册

A、调用端

接口形式:

await uniIdCo.loginByAlipay({
	code,
	inviteCode
})

参数说明:

参数名类型必填说明
codestring支付宝小程序 uni.login返回的code参数
inviteCodestring邀请码,仅注册时生效

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/login/login-by-alipay.js

const {initAlipay} = require('../../lib/third-party/index')
const {ERROR} = require('../../common/error')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {LOG_TYPE} = require('../../common/constants')

/**
 * 支付宝登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-alipay
 * @param {Object} params
 * @param {String} params.code        支付宝小程序客户端登录返回的code
 * @param {String} params.inviteCode  邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {code: 'string',inviteCode: {type: 'string',required: false}}
  
  this.middleware.validate(params, schema)
  
  const {code,inviteCode} = params
  const alipayApi = initAlipay.call(this)
  let getAlipayAccountResult
  try {
    getAlipayAccountResult = await alipayApi.code2Session(code)
  } catch (error) {
    console.error(error)
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.LOGIN})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }

  const {openid} = getAlipayAccountResult

  const {type,user} = await preUnifiedLogin.call(this, {user: {ali_openid: openid}})
  
  return postUnifiedLogin.call(this, {user,extraData: {},isThirdParty: true,type,inviteCode})
}

C、问题

暂无

14、loginByQQ

功能:QQ账号已存在时登录,否则注册

A、调用端

接口形式:

await uniIdCo.loginByQQ({
	code,
	accessToken,
	inviteCode
})

参数说明:

参数名类型必填说明
codestringQQ小程序 uni.login返回的code参数
accessTokenstringAPP端QQ登录返回的accessToken参数
accessTokenExpirednumberaccessToken过期时间,由APP端QQ登录
返回的expires_in参数
inviteCodestring邀请码,仅注册时生效

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/login/login-by-qq.js

const {initQQ} = require('../../lib/third-party/index')
const {ERROR} = require('../../common/error')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {LOG_TYPE} = require('../../common/constants')
const {getQQPlatform,generateQQCache,saveQQUserKey} = require('../../lib/utils/qq')
const url = require('url')

/**
 * QQ登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq
 * @param {Object} params
 * @param {String} params.code                  QQ小程序登录返回的code参数
 * @param {String} params.accessToken           App端QQ登录返回的accessToken参数
 * @param {String} params.accessTokenExpired    accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
 * @param {String} params.inviteCode            邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    code: 		 		{required: false,type: 'string'},
    accessToken: 		{required: false,type: 'string'},
    accessTokenExpired: {required: false,type: 'number'},
    inviteCode: 		{required: false,type: 'string'}
  }
  this.middleware.validate(params, schema)
  
  const {code,accessToken,accessTokenExpired,inviteCode} = params
  const {appId} = this.getUniversalClientInfo()
  const qqApi = initQQ.call(this)
  const qqPlatform = getQQPlatform.call(this)
  let apiName
  switch (qqPlatform) {
    case 'mp':
      apiName = 'code2Session'
      break
    case 'app':
      apiName = 'getOpenidByToken'
      break
    default:
      throw new Error('Unsupported qq platform')
  }
  let getQQAccountResult
  try {
    getQQAccountResult = await qqApi[apiName]({code,accessToken})
  } catch (error) {
    console.error(error)
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.LOGIN})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }

  const {openid,unionid,sessionKey} = getQQAccountResult

  const {type,user} = await preUnifiedLogin.call(this, {
    user: {qq_openid: {[qqPlatform]: openid},qq_unionid: unionid
    }})

  const extraData = {qq_openid: {[`${qqPlatform}_${appId}`]: openid},qq_unionid: unionid}
  if (type === 'register' && qqPlatform !== 'mp') {
    const {nickname,avatar} = await qqApi.getUserInfo({accessToken,openid})
    // eslint-disable-next-line n/no-deprecated-api
    const extName = url.parse(avatar).pathname.split('.').pop()
    const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
    const getAvatarRes = await uniCloud.httpclient.request(avatar)
    if (getAvatarRes.status >= 400) {
      throw {errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED}
    }
    const {fileID} = await uniCloud.uploadFile({cloudPath,fileContent: getAvatarRes.data})
    extraData.nickname = nickname
    extraData.avatar_file = {name: cloudPath,extname: extName,url: fileID}
  }
  await saveQQUserKey.call(this, {openid,sessionKey,accessToken,accessTokenExpired})
  
  return postUnifiedLogin.call(this, {user,
    extraData: {...extraData,
      ...generateQQCache.call(this, {openid,sessionKey, accessToken, accessTokenExpired })
    },isThirdParty: true,type,inviteCode
  })
}

C、问题

暂无

15、loginByApple

功能:苹果账号已存在时登录,否则注册

A、调用端

接口形式:

await uniIdCo.loginByApple({
	identityToken,
	nickname,
	inviteCode
})

参数说明:

参数名类型必填说明
identityTokenstringApp端苹果登录返回的identityTokenc参数
nicknamestring昵称
inviteCodestring邀请码,仅注册时生效

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/login/login-by-apple.js

const {initApple} = require('../../lib/third-party/index')
const {ERROR} = require('../../common/error')
const {preUnifiedLogin,postUnifiedLogin} = require('../../lib/utils/unified-login')
const {LOG_TYPE} = require('../../common/constants')

/**
 * 苹果登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-apple
 * @param {Object} params
 * @param {String} params.identityToken 苹果登录返回的identityToken
 * @param {String} params.nickname      用户昵称
 * @param {String} params.inviteCode    邀请码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    identityToken: 'string',
    nickname: 	{required: false,type: 'nickname'},
    inviteCode: {required: false,type: 'string'}
  }
  this.middleware.validate(params, schema)
  const {identityToken,nickname,inviteCode} = params
  
  const appleApi = initApple.call(this)
  let verifyResult
  try {
    verifyResult = await appleApi.verifyIdentityToken(identityToken)
  } catch (error) {
    console.error(error)
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.LOGIN})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }
  const {openid} = verifyResult

  const {type,user} = await preUnifiedLogin.call(this, {user: {apple_openid: openid}})
  
  return postUnifiedLogin.call(this, {user,extraData: {nickname},isThirdParty: true,type,inviteCode})
}

C、问题

暂无

16、logout

功能:登出

A、调用端

接口形式:

await uniIdCo.logout()

参数说明:

参数名类型必填说明

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息

实例()


B、服务端代码

/uni-id-co/module/logout/index.js

module.exports = {
  logout: require('./logout')
}

/uni-id-co/module/logout/logout.js

const {logout} = require('../../lib/utils/logout')

/**
 * 用户退出登录
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#logout
 * @returns
 */
module.exports = async function () {
  await logout.call(this)
  return {
    errCode: 0
  }
}

/lib/utils/logout

const {dbCmd,LOG_TYPE,deviceCollection,userCollection} = require('../../common/constants')

async function logout () {
  const {deviceId} = this.getUniversalClientInfo()
  const uniIdToken = this.getUniversalUniIdToken()
  const payload = await this.uniIdCommon.checkToken(uniIdToken,{autoRefresh: false})
  
  if (payload.errCode) {
    throw payload
  }
  const uid = payload.uid

  // 删除token
  await userCollection.doc(uid).update({token: dbCmd.pull(uniIdToken)})

  // 仅当device表的device_id和user_id均对应时才进行更新
  await deviceCollection.where({device_id: deviceId,user_id: uid}).update({token_expired: 0})
  await this.middleware.uniIdLog({data: {user_id: uid},type: LOG_TYPE.LOGOUT})
  
  return {errCode: 0}
}

module.exports = {logout}

C、问题

暂无

17、bindMobileBySms

功能:使用短信验证码绑定手机号

A、调用端

接口形式:

await uniIdCo.bindMobileBySms({
	mobile,
	code,
	captcha
})

参数说明:

参数名类型必填说明
mobilestring手机号码
codestring短信验证码
captchastring图形验证码

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/relate/index.js

module.exports = {
  bindMobileBySms: require('./bind-mobile-by-sms'),
  bindMobileByUniverify: require('./bind-mobile-by-univerify'),
  bindMobileByMpWeixin: require('./bind-mobile-by-mp-weixin'),
  bindAlipay: require('./bind-alipay'),
  bindApple: require('./bind-apple'),
  bindQQ: require('./bind-qq'),
  bindWeixin: require('./bind-weixin'),
  unbindWeixin: require('./unbind-weixin'),
  unbindAlipay: require('./unbind-alipay'),
  unbindQQ: require('./unbind-qq'),
  unbindApple: require('./unbind-apple')
}

/uni-id-co/module/relate/bind-mobile-by-sms.js

const {getNeedCaptcha,verifyCaptcha} = require('../../lib/utils/captcha')
const {LOG_TYPE,SMS_SCENE,CAPTCHA_SCENE} = require('../../common/constants')
const {verifyMobileCode} = require('../../lib/utils/verify-code')
const {preBind,postBind} = require('../../lib/utils/relate')

/**
 * 通过短信验证码绑定手机号
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-sms
 * @param {Object} params
 * @param {String} params.mobile    手机号
 * @param {String} params.code      短信验证码
 * @param {String} params.captcha   图形验证码
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    mobile: 'mobile',
    code: 	'string',
    captcha: {required: false,type: 'string'}
  }
  
  const {mobile,code,captcha} = params
  this.middleware.validate(params, schema)
  const uid = this.authInfo.uid

  // 判断是否需要验证码
  const needCaptcha = await getNeedCaptcha.call(this, {uid,type: LOG_TYPE.BIND_MOBILE})
  if (needCaptcha) {
    await verifyCaptcha.call(this, {captcha,scene: CAPTCHA_SCENE.BIND_MOBILE_BY_SMS})
  }

  try {
    // 验证手机号验证码,验证不通过时写入失败日志
    await verifyMobileCode({mobile,code,scene: SMS_SCENE.BIND_MOBILE_BY_SMS})
  } catch (error) {
    await this.middleware.uniIdLog({
      data: {user_id: uid},type: LOG_TYPE.BIND_MOBILE,success: false
    })
    throw error
  }
  const bindAccount = {mobile}
  await preBind.call(this, {uid,bindAccount,logType: LOG_TYPE.BIND_MOBILE})
  
  await postBind.call(this, {uid,bindAccount,extraData: {mobile_confirmed: 1},logType: LOG_TYPE.BIND_MOBILE})
  
  return {errCode: 0}
}

C、问题

暂无

18、bindMobileByUniverify

功能:通过一键登录绑定手机号

A、调用端

接口形式:

await uniIdCo.bindMobileByUniverify({
	openid,
	access_token
})

参数说明:

参数名类型必填说明
openidstring客户端一键登录返回的openid参数
access_tokenstring客户端一键登录返回的access_token参数

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/relate/bind-mobile-by-univerify.js

const {getPhoneNumber} = require('../../lib/utils/univerify')
const {LOG_TYPE} = require('../../common/constants')
const {preBind,postBind} = require('../../lib/utils/relate')

/**
 * 通过一键登录绑定手机号
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-univerify
 * @param {Object} params
 * @param {String} params.openid        APP端一键登录返回的openid
 * @param {String} params.access_token  APP端一键登录返回的access_token
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {openid: 'string',access_token: 'string'}
  const {openid,access_token} = params
  this.middleware.validate(params, schema
  
  const uid = this.authInfo.uid
  let mobile
  try {
    const phoneInfo = await getPhoneNumber.call(this, {access_token,openid})
    mobile = phoneInfo.phoneNumber
  } catch (error) {
    await this.middleware.uniIdLog({success: false,data: {user_id: uid},type: LOG_TYPE.BIND_MOBILE})
    throw error
  }

  const bindAccount = {mobile}
  await preBind.call(this, {uid,bindAccount,logType: LOG_TYPE.BIND_MOBILE})
  
  await postBind.call(this, {uid,bindAccount,extraData: {mobile_confirmed: 1},logType: LOG_TYPE.BIND_MOBILE})
  
  return {errCode: 0}
}

C、问题

暂无

19、bindMobileByMpWeixin

功能:通过微信绑定手机号

A、调用端

接口形式:

await uniIdCo.bindMobileByMpWeixin({
	code
})

参数说明:

参数名类型必填说明
codestring微信小程序获取手机号返回的code参数;

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/relate/index.js

module.exports = {
  bindMobileBySms: require('./bind-mobile-by-sms'),
  bindMobileByUniverify: require('./bind-mobile-by-univerify'),
  bindMobileByMpWeixin: require('./bind-mobile-by-mp-weixin'),
  bindAlipay: require('./bind-alipay'),
  bindApple: require('./bind-apple'),
  bindQQ: require('./bind-qq'),
  bindWeixin: require('./bind-weixin'),
  unbindWeixin: require('./unbind-weixin'),
  unbindAlipay: require('./unbind-alipay'),
  unbindQQ: require('./unbind-qq'),
  unbindApple: require('./unbind-apple')
}

/uni-id-co/module/relate/bind-mobile-by-mp-weixin.js

const {preBind,postBind} = require('../../lib/utils/relate')
const {LOG_TYPE} = require('../../common/constants')
const {decryptWeixinData,getWeixinCache, getWeixinAccessToken} = require('../../lib/utils/weixin')
const { initWeixin } = require('../../lib/third-party')
const { ERROR } = require('../../common/error')

/**
 * 通过微信绑定手机号
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-mp-weixin
 * @param {Object} params
 * @param {String} params.encryptedData   微信获取手机号返回的加密信息
 * @param {String} params.iv              微信获取手机号返回的初始向量
 * @param {String} params.code            微信获取手机号返回的code
 * @returns
 */
module.exports = async function (params = {}) {
  /**
   * 微信小程序的规则是客户端应先使用checkSession接口检测上次获取的sessionKey是否仍有效
   * 如果有效则直接使用上次存储的sessionKey即可
   * 如果无效应重新调用login接口再次刷新sessionKey
   * 因此此接口不应直接使用客户端login获取的code,只能使用缓存的sessionKey
   */
  const schema = {
    encryptedData: 	{required: false,type: 'string'},
    iv: 		{required: false,type: 'string'},
    code: 		{required: false,type: 'string'}
  }
  
  const {encryptedData,iv,code} = params
  this.middleware.validate(params, schema)

  if ((!encryptedData && !iv) && !code) {
    return {errCode: ERROR.INVALID_PARAM}
  }

  const uid = this.authInfo.uid

  let mobile
  if (code) {
    // 区分客户端类型 小程序还是App
    const accessToken = await getWeixinAccessToken.call(this)
    const weixinApi = initWeixin.call(this)
    const res = await weixinApi.getPhoneNumber(accessToken, code)

    mobile = res.purePhoneNumber
  } else {
    const sessionKey = await getWeixinCache.call(this, {uid,key: 'session_key'})
    if (!sessionKey) {
      throw new Error('Session key not found')
    }
    const res = decryptWeixinData.call(this, {encryptedData,sessionKey,iv})

    mobile = res.purePhoneNumber
  }

  const bindAccount = {mobile}
  
  await preBind.call(this, {uid,bindAccount,logType: LOG_TYPE.BIND_MOBILE})
  
  await postBind.call(this, {uid,bindAccount,extraData: {mobile_confirmed: 1},logType: LOG_TYPE.BIND_MOBILE})
  
  return {errCode: 0}
}

C、问题

暂无

20、bindWeixin

功能:绑定微信

A、调用端

接口形式:

await uniIdCo.bindWeixin({
	code
})

参数说明:

参数名类型必填说明
codestring微信登录返回的code参数

返回值:

参数名类型说明
errCodestring错误码
errMsgstring错误信息
newTokenobjecttoken信息
tokenstringtoken
tokenExpiredstringtoken过期时间

实例()


B、服务端代码

/uni-id-co/module/relate/index.js

module.exports = {
  bindMobileBySms: require('./bind-mobile-by-sms'),
  bindMobileByUniverify: require('./bind-mobile-by-univerify'),
  bindMobileByMpWeixin: require('./bind-mobile-by-mp-weixin'),
  bindAlipay: require('./bind-alipay'),
  bindApple: require('./bind-apple'),
  bindQQ: require('./bind-qq'),
  bindWeixin: require('./bind-weixin'),
  unbindWeixin: require('./unbind-weixin'),
  unbindAlipay: require('./unbind-alipay'),
  unbindQQ: require('./unbind-qq'),
  unbindApple: require('./unbind-apple')
}

/uni-id-co/module/relate/bind-weixin.js

const {preBind,postBind} = require('../../lib/utils/relate')
const {LOG_TYPE} = require('../../common/constants')
const {generateWeixinCache,saveWeixinUserKey,getWeixinPlatform} = require('../../lib/utils/weixin')
const {initWeixin} = require('../../lib/third-party/index')
const {ERROR} = require('../../common/error')

/**
 * 绑定微信
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-weixin
 * @param {Object} params
 * @param {String} params.code  微信登录返回的code
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {code: 'string'}
  this.middleware.validate(params, schema)
  
  const uid = this.authInfo.uid
  const {code} = params
  const weixinPlatform = getWeixinPlatform.call(this)
  const appId = this.getUniversalClientInfo().appId

  const weixinApi = initWeixin.call(this)
  const clientPlatform = this.clientPlatform
  const apiName = clientPlatform === 'mp-weixin' ? 'code2Session' : 'getOauthAccessToken'
  let getWeixinAccountResult
  try {
    getWeixinAccountResult = await weixinApi[apiName](code)
  } catch (error) {
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.BIND_WEIXIN})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }

  const {openid,unionid,
    sessionKey, // 微信小程序用户sessionKey
    accessToken, // App端微信用户accessToken
    refreshToken, // App端微信用户refreshToken
    expired: accessTokenExpired // App端微信用户accessToken过期时间
  } = getWeixinAccountResult

  const bindAccount = {wx_openid: {[weixinPlatform]: openid},wx_unionid: unionid}
  await preBind.call(this, {uid,bindAccount,logType: LOG_TYPE.BIND_WEIXIN})
  
  await saveWeixinUserKey.call(this, {openid,sessionKey,accessToken,refreshToken,accessTokenExpired})
  
  return postBind.call(this, {uid,bindAccount,
    extraData: {
      wx_openid: {[`${weixinPlatform}_${appId}`]: openid},
      ...generateWeixinCache.call(this, {openid,sessionKey, // 微信小程序用户sessionKey
        accessToken, // App端微信用户accessToken
        refreshToken, // App端微信用户refreshToken
        accessTokenExpired // App端微信用户accessToken过期时间
      })
    },
    logType: LOG_TYPE.BIND_WEIXIN
  })
}

C、问题

暂无

21、bindQQ

功能:绑定QQ

A、调用端

接口形式:

await uniIdCo.bindQQ({
	code,
	accessToken
})

参数说明:

参数名类型必填说明
codestringQQ小程序登录返回的code参数
accessTokenstringApp端QQ登录返回的accessToken参数

返回值:

参数名类型说明

实例()


B、服务端代码

/uni-id-co/module/relate/bind-qq.js

const {preBind,postBind} = require('../../lib/utils/relate')
const {LOG_TYPE} = require('../../common/constants')
const {ERROR} = require('../../common/error')
const {initQQ} = require('../../lib/third-party/index')
const {generateQQCache,getQQPlatform,saveQQUserKey} = require('../../lib/utils/qq')

/**
 * 绑定QQ
 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-qq
 * @param {Object} params
 * @param {String} params.code          小程序端QQ登录返回的code
 * @param {String} params.accessToken   APP端QQ登录返回的accessToken
 * @param {String} params.accessTokenExpired    accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
 * @returns
 */
module.exports = async function (params = {}) {
  const schema = {
    code: 				{required: false,type: 'string'},
    accessToken: 		{required: false,type: 'string'},
    accessTokenExpired: {required: false,type: 'number'}
  }
  this.middleware.validate(params, schema)
  const uid = this.authInfo.uid
  const {code,accessToken,accessTokenExpired} = params
  
  const qqPlatform = getQQPlatform.call(this)
  const appId = this.getUniversalClientInfo().appId
  const qqApi = initQQ.call(this)
  const clientPlatform = this.clientPlatform
  const apiName = clientPlatform === 'mp-qq' ? 'code2Session' : 'getOpenidByToken'
  let getQQAccountResult
  try {
    getQQAccountResult = await qqApi[apiName]({code,accessToken})
  } catch (error) {
    await this.middleware.uniIdLog({success: false,type: LOG_TYPE.BIND_QQ})
    throw {errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED}
  }

  const {openid,unionid,sessionKey // 微信小程序用户sessionKey
  } = getQQAccountResult

  const bindAccount = {qq_openid: {[qqPlatform]: openid},qq_unionid: unionid}
  await preBind.call(this, {uid,bindAccount,logType: LOG_TYPE.BIND_QQ})
  
  await saveQQUserKey.call(this, {openid,sessionKey,accessToken,accessTokenExpired})
  
  return postBind.call(this, {uid,bindAccount,
    extraData: {
      qq_openid: {[`${qqPlatform}_${appId}`]: openid},
      ...generateQQCache.call(this, {openid,sessionKey})
    },
	logType: LOG_TYPE.BIND_QQ
  })
}

…/…/lib/utils/relate

C、问题

暂无

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值