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();