const { getDB } = require('../database'); const timeoutService = require('./timeoutService'); const dayjs = require('dayjs'); /** * 获取本地时区的日期字符串(YYYY-MM-DD格式) * 确保在晚上12点正确切换到第二天 * @param {Date} date - 可选的日期对象,默认为当前时间 * @returns {string} 格式化的日期字符串 */ function getLocalDateString(date = new Date()) { return dayjs(date).format('YYYY-MM-DD'); } class MatchingService { // 创建匹配订单(支持两种模式) async createMatchingOrder(userId, matchingType = 'small', customAmount = null) { const db = getDB(); try { // 检查用户是否被拉黑 const isBlacklisted = await timeoutService.isUserBlacklisted(userId); if (isBlacklisted) { throw new Error('您已被拉黑,无法参与匹配。如有疑问请联系管理员。'); } // 检查用户审核状态、必要信息和余额 const [userResult] = await db.execute( `SELECT audit_status, balance, wechat_qr, alipay_qr, unionpay_qr, bank_card, business_license, id_card_front, id_card_back FROM users WHERE id = ?`, [userId] ); if (userResult.length === 0) { throw new Error('用户不存在'); } const user = userResult[0]; // 检查用户余额:只有负余额用户才能发起匹配 // if (user.balance > 0) { // throw new Error('只有余额为负数的用户才能发起匹配,这是为了防止公司资金损失的重要规则'); // } // 检查用户审核状态 if (user.audit_status !== 'approved') { if (user.audit_status === 'pending') { throw new Error('您的账户正在审核中,审核通过后才能参与匹配'); } else if (user.audit_status === 'rejected') { throw new Error('您的账户审核未通过,请联系管理员'); } } // 检查必要的收款信息是否已上传 const missingItems = []; if (!user.wechat_qr && !user.alipay_qr && !user.unionpay_qr) { missingItems.push('收款码(微信/支付宝/云闪付至少一种)'); } if (!user.bank_card) { missingItems.push('银行卡号'); } if (!user.business_license) { missingItems.push('营业执照'); } if (!user.id_card_front || !user.id_card_back) { missingItems.push('身份证正反面'); } if (missingItems.length > 0) { throw new Error(`请先上传以下信息:${missingItems.join('、')}`); } await db.query('START TRANSACTION'); let totalAmount, maxCycles; if (matchingType === 'small') { // 小额匹配:固定5000元金额 totalAmount = 5000; maxCycles = 1; } else if (matchingType === 'large') { // 大额匹配:用户自定义金额(最高5万) if (!customAmount || customAmount < 3000 || customAmount > 50000) { throw new Error('大额匹配金额必须在3000-50000之间'); } totalAmount = customAmount; maxCycles = 1; } else { throw new Error('不支持的匹配类型'); } // 创建匹配订单 const [result] = await db.execute( 'INSERT INTO matching_orders (initiator_id, amount, status, max_cycles, matching_type) VALUES (?, ?, "matching", ?, ?)', [userId, totalAmount, maxCycles, matchingType] ); const orderId = result.insertId; // 记录用户参与 await db.execute( 'INSERT INTO matching_records (matching_order_id, user_id, action, amount) VALUES (?, ?, "join", ?)', [orderId, userId, totalAmount] ); await db.query('COMMIT'); // 立即生成智能分配 const allocations = await this.generateSmartAllocationsWithDB(orderId, userId); // 检查并触发系统账户反向匹配 // await this.checkAndTriggerSystemMatching(); return { orderId, matchingType, totalAmount, allocations: allocations || [], allocationCount: allocations ? allocations.length : 0 }; } catch (error) { await db.query('ROLLBACK'); console.error('创建匹配订单失败:', error); throw error; } } /** * 检查资金平衡并触发系统账户反向匹配 * 当收款需求大于打款资金时,系统账户主动发起匹配 */ async checkAndTriggerSystemMatching() { const db = getDB(); try { // 计算当前待收款总额(负余额用户的资金缺口) const [negativeBalanceResult] = await db.execute(` SELECT SUM(ABS(balance)) as total_deficit FROM users WHERE is_system_account = FALSE AND balance < 0 `); const totalDeficit = negativeBalanceResult[0].total_deficit || 0; // 计算当前待打款总额(pending状态的分配) const [pendingPaymentsResult] = await db.execute(` SELECT SUM(oa.amount) as total_pending FROM transfers oa JOIN users u ON oa.from_user_id = u.id WHERE oa.status = 'pending' AND u.is_system_account = FALSE `); const totalPendingPayments = pendingPaymentsResult[0].total_pending || 0; console.log(`资金平衡检查: 总资金缺口=${totalDeficit}, 待打款总额=${totalPendingPayments}`); // 如果收款需求大于打款资金,触发系统账户反向匹配 if (totalDeficit > totalPendingPayments) { const shortfall = totalDeficit - totalPendingPayments; console.log(`检测到资金缺口: ${shortfall}元,触发系统账户反向匹配`); await this.createSystemReverseMatching(shortfall); } } catch (error) { console.error('检查资金平衡失败:', error); // 不抛出错误,避免影响主流程 } } /** * 创建系统账户反向匹配 * 系统账户作为付款方,向有资金缺口的用户打款 * @param {number} targetAmount - 目标匹配金额 */ async createSystemReverseMatching(targetAmount) { const db = getDB(); try { // 获取可用的系统账户 const [systemAccounts] = await db.execute(` SELECT id, balance FROM users WHERE is_system_account = TRUE AND balance > 1000 ORDER BY balance DESC LIMIT 1 `); if (systemAccounts.length === 0) { console.log('没有可用的系统账户进行反向匹配'); return; } const systemAccount = systemAccounts[0]; // 确定实际匹配金额(不超过系统账户余额的80%) const maxMatchAmount = Math.min(targetAmount, systemAccount.balance * 0.8); if (maxMatchAmount < 1000) { console.log('系统账户余额不足,无法进行反向匹配'); return; } // 创建系统反向匹配订单 const [result] = await db.execute( 'INSERT INTO matching_orders (initiator_id, amount, status, max_cycles, matching_type, is_system_reverse) VALUES (?, ?, "matching", 1, "system_reverse", TRUE)', [systemAccount.id, maxMatchAmount] ); const orderId = result.insertId; // 生成分配给负余额用户 await this.generateSystemReverseAllocations(orderId, maxMatchAmount, systemAccount.id); console.log(`系统反向匹配创建成功: 订单ID=${orderId}, 金额=${maxMatchAmount}`); } catch (error) { console.error('创建系统反向匹配失败:', error); } } /** * 为系统反向匹配生成分配 * @param {number} orderId - 匹配订单ID * @param {number} totalAmount - 总金额 * @param {number} systemUserId - 系统账户ID */ async generateSystemReverseAllocations(orderId, totalAmount, systemUserId) { const db = getDB(); try { // 获取负余额用户,按缺口大小排序 const [negativeUsers] = await db.execute(` SELECT id, balance, ABS(balance) as deficit FROM users WHERE is_system_account = FALSE AND balance < 0 ORDER BY deficit DESC LIMIT 10 `); if (negativeUsers.length === 0) { console.log('没有负余额用户需要反向匹配'); return; } // 按比例分配金额给负余额用户 const totalDeficit = negativeUsers.reduce((sum, user) => sum + user.deficit, 0); let remainingAmount = totalAmount; for (let i = 0; i < negativeUsers.length && remainingAmount > 0; i++) { const user = negativeUsers[i]; let allocationAmount; if (i === negativeUsers.length - 1) { // 最后一个用户分配剩余金额 allocationAmount = remainingAmount; } else { // 按比例分配 const proportion = user.deficit / totalDeficit; allocationAmount = Math.min( Math.floor(totalAmount * proportion), user.deficit, remainingAmount ); } if (allocationAmount > 0) { // 创建分配记录(系统账户向用户转账) await db.execute( 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status) VALUES (?, ?, ?, ?, 1, "pending")', [orderId, systemUserId, user.id, allocationAmount] ); remainingAmount -= allocationAmount; console.log(`系统反向分配: ${allocationAmount}元 从系统账户${systemUserId} 到用户${user.id}`); } } } catch (error) { console.error('生成系统反向分配失败:', error); throw error; } } /** * 生成大额匹配的随机金额分拆(15000以上) * @param {number} totalAmount - 总金额 * @returns {Array} 分拆后的金额数组 */ generateRandomLargeAmounts(totalAmount) { const amounts = []; let remaining = totalAmount; const minAmount = 1000; // 最小单笔金额 const maxAmount = 8000; // 最大单笔金额 while (remaining > maxAmount) { // 生成随机金额,确保剩余金额至少还能分一笔 const maxThisAmount = Math.min(maxAmount, remaining - minAmount); const amount = Math.floor(Math.random() * (maxThisAmount - minAmount + 1)) + minAmount; amounts.push(amount); remaining -= amount; } // 最后一笔是剩余金额 if (remaining > 0) { amounts.push(remaining); } return amounts; } /** * 生成3笔分配(兼容旧版本接口) * 确保不会分配给同一个用户 * @param {number} orderId - 订单ID * @param {Array} amounts - 金额数组 * @param {number} initiatorId - 发起人ID */ /** * 验证匹配金额是否符合业务规则 * @param {number} userId - 用户ID * @param {number} totalAmount - 总匹配金额 * @param {Array} amounts - 分配金额数组 * @returns {Object} 验证结果和建议金额 */ async validateMatchingAmount(userId, totalAmount, amounts) { const db = getDB(); try { // 获取昨天的日期(本地时区) const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); // 获取前一天所有用户的出款总数(系统总出款) const [systemOutboundResult] = await db.execute( `SELECT SUM(oa.amount) as total_outbound FROM transfers oa JOIN users u ON oa.from_user_id = u.id WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`, [yesterdayStr] ); const systemOutbound = systemOutboundResult[0].total_outbound || 0; // 获取前一天所有用户的具体出款金额(用于检查重复) const [yesterdayAmountsResult] = await db.execute( `SELECT DISTINCT oa.amount FROM transfers oa JOIN users u ON oa.from_user_id = u.id WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`, [yesterdayStr] ); const yesterdayAmounts = yesterdayAmountsResult.map(row => row.amount); // 检查每笔金额是否与前一天的金额不同 const duplicateAmounts = []; for (const amount of amounts) { if (yesterdayAmounts.includes(amount)) { duplicateAmounts.push(amount); } } return { isValid: duplicateAmounts.length === 0, systemOutbound, duplicateAmounts, suggestedAmount: systemOutbound, message: duplicateAmounts.length > 0 ? `以下金额与前一天重复: ${duplicateAmounts.join(', ')}元` : '匹配金额验证通过' }; } catch (error) { console.error('验证匹配金额失败:', error); return { isValid: false, systemOutbound: 0, duplicateAmounts: [], suggestedAmount: 0, message: '验证匹配金额时发生错误' }; } } /** * 生成智能分配并创建数据库记录 * @param {number} orderId - 订单ID * @param {number} initiatorId - 发起人ID * @returns {Promise} 返回分配结果数组 */ async generateSmartAllocationsWithDB(orderId, initiatorId) { const db = getDB(); try { // 获取订单总金额 const [orderResult] = await db.execute( 'SELECT amount FROM matching_orders WHERE id = ?', [orderId] ); if (orderResult.length === 0) { throw new Error('匹配订单不存在'); } const totalAmount = orderResult[0].amount; // 使用智能分配算法生成分配方案 const allocations = await this.generateSmartAllocations(totalAmount, initiatorId); if (allocations.length === 0) { throw new Error('无法生成有效的分配方案'); } // 验证总金额(简化版验证) const totalAllocated = allocations.reduce((sum, allocation) => sum + allocation.amount, 0); if (Math.abs(totalAllocated - totalAmount) > 0.01) { throw new Error(`分配金额不匹配:期望${totalAmount}元,实际分配${totalAllocated}元`); } console.log(`智能分配验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 分配${allocations.length}笔`); // 创建分配记录 const createdAllocations = []; for (let i = 0; i < allocations.length; i++) { const allocation = allocations[i]; // 设置出款日期为今天,可回款时间为明天的00:00:00 const today = dayjs(); const tomorrow = dayjs().add(1, 'day').startOf('day'); const [result] = await db.execute( 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status, outbound_date, can_return_after) VALUES (?, ?, ?, ?, 1, "pending", CURDATE(), ?)', [orderId, initiatorId, allocation.userId, allocation.amount, tomorrow.format('YYYY-MM-DD HH:mm:ss')] ); // 添加分配ID到结果中 const createdAllocation = { ...allocation, allocationId: result.insertId, status: 'pending', outboundDate: today.format('YYYY-MM-DD'), canReturnAfter: tomorrow.toISOString() }; createdAllocations.push(createdAllocation); console.log(`创建智能分配: ${allocation.amount}元 从用户${initiatorId} 到用户${allocation.userId}(${allocation.username}) [${allocation.userType}]`); } return createdAllocations; } catch (error) { console.error('生成智能分配失败:', error); throw error; } } /** * 生成传统三笔分配(保留原方法用于兼容性) * @param {number} orderId - 订单ID * @param {Array} amounts - 分配金额数组 * @param {number} initiatorId - 发起人ID * @returns {Promise} */ async generateThreeAllocations(orderId, amounts, initiatorId) { const db = getDB(); try { // 获取订单总金额 const [orderResult] = await db.execute( 'SELECT amount FROM matching_orders WHERE id = ?', [orderId] ); if (orderResult.length === 0) { throw new Error('匹配订单不存在'); } const totalAmount = orderResult[0].amount; // 验证匹配金额是否符合业务规则 const validation = await this.validateMatchingAmount(initiatorId, totalAmount, amounts); if (!validation.isValid) { throw new Error(`匹配金额不符合业务规则:${validation.message}。建议匹配金额:${validation.suggestedAmount}元`); } // 记录验证信息 console.log(`匹配金额验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 前一天系统出款${validation.systemOutbound}元`); const usedTargetUsers = new Set(); // 记录已使用的目标用户 for (let i = 0; i < amounts.length; i++) { const amount = amounts[i]; // 获取匹配目标,排除已使用的用户 const targetUser = await this.getMatchingTargetExcluding(initiatorId, usedTargetUsers); if (!targetUser) { throw new Error(`无法为第${i + 1}笔分配找到匹配目标`); } // 记录已使用的目标用户 usedTargetUsers.add(targetUser); // 创建分配记录,默认为第1轮 // 设置出款日期为今天,可回款时间为明天的00:00:00 const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); // 将Date对象转换为MySQL兼容的字符串格式 const tomorrowStr = tomorrow.toISOString().slice(0, 19).replace('T', ' '); await db.execute( 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status, outbound_date, can_return_after) VALUES (?, ?, ?, ?, 1, "pending", CURDATE(), ?)', [orderId, initiatorId, targetUser, amount, tomorrowStr] ); console.log(`创建分配: ${amount}元 从用户${initiatorId} 到用户${targetUser}`); } } catch (error) { console.error('生成分配失败:', error); throw error; } } /** * 获取匹配目标用户 * @param {number} excludeUserId - 要排除的用户ID * @returns {number} 目标用户ID */ /** * 获取匹配目标用户(排除指定用户集合) * @param {number} excludeUserId - 要排除的发起人用户ID * @param {Set} excludeUserIds - 要排除的用户ID集合 * @returns {number} 目标用户ID */ async getMatchingTargetExcluding(excludeUserId, excludeUserIds = new Set()) { const db = getDB(); try { // 获取今天和昨天的日期(本地时区) const today = new Date(); const todayStr = getLocalDateString(today); const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); const yesterdayStr = getLocalDateString(yesterday); // 获取前一天打款的用户ID列表(需要排除) const [yesterdayPayersResult] = await db.execute( `SELECT DISTINCT oa.from_user_id FROM transfers oa WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed'`, [yesterdayStr] ); const yesterdayPayers = yesterdayPayersResult.map(row => row.from_user_id); // 获取待确认/待处理/即将生成匹配金额总和超过0的普通用户(需要排除) const [pendingUsersResult] = await db.execute( `SELECT oa.to_user_id, SUM(oa.amount) as pending_amount FROM transfers oa JOIN users u ON oa.to_user_id = u.id WHERE oa.status IN ('pending', 'processing', 'generating') AND u.is_system_account = FALSE GROUP BY oa.to_user_id HAVING pending_amount > 0` ); const pendingUsers = pendingUsersResult.map(row => row.to_user_id); // 获取当天有转出订单的普通用户(需要排除) const [todayPayersResult] = await db.execute( `SELECT DISTINCT oa.from_user_id FROM transfers oa JOIN users u ON oa.from_user_id = u.id WHERE DATE(oa.created_at) = ? AND oa.status IN ('confirmed', 'pending', 'processing') AND u.is_system_account = FALSE`, [todayStr] ); const todayPayers = todayPayersResult.map(row => row.from_user_id); // 构建排除用户的条件(包括发起人、已使用的用户、昨天打款的用户、待处理用户、当天转出用户) const excludeList = [ excludeUserId, ...Array.from(excludeUserIds), ...yesterdayPayers, ...pendingUsers, ...todayPayers ]; const placeholders = excludeList.map(() => '?').join(','); // 第一优先级:最早成为负数且通过前面检查的普通用户 // 使用最早的转出记录时间作为成为负数的参考时间 const [earliestNegativeUsers] = await db.execute( `SELECT u.id, u.balance, (SELECT MIN(t.created_at) FROM transfers t WHERE t.from_user_id = u.id AND t.status IN ('confirmed', 'received')) as first_transfer_time FROM users u WHERE u.id NOT IN (${placeholders}) AND u.is_system_account = FALSE AND u.balance < 0 AND (SELECT COUNT(*) FROM transfers t WHERE t.from_user_id = u.id AND t.status IN ('confirmed', 'received')) > 0 ORDER BY first_transfer_time ASC, u.balance ASC, RAND() LIMIT 1`, excludeList ); if (earliestNegativeUsers.length > 0) { return earliestNegativeUsers[0].id; } // 第二优先级:有可回款分配的普通用户(昨天或更早出款,今天可以回款) // 但必须是负余额用户,且通过前面的检查 const [returnableUsers] = await db.execute( `SELECT DISTINCT oa.from_user_id as id, u.balance FROM transfers oa JOIN matching_orders mo ON oa.id = mo.id JOIN users u ON oa.from_user_id = u.id WHERE oa.from_user_id NOT IN (${placeholders}) AND oa.status = 'confirmed' AND oa.can_return_after <= NOW() AND oa.return_date IS NULL AND mo.status = 'matching' AND u.balance < 0 AND u.is_system_account = FALSE ORDER BY oa.can_return_after ASC, u.balance ASC, RAND() LIMIT 1`, excludeList ); if (returnableUsers.length > 0) { return returnableUsers[0].id; } // 第三优先级:其他负余额普通用户(余额为负数说明他们给其他用户转过钱,钱还没收回来) const [negativeBalanceUsers] = await db.execute( `SELECT id FROM users WHERE id NOT IN (${placeholders}) AND is_system_account = FALSE AND balance < 0 ORDER BY balance ASC, RAND() LIMIT 1`, excludeList ); if (negativeBalanceUsers.length > 0) { return negativeBalanceUsers[0].id; } // 最后优先级:虚拟用户(系统账户) const [systemUsers] = await db.execute( `SELECT id FROM users WHERE is_system_account = TRUE AND id NOT IN (${placeholders}) ORDER BY balance DESC, RAND() LIMIT 1`, excludeList ); if (systemUsers.length > 0) { return systemUsers[0].id; } // 如果连系统账户都没有,抛出错误 throw new Error('没有可用的匹配目标:所有符合条件的用户都被排除'); } catch (error) { console.error('获取匹配目标失败:', error); throw error; } } // 获取可用用户 /** * 生成智能分配金额 * 1. 排除今天打款的用户 * 2. 优先分配给负余额用户(余额+待确认收款为负数) * 3. 每笔最高5000,不够再分配给虚拟用户 * 4. 笔数3-10笔 * @param {number} totalAmount - 总金额 * @param {number} excludeUserId - 排除的用户ID(发起人) * @returns {Promise} 分配金额数组 */ async generateSmartAllocations(totalAmount, excludeUserId) { const db = getDB(); const minAmount = 100; const maxAmountPerTransfer = totalAmount; const minTransfers = totalAmount > 5000 ? 4 : 3; const maxTransfers = 10; try { // 首先获取当前用户的城市、省份和区域信息 const [currentUserResult] = await db.execute( `SELECT city, province, district_id FROM users WHERE id = ?`, [excludeUserId] ); const currentUserCity = currentUserResult[0]?.city; const currentUserProvince = currentUserResult[0]?.province; const currentUserDistrictId = currentUserResult[0]?.district_id; // 获取负余额用户,按区县、城市、省份优先级排序 let [userBalanceResult] = await db.execute( `SELECT u.id as user_id, u.balance as current_balance, u.city, u.province, u.district_id FROM users u WHERE u.is_system_account = FALSE AND u.is_distribute = TRUE AND u.id != ? AND u.balance < -100 AND u.audit_status = 'approved' AND u.user_type != 'directly_operated' AND u.payment_status = 'paid' ORDER BY CASE WHEN u.city = ? AND u.district_id = ? THEN 1 -- 相同城市且相同区县排第一 WHEN u.city = ? THEN 2 -- 相同城市但不同区县排第二 WHEN u.province = ? THEN 3 -- 相同省份但不同城市排第三 ELSE 4 -- 其他省份排第四 END, u.balance ASC`, [excludeUserId, currentUserCity, currentUserDistrictId, currentUserCity, currentUserProvince] ); // 处理查询到的负余额用户 const availableUsers = []; for (const user of userBalanceResult) { // 确保余额是数字类型 const currentBalance = parseFloat(user.current_balance) || 0; // 更新用户对象 user.current_balance = currentBalance; // 查询用户的分配订单金额统计 const [orderStatusResult] = await db.execute( `SELECT SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) as pending_amount FROM transfers WHERE to_user_id = ?`, [user.user_id] ); // 查询用户的分配订单金待确认金额统计 const [orderStatusConfirmedResult] = await db.execute( `SELECT SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as confirmed_amount FROM transfers WHERE to_user_id = ?`, [user.user_id] ); //查询用户给其他用户已确认的金额统计(要减去,因为款项还没回来) const [orderStatusConfirmedResultFrom] = await db.execute( `SELECT SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as confirmed_amount FROM transfers WHERE from_user_id = ?`, [user.user_id] ); // 查询用户当天在matching_orders表中打出去的款项 const today = getLocalDateString(); const [todayOutflowResult] = await db.execute( `SELECT SUM(amount) as today_outflow FROM matching_orders WHERE initiator_id = ? AND DATE(updated_at) = ?`, [user.user_id, today] ); // 添加分配金额信息到用户对象 const orderStatus = orderStatusResult[0] || { pending_amount: 0 }; const todayOutflow = todayOutflowResult[0] || { today_outflow: 0 }; const orderStatusConfirmedFrom = orderStatusConfirmedResultFrom[0] || { confirmed_amount: 0 }; const orderStatusConfirmed = orderStatusConfirmedResult[0] || { confirmed_amount: 0 }; user.today_outflow = parseFloat(todayOutflow.today_outflow) || 0; user.pending_amount = parseFloat(orderStatus.pending_amount) || 0; user.confirmed_amount = parseFloat(orderStatusConfirmed.confirmed_amount) || 0; user.has_active_allocations = user.current_balance + user.pending_amount + user.confirmed_amount + user.today_outflow - orderStatusConfirmedFrom.confirmed_amount; // 所有查询到的用户都是负余额用户,直接添加到可用列表 } userBalanceResult = userBalanceResult.filter(user => user.has_active_allocations < -100); userBalanceResult = userBalanceResult.sort((a, b) => a.has_active_allocations - b.has_active_allocations); for (const user of userBalanceResult) { if (maxTransfers > availableUsers.length + 1) { if (minTransfers === 3 && availableUsers.length < 3) { availableUsers.push(user); } if (minTransfers === 4) { availableUsers.push(user); } } console.log(user, '普通用户'); } console.log(`可参与分配的负余额用户数量: ${availableUsers.length}`); // 第二步:由于第一步已经筛选了余额小于0的用户,这里直接使用可用用户作为优先分配用户 // 用户已按余额升序排列(最负的优先),然后按可分配金额降序排列 const priorityUsers = availableUsers; // 所有可用用户都是负余额用户,无需再次筛选 // 第三步:获取虚拟用户作为备选 const [virtualUsersResult] = await db.execute( `SELECT id, username, balance FROM users WHERE is_system_account = TRUE ORDER BY balance DESC, RAND()` ); // 计算分配方案 const allocations = []; let remainingAmount = totalAmount; // 优先分配给当前余额为负的用户 for (const user of priorityUsers) { if (remainingAmount <= 0 || allocations.length >= maxTransfers) break; // 计算该用户可接受的最大分配金额 // 确保分配后用户余额不会变成正数 const currentBalance = Math.abs(user.has_active_allocations); // 使用随机分配而不是平均分配 const remainingTransfers = minTransfers - allocations.length; const minRequiredForRemaining = Math.max(0, (remainingTransfers - 1) * minAmount); // 为剩余转账预留的最小金额,确保不为负数 console.log(`用户${user.user_id}分配计算: remainingAmount=${remainingAmount}, remainingTransfers=${remainingTransfers}, minRequiredForRemaining=${minRequiredForRemaining}`); const maxRandomAllocation = Math.min( currentBalance, // 不能超过安全分配额度,确保接收后余额不会变成正数 maxAmountPerTransfer, // 单笔最大金额限制 remainingAmount - minRequiredForRemaining // 确保剩余金额足够分配给后续转账 ); // 生成随机分配金额,使用极度偏向大值的算法 let maxUserAllocation = 0; if (maxRandomAllocation >= minAmount) { const range = maxRandomAllocation - minAmount; if (range <= 0) { maxUserAllocation = minAmount; } else { if (maxRandomAllocation > 1000) { // 使用更均匀的分配策略 const randomFactor = Math.random(); // 使用均匀分布 // 基础分配:在整个范围内更均匀分布,减少偏向性 const baseOffset = Math.floor(range * 0.15); // 降低到15%的基础偏移 const adjustedRange = range - baseOffset; maxUserAllocation = Math.floor(randomFactor * adjustedRange) + minAmount + baseOffset; // 进一步减少额外增量的影响 const bonusRange = Math.min(range * 0.1, maxRandomAllocation - maxUserAllocation); // 降低到10% if (bonusRange > 0 && Math.random() > 0.7) { // 30%概率获得额外增量,进一步降低 const bonus = Math.floor(Math.random() * bonusRange * 0.3); // 使用30%的bonus范围 maxUserAllocation += bonus; } }else{ maxUserAllocation = maxRandomAllocation } // 确保不超过最大限制 maxUserAllocation = Math.min(maxUserAllocation, maxRandomAllocation); } } console.log(maxUserAllocation, minAmount, '+++++++++++++++'); if (maxUserAllocation >= minAmount) { allocations.push({ userId: user.user_id, username: user.username || `用户${user.user_id}`, amount: maxUserAllocation, userType: 'priority_user', currentBalance: user.current_balance, historicalNetBalance: user.historical_net_balance, totalPendingInflow: user.total_pending_inflow, availableForAllocation: user.available_for_allocation, todayOutflow: user.today_outflow, has_active_allocations: user.has_active_allocations }); remainingAmount -= maxUserAllocation; } } // 如果还有剩余金额且分配数量不足最小笔数,最后分配给虚拟用户 const availableVirtualUsers = virtualUsersResult // 如果有剩余金额,优先检查现有非虚拟用户是否还能消化 if (remainingAmount > 0) { // 筛选出非虚拟用户分配记录 if (allocations.length > 0) { let totalAvailableCapacity = 0; const userCapacities = []; // 计算每个用户的剩余可分配容量 for (const allocation of allocations) { // 获取用户当前的实际余额状态(使用has_active_allocations作为实际可分配余额) const maxSafeAmount = Math.abs(allocation.has_active_allocations); const remainingCapacity = maxSafeAmount - allocation.amount; if (remainingCapacity > 0) { userCapacities.push({ allocation, capacity: remainingCapacity }); totalAvailableCapacity += remainingCapacity; } } console.log(`现有用户剩余容量: ${totalAvailableCapacity}, 待分配金额: ${remainingAmount}`); // 如果现有用户能够消化剩余金额 if (totalAvailableCapacity >= remainingAmount && userCapacities.length > 0 && allocations.length >= 3) { // 按平均分配给这些用户,但需要检查每个用户的分配上限 const averageAmount = Math.floor(remainingAmount / userCapacities.length); let distributedAmount = 0; let remainingToDistribute = remainingAmount; for (let i = 0; i < userCapacities.length; i++) { const { allocation, capacity } = userCapacities[i]; // 计算本次可分配的金额 let amountToAdd = 0; if (i === userCapacities.length - 1) { // 最后一个用户分配剩余的所有金额,但不能超过其容量 amountToAdd = Math.min(remainingToDistribute, capacity); } else { // 其他用户按平均分配,但不能超过其容量 amountToAdd = Math.min(averageAmount, capacity); } if (amountToAdd > 0) { allocation.amount += amountToAdd; distributedAmount += amountToAdd; remainingToDistribute -= amountToAdd; console.log(`为用户${allocation.userId}追加分配${amountToAdd}元,总分配${allocation.amount}元,剩余容量${capacity - amountToAdd}元`); } } // 更新实际分配的剩余金额 remainingAmount = remainingToDistribute; if (remainingAmount === 0) { console.log('剩余金额已全部分配给现有用户'); } else { console.log(`部分剩余金额已分配给现有用户,仍有${remainingAmount}元未分配`); } } } } // 如果仍有剩余金额,检查是否有未分配的用户可以消化剩余金额 if (remainingAmount > 0) { // 获取已分配的用户ID列表 const allocatedUserIds = new Set(allocations.map(a => a.userId)); // 从原始用户列表中找到未分配的用户 const unallocatedUsers = userBalanceResult.filter(user => !allocatedUserIds.has(user.user_id)); if (unallocatedUsers.length > 0) { console.log(`发现${unallocatedUsers.length}个未分配的用户,剩余金额: ${remainingAmount}`); // 查找可分配金额大于剩余金额的用户 for (const user of unallocatedUsers) { const maxSafeAmount = Math.abs(user.has_active_allocations); if (maxSafeAmount >= remainingAmount) { // 找到合适的用户,分配剩余金额 allocations.push({ userId: user.user_id, username: user.username || `User${user.user_id}`, amount: remainingAmount, userType: 'priority_user', currentBalance: user.current_balance, availableForAllocation: user.has_active_allocations }); console.log(`为未分配用户${user.user_id}分配剩余金额${remainingAmount}元`); remainingAmount = 0; break; } } } } // 如果仍有剩余金额,分配给虚拟用户 if (remainingAmount > 0 && availableVirtualUsers.length > 0) { const maxPossibleTransfers = Math.min((minTransfers - allocations.length) <= 0 ? 1 : minTransfers - allocations.length, availableVirtualUsers.length); // 生成随机分配金额数组 const randomAmounts = this.generateRandomAmounts(remainingAmount, maxPossibleTransfers, minAmount, maxAmountPerTransfer); // 为每个随机金额分配虚拟用户 for (let i = 0; i < randomAmounts.length && availableVirtualUsers.length > 0; i++) { const randomIndex = Math.floor(Math.random() * availableVirtualUsers.length); const virtualUser = availableVirtualUsers[randomIndex]; allocations.push({ userId: virtualUser.id, username: virtualUser.username, amount: randomAmounts[i], userType: 'virtual', balance: virtualUser.balance }); remainingAmount -= randomAmounts[i]; availableVirtualUsers.splice(randomIndex, 1); } } // 检查是否有足够的用户来完成分配 if (remainingAmount > 0 && allocations.length < minTransfers && availableVirtualUsers.length === 0) { throw new Error('没有足够的可用用户来完成分配(避免重复分配给同一用户)'); } // 确保至少有最小笔数 if (allocations.length < minTransfers) { throw new Error(`无法生成足够的分配:需要至少${minTransfers}笔,但只能生成${allocations.length}笔`); } // 精确控制总金额,避免超出预期 const currentTotal = allocations.reduce((sum, a) => sum + a.amount, 0); console.log('剩余金额处理前:', remainingAmount, '当前总分配金额:', currentTotal, '期望总金额:', totalAmount); if (remainingAmount > 0 && allocations.length > 0) { // 检查是否会超出总金额 if (currentTotal + remainingAmount <= totalAmount) { console.log('将剩余金额', remainingAmount, '加到最后一笔分配'); allocations[allocations.length - 1].amount += remainingAmount; } else { // 如果会超出,只加到刚好等于总金额的部分 const allowedAmount = totalAmount - currentTotal; if (allowedAmount > 0) { console.log('调整最后一笔分配,增加', allowedAmount, '元以达到精确总金额'); allocations[allocations.length - 1].amount += allowedAmount; } } remainingAmount = 0; // 重置剩余金额 } console.log(`智能分配完成: 总金额${totalAmount}元,分配${allocations.length}笔`); console.log('分配详情:', allocations.map(a => `${a.amount}元 -> 用户${a.userId}(${a.username}) [${a.userType}]` ).join(', ')); return allocations; } catch (error) { console.error('智能分配失败:', error); throw error; } } /** * 生成随机分配金额数组(更均匀的分配策略) * @param {number} totalAmount - 总金额 * @param {number} transferCount - 分配笔数 * @param {number} minAmount - 最小金额 * @param {number} maxAmount - 最大金额 * @returns {number[]} 随机金额数组 */ generateRandomAmounts(totalAmount, transferCount, minAmount, maxAmount) { if (transferCount <= 0 || totalAmount < minAmount * transferCount) { return []; } // 使用更均匀的分配策略 const amounts = []; // 首先为每笔分配最小金额 for (let i = 0; i < transferCount; i++) { amounts.push(minAmount); } let remainingToDistribute = totalAmount - (minAmount * transferCount); // 计算平均每笔应该额外分配的金额 const averageExtra = Math.floor(remainingToDistribute / transferCount); // 为每笔添加平均额外金额,但加入一些随机性 for (let i = 0; i < transferCount && remainingToDistribute > 0; i++) { // 计算这笔最多还能增加多少(不超过maxAmount) const maxPossibleIncrease = Math.min( maxAmount - amounts[i], remainingToDistribute ); if (maxPossibleIncrease > 0) { // 在平均值附近随机分配,但控制在更小的范围内以保证更均匀 const baseIncrease = Math.min(averageExtra, maxPossibleIncrease); const randomVariation = Math.floor(baseIncrease * 0.15); // 减少到15%的随机变化 const minIncrease = Math.max(0, baseIncrease - randomVariation); const maxIncrease = Math.min(maxPossibleIncrease, baseIncrease + randomVariation); const increase = Math.floor(Math.random() * (maxIncrease - minIncrease + 1)) + minIncrease; amounts[i] += increase; remainingToDistribute -= increase; } } // 如果还有剩余金额,尽量均匀分配给还能接受的笔数 while (remainingToDistribute > 0) { const availableIndices = []; for (let i = 0; i < transferCount; i++) { if (amounts[i] < maxAmount) { availableIndices.push(i); } } if (availableIndices.length === 0) { break; // 无法继续分配 } // 计算每个可用位置应该分配多少 const perIndexAmount = Math.floor(remainingToDistribute / availableIndices.length); const remainder = remainingToDistribute % availableIndices.length; // 为每个可用位置分配相等的金额 for (let i = 0; i < availableIndices.length && remainingToDistribute > 0; i++) { const index = availableIndices[i]; const maxIncrease = Math.min(maxAmount - amounts[index], remainingToDistribute); if (maxIncrease > 0) { // 基础分配金额 let increase = Math.min(perIndexAmount, maxIncrease); // 如果是前几个位置,额外分配余数 if (i < remainder) { increase = Math.min(increase + 1, maxIncrease); } amounts[index] += increase; remainingToDistribute -= increase; } } // 如果所有位置都已达到最大值,退出循环 if (perIndexAmount === 0 && remainder === 0) { break; } } // 如果还有剩余金额无法分配,返回空数组表示失败 if (remainingToDistribute > 0) { return []; } return amounts; } // 生成3笔随机金额,总计指定金额(保留原方法用于兼容性) generateThreeRandomAmounts(totalAmount) { // 确保总金额足够分配三笔最小金额 const minAmount = 500; const maxAmount = Math.min(5000, totalAmount - 2 * minAmount); // 生成第一笔金额 (500-5000) const amount1 = Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; // 生成第二笔金额 (500-剩余金额-500) const remaining1 = totalAmount - amount1; const maxAmount2 = Math.min(5000, remaining1 - minAmount); const amount2 = Math.floor(Math.random() * (maxAmount2 - minAmount + 1)) + minAmount; // 第三笔是剩余金额 const amount3 = totalAmount - amount1 - amount2; return [amount1, amount2, amount3]; } // 生成随机金额(保留原方法用于其他地方) /** * 确认分配并创建转账记录 * @param {number} allocationId - 分配ID * @param {number} userId - 用户ID * @param {number} transferAmount - 实际转账金额(用于校验) * @param {string} description - 转账描述 * @param {string} voucher - 转账凭证URL * @returns {number} 转账记录ID */ async confirmAllocation(allocationId, userId, transferAmount = null, description = null, voucher = null) { const db = getDB(); try { await db.query('START TRANSACTION'); // 获取分配信息 const [allocations] = await db.execute( 'SELECT * FROM transfers WHERE id = ? AND from_user_id = ?', [allocationId, userId] ); if (allocations.length === 0) { throw new Error('分配不存在或无权限'); } const allocation = allocations[0]; // 校验转账金额(如果提供了转账金额) if (transferAmount !== null) { const expectedAmount = parseFloat(allocation.amount); const actualAmount = parseFloat(transferAmount); if (Math.abs(expectedAmount - actualAmount) > 0.01) { throw new Error(`转账金额不匹配!应转账 ${expectedAmount} 元,实际转账 ${actualAmount} 元`); } } // 检查分配状态 if (allocation.status !== 'pending') { throw new Error('该分配已处理,无法重复确认'); } // 检查匹配订单是否已超时 const [matchingOrder] = await db.execute( 'SELECT * FROM matching_orders WHERE id = ?', [allocation.matching_order_id] ); if (matchingOrder.length === 0) { throw new Error('匹配订单不存在'); } // 检查订单是否已被取消(超时会导致订单被取消) if (matchingOrder[0].status === 'cancelled') { throw new Error('该匹配订单已超时取消,无法进行转账'); } // 检查是否存在相关的超时转账记录 const [timeoutTransfers] = await db.execute( `SELECT COUNT(*) as count FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) AND is_overdue = 1 AND description LIKE ?`, [userId, userId, `%匹配订单 ${allocation.matching_order_id}%`] ); if (timeoutTransfers[0].count > 0) { throw new Error('该匹配订单存在超时记录,无法继续转账。请联系管理员处理'); } // 计算3小时后的截止时间 const deadline = dayjs().add(3, 'hour').toDate(); // 更新转账记录状态为confirmed,跳过待确认环节 const transferDescription = description || `匹配订单 ${allocation.matching_order_id} 第 ${allocation.cycle_number} 轮转账`; const [transferResult] = await db.execute( `UPDATE transfers SET status = "confirmed", description = ?, deadline_at = ?, confirmed_at = NOW(), voucher_url = ? WHERE id = ?`, [ transferDescription, deadline, voucher, allocationId ] ); // 注意:发送方余额将在接收方确认收款时扣除,而不是在确认转账时扣除 // 这样可以避免资金被锁定但收款方未确认的情况 // 记录确认动作 await db.execute( 'INSERT INTO matching_records (matching_order_id, user_id, action, amount, note) VALUES (?, ?, "confirm", ?, ?)', [ allocation.matching_order_id, userId, allocation.amount, transferAmount ? `实际转账金额: ${transferAmount}` : null ] ); await db.query('COMMIT'); // 检查是否需要进入下一轮 await this.checkCycleCompletion(allocation.matching_order_id, allocation.cycle_number); return transferResult.insertId; } catch (error) { await db.query('ROLLBACK'); throw error; } } // 检查轮次完成情况 async checkCycleCompletion(matchingOrderId, cycleNumber) { const db = getDB(); try { // 检查当前轮次是否全部确认 const [pending] = await db.execute( 'SELECT COUNT(*) as count FROM transfers WHERE matching_order_id = ? AND cycle_number = ? AND status = "pending"', [matchingOrderId, cycleNumber] ); if (pending[0].count === 0) { // 当前轮次完成,检查是否需要下一轮 const [order] = await db.execute( 'SELECT * FROM matching_orders WHERE id = ?', [matchingOrderId] ); const currentOrder = order[0]; if (currentOrder.cycle_count + 1 < currentOrder.max_cycles) { // 开始下一轮 await db.execute( 'UPDATE matching_orders SET cycle_count = cycle_count + 1 WHERE id = ?', [matchingOrderId] ); // 生成下一轮分配 const amounts = this.generateThreeRandomAmounts(currentOrder.amount); await this.generateThreeAllocations(matchingOrderId, amounts, currentOrder.initiator_id); } else { // 完成所有轮次 await db.execute( 'UPDATE matching_orders SET status = "completed" WHERE id = ?', [matchingOrderId] ); console.log(`匹配订单 ${matchingOrderId} 已完成所有轮次`); // 检查用户是否完成第三次匹配,如果是则给代理分佣 await this.checkAndProcessAgentCommission(currentOrder.initiator_id); } } } catch (error) { console.error('检查轮次完成情况失败:', error); throw error; } } // 获取用户的匹配订单 async getUserMatchingOrders(userId, page = 1, limit = 10) { const db = getDB(); const offset = (parseInt(page) - 1) * parseInt(limit); try { // 获取用户发起的订单 const [orders] = await db.execute( `SELECT mo.*, u.username as initiator_name,u.real_name as initiator_real_name FROM matching_orders mo JOIN users u ON mo.initiator_id = u.id WHERE mo.initiator_id = ? ORDER BY mo.created_at DESC LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, [userId] ); // 同时获取系统反向匹配订单(如果用户参与了分配) const [systemOrders] = await db.execute( `SELECT DISTINCT mo.*, u.username as initiator_name FROM matching_orders mo JOIN users u ON mo.initiator_id = u.id JOIN transfers oa ON mo.id = oa.id WHERE mo.is_system_reverse = TRUE AND oa.to_user_id = ? ORDER BY mo.created_at DESC LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, [userId] ); // 合并订单列表 const allOrders = [...orders, ...systemOrders]; // 按创建时间排序 allOrders.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // 为每个订单获取分配信息 for (let order of allOrders) { const [allocations] = await db.execute( `SELECT * FROM transfers WHERE matching_order_id = ? ORDER BY cycle_number, created_at`, [order.id] ); order.allocations = allocations; } return allOrders; } catch (error) { console.error('获取用户匹配订单失败:', error); throw error; } } // 获取用户待处理的分配 async getUserPendingAllocations(userId) { const db = getDB(); try { const [allocations] = await db.execute( `(SELECT oa.*, mo.amount as total_amount, mo.status as order_status, u.username as to_user_name, u.real_name as to_user_real_name, DATE_ADD(oa.created_at, INTERVAL 150 MINUTE) as expected_deadline, oa.outbound_date, oa.return_date, oa.can_return_after, oa.confirmed_at FROM transfers oa JOIN matching_orders mo ON oa.matching_order_id = mo.id JOIN users u ON oa.to_user_id = u.id WHERE oa.from_user_id = ? AND oa.status = "pending" AND mo.status != "cancelled" AND (oa.source_type IS NULL)) UNION ALL (SELECT oa.*, oa.amount as total_amount, 'active' as order_status, u.username as to_user_name, u.real_name as to_user_real_name, DATE_ADD(oa.created_at, INTERVAL 150 MINUTE) as expected_deadline, oa.outbound_date, oa.return_date, oa.can_return_after, oa.confirmed_at FROM transfers oa JOIN users u ON oa.to_user_id = u.id WHERE oa.from_user_id = ? AND oa.status = "pending") ORDER BY created_at ASC`, [userId, userId] ); // 检查每个分配的超时状态,但不过滤掉 const allocationsWithTimeoutStatus = []; for (const allocation of allocations) { // 检查是否存在相关的超时转账记录 const [timeoutTransfers] = await db.execute( `SELECT COUNT(*) as count FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) AND is_overdue = 1 AND description LIKE ?`, [userId, userId, `%匹配订单 ${allocation.matching_order_id}%`] ); // 添加超时状态标记 allocation.has_timeout_record = timeoutTransfers[0].count > 0; allocationsWithTimeoutStatus.push(allocation); } // 检查并处理超时订单 const now = new Date(); // 隐藏系统账户身份并添加时效状态 const processedAllocations = allocationsWithTimeoutStatus.map(allocation => { const deadline = allocation.transfer_deadline || allocation.expected_deadline; const deadlineDate = new Date(deadline); const timeLeft = deadlineDate - now; // 计算剩余时间 let timeStatus = 'normal'; let timeLeftText = ''; if (timeLeft <= 0) { timeStatus = 'expired'; timeLeftText = '已超时'; } else if (timeLeft <= 2.5 * 60 * 60 * 1000) { // 2.5小时内 timeStatus = 'urgent'; const hours = Math.floor(timeLeft / (60 * 60 * 1000)); const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000)); timeLeftText = hours > 0 ? `${hours}小时${minutes}分钟` : `${minutes}分钟`; } else { const hours = Math.floor(timeLeft / (60 * 60 * 1000)); const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000)); timeLeftText = `${hours}小时${minutes}分钟`; } return { ...allocation, to_user_name: allocation.to_user_name || '匿名用户', is_system_account: undefined, // 移除系统账户标识 deadline: deadline, time_status: timeStatus, time_left: timeLeftText, can_transfer: !allocation.has_timeout_record, // 是否可以转账 timeout_reason: allocation.has_timeout_record ? '该匹配订单存在超时记录,无法继续转账' : null }; }); return processedAllocations; } catch (error) { console.error('获取用户待处理分配失败:', error); throw error; } } // 检查并处理代理佣金 async checkAndProcessAgentCommission(userId) { const db = getDB(); try { // 检查用户是否有代理关系 const [agentRelation] = await db.execute( 'SELECT agent_id, created_at FROM agent_merchants WHERE merchant_id = ?', [userId] ); if (agentRelation.length === 0) { return; // 用户没有代理,无需处理 } const agentId = agentRelation[0].agent_id; const agentJoinTime = agentRelation[0].created_at; // 检查用户给他人转账的次数(状态为已收款,且转账时间在代理商入驻之后) const [completedTransfers] = await db.execute( 'SELECT COUNT(*) as count FROM transfers WHERE from_user_id = ? AND status = "received" AND created_at >= ?', [userId, agentJoinTime] ); const transferCount = completedTransfers[0].count; // 如果完成至少三次转账,给代理分佣 if (transferCount >= 3) { // 检查是否已经给过佣金(防止重复分佣) const [existingCommission] = await db.execute( 'SELECT id FROM agent_commission_records WHERE agent_id = ? AND merchant_id = ? AND description LIKE "%第三次转账%"', [agentId, userId] ); if (existingCommission.length === 0) { // 计算佣金:399元的10% = 39.9元 const commissionAmount = 399 * 0.10; // 记录佣金 await db.execute( 'INSERT INTO agent_commission_records (agent_id, merchant_id, commission_amount, commission_type, description, created_at) VALUES (?, ?, ?, "matching", "用户完成第三次转账获得的代理佣金", NOW())', [agentId, userId, commissionAmount] ); console.log(`用户 ${userId} 完成第三次转账,为代理 ${agentId} 分佣 ${commissionAmount} 元`); } } } catch (error) { console.error('处理代理佣金失败:', error); // 不抛出错误,避免影响主流程 } } } module.exports = new MatchingService();