zhaoxing
2023-05-17 2e0814719b06ae7b86dfa48093ab4edb6eaad6b3
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
}