const {getDB, initDB} = require('./database'); const {logger} = require('./config/logger'); const fs = require('fs'); const path = require('path'); /** * 余额一致性审计工具 * 用于检查所有用户的余额是否与转账记录一致 */ class BalanceAuditor { constructor() { this.db = null; this.auditResults = { totalUsers: 0, problematicUsers: [], summary: { totalDiscrepancy: 0, usersWithPositiveDiscrepancy: 0, usersWithNegativeDiscrepancy: 0, maxDiscrepancy: 0, minDiscrepancy: 0 } }; } /** * 初始化数据库连接 */ async init() { await initDB(); this.db = getDB(); console.log('数据库连接已初始化'); } /** * 计算用户的理论余额 * @param {number} userId - 用户ID * @returns {Object} 包含收入、支出和理论余额的对象 */ async calculateUserBalance(userId) { // 查询所有已确认和已收到的转账记录 const [transfers] = await this.db.execute(` SELECT from_user_id, to_user_id, amount, status, transfer_type FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) AND status IN ('confirmed', 'received') `, [userId, userId]); let totalReceived = 0; let totalSent = 0; transfers.forEach(transfer => { const amount = parseFloat(transfer.amount); // 计算收入(作为接收方的转账) if (transfer.to_user_id === userId) { totalReceived += amount; } // 计算支出(作为发送方的转账) if (transfer.from_user_id === userId) { totalSent += amount; } }); return { totalReceived, totalSent, theoreticalBalance: totalReceived - totalSent, transferCount: transfers.length }; } /** * 分析用户的问题转账记录 * @param {number} userId - 用户ID * @returns {Object} 问题转账分析结果 */ async analyzeProblematicTransfers(userId) { // 查询被拒绝、取消的转账 const [problemTransfers] = await this.db.execute(` SELECT id, from_user_id, to_user_id, amount, status, admin_modified_at, admin_modified_by, admin_note FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) AND status IN ('rejected', 'cancelled', 'not_received') ORDER BY admin_modified_at DESC `, [userId, userId]); let shouldBeRefunded = 0; const problemDetails = []; problemTransfers.forEach(transfer => { const amount = parseFloat(transfer.amount); if (transfer.from_user_id === userId) { shouldBeRefunded += amount; problemDetails.push({ transferId: transfer.id, amount: amount, status: transfer.status, modifiedAt: transfer.admin_modified_at, modifiedBy: transfer.admin_modified_by, note: transfer.admin_note }); } }); return { shouldBeRefunded, problemCount: problemDetails.length, details: problemDetails }; } /** * 审计单个用户的余额 * @param {Object} user - 用户对象 * @returns {Object} 审计结果 */ async auditUser(user) { const userId = user.id; const actualBalance = parseFloat(user.balance); // 计算理论余额 const balanceCalc = await this.calculateUserBalance(userId); // 分析问题转账 const problemAnalysis = await this.analyzeProblematicTransfers(userId); // 计算调整后的理论余额(考虑应退还金额) const adjustedTheoreticalBalance = balanceCalc.theoreticalBalance + problemAnalysis.shouldBeRefunded; // 计算差异 const discrepancy = actualBalance - adjustedTheoreticalBalance; const result = { userId: userId, username: user.username, realName: user.real_name, role: user.role, actualBalance: actualBalance, theoreticalBalance: balanceCalc.theoreticalBalance, adjustedTheoreticalBalance: adjustedTheoreticalBalance, discrepancy: discrepancy, totalReceived: balanceCalc.totalReceived, totalSent: balanceCalc.totalSent, transferCount: balanceCalc.transferCount, shouldBeRefunded: problemAnalysis.shouldBeRefunded, problemTransferCount: problemAnalysis.problemCount, problemDetails: problemAnalysis.details, isProblematic: Math.abs(discrepancy) > 0.01 // 考虑浮点数精度 }; return result; } /** * 审计所有用户的余额 */ async auditAllUsers() { console.log('开始审计所有用户余额...'); // 获取所有用户 const [users] = await this.db.execute(` SELECT id, username, real_name, balance, role, created_at FROM users WHERE role != 'system' ORDER BY id `); this.auditResults.totalUsers = users.length; console.log(`找到 ${users.length} 个用户需要审计`); let processedCount = 0; for (const user of users) { try { const auditResult = await this.auditUser(user); if (auditResult.isProblematic) { this.auditResults.problematicUsers.push(auditResult); console.log(`发现问题用户: ${user.username} (ID: ${user.id}), 差异: ${auditResult.discrepancy.toFixed(2)}`); } // 更新统计信息 this.updateSummary(auditResult); processedCount++; if (processedCount % 100 === 0) { console.log(`已处理 ${processedCount}/${users.length} 个用户`); } } catch (error) { console.error(`审计用户 ${user.id} 时出错:`, error.message); } } console.log('审计完成!'); } /** * 更新汇总统计信息 * @param {Object} auditResult - 单个用户的审计结果 */ updateSummary(auditResult) { const discrepancy = auditResult.discrepancy; this.auditResults.summary.totalDiscrepancy += Math.abs(discrepancy); if (discrepancy > 0) { this.auditResults.summary.usersWithPositiveDiscrepancy++; } else if (discrepancy < 0) { this.auditResults.summary.usersWithNegativeDiscrepancy++; } if (discrepancy > this.auditResults.summary.maxDiscrepancy) { this.auditResults.summary.maxDiscrepancy = discrepancy; } if (discrepancy < this.auditResults.summary.minDiscrepancy) { this.auditResults.summary.minDiscrepancy = discrepancy; } } /** * 生成审计报告 */ generateReport() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const reportPath = path.join(__dirname, `balance_audit_report_${timestamp}.json`); const report = { auditTime: new Date().toISOString(), summary: { totalUsers: this.auditResults.totalUsers, problematicUsersCount: this.auditResults.problematicUsers.length, totalDiscrepancy: this.auditResults.summary.totalDiscrepancy.toFixed(2), usersWithPositiveDiscrepancy: this.auditResults.summary.usersWithPositiveDiscrepancy, usersWithNegativeDiscrepancy: this.auditResults.summary.usersWithNegativeDiscrepancy, maxDiscrepancy: this.auditResults.summary.maxDiscrepancy.toFixed(2), minDiscrepancy: this.auditResults.summary.minDiscrepancy.toFixed(2) }, problematicUsers: this.auditResults.problematicUsers.map(user => ({ userId: user.userId, username: user.username, realName: user.realName, role: user.role, actualBalance: user.actualBalance.toFixed(2), theoreticalBalance: user.theoreticalBalance.toFixed(2), adjustedTheoreticalBalance: user.adjustedTheoreticalBalance.toFixed(2), discrepancy: user.discrepancy.toFixed(2), shouldBeRefunded: user.shouldBeRefunded.toFixed(2), problemTransferCount: user.problemTransferCount, problemDetails: user.problemDetails })) }; fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf8'); console.log(`\n审计报告已生成: ${reportPath}`); return report; } /** * 打印控制台报告 */ printConsoleReport() { console.log('\n=== 余额审计报告 ==='); console.log(`审计时间: ${new Date().toLocaleString()}`); console.log(`总用户数: ${this.auditResults.totalUsers}`); console.log(`问题用户数: ${this.auditResults.problematicUsers.length}`); console.log(`总差异金额: ${this.auditResults.summary.totalDiscrepancy.toFixed(2)}`); console.log(`余额偏高用户数: ${this.auditResults.summary.usersWithPositiveDiscrepancy}`); console.log(`余额偏低用户数: ${this.auditResults.summary.usersWithNegativeDiscrepancy}`); console.log(`最大差异: ${this.auditResults.summary.maxDiscrepancy.toFixed(2)}`); console.log(`最小差异: ${this.auditResults.summary.minDiscrepancy.toFixed(2)}`); if (this.auditResults.problematicUsers.length > 0) { console.log('\n=== 问题用户详情 ==='); this.auditResults.problematicUsers.forEach((user, index) => { console.log(`\n${index + 1}. 用户: ${user.username} (ID: ${user.userId})`); console.log(` 实际余额: ${user.actualBalance.toFixed(2)}`); console.log(` 理论余额: ${user.theoreticalBalance.toFixed(2)}`); console.log(` 调整后理论余额: ${user.adjustedTheoreticalBalance.toFixed(2)}`); console.log(` 差异: ${user.discrepancy.toFixed(2)}`); console.log(` 应退还金额: ${user.shouldBeRefunded.toFixed(2)}`); console.log(` 问题转账数: ${user.problemTransferCount}`); if (user.problemDetails.length > 0) { console.log(' 问题转账详情:'); user.problemDetails.forEach((detail, i) => { console.log(` ${i + 1}. 转账ID: ${detail.transferId}, 金额: ${detail.amount}, 状态: ${detail.status}`); }); } }); } } /** * 运行完整的审计流程 */ async runAudit() { try { await this.init(); await this.auditAllUsers(); this.printConsoleReport(); const report = this.generateReport(); // 记录到日志 logger.info('Balance audit completed', { totalUsers: this.auditResults.totalUsers, problematicUsers: this.auditResults.problematicUsers.length, totalDiscrepancy: this.auditResults.summary.totalDiscrepancy }); return report; } catch (error) { console.error('审计过程中发生错误:', error); logger.error('Balance audit failed', { error: error.message }); throw error; } finally { process.exit(0); } } } // 如果直接运行此脚本 if (require.main === module) { const auditor = new BalanceAuditor(); auditor.runAudit().catch(console.error); } module.exports = BalanceAuditor;