Files
jurong_circle_black/balance_audit.js
2025-08-26 10:06:23 +08:00

347 lines
12 KiB
JavaScript

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;