fission.js 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
const {
  dbCmd,
  userCollection
} = require('../../common/constants')
const {
  ERROR
} = require('../../common/error')
/**
 * 获取随机邀请码,邀请码由大写字母加数字组成,由于存在手动输入邀请码的场景,从可选字符中去除 0、1、I、O
 * @param {number} len 邀请码长度,默认6位
 * @returns {string} 随机邀请码
 */
function getRandomInviteCode (len = 6) {
  const charArr = ['2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  let code = ''
  for (let i = 0; i < len; i++) {
    code += charArr[Math.floor(Math.random() * charArr.length)]
  }
  return code
}

/**
 * 获取可用的邀请码,至多尝试十次以获取可用邀请码。从10亿可选值中随机,碰撞概率较低
 * 也有其他方案可以尝试,比如在数据库内设置一个从0开始计数的数字,每次调用此方法时使用updateAndReturn使数字加1并返回加1后的值,根据这个值生成对应的邀请码,比如(22222A + 1 == 22222B),此方式性能理论更好,但是不适用于旧项目
 * @param {object} param
 * @param {string} param.inviteCode 初始随机邀请码
 */
async function getValidInviteCode () {
  let retry = 10
  let code
  let codeValid = false
  while (retry > 0 && !codeValid) {
    retry--
    code = getRandomInviteCode()
    const getUserRes = await userCollection.where({
      my_invite_code: code
    }).limit(1).get()
    if (getUserRes.data.length === 0) {
      codeValid = true
      break
    }
  }
  if (!codeValid) {
    throw {
      errCode: ERROR.SET_INVITE_CODE_FAILED
    }
  }
  return code
}

/**
 * 根据邀请码查询邀请人
 * @param {object} param
 * @param {string} param.inviteCode 邀请码
 * @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
 * @returns
 */
async function findUserByInviteCode ({
  inviteCode,
  queryUid
} = {}) {
  if (typeof inviteCode !== 'string') {
    throw {
      errCode: ERROR.SYSTEM_ERROR
    }
  }
  // 根据邀请码查询邀请人
  let getInviterRes
  if (queryUid) {
    getInviterRes = await userCollection.where({
      _id: dbCmd.neq(queryUid),
      inviter_uid: dbCmd.not(dbCmd.all([queryUid])),
      my_invite_code: inviteCode
    }).get()
  } else {
    getInviterRes = await userCollection.where({
      my_invite_code: inviteCode
    }).get()
  }
  if (getInviterRes.data.length > 1) {
    // 正常情况下不可能进入此条件,以防用户自行修改数据库出错,在此做出判断
    throw {
      errCode: ERROR.SYSTEM_ERROR
    }
  }
  const inviterRecord = getInviterRes.data[0]
  if (!inviterRecord) {
    throw {
      errCode: ERROR.INVALID_INVITE_CODE
    }
  }
  return inviterRecord
}

/**
 * 根据邀请码生成邀请信息
 * @param {object} param
 * @param {string} param.inviteCode 邀请码
 * @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
 * @returns
 */
async function generateInviteInfo ({
  inviteCode,
  queryUid
} = {}) {
  const inviterRecord = await findUserByInviteCode({
    inviteCode,
    queryUid
  })
  // 倒叙拼接当前用户邀请链
  const inviterUid = inviterRecord.inviter_uid || []
  inviterUid.unshift(inviterRecord._id)
  return {
    inviterUid,
    inviteTime: Date.now()
  }
}

/**
 * 检查当前用户是否可以接受邀请,如果可以返回用户记录
 * @param {string} uid
 */
async function checkInviteInfo (uid) {
  // 检查当前用户是否已有邀请人
  const getUserRes = await userCollection.doc(uid).field({
    my_invite_code: true,
    inviter_uid: true
  }).get()
  const userRecord = getUserRes.data[0]
  if (!userRecord) {
    throw {
      errCode: ERROR.ACCOUNT_NOT_EXISTS
    }
  }
  if (userRecord.inviter_uid && userRecord.inviter_uid.length > 0) {
    throw {
      errCode: ERROR.CHANGE_INVITER_FORBIDDEN
    }
  }
  return userRecord
}

/**
 * 指定用户接受邀请码邀请
 * @param {object} param
 * @param {string} param.uid 用户uid
 * @param {string} param.inviteCode 邀请人的邀请码
 * @returns
 */
async function acceptInvite ({
  uid,
  inviteCode
} = {}) {
  await checkInviteInfo(uid)
  const {
    inviterUid,
    inviteTime
  } = await generateInviteInfo({
    inviteCode,
    queryUid: uid
  })

  if (inviterUid === uid) {
    throw {
      errCode: ERROR.INVALID_INVITE_CODE
    }
  }

  // 更新当前用户的邀请人信息
  await userCollection.doc(uid).update({
    inviter_uid: inviterUid,
    invite_time: inviteTime
  })

  // 更新当前用户邀请的用户的邀请人信息,这步可能较为耗时
  await userCollection.where({
    inviter_uid: uid
  }).update({
    inviter_uid: dbCmd.push(inviterUid)
  })

  return {
    errCode: 0,
    errMsg: ''
  }
}

module.exports = {
  acceptInvite,
  generateInviteInfo,
  getValidInviteCode
}