Files
jurong_circle_agent_black/routes/transfers.js
2025-09-17 14:01:10 +08:00

439 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const {getDB} = require('../database');
const {agentAuth} = require('../middleware/agentAuth');
const {logger} = require('../config/logger');
/**
* 获取代理下级用户转账记录列表
* GET /api/transfers
*/
router.get('/', agentAuth, async (req, res) => {
try {
const agentId = req.agent.id;
const {
page = 1,
limit = 20,
size = 20,
search,
status,
type,
start_date,
end_date,
min_amount,
max_amount,
sort_by = 'created_at',
sort_order = 'desc'
} = req.query;
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(size ||limit ) || 20;
const offset = (pageNum - 1) * limitNum;
// 构建查询条件
let whereConditions = [
'(am1.agent_id = ? OR am2.agent_id = ?)' // 转出方或转入方属于当前代理
];
let queryParams = [agentId, agentId];
if (search) {
whereConditions.push('(u1.username LIKE ? OR u1.real_name LIKE ? OR u1.phone LIKE ? OR u2.username LIKE ? OR u2.real_name LIKE ? OR u2.phone LIKE ?)');
queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`);
}
if (status) {
whereConditions.push('t.status = ?');
queryParams.push(status);
}
if (type) {
whereConditions.push('t.source_type = ?');
queryParams.push(type);
}
if (start_date) {
whereConditions.push('DATE(t.created_at) >= ?');
queryParams.push(start_date);
}
if (end_date) {
whereConditions.push('DATE(t.created_at) <= ?');
queryParams.push(end_date);
}
if (min_amount) {
whereConditions.push('t.amount >= ?');
queryParams.push(parseFloat(min_amount));
}
if (max_amount) {
whereConditions.push('t.amount <= ?');
queryParams.push(parseFloat(max_amount));
}
const whereClause = whereConditions.join(' AND ');
// 验证排序字段
const allowedSortFields = ['created_at', 'amount', 'status'];
const sortBy = allowedSortFields.includes(sort_by) ? sort_by : 'created_at';
const sortOrder = sort_order.toLowerCase() === 'asc' ? 'ASC' : 'DESC';
// 查询转账记录列表
const transfersQuery = `
SELECT t.id,
t.from_user_id,
t.to_user_id,
t.amount,
t.source_type,
t.status,
t.description,
t.matching_order_id,
t.created_at,
t.updated_at,
u1.username as from_username,
u1.real_name as from_real_name,
u1.phone as from_phone,
u1.avatar as from_avatar,
u2.username as to_username,
u2.real_name as to_real_name,
u2.phone as to_phone,
u2.avatar as to_avatar,
CASE
WHEN am1.agent_id = ? THEN 'out'
WHEN am2.agent_id = ? THEN 'in'
ELSE 'both'
END as direction
FROM transfers t
LEFT JOIN users u1 ON t.from_user_id = u1.id
LEFT JOIN users u2 ON t.to_user_id = u2.id
LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
WHERE ${whereClause}
ORDER BY t.${sortBy} ${sortOrder}
LIMIT ${limitNum}
OFFSET ${offset}
`;
console.log(transfersQuery, [agentId, agentId, ...queryParams]);
const [transfers] = await getDB().execute(transfersQuery, [agentId, agentId, ...queryParams]);
// 查询总数
const countQuery = `
SELECT COUNT(*) as total
FROM transfers t
LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
LEFT JOIN users u1 ON t.from_user_id = u1.id
LEFT JOIN users u2 ON t.to_user_id = u2.id
WHERE ${whereClause}
`;
console.log(countQuery, [agentId, agentId, ...queryParams]);
const [countResult] = await getDB().execute(countQuery, [...queryParams]);
const total = countResult[0]?.total || 0;
// 查询统计信息
let statsResult;
[statsResult] = await getDB().execute(`
SELECT COUNT(*) as total_transfers,
COUNT(CASE WHEN t.status = 'received' THEN 1 END) as completed_transfers,
COUNT(CASE WHEN t.status = 'pending' THEN 1 END) as pending_transfers,
COUNT(CASE WHEN t.status = 'failed' THEN 1 END) as failed_transfers,
CAST(COALESCE(SUM(CASE WHEN t.status = 'received' THEN t.amount ELSE 0 END),
0) AS DECIMAL(10, 2)) as total_amount,
CAST(COALESCE(
SUM(CASE WHEN t.status = 'received' AND DATE (t.created_at) = CURDATE() THEN t.amount ELSE 0
END), 0) AS DECIMAL(10, 2)) as today_amount,
COUNT(CASE WHEN DATE (t.created_at) = CURDATE() THEN 1 END) as today_transfers
FROM transfers t
LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
WHERE (am1.agent_id = ? OR am2.agent_id = ?)
`, [agentId, agentId]);
const stats = statsResult[0] || {
total_transfers: 0,
completed_transfers: 0,
pending_transfers: 0,
failed_transfers: 0,
total_amount: '0.00',
today_amount: '0.00',
today_transfers: 0
};
res.json({
success: true,
data: {
transfers,
pagination: {
current_page: pageNum,
per_page: limitNum,
total,
total_pages: Math.ceil(total / limitNum)
},
stats
}
});
} catch (error) {
logger.error('获取转账记录失败', {
error: error.message,
stack: error.stack,
agentId: req.agent?.id
});
res.status(500).json({
success: false,
message: '获取转账记录失败'
});
}
});
/**
* 获取单个转账记录详情
* GET /api/transfers/:id
*/
router.get('/:id', agentAuth, async (req, res) => {
try {
const agentId = req.agent.id;
const transferId = req.params.id;
// 查询转账记录详情
const [transfers] = await getDB().execute(`
SELECT t.id,
t.from_user_id,
t.to_user_id,
t.amount,
t.source_type,
t.status,
t.description,
t.matching_order_id,
t.created_at,
t.updated_at,
u1.username as from_username,
u1.real_name as from_real_name,
u1.phone as from_phone,
u1.avatar as from_avatar,
u1.city as from_city,
u1.district as from_district,
u2.username as to_username,
u2.real_name as to_real_name,
u2.phone as to_phone,
u2.avatar as to_avatar,
u2.city as to_city,
u2.district as to_district
FROM transfers t
LEFT JOIN users u1 ON t.from_user_id = u1.id
LEFT JOIN users u2 ON t.to_user_id = u2.id
LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
WHERE t.id = ?
AND (am1.agent_id = ? OR am2.agent_id = ?)
`, [transferId, agentId, agentId]);
if (transfers.length === 0) {
return res.status(404).json({
success: false,
message: '转账记录不存在或无权限查看'
});
}
const transfer = transfers[0];
res.json({
success: true,
data: transfer
});
} catch (error) {
logger.error('获取转账记录详情失败', {
error: error.message,
stack: error.stack,
agentId: req.agent?.id,
transferId: req.params.id
});
res.status(500).json({
success: false,
message: '获取转账记录详情失败'
});
}
});
/**
* 获取转账趋势数据
* GET /api/transfers/trend
*/
router.get('/trend/data', agentAuth, async (req, res) => {
try {
const agentId = req.agent.id;
const {days = 7, type = 'amount'} = req.query;
let selectField = 'CAST(COALESCE(SUM(t.amount), 0) AS DECIMAL(10,2)) as value';
if (type === 'count') {
selectField = 'COUNT(*) as value';
}
const [trendData] = await getDB().execute(`
SELECT
DATE (t.created_at) as date, ${selectField}
FROM transfers t
LEFT JOIN agent_merchants am1
ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
WHERE (am1.agent_id = ?
OR am2.agent_id = ?)
AND t.status = 'completed'
AND t.created_at >= DATE_SUB(CURDATE()
, INTERVAL ? DAY)
GROUP BY DATE (t.created_at)
ORDER BY date ASC
`, [agentId, agentId, parseInt(days)]);
res.json({
success: true,
data: trendData
});
} catch (error) {
logger.error('获取转账趋势失败', {
error: error.message,
stack: error.stack,
agentId: req.agent?.id
});
res.status(500).json({
success: false,
message: '获取转账趋势失败'
});
}
});
/**
* 导出转账记录
* GET /api/transfers/export
*/
router.get('/export/data', agentAuth, async (req, res) => {
try {
const agentId = req.agent.id;
const {
format = 'json',
search,
status,
type,
start_date,
end_date,
min_amount,
max_amount
} = req.query;
// 构建查询条件
let whereConditions = ['(am1.agent_id = ? OR am2.agent_id = ?)'];
let queryParams = [agentId, agentId];
if (search) {
whereConditions.push('(u1.username LIKE ? OR u1.real_name LIKE ? OR u2.username LIKE ? OR u2.real_name LIKE ?)');
queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`);
}
if (status) {
whereConditions.push('t.status = ?');
queryParams.push(status);
}
if (type) {
whereConditions.push('t.source_type = ?');
queryParams.push(type);
}
if (start_date) {
whereConditions.push('DATE(t.created_at) >= ?');
queryParams.push(start_date);
}
if (end_date) {
whereConditions.push('DATE(t.created_at) <= ?');
queryParams.push(end_date);
}
if (min_amount) {
whereConditions.push('t.amount >= ?');
queryParams.push(parseFloat(min_amount));
}
if (max_amount) {
whereConditions.push('t.amount <= ?');
queryParams.push(parseFloat(max_amount));
}
const whereClause = whereConditions.join(' AND ');
// 查询转账记录
const [transfers] = await getDB().execute(`
SELECT t.id,
t.amount,
t.source_type,
t.status,
t.description,
t.matching_order_id,
t.created_at,
u1.username as from_username,
u1.real_name as from_real_name,
u1.phone as from_phone,
u2.username as to_username,
u2.real_name as to_real_name,
u2.phone as to_phone
FROM transfers t
LEFT JOIN users u1 ON t.from_user_id = u1.id
LEFT JOIN users u2 ON t.to_user_id = u2.id
LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id
LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id
WHERE ${whereClause}
ORDER BY t.created_at DESC
`, queryParams);
if (format === 'csv') {
// 生成CSV格式
const csvHeader = 'ID,金额,类型,状态,描述,交易ID,转出用户,转出手机,转入用户,转入手机,创建时间\n';
const csvData = transfers.map(transfer => {
return [
transfer.id,
transfer.amount,
transfer.type || '',
transfer.status || '',
(transfer.description || '').replace(/,/g, ''), // 替换逗号避免CSV格式问题
transfer.matching_order_id || '',
transfer.from_real_name || transfer.from_username || '',
transfer.from_phone || '',
transfer.to_real_name || transfer.to_username || '',
transfer.to_phone || '',
transfer.created_at || ''
].join(',');
}).join('\n');
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', `attachment; filename="transfers_${Date.now()}.csv"`);
res.send(csvHeader + csvData);
} else {
// 默认JSON格式
res.json({
success: true,
data: transfers,
exported_at: new Date().toISOString(),
total: transfers.length
});
}
} catch (error) {
logger.error('导出转账记录失败', {
error: error.message,
stack: error.stack,
agentId: req.agent?.id
});
res.status(500).json({
success: false,
message: '导出转账记录失败'
});
}
});
module.exports = router;