const API = require("../../lib/API") const db = require("../../plugin/DataBase/db") const AccessControl = require("../../lib/AccessControl") const { BaseStdResponse } = require("../../BaseStdResponse") const EmailTemplate = require("../../plugin/Email/emailTemplate") const { insertLedgerRecord } = require("../../lib/Lepao/CountLedger") class SendCount extends API { constructor() { super() this.setPath("/Goods/SendCount") this.setMethod("POST") } async onRequest(req, res) { let { uuid, session, username, count } = req.body username = typeof username === "string" ? username.trim() : username count = Number(count) if ([uuid, session, username, count].some(v => v == null || v === "" || Number.isNaN(count))) return res.json({ ...BaseStdResponse.MISSING_PARAMETER }) if (!Number.isInteger(count) || count < 1 || count > 9999) return res.json({ ...BaseStdResponse.ERR, msg: "超出赠送的次数范围,请重新选择赠送次数" }) if (!(await AccessControl.checkSession(uuid, session))) return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED }) const conn = await db.connect() // 这里直接拿 connection try { await conn.beginTransaction() const [senderRows] = await conn.execute( "SELECT id, username, lepao_count, COALESCE(send_count_auto_approve, 0) AS send_count_auto_approve FROM users WHERE uuid = ?", [uuid] ) if (!senderRows || senderRows.length !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.MISSING_FILE, msg: "获取用户信息失败!" }) } const [targetRows] = await conn.execute( "SELECT id, uuid FROM users WHERE username = ?", [username] ) if (!targetRows || targetRows.length !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "未找到接收用户,请检查用户名是否正确!" }) } if (targetRows[0].uuid === uuid) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "不能给自己赠送次数!" }) } const [decResult] = await conn.execute( "UPDATE users SET lepao_count = lepao_count - ? WHERE uuid = ? AND lepao_count >= ?", [count, uuid, count] ) if (decResult.affectedRows !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "剩余乐跑次数不足,请购买后再赠送!" }) } const senderLepaoBefore = Number(senderRows[0].lepao_count || 0) const autoApprove = Number(senderRows[0].send_count_auto_approve) === 1 if (autoApprove) { const [recvRows] = await conn.execute( "SELECT uuid, lepao_count FROM users WHERE id = ? FOR UPDATE", [targetRows[0].id] ) if (!recvRows || recvRows.length !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "未找到接收用户,请检查用户名是否正确!" }) } const receiverUuid = recvRows[0].uuid const beforeRecv = Number(recvRows[0].lepao_count || 0) const [incResult] = await conn.execute( "UPDATE users SET lepao_count = lepao_count + ? WHERE id = ?", [count, targetRows[0].id] ) if (!incResult || incResult.affectedRows !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "接收方入账失败,请稍后再试!" }) } const [insertResult] = await conn.execute( `INSERT INTO lepao_send_count_request (sender_uuid, receiver_user_id, count, status, created_at, reviewed_at, reviewer_uuid) VALUES (?, ?, ?, 'approved', NOW(), NOW(), NULL)`, [uuid, targetRows[0].id, count] ) if (!insertResult || insertResult.affectedRows !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "记录赠送失败,请稍后再试!" }) } const requestId = insertResult.insertId await insertLedgerRecord({ executor: conn, userUuid: uuid, delta: -count, balanceBefore: senderLepaoBefore, balanceAfter: senderLepaoBefore - count, bizType: "gift_send_lock", bizId: `send_request:${requestId}`, remark: `向${username}赠送${count}次` }) await insertLedgerRecord({ executor: conn, userUuid: receiverUuid, delta: count, balanceBefore: beforeRecv, balanceAfter: beforeRecv + count, bizType: "gift_receive", bizId: `send_request:${requestId}`, operatorUuid: null, remark: `${senderRows[0].username}赠送${count}次` }) await conn.commit() const reviewTime = new Date().getTime() Promise.resolve().then(async () => { try { const infoSql = ` SELECT ru.email AS receiver_email, ru.username AS receiver_username FROM users ru WHERE ru.id = ? ` const infoRows = await db.query(infoSql, [targetRows[0].id]) if (!infoRows || infoRows.length !== 1 || !infoRows[0].receiver_email) { this.logger.warn(`[SendCountNotify][auto][requestId=${requestId}] 接收人邮箱为空,跳过通知`) return } await EmailTemplate.sendCountRequestApproved(infoRows[0].receiver_email, { requestId, senderUsername: senderRows[0].username, count, reviewTime }) } catch (mailErr) { this.logger.error(`[SendCountNotify][auto][requestId=${requestId}] 接收人通知发送失败:${mailErr.message || "未知错误"}`) } }) return res.json({ ...BaseStdResponse.OK, msg: "赠送成功,对方已到账" }) } const [insertResult] = await conn.execute( `INSERT INTO lepao_send_count_request (sender_uuid, receiver_user_id, count, status, created_at) VALUES (?, ?, ?, 'pending', NOW())`, [uuid, targetRows[0].id, count] ) if (!insertResult || insertResult.affectedRows !== 1) { await conn.rollback() return res.json({ ...BaseStdResponse.ERR, msg: "提交赠送审核失败,请稍后再试!" }) } const requestId = insertResult.insertId await insertLedgerRecord({ executor: conn, userUuid: uuid, delta: -count, balanceBefore: senderLepaoBefore, balanceAfter: senderLepaoBefore - count, bizType: "gift_send_lock", bizId: `send_request:${requestId}`, remark: `向${username}赠送${count}次` }) await conn.commit() const createTime = new Date().getTime() // 非阻塞通知管理员,不影响主业务流程 Promise.resolve().then(async () => { try { const adminSql = ` SELECT email FROM users WHERE email IS NOT NULL AND email <> '' AND (JSON_CONTAINS(permission, '"admin"') OR JSON_CONTAINS(permission, '"service"')) ` const adminRows = await db.query(adminSql) if (!adminRows || adminRows.length === 0) { this.logger.warn(`[SendCountNotify][submit][requestId=${requestId}] 未找到可通知的管理员邮箱`) return } const emails = [...new Set(adminRows.map(row => row.email).filter(Boolean))] for (const email of emails) { await EmailTemplate.sendCountRequestNotifyAdmins(email, { requestId, senderUsername: senderRows[0].username, receiverUsername: username, count, createTime }) } } catch (mailErr) { this.logger.error(`[SendCountNotify][submit][requestId=${requestId}] 管理员通知发送失败:${mailErr.message || "未知错误"}`) } }) return res.json({ ...BaseStdResponse.OK, msg: "已提交审核,审核通过后接收方将到账" }) } catch (err) { try { await conn.rollback() } catch (_) { } this.logger.error(`赠送乐跑次数失败!${err.message || "未知错误"}`) return res.json({ ...BaseStdResponse.ERR, msg: `赠送次数失败,请稍后再试!` }) } finally { if (conn?.connection && typeof conn.connection.release === 'function' && typeof conn?.release === 'function') { conn.release() } } } } module.exports.SendCount = SendCount