Files
jurong_circle_black/routes/matchingAdmin.js
2025-08-26 10:06:23 +08:00

472 lines
17 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 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;