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(); router.get('/', authenticateToken, validateQuery(transferSchemas.query), async (req, res, next) => { try { const {page, limit, status, start_date, end_date, search, sort, order} = req.query; const filters = { status, 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('/history', authenticateToken, async (req, res, next) => { try { const {page, limit, start_date, end_date, search, sort, order} = req.query; const filters = { start_date, end_date, search }; // 非管理员只能查看自己相关的转账 if (req.user.role !== 'admin') { filters.user_id = req.user.id; } const result = await transferService.getTransfersHistory(filters, {page, limit, sort, order}); 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); } } ); // 获取用户转账记录 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 source_type='manual' AND (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 ${limitNum} OFFSET ${offset} `, countParams); 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, (SELECT SUM(balance) FROM users WHERE role = 'user' AND is_system_account != 1) as total_user_balance, SUM(CASE WHEN source_type IN ('system') THEN amount END) as participated_transfers, SUM(CASE WHEN source_type IN ('agent') THEN amount END) as agent_total, SUM(CASE WHEN source_type IN ('operated_agent') THEN amount END) as operated_agent_total 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, SUM(CASE WHEN source_type IN ('system') THEN amount 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, SUM(CASE WHEN source_type IN ('system') THEN amount 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, total_user_balance:totalStats[0].total_user_balance || 0, agent_total:totalStats[0].agent_total || 0,//代理收入 operated_agent_total:totalStats[0].operated_agent_total || 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; console.log('newStatus:', newStatus); console.log('status:', status); console.log('reason:', reason); console.log('adjust_balance:', adjust_balance); // 兼容两种参数名: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, COALESCE(confirmed_from.confirmed_amount, 0) as confirmed_from_amount, CASE WHEN (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0)) > ABS(u.balance) THEN ABS(u.balance) ELSE (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_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 left join (select from_user_id, sum(amount) as confirmed_amount from transfers where status = 'received' and created_at >= ? and created_at <= ? group by from_user_id) as confirmed_from on u.id = confirmed_from.from_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, todayStartStr, todayEndStr]); // userStats = userStats.filter(item=>item.balance_needed >= 100) userStats.forEach(item => { item.balance_needed = Math.abs(item.balance_needed) }) 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;