Files
jurong_circle_black/services/timeoutService.js

380 lines
11 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
const { getDB } = require('../database');
const { logger, auditLogger } = require('../config/logger');
class TimeoutService {
/**
* 检查转账超时情况
* 标记超时转账和风险用户自动取消超过2.5小时的pending转账
*/
async checkTransferTimeouts() {
const db = getDB();
try {
// 只在调试模式下输出开始检查的日志
// console.log('开始检查转账超时情况...');
// 1. 查找所有超时的转账记录有deadline_at的
const [overdueTransfers] = await db.execute(
`SELECT t.*, u.username, u.real_name
FROM transfers t
JOIN users u ON t.from_user_id = u.id
WHERE t.status = 'pending'
AND t.deadline_at IS NOT NULL
AND t.deadline_at < NOW()
AND t.is_overdue = 0`
);
// 2. 查找所有pending状态超过2.5小时的转账记录
const [longPendingTransfers] = await db.execute(
`SELECT t.*, u.username, u.real_name
FROM transfers t
JOIN users u ON t.from_user_id = u.id
WHERE t.status = 'pending'
AND t.created_at < DATE_SUB(NOW(), INTERVAL 150 MINUTE)`
);
let hasWork = false;
// 处理有deadline的超时转账
if (overdueTransfers.length > 0) {
hasWork = true;
console.log(`⚠️ 发现 ${overdueTransfers.length} 笔超时转账,开始处理...`);
for (const transfer of overdueTransfers) {
await this.handleOverdueTransfer(transfer);
}
}
// 处理超过2.5小时的pending转账
if (longPendingTransfers.length > 0) {
hasWork = true;
console.log(`⚠️ 发现 ${longPendingTransfers.length} 笔超过2.5小时的pending转账开始自动取消...`);
for (const transfer of longPendingTransfers) {
await this.handleLongPendingTransfer(transfer);
}
}
if (hasWork) {
console.log('✅ 转账超时检查完成');
}
} catch (error) {
console.error('检查转账超时失败:', error);
logger.error('Transfer timeout check failed', { error: error.message });
}
}
/**
* 处理超时转账
* @param {Object} transfer - 转账记录
*/
async handleOverdueTransfer(transfer) {
const db = getDB();
try {
await db.query('START TRANSACTION');
// 标记转账为超时和坏账
await db.execute(
'UPDATE transfers SET is_overdue = 1, is_bad_debt = 1, overdue_at = NOW() WHERE id = ?',
[transfer.id]
);
// 标记用户为风险用户
await db.execute(
`UPDATE users SET
is_risk_user = 1,
risk_reason = CONCAT(IFNULL(risk_reason, ''), '转账超时(转账ID: ', ?, ', 金额: ', ?, '元, 超时时间: ', NOW(), '); ')
WHERE id = ?`,
[transfer.id, transfer.amount, transfer.from_user_id]
);
await db.query('COMMIT');
// 记录审计日志
auditLogger.info('Transfer marked as overdue and bad debt, user marked as risk', {
transferId: transfer.id,
userId: transfer.from_user_id,
username: transfer.username,
amount: transfer.amount,
deadlineAt: transfer.deadline_at
});
console.log(`转账 ${transfer.id} 已标记为超时和坏账,用户 ${transfer.username}(ID: ${transfer.from_user_id}) 已标记为风险用户`);
} catch (error) {
await db.query('ROLLBACK');
console.error(`处理超时转账 ${transfer.id} 失败:`, error);
throw error;
}
}
/**
* 处理超过2.5小时的pending转账
* @param {Object} transfer - 转账记录
*/
async handleLongPendingTransfer(transfer) {
const db = getDB();
try {
await db.query('START TRANSACTION');
// 将转账状态改为cancelled
await db.execute(
'UPDATE transfers SET status = "cancelled", updated_at = NOW() WHERE id = ?',
[transfer.id]
);
// 如果有关联的matching_order_id检查并更新matching_orders状态
if (transfer.matching_order_id) {
// 检查该matching_order下是否还有非cancelled状态的transfers
const [remainingTransfers] = await db.execute(
'SELECT COUNT(*) as count FROM transfers WHERE matching_order_id = ? AND status != "cancelled"',
[transfer.matching_order_id]
);
// 如果所有关联的transfers都是cancelled状态则更新matching_order状态为cancelled
if (remainingTransfers[0].count === 0) {
await db.execute(
'UPDATE matching_orders SET status = "cancelled", updated_at = NOW() WHERE id = ?',
[transfer.matching_order_id]
);
console.log(`匹配订单 ${transfer.matching_order_id} 的所有转账都已取消订单状态已更新为cancelled`);
}
}
await db.query('COMMIT');
// 记录审计日志
auditLogger.info('Long pending transfer auto-cancelled', {
transferId: transfer.id,
userId: transfer.from_user_id,
username: transfer.username,
amount: transfer.amount,
createdAt: transfer.created_at,
matchingOrderId: transfer.matching_order_id
});
console.log(`转账 ${transfer.id} 超过2.5小时未处理,已自动取消 (用户: ${transfer.username}, 金额: ${transfer.amount}元)`);
} catch (error) {
await db.query('ROLLBACK');
console.error(`处理长时间pending转账 ${transfer.id} 失败:`, error);
throw error;
}
}
/**
* 获取风险用户列表
* @param {Object} filters - 筛选条件
* @param {Object} pagination - 分页参数
* @returns {Object} 风险用户列表和分页信息
*/
async getRiskUsers(filters = {}, pagination = {}) {
const db = getDB();
const { page = 1, limit = 10 } = pagination;
const pageNum = parseInt(page, 10) || 1;
const limitNum = parseInt(limit, 10) || 10;
const offset = (pageNum - 1) * limitNum;
let whereClause = 'WHERE is_risk_user = 1';
const params = [];
// 构建查询条件
if (filters.is_blacklisted !== undefined) {
whereClause += ' AND is_blacklisted = ?';
params.push(filters.is_blacklisted);
}
if (filters.username) {
whereClause += ' AND username LIKE ?';
params.push(`%${filters.username}%`);
}
try {
// 获取总数
const [countResult] = await db.execute(
`SELECT COUNT(*) as total FROM users ${whereClause}`,
params
);
const total = countResult[0].total;
// 获取数据
const [users] = await db.execute(
`SELECT id, username, real_name, is_risk_user, is_blacklisted,
risk_reason, blacklist_reason, blacklisted_at, created_at,phone
FROM users
${whereClause}
ORDER BY created_at DESC
LIMIT ${limitNum} OFFSET ${offset}`,
params
);
return {
users,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
};
} catch (error) {
logger.error('Failed to get risk users', { error: error.message, filters });
throw error;
}
}
/**
* 拉黑用户
* @param {number} userId - 用户ID
* @param {string} reason - 拉黑原因
* @param {number} operatorId - 操作员ID
*/
async blacklistUser(userId, reason, operatorId) {
const db = getDB();
try {
// 检查用户是否存在
const [users] = await db.execute(
'SELECT id, username, phone FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
throw new Error('用户不存在');
}
const user = users[0];
if (user.is_blacklisted) {
throw new Error('用户已被拉黑');
}
// 拉黑用户
await db.execute(
`UPDATE users SET
is_blacklisted = 1,
blacklist_reason = ?,
blacklisted_at = NOW()
WHERE id = ?`,
[reason, userId]
);
// 记录审计日志
auditLogger.info('User blacklisted', {
userId,
username: user.username,
reason,
operatorId
});
logger.info('User blacklisted successfully', { userId, operatorId });
} catch (error) {
logger.error('Failed to blacklist user', {
error: error.message,
userId,
operatorId
});
throw error;
}
}
/**
* 解除拉黑
* @param {number} userId - 用户ID
* @param {number} operatorId - 操作员ID
*/
async unblacklistUser(userId, operatorId) {
const db = getDB();
try {
// 检查用户是否存在
const [users] = await db.execute(
'SELECT id, username, is_blacklisted FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
throw new Error('用户不存在');
}
const user = users[0];
if (!user.is_blacklisted) {
throw new Error('用户未被拉黑');
}
// 解除拉黑
await db.execute(
`UPDATE users SET
is_blacklisted = 0,
blacklist_reason = NULL,
blacklisted_at = NULL
WHERE id = ?`,
[userId]
);
// 记录审计日志
auditLogger.info('User unblacklisted', {
userId,
username: user.username,
operatorId
});
logger.info('User unblacklisted successfully', { userId, operatorId });
} catch (error) {
logger.error('Failed to unblacklist user', {
error: error.message,
userId,
operatorId
});
throw error;
}
}
/**
* 检查用户是否被拉黑
* @param {number} userId - 用户ID
* @returns {boolean} 是否被拉黑
*/
async isUserBlacklisted(userId) {
const db = getDB();
try {
const [users] = await db.execute(
'SELECT is_blacklisted FROM users WHERE id = ?',
[userId]
);
return users.length > 0 && users[0].is_blacklisted === 1;
} catch (error) {
logger.error('Failed to check user blacklist status', {
error: error.message,
userId
});
throw error;
}
}
/**
* 启动定时检查任务
* 每5分钟检查一次转账超时情况
*/
startTimeoutChecker() {
console.log('启动转账超时检查定时任务...');
// 立即执行一次
this.checkTransferTimeouts();
// 每5分钟执行一次
setInterval(() => {
this.checkTransferTimeouts();
}, 5 * 1000); // 5秒
}
}
module.exports = new TimeoutService();