AddAccount.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. const API = require("../../../lib/API.js");
  2. const db = require("../../../plugin/DataBase/db.js");
  3. const { BaseStdResponse } = require("../../../BaseStdResponse.js");
  4. const AccessControl = require("../../../lib/AccessControl.js");
  5. const { getJkesSettings } = require("../../../plugin/jkes/jkesSettings.js");
  6. const { isJkesRunZone, normalizeJkesRunZone } = require("../../../plugin/jkes/runZones.js");
  7. const {
  8. DEFAULT_AUTO_RUN_DISTANCE_KM,
  9. validateAutoRunPresetForSave
  10. } = require("../../../plugin/jkes/autoRunAccountOptions.js");
  11. const { resolveQQAvatarFromEmail } = require("../../../plugin/jkes/resolveQQAvatar.js");
  12. function pickBodyOrDb(bodyVal, dbVal, fallback) {
  13. if (bodyVal !== undefined && bodyVal !== null && bodyVal !== "") return bodyVal;
  14. if (dbVal !== undefined && dbVal !== null && dbVal !== "") return dbVal;
  15. return fallback;
  16. }
  17. class AddAccount extends API {
  18. constructor() {
  19. super();
  20. this.setPath('/Lepao/Account')
  21. this.setMethod('POST')
  22. this.emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
  23. this.banEmailList = ['icloud.com']
  24. }
  25. // 生成 6 位数字 + 字母混合码
  26. async generateCode() {
  27. try {
  28. const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  29. let code = ""
  30. for (let i = 0; i < 6; i++) {
  31. code += chars.charAt(Math.floor(Math.random() * chars.length))
  32. }
  33. let sql = 'SELECT id FROM lepao_extra WHERE bind_code = ?'
  34. let rows = await db.query(sql, [code])
  35. if (!rows)
  36. throw new Error('数据库错误,请稍后再试')
  37. if (rows.length > 0)
  38. return await this.generateCode()
  39. return code
  40. } catch (error) {
  41. throw error
  42. }
  43. }
  44. async onRequest(req, res) {
  45. let {
  46. uuid,
  47. session,
  48. student_num,
  49. email,
  50. id,
  51. area,
  52. auto_time,
  53. auto_run,
  54. target_count,
  55. auto_day,
  56. notice_type,
  57. notes,
  58. auto_run_distance_km,
  59. auto_run_distance_min_km,
  60. auto_run_distance_max_km,
  61. pace_min_sec_per_km,
  62. pace_max_sec_per_km
  63. } = req.body
  64. if ([uuid, session, student_num, auto_time, target_count, auto_day].some(value => value === '' || value === null || value === undefined))
  65. return res.json({
  66. ...BaseStdResponse.MISSING_PARAMETER
  67. })
  68. const targetKm = Number(target_count)
  69. if (!Number.isFinite(targetKm) || targetKm < 0 || targetKm > 200) {
  70. return res.json({
  71. ...BaseStdResponse.ERR,
  72. msg: '乐跑目标里程(公里)不在合法范围内(0–200)'
  73. })
  74. }
  75. if (area !== undefined && area !== null && area !== '' && !isJkesRunZone(area)) {
  76. return res.json({
  77. ...BaseStdResponse.ERR,
  78. msg: '跑区无效,请选择学府大道校区或南山校区'
  79. })
  80. }
  81. area = normalizeJkesRunZone(area)
  82. if (notice_type === 'email') {
  83. if (!this.emailRegex.test(email)) {
  84. return res.json({
  85. ...BaseStdResponse.ERR,
  86. msg: '请检查邮箱格式是否正确'
  87. })
  88. }
  89. const emailDomain = email.split('@')[1].toLowerCase()
  90. if (this.banEmailList.includes(emailDomain))
  91. return res.json({
  92. ...BaseStdResponse.ERR,
  93. msg: `暂不支持使用 ${emailDomain} 域名的邮箱,请更换其他邮箱后重试`
  94. })
  95. }
  96. if (auto_run === 1 && (!Array.isArray(auto_day) || !auto_day.every(v => Number.isInteger(v) && v >= 0 && v <= 6)))
  97. return res.json({
  98. ...BaseStdResponse.ERR,
  99. msg: '自动乐跑日期格式不合法'
  100. })
  101. if (!await AccessControl.checkSession(uuid, session))
  102. return res.status(401).json({
  103. ...BaseStdResponse.ACCESS_DENIED
  104. })
  105. let mergeForPreset = null
  106. if (id) {
  107. const ownerRows = await db.query(
  108. "SELECT auto_run_distance_min_km, auto_run_distance_max_km, pace_min_sec_per_km, pace_max_sec_per_km FROM lepao_account WHERE id = ? AND create_user = ?",
  109. [id, uuid]
  110. );
  111. if (!ownerRows || ownerRows.length === 0) {
  112. return res.json({ ...BaseStdResponse.ERR, msg: "未找到该乐跑账号或无权限编辑" });
  113. }
  114. mergeForPreset = ownerRows[0];
  115. }
  116. let countSql =
  117. "SELECT id, create_user, total_num, term_num, auto_run_distance_min_km, auto_run_distance_max_km, pace_min_sec_per_km, pace_max_sec_per_km FROM lepao_account WHERE student_num = ?";
  118. let countRows = await db.query(countSql, [student_num])
  119. if (!countRows)
  120. return res.json({ ...BaseStdResponse.ERR, msg: '添加乐跑账号失败!数据库错误' })
  121. if (!id && countRows.length > 0) {
  122. mergeForPreset = countRows[0];
  123. }
  124. const cfg = getJkesSettings();
  125. const paceDefLo = cfg.paceRandomMinSecPerKm ?? 180;
  126. const paceDefHi = cfg.paceRandomMaxSecPerKm ?? 600;
  127. const minMissing =
  128. auto_run_distance_min_km === undefined ||
  129. auto_run_distance_min_km === null ||
  130. auto_run_distance_min_km === ''
  131. const maxMissing =
  132. auto_run_distance_max_km === undefined ||
  133. auto_run_distance_max_km === null ||
  134. auto_run_distance_max_km === ''
  135. const legacyOnly =
  136. minMissing &&
  137. maxMissing &&
  138. auto_run_distance_km !== undefined &&
  139. auto_run_distance_km !== null &&
  140. auto_run_distance_km !== ''
  141. const presetBody = legacyOnly
  142. ? {
  143. auto_run_distance_km,
  144. pace_min_sec_per_km: pickBodyOrDb(
  145. pace_min_sec_per_km,
  146. mergeForPreset?.pace_min_sec_per_km,
  147. paceDefLo
  148. ),
  149. pace_max_sec_per_km: pickBodyOrDb(
  150. pace_max_sec_per_km,
  151. mergeForPreset?.pace_max_sec_per_km,
  152. paceDefHi
  153. )
  154. }
  155. : {
  156. auto_run_distance_min_km: pickBodyOrDb(
  157. auto_run_distance_min_km,
  158. mergeForPreset?.auto_run_distance_min_km,
  159. DEFAULT_AUTO_RUN_DISTANCE_KM
  160. ),
  161. auto_run_distance_max_km: pickBodyOrDb(
  162. auto_run_distance_max_km,
  163. mergeForPreset?.auto_run_distance_max_km,
  164. DEFAULT_AUTO_RUN_DISTANCE_KM
  165. ),
  166. pace_min_sec_per_km: pickBodyOrDb(
  167. pace_min_sec_per_km,
  168. mergeForPreset?.pace_min_sec_per_km,
  169. paceDefLo
  170. ),
  171. pace_max_sec_per_km: pickBodyOrDb(
  172. pace_max_sec_per_km,
  173. mergeForPreset?.pace_max_sec_per_km,
  174. paceDefHi
  175. )
  176. }
  177. const preset = validateAutoRunPresetForSave(presetBody);
  178. if (!preset.ok) {
  179. return res.json({ ...BaseStdResponse.ERR, msg: preset.msg });
  180. }
  181. // 判断是否重复注册
  182. if (!id) {
  183. if (countRows.length !== 0 && countRows[0].create_user != null) {
  184. if (countRows[0].create_user !== uuid)
  185. return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号已被其他用户绑定,请联系客服解绑' })
  186. return res.json({ ...BaseStdResponse.ERR, msg: '该乐跑账号您已绑定' })
  187. }
  188. }
  189. if (countRows.length !== 0) {
  190. if (auto_run === 1 && countRows[0].term_num >= targetKm && targetKm !== 0)
  191. return res.json({
  192. ...BaseStdResponse.ERR,
  193. msg: '该账号本月跑步里程已达到或超过预设目标里程,请增大目标后再试'
  194. })
  195. }
  196. const time = new Date().getTime()
  197. let sql, r, user_avatar
  198. // 生成用户头像
  199. user_avatar = resolveQQAvatarFromEmail(email)
  200. if (!user_avatar) {
  201. let userSex = 2;
  202. const urows = await db.query("SELECT sex FROM users WHERE uuid = ? LIMIT 1", [uuid]);
  203. if (urows?.[0]?.sex === 1) userSex = 1;
  204. user_avatar =
  205. userSex === 1
  206. ? "https://lepao-cloud.xxoo365.top/view.php/aee85ff43fd30d0df03c6a7dd9797d22.png"
  207. : "https://lepao-cloud.xxoo365.top/view.php/fcb54dcc5e6209381e972ef73bdb4a93.png";
  208. }
  209. if (!id) {
  210. if (countRows.length !== 0) {
  211. sql = "UPDATE lepao_account SET create_user = ?, email = ?, user_avatar = ?, area = ?, auto_time = ?, auto_run = ?, target_count = ?, create_time = ?, update_time = ?, notes = ?, auto_day = ?, notice_type = ?, auto_run_distance_min_km = ?, auto_run_distance_max_km = ?, pace_min_sec_per_km = ?, pace_max_sec_per_km = ? WHERE id = ?";
  212. r = await db.query(sql, [
  213. uuid,
  214. email ?? "",
  215. user_avatar ?? "",
  216. area,
  217. auto_time,
  218. auto_run,
  219. targetKm,
  220. time,
  221. time,
  222. notes ?? "",
  223. JSON.stringify(auto_day),
  224. notice_type,
  225. preset.autoRunDistanceMinKm,
  226. preset.autoRunDistanceMaxKm,
  227. preset.paceMinSecPerKm,
  228. preset.paceMaxSecPerKm,
  229. countRows[0].id
  230. ]);
  231. }
  232. else {
  233. const bind_code = await this.generateCode()
  234. sql = "INSERT INTO lepao_account (student_num, email, user_avatar, area, auto_time, auto_run, target_count, create_user, create_time, notes, auto_day, notice_type, auto_run_distance_min_km, auto_run_distance_max_km, pace_min_sec_per_km, pace_max_sec_per_km) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
  235. r = await db.query(sql, [
  236. student_num,
  237. email ?? "",
  238. user_avatar ?? "",
  239. area,
  240. auto_time,
  241. auto_run,
  242. targetKm,
  243. uuid,
  244. time,
  245. notes ?? "",
  246. JSON.stringify(auto_day),
  247. notice_type,
  248. preset.autoRunDistanceMinKm,
  249. preset.autoRunDistanceMaxKm,
  250. preset.paceMinSecPerKm,
  251. preset.paceMaxSecPerKm
  252. ]);
  253. let faceSql = 'INSERT INTO lepao_extra (student_num, bind_code) VALUES (?, ?)'
  254. let faceRows = await db.query(faceSql, [student_num, bind_code])
  255. if (!faceRows || faceRows.affectedRows !== 1)
  256. return res.json({ ...BaseStdResponse.ERR, msg: '添加乐跑账号失败!数据库错误' })
  257. }
  258. } else {
  259. sql = "UPDATE lepao_account SET student_num = ?, email = ?, user_avatar = ?, area = ?, auto_time = ?, target_count = ?, auto_run = ?, notes = ?, auto_day = ?, update_time = ?, notice_type = ?, auto_run_distance_min_km = ?, auto_run_distance_max_km = ?, pace_min_sec_per_km = ?, pace_max_sec_per_km = ? WHERE id = ? AND create_user = ?";
  260. r = await db.query(sql, [
  261. student_num,
  262. email ?? "",
  263. user_avatar ?? "",
  264. area,
  265. auto_time,
  266. targetKm,
  267. auto_run,
  268. notes ?? "",
  269. JSON.stringify(auto_day),
  270. time,
  271. notice_type,
  272. preset.autoRunDistanceMinKm,
  273. preset.autoRunDistanceMaxKm,
  274. preset.paceMinSecPerKm,
  275. preset.paceMaxSecPerKm,
  276. id,
  277. uuid
  278. ]);
  279. }
  280. try {
  281. if (r && r.affectedRows > 0) {
  282. const selectSql = `
  283. SELECT
  284. a.id, a.create_user, a.total_num, e.bind_code, e.bot_account
  285. FROM
  286. lepao_account a
  287. LEFT JOIN
  288. lepao_extra e
  289. ON
  290. a.student_num = e.student_num
  291. WHERE
  292. a.student_num = ?
  293. `
  294. const selectRows = await db.query(selectSql, [student_num])
  295. if (!selectRows)
  296. return res.json({ ...BaseStdResponse.ERR, msg: '添加乐跑账号失败!数据库错误' })
  297. res.json({
  298. ...BaseStdResponse.OK,
  299. id: r.insertId,
  300. data: {
  301. student_num,
  302. email,
  303. id,
  304. area,
  305. auto_time,
  306. auto_run,
  307. target_count: targetKm,
  308. auto_day,
  309. notice_type,
  310. notes,
  311. auto_run_distance_min_km: preset.autoRunDistanceMinKm,
  312. auto_run_distance_max_km: preset.autoRunDistanceMaxKm,
  313. pace_min_sec_per_km: preset.paceMinSecPerKm,
  314. pace_max_sec_per_km: preset.paceMaxSecPerKm,
  315. bind_code: selectRows.length !== 0 ? selectRows[0].bind_code : undefined,
  316. bot_account: selectRows.length !== 0 ? selectRows[0].bot_account : undefined
  317. }
  318. })
  319. } else {
  320. return res.json({ ...BaseStdResponse.ERR, msg: '添加乐跑账号失败!数据库错误' })
  321. }
  322. } catch (err) {
  323. this.logger.error(`添加乐跑账号失败!${err.stack}`)
  324. res.json({
  325. ...BaseStdResponse.ERR,
  326. msg: "添加乐跑账号失败!",
  327. });
  328. }
  329. }
  330. }
  331. module.exports.AddAccount = AddAccount;