const express = require('express'); const transferService = require('../services/transferService'); const { auth: authenticateToken } = require('../middleware/auth'); const { validate, validateQuery, transferSchemas, commonSchemas } = require('../middleware/validation'); const { logger } = require('../config/logger'); const { HTTP_STATUS } = require('../config/constants'); const { getDB } = require('../database'); const multer = require('multer'); const path = require('path'); const dayjs = require('dayjs'); const router = express.Router(); // 配置文件上传 const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/') }, filename: function (req, file, cb) { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) cb(null, 'voucher-' + uniqueSuffix + path.extname(file.originalname)) } }); const upload = multer({ storage: storage, fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('只允许上传图片文件')); } }, limits: { fileSize: 5 * 1024 * 1024 // 5MB } }); /** * 获取转账列表 * @param {string} status - 转账状态过滤 * @param {string} transfer_type - 转账类型过滤 * @param {string} start_date - 开始日期过滤 * @param {string} end_date - 结束日期过滤 * @param {string} search - 搜索关键词(用户名或真实姓名) * @param {number} page - 页码 * @param {number} limit - 每页数量 * @param {string} sort - 排序字段 * @param {string} order - 排序方向(asc/desc) */ router.get('/', authenticateToken, validateQuery(transferSchemas.query), async (req, res, next) => { try { const { page, limit, status, transfer_type, start_date, end_date, search, sort, order } = req.query; const filters = { status, transfer_type, start_date, end_date, search }; // 非管理员只能查看自己相关的转账 if (req.user.role !== 'admin') { filters.user_id = req.user.id; } const result = await transferService.getTransfers(filters, { page, limit, sort, order }); logger.info('Transfer list requested', { userId: req.user.id, filters, resultCount: result.transfers.length }); res.json({ success: true, data: result }); } catch (error) { next(error); } } ); // 获取转账记录列表 router.get('/list', authenticateToken, validateQuery(transferSchemas.query), async (req, res, next) => { try { const { page, limit, status, transfer_type, start_date, end_date, sort, order } = req.query; const filters = { status, transfer_type, start_date, end_date }; // 非管理员只能查看自己相关的转账 if (req.user.role !== 'admin') { filters.user_id = req.user.id; } const result = await transferService.getTransfers(filters, { page, limit, sort, order }); logger.info('Transfer list requested', { userId: req.user.id, filters, resultCount: result.transfers.length }); res.json({ success: true, data: result }); } catch (error) { next(error); } } ); // 获取公户信息 router.get('/public-account', authenticateToken, async (req, res) => { try { const db = getDB(); const [publicUser] = await db.execute(` SELECT id, username, real_name, balance FROM users WHERE username = 'public_account' AND is_system_account = TRUE `); if (publicUser.length === 0) { return res.status(404).json({ success: false, message: '公户不存在' }); } res.json({ success: true, data: publicUser[0] }); } catch (error) { console.error('获取公户信息失败:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); // 创建转账记录 router.post('/create', authenticateToken, validate(transferSchemas.create), async (req, res, next) => { try { const result = await transferService.createTransfer(req.user.id, req.body); logger.info('Transfer creation requested', { userId: req.user.id, transferId: result.transfer_id, amount: req.body.amount }); res.status(HTTP_STATUS.CREATED).json({ success: true, message: '转账记录创建成功,等待确认', data: result }); } catch (error) { next(error); } } ); // 管理员创建转账记录 router.post('/admin/create', authenticateToken, async (req, res, next) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const { from_user_id, to_user_id, amount, transfer_type, description } = req.body; // 验证必填字段 if (!from_user_id || !to_user_id || !amount || !transfer_type) { return res.status(400).json({ success: false, message: '缺少必填字段' }); } const result = await transferService.createTransfer(from_user_id, { to_user_id, amount, transfer_type, description: description || '管理员分配转账' }); logger.info('Admin transfer creation requested', { adminId: req.user.id, fromUserId: from_user_id, toUserId: to_user_id, transferId: result.transfer_id, amount: amount }); res.status(HTTP_STATUS.CREATED).json({ success: true, message: '转账分配成功', data: result }); } catch (error) { next(error); } } ); // 确认转账 router.post('/confirm', authenticateToken, validate(transferSchemas.confirm), async (req, res, next) => { try { const { transfer_id, note } = req.body; await transferService.confirmTransfer(transfer_id, note, req.user.id); logger.info('Transfer confirmed', { transferId: transfer_id, operatorId: req.user.id }); res.json({ success: true, message: '转账确认成功' }); } catch (error) { next(error); } } ); // 确认转账(路径参数形式) router.post('/confirm/:id', authenticateToken, async (req, res, next) => { try { const transfer_id = req.params.id; const { action, note } = req.body; // 验证action参数 if (action !== 'confirm') { return res.status(400).json({ success: false, message: 'action参数必须为confirm' }); } await transferService.confirmTransfer(transfer_id, note, req.user.id); logger.info('Transfer confirmed via path param', { transferId: transfer_id, operatorId: req.user.id }); res.json({ success: true, message: '转账确认成功' }); } catch (error) { next(error); } } ); // 拒绝转账 router.post('/reject', authenticateToken, validate(transferSchemas.reject), async (req, res, next) => { try { const { transfer_id, note='' } = req.body; await transferService.rejectTransfer(transfer_id, note, req.user.id); logger.info('Transfer rejected', { transferId: transfer_id, operatorId: req.user.id }); res.json({ success: true, message: '转账已拒绝' }); } catch (error) { next(error); } } ); // 用户确认收到转账 router.post('/confirm-received', authenticateToken, async (req, res, next) => { try { const { transfer_id } = req.body; if (!transfer_id) { return res.status(400).json({ success: false, message: '缺少转账ID' }); } await transferService.confirmReceived(transfer_id, req.user.id); logger.info('Transfer received confirmed by user', { transferId: transfer_id, userId: req.user.id }); res.json({ success: true, message: '已确认收到转账,余额已更新' }); } catch (error) { next(error); } } ); // 用户确认未收到转账 router.post('/confirm-not-received', authenticateToken, async (req, res, next) => { try { const { transfer_id } = req.body; if (!transfer_id) { return res.status(400).json({ success: false, message: '缺少转账ID' }); } await transferService.confirmNotReceived(transfer_id, req.user.id); logger.info('Transfer not received confirmed by user', { transferId: transfer_id, userId: req.user.id }); res.json({ success: true, message: '已确认未收到转账' }); } catch (error) { next(error); } } ); // 触发返还转账逻辑 async function triggerReturnTransfers(db, user_id, total_amount) { // 将总金额分成3笔随机金额 const amounts = generateRandomAmounts(total_amount, 3); const batch_id = `return_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 获取公户ID const [publicAccount] = await db.execute(` SELECT u.id FROM users u WHERE u.username = 'public_account' `); if (publicAccount.length === 0) { throw new Error('公户不存在'); } const public_user_id = publicAccount[0].id; // 创建3笔返还转账记录 for (let i = 0; i < amounts.length; i++) { await db.execute(` INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, description, batch_id, status) VALUES (?, ?, ?, 'return', ?, ?, 'pending') `, [ public_user_id, user_id, amounts[i], `返还转账 ${i + 1}/3`, batch_id ]); } } // 生成随机金额分配 function generateRandomAmounts(total, count) { const amounts = []; let remaining = parseFloat(total); for (let i = 0; i < count - 1; i++) { // 确保每笔至少1元,最多不超过剩余金额的80% const min = 1; const max = Math.max(min, remaining * 0.8); const amount = Math.round((Math.random() * (max - min) + min) * 100) / 100; amounts.push(amount); remaining -= amount; } // 最后一笔是剩余金额 amounts.push(Math.round(remaining * 100) / 100); return amounts; } // 获取用户转账记录 router.get('/user/:userId', authenticateToken, async (req, res) => { try { const userId = req.params.userId; const { page = 1, limit = 10, status } = req.query; // 检查权限(只能查看自己的记录或管理员查看所有) // if (req.user.id != userId && req.user.role !== 'admin') { // return res.status(403).json({ success: false, message: '权限不足' }); // } const db = getDB(); // 确保参数为有效数字 const pageNum = Math.max(1, parseInt(page) || 1); const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); const offset = Math.max(0, (pageNum - 1) * limitNum); let whereClause = 'WHERE (t.from_user_id = ? OR t.to_user_id = ?)'; const userIdInt = parseInt(userId); let listParams = [userIdInt, userIdInt]; let countParams = [userIdInt, userIdInt]; if (status) { whereClause += ' AND t.status = ?'; listParams.push(status); countParams.push(status); } // 添加分页参数 listParams.push(limitNum.toString(), offset.toString()); const [transfers] = await db.execute(` SELECT t.*, from_user.username as from_username, from_user.real_name as from_real_name, to_user.username as to_username, to_user.real_name as to_real_name FROM transfers t LEFT JOIN users from_user ON t.from_user_id = from_user.id LEFT JOIN users to_user ON t.to_user_id = to_user.id ${whereClause} ORDER BY t.created_at DESC LIMIT ? OFFSET ? `, listParams); const [countResult] = await db.execute(` SELECT COUNT(*) as total FROM transfers t ${whereClause} `, countParams); res.json({ success: true, data: { transfers, pagination: { page: pageNum, limit: limitNum, total: countResult[0].total, pages: Math.ceil(countResult[0].total / limitNum) } } }); } catch (error) { console.error('获取转账记录失败:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); // 获取转账统计信息 router.get('/stats', authenticateToken, async (req, res) => { try { const userId = req.user.id; const isAdmin = req.user.role === 'admin'; const db = getDB(); let stats = {}; if (isAdmin) { // 管理员可以查看全局统计 const [totalStats] = await db.execute(` SELECT COUNT(*) as total_transfers, SUM(amount) as total_flow_amount, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received_count, SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count, SUM(CASE WHEN status = 'not_received' THEN 1 ELSE 0 END) as not_received_count, SUM(CASE WHEN is_overdue = 1 THEN 1 ELSE 0 END) as overdue_count, SUM(CASE WHEN is_bad_debt = 1 THEN 1 ELSE 0 END) as bad_debt_count, SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as total_amount, SUM(CASE WHEN is_bad_debt = 1 THEN amount ELSE 0 END) as bad_debt_amount, SUM(CASE WHEN transfer_type = 'initial' AND status = 'confirmed' THEN amount ELSE 0 END) as initial_amount, SUM(CASE WHEN transfer_type = 'return' AND status = 'confirmed' THEN amount ELSE 0 END) as return_amount, SUM(CASE WHEN transfer_type = 'user_to_user' AND status = 'confirmed' THEN amount ELSE 0 END) as user_to_user_amount, (SELECT SUM(balance) FROM users WHERE role = 'user' AND is_system_account = 1) as total_merchant_balance, COUNT(CASE WHEN status IN ('confirmed', 'received') THEN 1 END) as participated_transfers FROM transfers `); const todayStr = dayjs().format('YYYY-MM-DD'); const currentYear = dayjs().year(); const currentMonth = dayjs().month() + 1; console.log(todayStr,'todayStr'); const [todayStats] = await db.execute(` SELECT COUNT(*) as today_transfers, ( COALESCE((SELECT SUM(amount) FROM transfers WHERE DATE(created_at) = ? AND to_user_id IN (SELECT id FROM users WHERE is_system_account = 1) AND status = 'received'), 0) - COALESCE((SELECT SUM(amount) FROM transfers WHERE DATE(created_at) = ? AND from_user_id IN (SELECT id FROM users WHERE is_system_account = 1) AND status = 'received'), 0) ) as today_amount FROM transfers WHERE DATE(created_at) = ? `, [todayStr, todayStr, todayStr]); const [monthlyStats] = await db.execute(` SELECT COUNT(*) as monthly_transfers, SUM(CASE WHEN status = 'received' THEN amount ELSE 0 END) as monthly_amount, COUNT(CASE WHEN status IN ('confirmed', 'received') THEN 1 END) as monthly_participated_transfers FROM transfers WHERE YEAR(created_at) = ? AND MONTH(created_at) = ? `, [currentYear, currentMonth]); // 获取上月统计数据用于对比 const lastMonth = currentMonth === 1 ? 12 : currentMonth - 1; const lastMonthYear = currentMonth === 1 ? currentYear - 1 : currentYear; const [lastMonthStats] = await db.execute(` SELECT COUNT(*) as last_monthly_transfers, SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as last_monthly_amount, COUNT(CASE WHEN status IN ('confirmed', 'received') THEN 1 END) as last_monthly_participated_transfers FROM transfers WHERE YEAR(created_at) = ? AND MONTH(created_at) = ? `, [lastMonthYear, lastMonth]); stats = { total: { transfers: totalStats[0].total_transfers || 0, pending: parseFloat(totalStats[0].total_flow_amount || 0), pending_count: totalStats[0].pending_count || 0, confirmed: totalStats[0].confirmed_count || 0, received_count: totalStats[0].received_count || 0, rejected: totalStats[0].rejected_count || 0, cancelled_count: totalStats[0].cancelled_count || 0, not_received_count: totalStats[0].not_received_count || 0, overdue: totalStats[0].overdue_count || 0, bad_debt: totalStats[0].bad_debt_count || 0, amount: parseFloat(totalStats[0].total_amount || 0), bad_debt_amount: parseFloat(totalStats[0].bad_debt_amount || 0), total_merchant_balance: parseFloat(totalStats[0].total_merchant_balance || 0), initial_amount: parseFloat(totalStats[0].initial_amount || 0), return_amount: parseFloat(totalStats[0].return_amount || 0), user_to_user_amount: parseFloat(totalStats[0].user_to_user_amount || 0), participated_transfers: totalStats[0].participated_transfers || 0 }, today: { transfers: todayStats[0].today_transfers || 0, amount: parseFloat(todayStats[0].today_amount || 0) }, monthly: { transfers: monthlyStats[0].monthly_transfers || 0, amount: parseFloat(monthlyStats[0].monthly_amount || 0), participated_transfers: monthlyStats[0].monthly_participated_transfers || 0 }, lastMonth: { transfers: lastMonthStats[0].last_monthly_transfers || 0, amount: parseFloat(lastMonthStats[0].last_monthly_amount || 0), participated_transfers: lastMonthStats[0].last_monthly_participated_transfers || 0 } }; } else { // 普通用户只能查看自己的统计 const [userStats] = await db.execute(` SELECT COUNT(*) as total_transfers, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, SUM(CASE WHEN status = 'confirmed' AND from_user_id = ? THEN amount ELSE 0 END) as sent_amount, SUM(CASE WHEN status = 'confirmed' AND to_user_id = ? THEN amount ELSE 0 END) as received_amount FROM transfers WHERE from_user_id = ? OR to_user_id = ? `, [userId, userId, userId, userId]); const todayStr = dayjs().format('YYYY-MM-DD'); const [todayStats] = await db.execute(` SELECT COUNT(*) as today_transfers, SUM(CASE WHEN status = 'confirmed' AND from_user_id = ? THEN amount ELSE 0 END) as today_sent, SUM(CASE WHEN status = 'confirmed' AND to_user_id = ? THEN amount ELSE 0 END) as today_received FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) AND DATE(created_at) = ? `, [userId, userId, userId, userId, todayStr]); stats = { total: { transfers: userStats[0].total_transfers || 0, pending: userStats[0].pending_count || 0, confirmed: userStats[0].confirmed_count || 0, rejected: userStats[0].rejected_count || 0, sent_amount: parseFloat(userStats[0].sent_amount || 0), received_amount: parseFloat(userStats[0].received_amount || 0) }, today: { transfers: todayStats[0].today_transfers || 0, sent_amount: parseFloat(todayStats[0].today_sent || 0), received_amount: parseFloat(todayStats[0].today_received || 0) } }; } res.json({ success: true, data: stats }); } catch (error) { console.error('获取转账统计失败:', error); res.status(500).json({ success: false, message: '获取转账统计失败' }); } }); // 获取待确认的转账 router.get('/pending', authenticateToken, async (req, res) => { try { const userId = parseInt(req.user.id); const db = getDB(); const [transfers] = await db.execute(` SELECT t.*, from_user.username as from_username, from_user.real_name as from_real_name FROM transfers t LEFT JOIN users from_user ON t.from_user_id = from_user.id WHERE t.to_user_id = ? AND t.status = 'pending' ORDER BY t.created_at DESC `, [userId]); res.json({ success: true, data: transfers }); } catch (error) { console.error('获取待确认转账失败:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); // 获取当前用户账户信息(不需要传递用户ID) router.get('/account', authenticateToken, async (req, res) => { try { const userId = req.user.id; const db = getDB(); const [user] = await db.execute(` SELECT id, username, real_name, balance, created_at, updated_at, points FROM users WHERE id = ? `, [userId]); if (user.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } // 返回用户账户信息,格式与原来的 accounts 表保持一致 const accountData = { id: user[0].id, user_id: user[0].id, account_type: 'user', balance: user[0].balance, username: user[0].username, real_name: user[0].real_name, created_at: user[0].created_at, updated_at: user[0].updated_at, points: user[0].points }; res.json({ success: true, data: accountData }); } catch (error) { console.error('获取账户信息失败:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); // 获取指定用户账户信息(管理员权限或用户本人) router.get('/account/:userId', authenticateToken, async (req, res) => { try { const userId = req.params.userId; // 检查权限 if (req.user.id != userId && req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const db = getDB(); const [user] = await db.execute(` SELECT id, username, real_name, balance, created_at, updated_at FROM users WHERE id = ? `, [userId]); if (user.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } // 返回用户账户信息,格式与原来的 accounts 表保持一致 const accountData = { id: user[0].id, user_id: user[0].id, account_type: 'user', balance: user[0].balance, username: user[0].username, real_name: user[0].real_name, created_at: user[0].created_at, updated_at: user[0].updated_at }; res.json({ success: true, data: accountData }); } catch (error) { console.error('获取账户信息失败:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); // 获取转账趋势数据(管理员权限) router.get('/trend', authenticateToken, async (req, res) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const db = getDB(); const { days = 7 } = req.query; const daysNum = Math.min(30, Math.max(1, parseInt(days) || 7)); // 首先获取数据库中最早和最晚的转账日期 const [dateRange] = await db.execute(` SELECT MIN(DATE(created_at)) as min_date, MAX(DATE(created_at)) as max_date, COUNT(*) as total_count FROM transfers `); if (dateRange[0].total_count === 0) { // 如果没有转账记录,返回空数据 const result = []; const now = new Date(); for (let i = daysNum - 1; i >= 0; i--) { const date = dayjs().subtract(i, 'day'); result.push({ date: date.format('MM-DD'), count: 0, amount: 0 }); } return res.json({ success: true, data: result }); } // 获取最近的转账数据(基于实际数据的最大日期) const maxDate = dayjs(dateRange[0].max_date); // 获取指定天数内的转账趋势(从最大日期往前推) const [trendData] = await db.execute(` SELECT DATE(created_at) as date, COUNT(*) as count, SUM(amount) as amount FROM transfers WHERE DATE(created_at) >= DATE_SUB(?, INTERVAL ? DAY) AND status IN ('confirmed', 'received') GROUP BY DATE(created_at) ORDER BY date ASC `, [dateRange[0].max_date, daysNum - 1]); // 填充缺失的日期(转账数为0) const result = []; for (let i = daysNum - 1; i >= 0; i--) { const date = maxDate.subtract(i, 'day'); const dateStr = date.format('YYYY-MM-DD'); // 修复日期比较:将数据库返回的Date对象转换为字符串进行比较 const existingData = trendData.find(item => { const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); return itemDateStr === dateStr; }); result.push({ date: date.format('MM-DD'), count: existingData ? existingData.count : 0, amount: existingData ? parseFloat(existingData.amount) : 0 }); } res.json({ success: true, data: result }); } catch (error) { console.error('获取转账趋势错误:', error); res.status(500).json({ success: false, message: '获取转账趋势失败' }); } }); // 管理员解除坏账(管理员权限) router.post('/remove-bad-debt/:transferId', authenticateToken, async (req, res) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const { transferId } = req.params; const { reason } = req.body; const adminId = req.user.id; // 验证转账ID if (!transferId || isNaN(transferId)) { return res.status(400).json({ success: false, message: '无效的转账ID' }); } const result = await transferService.removeBadDebt(transferId, adminId, reason); res.json({ success: true, message: '坏账标记已解除', data: result }); } catch (error) { console.error('解除坏账失败:', error); if (error.statusCode) { return res.status(error.statusCode).json({ success: false, message: error.message }); } res.status(500).json({ success: false, message: '解除坏账失败' }); } }); // 强制变更转账状态(管理员权限) router.post('/force-change-status/:transferId', authenticateToken, async (req, res) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const { transferId } = req.params; const { newStatus, status, reason, adjust_balance = false } = req.body; // 兼容两种参数名:newStatus 和 status const actualNewStatus = newStatus || status; const adminId = req.user.id; // 验证转账ID if (!transferId || isNaN(transferId)) { return res.status(400).json({ success: false, message: '无效的转账ID' }); } // 验证必填参数 if (!actualNewStatus) { return res.status(400).json({ success: false, message: '新状态不能为空' }); } if (!reason) { return res.status(400).json({ success: false, message: '变更原因不能为空' }); } const result = await transferService.forceChangeTransferStatus( transferId, actualNewStatus, reason, adminId, adjust_balance ); res.json({ success: true, message: `转账状态已从 ${result.oldStatus} 变更为 ${result.newStatus}`, data: result }); } catch (error) { console.error('强制变更转账状态失败:', error); if (error.statusCode) { return res.status(error.statusCode).json({ success: false, message: error.message }); } res.status(500).json({ success: false, message: '变更转账状态失败' }); } }); // 管理员查看数据库连接状态 router.get('/admin/database/status', authenticateToken, async (req, res) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const dbMonitor = require('../db-monitor'); const diagnosis = await dbMonitor.diagnose(); res.json({ success: true, data: diagnosis }); } catch (error) { logger.error('Get database status failed', { adminId: req.user.id, error: error.message }); res.status(500).json({ success: false, message: '获取数据库状态失败: ' + error.message }); } }); // 管理员获取数据库监控报告 router.get('/admin/database/report', authenticateToken, async (req, res) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const dbMonitor = require('../db-monitor'); const report = await dbMonitor.generateReport(); res.json({ success: true, data: { report, timestamp: new Date().toISOString() } }); } catch (error) { logger.error('Get database report failed', { adminId: req.user.id, error: error.message }); res.status(500).json({ success: false, message: '获取数据库报告失败: ' + error.message }); } }); /** * 获取待处理的匹配转账订单 * @param {number} page - 页码 * @param {number} limit - 每页数量 * @param {string} status - 状态过滤 * @param {string} search - 搜索关键词(用户名或真实姓名) * @param {string} sort - 排序字段 * @param {string} order - 排序方向(asc/desc) */ router.get('/pending-allocations', authenticateToken, async (req, res, next) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const { page = 1, limit = 20, status = '', search = '', sort = 'created_at', order = 'desc' } = req.query; const db = getDB(); const offset = (parseInt(page) - 1) * parseInt(limit); // 构建查询条件 let whereConditions = []; let queryParams = []; // 状态过滤 if (status) { whereConditions.push('oa.status = ?'); queryParams.push(status); } // 搜索过滤(用户名或真实姓名) if (search) { whereConditions.push('(uf.username LIKE ? OR uf.real_name LIKE ? OR ut.username LIKE ? OR ut.real_name LIKE ?)'); const searchPattern = `%${search}%`; queryParams.push(searchPattern, searchPattern, searchPattern, searchPattern); } const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : ''; // 验证排序字段 const allowedSortFields = ['created_at', 'amount', 'status', 'cycle_number']; const sortField = allowedSortFields.includes(sort) ? sort : 'created_at'; const sortOrder = order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; // 获取总数 const countQuery = ` SELECT COUNT(*) as total FROM transfers oa JOIN users uf ON oa.from_user_id = uf.id JOIN users ut ON oa.to_user_id = ut.id JOIN matching_orders mo ON oa.id = mo.id ${whereClause} `; const [countResult] = await db.execute(countQuery, queryParams); const total = countResult[0].total; // 使用 query 方法避免 LIMIT/OFFSET 参数问题 const dataQuery = ` SELECT oa.id, oa.from_user_id, oa.to_user_id, oa.amount, oa.cycle_number, oa.status, oa.outbound_date, oa.return_date, oa.can_return_after, oa.confirmed_at, oa.created_at, oa.updated_at, uf.username as from_username, uf.real_name as from_real_name, ut.username as to_username, ut.real_name as to_real_name, mo.amount as order_total_amount, mo.status as order_status, mo.matching_type, t.status as transfer_status, t.voucher_url FROM transfers oa JOIN users uf ON oa.from_user_id = uf.id JOIN users ut ON oa.to_user_id = ut.id JOIN matching_orders mo ON oa.id = mo.id ${whereClause} ORDER BY oa.${sortField} ${sortOrder} LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)} `; const [allocations] = queryParams.length > 0 ? await db.execute(dataQuery, queryParams) : await db.query(dataQuery); // 计算分页信息 const totalPages = Math.ceil(total / parseInt(limit)); logger.info('Pending allocations list requested', { userId: req.user.id, page: parseInt(page), limit: parseInt(limit), total, resultCount: allocations.length }); res.json({ success: true, data: { allocations, pagination: { page: parseInt(page), limit: parseInt(limit), total, totalPages } } }); } catch (error) { logger.error('Get pending allocations failed', { userId: req.user.id, error: error.message }); next(error); } } ); /** * 获取待处理匹配订单的统计信息 */ router.get('/pending-allocations/stats', authenticateToken, async (req, res, next) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const db = getDB(); // 获取统计数据 const [stats] = await db.execute(` SELECT COUNT(*) as total_allocations, COUNT(CASE WHEN oa.status = 'pending' THEN 1 END) as pending_count, COUNT(CASE WHEN oa.status = 'confirmed' THEN 1 END) as confirmed_count, COUNT(CASE WHEN oa.status = 'completed' THEN 1 END) as completed_count, SUM(oa.amount) as total_amount, SUM(CASE WHEN oa.status = 'pending' THEN oa.amount ELSE 0 END) as pending_amount FROM transfers oa JOIN matching_orders mo ON oa.id = mo.id WHERE mo.status != 'cancelled' `); res.json({ success: true, data: stats[0] }); } catch (error) { logger.error('Get pending allocations stats failed', { userId: req.user.id, error: error.message }); next(error); } } ); /** * 获取昨日用户转账统计列表 */ router.get('/daily-stats', authenticateToken, async (req, res, next) => { try { // 检查管理员权限 if (req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '权限不足' }); } const db = getDB(); // 获取昨日和今日的日期范围(从0点开始计算) // 今日0点到23:59:59 const todayStart = dayjs().startOf('day'); const todayEnd = dayjs().endOf('day'); // 昨日0点到23:59:59 const yesterdayStart = dayjs().subtract(1, 'day').startOf('day'); const yesterdayEnd = dayjs().subtract(1, 'day').endOf('day'); // 转换为MySQL兼容的字符串格式 const todayStartStr = todayStart.format('YYYY-MM-DD HH:mm:ss'); const todayEndStr = todayEnd.format('YYYY-MM-DD HH:mm:ss'); const yesterdayStartStr = yesterdayStart.format('YYYY-MM-DD HH:mm:ss'); const yesterdayEndStr = yesterdayEnd.format('YYYY-MM-DD HH:mm:ss'); // 使用dayjs格式化日期字符串用于返回 const todayStr = todayStart.format('YYYY-MM-DD'); const yesterdayStr = yesterdayStart.format('YYYY-MM-DD'); // 获取所有用户的昨日转出和今日入账统计 let [userStats] = await db.execute(` SELECT u.id as user_id, u.username, u.real_name, u.phone, u.balance, COALESCE(yesterday_out.amount, 0) as yesterday_out_amount, COALESCE(today_in.amount, 0) as today_in_amount, CASE WHEN (COALESCE(yesterday_out.amount, 0) - COALESCE(today_in.amount, 0)) > ABS(u.balance) THEN ABS(u.balance) ELSE (COALESCE(yesterday_out.amount, 0) - COALESCE(today_in.amount, 0)) END as balance_needed FROM users u LEFT JOIN ( SELECT from_user_id, SUM(amount) as amount FROM transfers WHERE created_at >= ? AND created_at <= ? AND status IN ('confirmed', 'received') GROUP BY from_user_id ) yesterday_out ON u.id = yesterday_out.from_user_id LEFT JOIN ( SELECT to_user_id, SUM(amount) as amount FROM transfers WHERE created_at >= ? AND created_at <= ? AND status IN ('confirmed', 'received') GROUP BY to_user_id ) today_in ON u.id = today_in.to_user_id WHERE u.role != 'admin' AND u.is_system_account != 1 AND yesterday_out.amount > 0 AND u.balance < 0 ORDER BY balance_needed DESC, yesterday_out_amount DESC `, [yesterdayStartStr, yesterdayEndStr, todayStartStr, todayEndStr]); userStats = userStats.filter(item=>item.balance_needed >= 100) res.json({ success: true, data: { date: { yesterday: yesterdayStr, today: todayStr }, users: userStats } }); } catch (error) { logger.error('Get daily transfer stats failed', { userId: req.user.id, error: error.message }); next(error); } } ); module.exports = router;