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 }