Files
jurong_circle_black/routes/transfers.js

1257 lines
47 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
const express = require('express');
const transferService = require('../services/transferService');
2025-09-15 17:27:13 +08:00
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');
2025-08-26 10:06:23 +08:00
const multer = require('multer');
const path = require('path');
const dayjs = require('dayjs');
const router = express.Router();
2025-08-28 09:14:56 +08:00
2025-09-26 14:40:02 +08:00
// router.get('/tmp', async (req, res) => {
// const db = getDB();
// // 1. 查询所有转账记录,按 id 升序
// let [transfers] = await db.execute(`
// SELECT *
// FROM transfers
// WHERE status='received' or status='confirmed'
// ORDER BY id ASC`);
//
// // 2. 用对象维护每个用户的最新余额
// const userBalances = {};
//
// // 3. 遍历每条转账记录,计算余额
// for (const trx of transfers) {
// const fromId = trx.from_user_id;
// const toId = trx.to_user_id;
// const amount = Number(trx.amount);
//
// if (!(fromId in userBalances)) userBalances[fromId] = 0;
// if (!(toId in userBalances)) userBalances[toId] = 0;
//
// const fromBalance = userBalances[fromId] - amount;
// const toBalance = userBalances[toId] + amount;
//
// userBalances[fromId] = fromBalance;
// userBalances[toId] = toBalance;
//
// const balanceDiff = toBalance - fromBalance;
//
// // 4. 更新当前行的字段
// await db.execute(
// `UPDATE transfers
// SET from_user_balance = ?,
// to_user_balance = ?,
// balance_diff = ?
// WHERE id = ?`,
// [fromBalance, toBalance, amount, trx.id]
// );
//
// }
// res.json('更新成功')
// })
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
2025-09-15 17:27:13 +08:00
router.get('/history', authenticateToken, async (req, res, next) => {
2025-09-10 18:10:40 +08:00
try {
2025-09-15 17:27:13 +08:00
const {page, limit, start_date, end_date, search, sort, order} = req.query;
2025-09-10 18:10:40 +08:00
const filters = {
start_date,
end_date,
search
};
// 非管理员只能查看自己相关的转账
if (req.user.role !== 'admin') {
filters.user_id = req.user.id;
}
2025-09-15 17:27:13 +08:00
const result = await transferService.getTransfersHistory(filters, {page, limit, sort, order});
2025-09-10 18:10:40 +08:00
res.json({
success: true,
data: result
});
} catch (error) {
next(error);
}
})
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
2025-09-10 18:10:40 +08:00
2025-08-26 10:06:23 +08:00
router.get('/public-account', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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: '公户不存在'});
}
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
res.json({success: true, data: publicUser[0]});
} catch (error) {
console.error('获取公户信息失败:', error);
res.status(500).json({success: false, message: '服务器错误'});
2025-08-26 10:06:23 +08:00
}
});
2025-09-10 18:10:40 +08:00
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
2025-09-10 18:10:40 +08:00
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
// 确认转账
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
// 确认转账(路径参数形式)
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
// 拒绝转账
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
// 用户确认收到转账
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
// 用户确认未收到转账
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
2025-09-15 17:27:13 +08:00
2025-08-26 10:06:23 +08:00
// 获取用户转账记录
router.get('/user/:userId', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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);
2025-09-16 17:39:51 +08:00
let whereClause = `WHERE source_type='manual' AND (t.from_user_id = ? OR t.to_user_id = ?)`;
2025-09-15 17:27:13 +08:00
const userIdInt = parseInt(userId);
let listParams = [userIdInt, userIdInt];
let countParams = [userIdInt, userIdInt];
if (status) {
whereClause += ' AND t.status = ?';
listParams.push(status);
countParams.push(status);
2025-08-26 10:06:23 +08:00
}
2025-09-15 17:27:13 +08:00
// 添加分页参数
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
2025-09-26 14:40:02 +08:00
DESC
LIMIT ${limitNum}
OFFSET ${offset}
2025-09-15 17:27:13 +08:00
`, 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: '服务器错误'});
}
2025-08-26 10:06:23 +08:00
});
// 获取转账统计信息
router.get('/stats', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
try {
const userId = req.user.id;
const isAdmin = req.user.role === 'admin';
const db = getDB();
let stats = {};
if (isAdmin) {
// 管理员可以查看全局统计
const [totalStats] = await db.execute(`
2025-09-26 14:40:02 +08:00
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,
2025-09-15 17:27:13 +08:00
SUM(CASE
WHEN transfer_type = 'initial' AND status = 'confirmed' THEN amount
2025-09-26 14:40:02 +08:00
ELSE 0 END) as initial_amount,
2025-09-15 17:27:13 +08:00
SUM(CASE
WHEN transfer_type = 'return' AND status = 'confirmed' THEN amount
2025-09-26 14:40:02 +08:00
ELSE 0 END) as return_amount,
2025-09-15 17:27:13 +08:00
SUM(CASE
WHEN transfer_type = 'user_to_user' AND status = 'confirmed' THEN amount
2025-09-26 14:40:02 +08:00
ELSE 0 END) as user_to_user_amount,
2025-09-15 17:27:13 +08:00
(SELECT SUM(balance)
FROM users
WHERE role = 'user'
2025-09-26 14:40:02 +08:00
AND is_system_account = 1) as total_merchant_balance,
2025-09-15 17:27:13 +08:00
(SELECT SUM(balance)
FROM users
WHERE role = 'user'
AND is_system_account != 1) as total_user_balance,
2025-09-26 14:40:02 +08:00
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,
2025-09-15 17:27:13 +08:00
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
2025-09-26 14:40:02 +08:00
WHERE DATE (created_at) = ?
AND to_user_id IN (SELECT id FROM users WHERE is_system_account = 1)
AND status = 'received'), 0) -
2025-09-15 17:27:13 +08:00
COALESCE((SELECT SUM(amount)
FROM transfers
2025-09-26 14:40:02 +08:00
WHERE DATE (created_at) = ?
AND from_user_id IN (SELECT id FROM users WHERE is_system_account = 1)
AND status = 'received'), 0)
2025-09-15 17:27:13 +08:00
) as today_amount
FROM transfers
2025-09-26 14:40:02 +08:00
WHERE DATE (created_at) = ?
2025-09-15 17:27:13 +08:00
`, [todayStr, todayStr, todayStr]);
const [monthlyStats] = await db.execute(`
2025-09-26 14:40:02 +08:00
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
2025-09-15 17:27:13 +08:00
FROM transfers
2025-09-26 14:40:02 +08:00
WHERE YEAR (created_at) = ?
AND MONTH (created_at) = ?
2025-09-15 17:27:13 +08:00
`, [currentYear, currentMonth]);
// 获取上月统计数据用于对比
const lastMonth = currentMonth === 1 ? 12 : currentMonth - 1;
const lastMonthYear = currentMonth === 1 ? currentYear - 1 : currentYear;
const [lastMonthStats] = await db.execute(`
2025-09-26 14:40:02 +08:00
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
2025-09-15 17:27:13 +08:00
FROM transfers
2025-09-26 14:40:02 +08:00
WHERE YEAR (created_at) = ?
AND MONTH (created_at) = ?
2025-09-15 17:27:13 +08:00
`, [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,
2025-09-26 14:40:02 +08:00
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,//直营代理收入
2025-09-15 17:27:13 +08:00
},
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 = ?)
2025-09-26 14:40:02 +08:00
AND DATE (created_at) = ?
2025-09-15 17:27:13 +08:00
`, [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)
}
};
2025-08-26 10:06:23 +08:00
}
2025-09-15 17:27:13 +08:00
res.json({success: true, data: stats});
} catch (error) {
console.error('获取转账统计失败:', error);
res.status(500).json({success: false, message: '获取转账统计失败'});
2025-08-26 10:06:23 +08:00
}
});
// 获取待确认的转账
router.get('/pending', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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: '服务器错误'});
}
2025-08-26 10:06:23 +08:00
});
// 获取当前用户账户信息不需要传递用户ID
router.get('/account', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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: '用户不存在'});
}
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
// 返回用户账户信息,格式与原来的 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: '服务器错误'});
}
2025-08-26 10:06:23 +08:00
});
// 获取指定用户账户信息(管理员权限或用户本人)
router.get('/account/:userId', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
try {
const userId = req.params.userId;
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
// 检查权限
if (req.user.id != userId && req.user.role !== 'admin') {
return res.status(403).json({success: false, message: '权限不足'});
}
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
const db = getDB();
const [user] = await db.execute(`
SELECT id, username, real_name, balance, created_at, updated_at
FROM users
WHERE id = ?
`, [userId]);
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
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
};
2025-08-26 10:06:23 +08:00
2025-09-15 17:27:13 +08:00
res.json({success: true, data: accountData});
} catch (error) {
console.error('获取账户信息失败:', error);
res.status(500).json({success: false, message: '服务器错误'});
}
2025-08-26 10:06:23 +08:00
});
// 获取转账趋势数据(管理员权限)
router.get('/trend', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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(`
2025-09-26 14:40:02 +08:00
SELECT MIN(DATE (created_at)) as min_date,
MAX(DATE (created_at)) as max_date,
COUNT(*) as total_count
2025-09-15 17:27:13 +08:00
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(`
2025-09-26 14:40:02 +08:00
SELECT DATE (created_at) as date, COUNT (*) as count, SUM (amount) as amount
2025-09-15 17:27:13 +08:00
FROM transfers
2025-09-26 14:40:02 +08:00
WHERE DATE (created_at) >= DATE_SUB(?
, INTERVAL ? DAY)
AND status IN ('confirmed'
, 'received')
GROUP BY DATE (created_at)
2025-09-15 17:27:13 +08:00
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: '获取转账趋势失败'});
2025-08-26 10:06:23 +08:00
}
});
// 管理员解除坏账(管理员权限)
router.post('/remove-bad-debt/:transferId', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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: '解除坏账失败'});
2025-08-26 10:06:23 +08:00
}
});
// 强制变更转账状态(管理员权限)
router.post('/force-change-status/:transferId', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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: '变更转账状态失败'});
2025-08-26 10:06:23 +08:00
}
});
// 管理员查看数据库连接状态
router.get('/admin/database/status', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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
});
2025-08-26 10:06:23 +08:00
}
});
// 管理员获取数据库监控报告
router.get('/admin/database/report', authenticateToken, async (req, res) => {
2025-09-15 17:27:13 +08:00
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
});
2025-08-26 10:06:23 +08:00
}
});
/**
* 获取待处理的匹配转账订单
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @param {string} status - 状态过滤
* @param {string} search - 搜索关键词用户名或真实姓名
* @param {string} sort - 排序字段
* @param {string} order - 排序方向asc/desc
*/
2025-09-15 17:27:13 +08:00
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}
2025-09-26 14:40:02 +08:00
LIMIT ${parseInt(limit)}
OFFSET ${parseInt(offset)}
2025-09-15 17:27:13 +08:00
`;
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);
2025-08-26 10:06:23 +08:00
}
}
);
/**
* 获取待处理匹配订单的统计信息
*/
2025-09-15 17:27:13 +08:00
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);
}
2025-08-26 10:06:23 +08:00
}
);
/**
* 获取昨日用户转账统计列表
*/
2025-09-15 17:27:13 +08:00
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(`
2025-09-26 14:40:02 +08:00
SELECT u.id as user_id,
2025-09-15 17:27:13 +08:00
u.username,
u.real_name,
u.phone,
u.balance,
2025-09-26 14:40:02 +08:00
COALESCE(yesterday_out.amount, 0) -
COALESCE(yesterday_system_num.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,
COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0) as balance_needed
2025-09-15 17:27:13 +08:00
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
2025-09-26 14:40:02 +08:00
LEFT JOIN (SELECT to_user_id,
SUM(amount) as amount
FROM transfers
WHERE created_at >= ?
AND created_at <= ?
AND transfer_type != 'user_to_user'
AND status IN ('received')
GROUP BY to_user_id) yesterday_system_num
ON u.id = yesterday_system_num.to_user_id
2025-09-15 17:27:13 +08:00
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
2025-09-26 14:40:02 +08:00
WHERE status IN ('confirmed', '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.user_type !='directly_operated'
2025-09-15 17:27:13 +08:00
AND u.is_system_account != 1
AND yesterday_out.amount > 0
AND u.balance < 0
ORDER BY balance_needed DESC, yesterday_out_amount DESC
2025-09-26 14:40:02 +08:00
`, [yesterdayStartStr, yesterdayEndStr, yesterdayStartStr, yesterdayEndStr,todayStartStr, todayEndStr, todayStartStr, todayEndStr]);
// userStats = userStats.filter(item=>item.balance_needed > 100)
2025-09-15 17:27:13 +08:00
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);
2025-08-26 10:06:23 +08:00
}
}
);
module.exports = router;