RejectSendCountRequest.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. const API = require("../../../lib/API")
  2. const db = require("../../../plugin/DataBase/db")
  3. const AccessControl = require("../../../lib/AccessControl")
  4. const { BaseStdResponse } = require("../../../BaseStdResponse")
  5. const EmailTemplate = require("../../../plugin/Email/emailTemplate")
  6. const { insertLedgerRecord } = require("../../../lib/Lepao/CountLedger")
  7. class RejectSendCountRequest extends API {
  8. constructor() {
  9. super()
  10. this.setPath("/Admin/Goods/SendCountRequest/Reject")
  11. this.setMethod("POST")
  12. }
  13. async onRequest(req, res) {
  14. let { uuid, session, id, reject_reason } = req.body
  15. id = Number(id)
  16. reject_reason = typeof reject_reason === "string" ? reject_reason.trim() : ""
  17. if ([uuid, session, id].some(value => value === "" || value === null || value === undefined || Number.isNaN(id)))
  18. return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
  19. if (!Number.isInteger(id) || id <= 0)
  20. return res.json({ ...BaseStdResponse.ERR, msg: "参数错误" })
  21. if (reject_reason.length > 255)
  22. return res.json({ ...BaseStdResponse.ERR, msg: "拒绝原因过长,请控制在255字以内!" })
  23. if (!await AccessControl.checkSession(uuid, session))
  24. return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED })
  25. if (!await AccessControl.canAccess(uuid, ['action.goods.reviewSendCount']))
  26. return res.json({ ...BaseStdResponse.PERMISSION_DENIED })
  27. const conn = await db.connect()
  28. try {
  29. await conn.beginTransaction()
  30. const [requestRows] = await conn.execute(
  31. `SELECT id, sender_uuid, receiver_user_id, count, status
  32. FROM lepao_send_count_request
  33. WHERE id = ?
  34. FOR UPDATE`,
  35. [id]
  36. )
  37. if (!requestRows || requestRows.length !== 1) {
  38. await conn.rollback()
  39. return res.json({ ...BaseStdResponse.MISSING_FILE, msg: "未找到赠送申请记录!" })
  40. }
  41. const request = requestRows[0]
  42. if (request.status !== "pending") {
  43. await conn.rollback()
  44. return res.json({ ...BaseStdResponse.ERR, msg: "该申请已审核,请刷新后重试!" })
  45. }
  46. const [senderRows] = await conn.execute(
  47. "SELECT lepao_count FROM users WHERE uuid = ? FOR UPDATE",
  48. [request.sender_uuid]
  49. )
  50. if (!senderRows || senderRows.length !== 1) {
  51. await conn.rollback()
  52. return res.json({ ...BaseStdResponse.ERR, msg: "赠送人不存在,退回次数失败!" })
  53. }
  54. const beforeCount = Number(senderRows[0].lepao_count || 0)
  55. const [refundResult] = await conn.execute(
  56. "UPDATE users SET lepao_count = lepao_count + ? WHERE uuid = ?",
  57. [request.count, request.sender_uuid]
  58. )
  59. if (!refundResult || refundResult.affectedRows !== 1) {
  60. await conn.rollback()
  61. return res.json({ ...BaseStdResponse.ERR, msg: "赠送人不存在,退回次数失败!" })
  62. }
  63. const [updateResult] = await conn.execute(
  64. `UPDATE lepao_send_count_request
  65. SET status = 'rejected', reviewer_uuid = ?, reviewed_at = NOW(), reject_reason = ?
  66. WHERE id = ?`,
  67. [uuid, reject_reason || null, id]
  68. )
  69. if (!updateResult || updateResult.affectedRows !== 1) {
  70. await conn.rollback()
  71. return res.json({ ...BaseStdResponse.ERR, msg: "更新审核状态失败,请稍后再试!" })
  72. }
  73. await insertLedgerRecord({
  74. executor: conn,
  75. userUuid: request.sender_uuid,
  76. delta: Number(request.count || 0),
  77. balanceBefore: beforeCount,
  78. balanceAfter: beforeCount + Number(request.count || 0),
  79. bizType: 'gift_send_refund',
  80. bizId: `send_request:${id}`,
  81. operatorUuid: uuid,
  82. remark: reject_reason || ''
  83. })
  84. await conn.commit()
  85. const requestId = request.id
  86. const reviewTime = new Date().getTime()
  87. // 非阻塞通知赠送人,不影响审核结果
  88. Promise.resolve().then(async () => {
  89. try {
  90. const infoSql = `
  91. SELECT
  92. su.email AS sender_email,
  93. su.username AS sender_username,
  94. ru.username AS receiver_username
  95. FROM users su
  96. LEFT JOIN users ru ON ru.id = ?
  97. WHERE su.uuid = ?
  98. `
  99. const infoRows = await db.query(infoSql, [request.receiver_user_id, request.sender_uuid])
  100. if (!infoRows || infoRows.length !== 1 || !infoRows[0].sender_email) {
  101. this.logger.warn(`[SendCountNotify][reject][requestId=${requestId}] 赠送人邮箱为空或用户不存在,跳过通知`)
  102. return
  103. }
  104. await EmailTemplate.sendCountRequestRejected(infoRows[0].sender_email, {
  105. requestId,
  106. receiverUsername: infoRows[0].receiver_username || "未知用户",
  107. count: request.count,
  108. reviewTime,
  109. rejectReason: reject_reason || "无"
  110. })
  111. } catch (mailErr) {
  112. this.logger.error(`[SendCountNotify][reject][requestId=${requestId}] 赠送人通知发送失败:${mailErr.message || "未知错误"}`)
  113. }
  114. })
  115. return res.json({ ...BaseStdResponse.OK, msg: "已拒绝该赠送申请" })
  116. } catch (err) {
  117. try { await conn.rollback() } catch (_) { }
  118. this.logger.error(`拒绝赠送申请失败!${err.message || "未知错误"}`)
  119. return res.json({ ...BaseStdResponse.ERR, msg: "拒绝申请失败,请稍后再试!" })
  120. } finally {
  121. if (conn?.connection && typeof conn.connection.release === 'function' && typeof conn?.release === 'function') {
  122. conn.release()
  123. }
  124. }
  125. }
  126. }
  127. module.exports.RejectSendCountRequest = RejectSendCountRequest