|
|
@@ -1,5 +1,6 @@
|
|
|
const API = require("../../../lib/API.js");
|
|
|
const db = require("../../../plugin/DataBase/db.js");
|
|
|
+const Redis = require("../../../plugin/DataBase/Redis.js");
|
|
|
const { BaseStdResponse } = require("../../../BaseStdResponse.js");
|
|
|
const AccessControl = require("../../../lib/AccessControl.js");
|
|
|
const { insertBindAudit, BindAuditAction, BindAuditSource } = require("../../../lib/Lepao/BindAudit.js");
|
|
|
@@ -13,6 +14,7 @@ class AddAccount extends API {
|
|
|
|
|
|
this.emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
|
this.banEmailList = ['icloud.com']
|
|
|
+ this.autoUnbindDailyLimit = 10
|
|
|
}
|
|
|
|
|
|
// 生成 6 位数字 + 字母混合码
|
|
|
@@ -36,6 +38,31 @@ class AddAccount extends API {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ getSemesterStartTimestamp() {
|
|
|
+ const now = new Date()
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const feb1ThisYear = new Date(year, 1, 1, 0, 0, 0, 0)
|
|
|
+ const aug31ThisYear = new Date(year, 7, 31, 0, 0, 0, 0)
|
|
|
+ if (now >= feb1ThisYear && now < aug31ThisYear) {
|
|
|
+ return feb1ThisYear.getTime()
|
|
|
+ }
|
|
|
+ return new Date(now < feb1ThisYear ? year - 1 : year, 7, 31, 0, 0, 0, 0).getTime()
|
|
|
+ }
|
|
|
+
|
|
|
+ getAutoUnbindDailyRedisKey() {
|
|
|
+ const now = new Date()
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const month = `${now.getMonth() + 1}`.padStart(2, '0')
|
|
|
+ const day = `${now.getDate()}`.padStart(2, '0')
|
|
|
+ return `lepao:auto_unbind:daily:${year}${month}${day}`
|
|
|
+ }
|
|
|
+
|
|
|
+ getSecondsToDayEnd() {
|
|
|
+ const now = new Date()
|
|
|
+ const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0)
|
|
|
+ return Math.max(1, Math.floor((tomorrow.getTime() - now.getTime()) / 1000))
|
|
|
+ }
|
|
|
+
|
|
|
async onRequest(req, res) {
|
|
|
let { uuid, session, student_num, email, id, area, auto_time, auto_run, target_count, auto_day, notice_type, notes } = req.body
|
|
|
|
|
|
@@ -78,7 +105,7 @@ class AddAccount extends API {
|
|
|
...BaseStdResponse.ACCESS_DENIED
|
|
|
})
|
|
|
|
|
|
- let countSql = 'SELECT id, create_user, total_num FROM lepao_account WHERE student_num = ?'
|
|
|
+ let countSql = 'SELECT id, create_user, total_num, auto_run, update_time FROM lepao_account WHERE student_num = ?'
|
|
|
let countRows = await db.query(countSql, [student_num])
|
|
|
|
|
|
if (!countRows)
|
|
|
@@ -87,9 +114,18 @@ class AddAccount extends API {
|
|
|
// 判断是否重复注册
|
|
|
if (!id) {
|
|
|
if (countRows.length !== 0 && countRows[0].create_user != null) {
|
|
|
- if (countRows[0].create_user !== uuid)
|
|
|
- return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号已被其他用户绑定,请联系客服解绑' })
|
|
|
- return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号您已绑定' })
|
|
|
+ if (countRows[0].create_user !== uuid) {
|
|
|
+ const semesterStartTimestamp = this.getSemesterStartTimestamp()
|
|
|
+ const dailyAutoUnbindKey = this.getAutoUnbindDailyRedisKey()
|
|
|
+ const dailyAutoUnbindCount = Number(await Redis.get(dailyAutoUnbindKey) || 0)
|
|
|
+ const canAutoUnbindAndRebind = (countRows[0].auto_run === 0) &&
|
|
|
+ (!countRows[0].update_time || countRows[0].update_time < semesterStartTimestamp) &&
|
|
|
+ (dailyAutoUnbindCount < this.autoUnbindDailyLimit)
|
|
|
+ if (!canAutoUnbindAndRebind)
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号已被其他用户绑定,请联系客服处理' })
|
|
|
+ } else {
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号您已绑定' })
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -100,6 +136,10 @@ class AddAccount extends API {
|
|
|
|
|
|
const time = new Date().getTime()
|
|
|
const previousOwner = countRows.length !== 0 ? countRows[0].create_user : null
|
|
|
+ const shouldAutoUnbindAndRebind = !id &&
|
|
|
+ countRows.length !== 0 &&
|
|
|
+ previousOwner != null &&
|
|
|
+ previousOwner !== uuid
|
|
|
const shouldRecordBind = !id && previousOwner !== uuid
|
|
|
|
|
|
let sql, r
|
|
|
@@ -154,6 +194,26 @@ class AddAccount extends API {
|
|
|
})
|
|
|
|
|
|
if (shouldRecordBind) {
|
|
|
+ if (shouldAutoUnbindAndRebind) {
|
|
|
+ const unbindAuditOk = await insertBindAudit({
|
|
|
+ studentNum: student_num,
|
|
|
+ ownerUuid: previousOwner,
|
|
|
+ action: BindAuditAction.PLATFORM_UNBIND,
|
|
|
+ source: BindAuditSource.USER_API,
|
|
|
+ operatorUuid: uuid,
|
|
|
+ detail: { via: 'AddAccount:auto_unbind_rebind' },
|
|
|
+ createdAt: time
|
|
|
+ })
|
|
|
+ if (!unbindAuditOk) {
|
|
|
+ this.logger.warn(`自动解绑审计写入失败 student_num=${student_num}`)
|
|
|
+ } else {
|
|
|
+ const dailyAutoUnbindKey = this.getAutoUnbindDailyRedisKey()
|
|
|
+ const latestAutoUnbindCount = await Redis.incr(dailyAutoUnbindKey)
|
|
|
+ if (latestAutoUnbindCount === 1) {
|
|
|
+ await Redis.expire(dailyAutoUnbindKey, this.getSecondsToDayEnd())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
const auditOk = await insertBindAudit({
|
|
|
studentNum: student_num,
|
|
|
ownerUuid: uuid,
|