Files
jurong_circle_black/routes/matchingAdmin.js

472 lines
17 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
const express = require('express');
const router = express.Router();
const db = require('../database');
const { auth, adminAuth } = require('../middleware/auth');
const logger = require('../config/logger');
const matchingService = require('../services/matchingService');
const dayjs = require('dayjs');
// 获取不合理的匹配记录(正余额用户被匹配的情况)
router.get('/unreasonable-matches', auth, adminAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
// 查找正余额用户被匹配的情况
const query = `SELECT
oa.id as allocation_id,
oa.from_user_id,
oa.to_user_id,
oa.amount,
oa.status,
oa.outbound_date,
oa.created_at,
u_to.username as to_username,
u_to.balance as to_user_balance,
u_from.username as from_username,
u_from.balance as from_user_balance,
mo.amount as total_order_amount
FROM transfers oa
JOIN users u_to ON oa.to_user_id = u_to.id
JOIN users u_from ON oa.from_user_id = u_from.id
JOIN matching_orders mo ON oa.id = mo.id
WHERE oa.source_type = 'allocation'
AND u_to.balance > 0
AND u_to.is_system_account = FALSE
AND oa.status IN ('pending', 'confirmed')
ORDER BY oa.created_at DESC
LIMIT ${offset}, ${limit}`;
const countQuery = `SELECT COUNT(*) as total
FROM transfers oa
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation'
AND u_to.balance > 0
AND u_to.is_system_account = FALSE
AND oa.status IN ('pending', 'confirmed')`;
const unreasonableMatches = await db.executeQuery(query);
// 获取总数
const countResult = await db.executeQuery(countQuery);
const total = countResult[0].total;
res.json({
success: true,
data: {
matches: unreasonableMatches,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取不合理匹配记录失败:', error);
res.status(500).json({ message: '获取不合理匹配记录失败' });
}
});
// 修复不合理的匹配记录
router.post('/fix-unreasonable-match/:allocationId', auth, adminAuth, async (req, res) => {
try {
const { allocationId } = req.params;
const { action } = req.body; // 'cancel' 或 'reassign'
const connection = await db.getDB().getConnection();
await connection.query('START TRANSACTION');
try {
// 获取分配详情
const [allocationResult] = await connection.execute(
`SELECT oa.*, u_to.balance as to_user_balance, u_to.username as to_username
FROM transfers oa
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation' AND oa.id = ?`,
[allocationId]
);
if (allocationResult.length === 0) {
await connection.query('ROLLBACK');
connection.release();
return res.status(404).json({ message: '分配记录不存在' });
}
const allocation = allocationResult[0];
if (allocation.to_user_balance <= 0) {
await connection.query('ROLLBACK');
connection.release();
return res.status(400).json({ message: '该用户余额已为负数,无需修复' });
}
if (action === 'cancel') {
// 获取当前时间
const currentTime = new Date();
// 取消分配
await connection.execute(
'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?',
[currentTime, allocationId]
);
// 记录操作日志
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "fix_matching", "allocation", ?, ?, ?)',
[req.user.id, allocationId, `取消不合理匹配:正余额用户${allocation.to_username}(余额${allocation.to_user_balance}元)被匹配${allocation.amount}`, currentTime]
);
} else if (action === 'reassign') {
// 重新分配给负余额用户
const usedTargetUsers = new Set([allocation.to_user_id]);
const newTargetUser = await matchingService.getMatchingTargetExcluding(allocation.from_user_id, usedTargetUsers);
if (!newTargetUser) {
await connection.query('ROLLBACK');
connection.release();
return res.status(400).json({ message: '没有可用的负余额用户进行重新分配' });
}
// 获取当前时间
const currentTime = new Date();
// 更新分配目标
await connection.execute(
'UPDATE transfers SET to_user_id = ?, updated_at = ? WHERE id = ?',
[newTargetUser, currentTime, allocationId]
);
// 获取新目标用户信息
const [newUserResult] = await connection.execute(
'SELECT username, balance FROM users WHERE id = ?',
[newTargetUser]
);
const newUser = newUserResult[0];
// 记录操作日志
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "fix_matching", "allocation", ?, ?, ?)',
[req.user.id, allocationId, `修复不合理匹配:从正余额用户${allocation.to_username}(余额${allocation.to_user_balance}元)重新分配给负余额用户${newUser.username}(余额${newUser.balance}元)`, currentTime]
);
} else {
await connection.query('ROLLBACK');
connection.release();
return res.status(400).json({ message: '无效的操作类型' });
}
await connection.query('COMMIT');
connection.release();
res.json({
success: true,
message: action === 'cancel' ? '已取消不合理匹配' : '已重新分配给负余额用户'
});
} catch (innerError) {
await connection.query('ROLLBACK');
connection.release();
throw innerError;
}
} catch (error) {
console.error('修复不合理匹配失败:', error);
res.status(500).json({ message: error.message || '修复不合理匹配失败' });
}
});
// 获取匹配统计信息
router.get('/matching-stats', auth, adminAuth, async (req, res) => {
try {
// 获取各种统计数据
const stats = await db.executeQuery(
`SELECT
COUNT(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as unreasonable_matches,
COUNT(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as reasonable_matches,
COUNT(CASE WHEN u_to.is_system_account = TRUE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as system_matches,
SUM(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as unreasonable_amount,
SUM(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as reasonable_amount
FROM transfers oa
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation'`
);
// 获取昨天的匹配验证统计
const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD');
const yesterdayStats = await db.executeQuery(
`SELECT
SUM(oa.amount) as total_outbound,
COUNT(DISTINCT oa.amount) as unique_amounts
FROM transfers oa
JOIN users u ON oa.from_user_id = u.id
WHERE oa.source_type = 'allocation' AND DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`,
[yesterdayStr]
);
res.json({
success: true,
data: {
currentStats: stats[0],
yesterdayStats: yesterdayStats[0]
}
});
} catch (error) {
console.error('获取匹配统计失败:', error);
res.status(500).json({ message: '获取匹配统计失败' });
}
});
// 批量修复所有不合理匹配
router.post('/fix-all-unreasonable', auth, adminAuth, async (req, res) => {
try {
let fixedCount = 0;
let errorCount = 0;
const errors = [];
// 获取所有不合理的匹配记录
const unreasonableMatches = await db.executeQuery(
`SELECT oa.id, oa.from_user_id, oa.to_user_id, oa.amount, u_to.username, u_to.balance
FROM transfers oa
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation'
AND u_to.balance > 0
AND u_to.is_system_account = FALSE
AND oa.status IN ('pending', 'confirmed')
ORDER BY u_to.balance DESC`
);
for (const match of unreasonableMatches) {
const connection = await db.getDB().getConnection();
try {
await connection.query('START TRANSACTION');
// 尝试重新分配给负余额用户
const usedTargetUsers = new Set([match.to_user_id]);
const newTargetUser = await matchingService.getMatchingTargetExcluding(match.from_user_id, usedTargetUsers);
// 获取当前时间
const currentTime = new Date();
if (newTargetUser) {
// 更新分配目标
await connection.execute(
'UPDATE transfers SET to_user_id = ?, updated_at = ? WHERE id = ?',
[newTargetUser, currentTime, match.id]
);
// 记录操作日志
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)',
[req.user.id, match.id, `批量修复:从正余额用户${match.username}(余额${match.balance}元)重新分配${match.amount}元给负余额用户`, currentTime]
);
fixedCount++;
} else {
// 如果没有可用的负余额用户,取消分配
await connection.execute(
'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?',
[currentTime, match.id]
);
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)',
[req.user.id, match.id, `批量修复:取消正余额用户${match.username}(余额${match.balance}元)的${match.amount}元分配`, currentTime]
);
fixedCount++;
}
await connection.query('COMMIT');
connection.release();
} catch (error) {
await connection.query('ROLLBACK');
connection.release();
errorCount++;
errors.push(`分配ID ${match.id}: ${error.message}`);
console.error(`修复分配${match.id}失败:`, error);
}
}
res.json({
success: true,
message: `批量修复完成:成功修复${fixedCount}条记录,失败${errorCount}条记录`,
data: {
fixedCount,
errorCount,
errors: errors.slice(0, 10) // 只返回前10个错误
}
});
} catch (error) {
console.error('批量修复不合理匹配失败:', error);
res.status(500).json({ message: '批量修复不合理匹配失败' });
}
});
// 确认分配
router.post('/confirm-allocation/:allocationId', auth, adminAuth, async (req, res) => {
try {
const { allocationId } = req.params;
const adminId = req.user.id;
const connection = await db.getDB().getConnection();
try {
await connection.query('START TRANSACTION');
// 检查分配是否存在且状态为pending
const [allocations] = await connection.execute(
`SELECT oa.*, u_from.username as from_username, u_to.username as to_username
FROM transfers oa
JOIN users u_from ON oa.from_user_id = u_from.id
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`,
[allocationId]
);
if (allocations.length === 0) {
await connection.query('ROLLBACK');
connection.release();
return res.status(404).json({ message: '分配不存在或状态不是待处理' });
}
const allocation = allocations[0];
// 获取当前时间
const currentTime = new Date();
// 计算3小时后的截止时间
const deadline = new Date();
deadline.setHours(deadline.getHours() + 3);
// 创建转账记录直接设置为confirmed状态
const transferDescription = `匹配订单 ${allocation.matching_order_id}${allocation.cycle_number} 轮转账(管理员确认)`;
const [transferResult] = await connection.execute(
`INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, status, description, deadline_at, confirmed_at, source_type) VALUES (?, ?, ?, "user_to_user", "confirmed", ?, ?, ?, 'allocation')`,
[
allocation.from_user_id,
allocation.to_user_id,
allocation.amount,
transferDescription,
deadline,
currentTime
]
);
// 更新分配状态为已确认,并关联转账记录
await connection.execute(
'UPDATE transfers SET status = "confirmed", transfer_id = ?, confirmed_at = ?, updated_at = ? WHERE id = ?',
[transferResult.insertId, currentTime, currentTime, allocationId]
);
// 记录管理员操作日志
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "confirm_allocation", "allocation", ?, ?, ?)',
[adminId, allocationId, `管理员确认分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}`, currentTime]
);
// 记录确认动作到匹配记录
await connection.execute(
'INSERT INTO matching_records (matching_order_id, user_id, action, amount, note) VALUES (?, ?, "confirm", ?, ?)',
[
allocation.matching_order_id,
adminId,
allocation.amount,
'管理员确认分配'
]
);
await connection.query('COMMIT');
connection.release();
res.json({
success: true,
message: '分配已确认'
});
} catch (innerError) {
await connection.query('ROLLBACK');
connection.release();
throw innerError;
}
} catch (error) {
console.error('确认分配失败:', error);
res.status(500).json({ message: error.message || '确认分配失败' });
}
});
// 取消分配
router.post('/cancel-allocation/:allocationId', auth, adminAuth, async (req, res) => {
try {
const { allocationId } = req.params;
const adminId = req.user.id;
const connection = await db.getDB().getConnection();
try {
await connection.query('START TRANSACTION');
// 检查分配是否存在且状态为pending
const [allocations] = await connection.execute(
`SELECT oa.*, u_from.username as from_username, u_to.username as to_username
FROM transfers oa
JOIN users u_from ON oa.from_user_id = u_from.id
JOIN users u_to ON oa.to_user_id = u_to.id
WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`,
[allocationId]
);
if (allocations.length === 0) {
await connection.query('ROLLBACK');
connection.release();
return res.status(404).json({ message: '分配不存在或状态不是待处理' });
}
const allocation = allocations[0];
// 获取当前时间
const currentTime = new Date();
// 更新分配状态为已取消
await connection.execute(
'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?',
[currentTime, allocationId]
);
// 记录管理员操作日志
await connection.execute(
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "cancel_allocation", "allocation", ?, ?, ?)',
[adminId, allocationId, `管理员取消分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}`, currentTime]
);
await connection.query('COMMIT');
connection.release();
res.json({
success: true,
message: '分配已取消'
});
} catch (innerError) {
await connection.query('ROLLBACK');
connection.release();
throw innerError;
}
} catch (error) {
console.error('取消分配失败:', error);
res.status(500).json({ message: error.message || '取消分配失败' });
}
});
module.exports = router;