diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml index 31f6ee5..9ded1f3 100644 --- a/.idea/data_source_mapping.xml +++ b/.idea/data_source_mapping.xml @@ -2,7 +2,10 @@ + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index 56782ca..0000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/routes/matching.js b/routes/matching.js index d9e1db5..e34b2d8 100644 --- a/routes/matching.js +++ b/routes/matching.js @@ -310,12 +310,12 @@ router.post('/confirm-allocation/:allocationId', auth, async (req, res) => { } // 调用服务层方法,传递完整的转账信息 - const transferId = await matchingService.confirmAllocation( - allocationId, - userId, - transferAmount, - description, - voucher + const transferId = matchingService.confirmAllocation( + allocationId, + userId, + transferAmount, + description, + voucher ); res.json({ @@ -324,11 +324,11 @@ router.post('/confirm-allocation/:allocationId', auth, async (req, res) => { data: { transferId } }); - axios.post('http://localhost:5000/ocr',{ - id: allocationId - }).then(res => { - console.log(res.data) - }) + // axios.post('http://localhost:5000/ocr',{ + // id: allocationId + // }).then(res => { + // console.log(res.data) + // }) } catch (error) { console.error('确认分配失败:', error); diff --git a/routes/sms.js b/routes/sms.js index 787bd56..d326fa0 100644 --- a/routes/sms.js +++ b/routes/sms.js @@ -141,11 +141,6 @@ router.post('/send', async (req, res) => { // 生产环境发送真实短信 try { console.log(code); - res.json({ - success: true, - message: '验证码发送成功' - }) - return const sendSmsRequest = new Dysmsapi20170525.SendSmsRequest({ phoneNumbers: phone, signName: SMS_CONFIG.signName, diff --git a/routes/transfers.js b/routes/transfers.js index 91ff2fd..d8b6494 100644 --- a/routes/transfers.js +++ b/routes/transfers.js @@ -12,6 +12,48 @@ const dayjs = require('dayjs'); const router = express.Router(); +// router.get('/tmp', async (req, res) => { +// const db = getDB(); +// // 1. 查询所有转账记录,按 id 升序 +// let [transfers] = await db.execute(` +// SELECT * +// FROM transfers +// WHERE status='received' or status='confirmed' +// ORDER BY id ASC`); +// +// // 2. 用对象维护每个用户的最新余额 +// const userBalances = {}; +// +// // 3. 遍历每条转账记录,计算余额 +// for (const trx of transfers) { +// const fromId = trx.from_user_id; +// const toId = trx.to_user_id; +// const amount = Number(trx.amount); +// +// if (!(fromId in userBalances)) userBalances[fromId] = 0; +// if (!(toId in userBalances)) userBalances[toId] = 0; +// +// const fromBalance = userBalances[fromId] - amount; +// const toBalance = userBalances[toId] + amount; +// +// userBalances[fromId] = fromBalance; +// userBalances[toId] = toBalance; +// +// const balanceDiff = toBalance - fromBalance; +// +// // 4. 更新当前行的字段 +// await db.execute( +// `UPDATE transfers +// SET from_user_balance = ?, +// to_user_balance = ?, +// balance_diff = ? +// WHERE id = ?`, +// [fromBalance, toBalance, amount, trx.id] +// ); +// +// } +// res.json('更新成功') +// }) router.get('/', authenticateToken, validateQuery(transferSchemas.query), @@ -342,7 +384,6 @@ router.post('/confirm-not-received', ); - // 获取用户转账记录 router.get('/user/:userId', authenticateToken, async (req, res) => { try { @@ -386,8 +427,9 @@ router.get('/user/:userId', authenticateToken, async (req, res) => { LEFT JOIN users to_user ON t.to_user_id = to_user.id ${whereClause} ORDER BY t.created_at - DESC - LIMIT ${limitNum} OFFSET ${offset} + DESC + LIMIT ${limitNum} + OFFSET ${offset} `, countParams); const [countResult] = await db.execute(` @@ -425,37 +467,37 @@ router.get('/stats', authenticateToken, async (req, res) => { if (isAdmin) { // 管理员可以查看全局统计 const [totalStats] = await db.execute(` - SELECT COUNT(*) as total_transfers, - SUM(amount) as total_flow_amount, - SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, - SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, - SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received_count, - SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, - SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count, - SUM(CASE WHEN status = 'not_received' THEN 1 ELSE 0 END) as not_received_count, - SUM(CASE WHEN is_overdue = 1 THEN 1 ELSE 0 END) as overdue_count, - SUM(CASE WHEN is_bad_debt = 1 THEN 1 ELSE 0 END) as bad_debt_count, - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as total_amount, - SUM(CASE WHEN is_bad_debt = 1 THEN amount ELSE 0 END) as bad_debt_amount, + SELECT COUNT(*) as total_transfers, + SUM(amount) as total_flow_amount, + SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, + SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, + SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received_count, + SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, + SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count, + SUM(CASE WHEN status = 'not_received' THEN 1 ELSE 0 END) as not_received_count, + SUM(CASE WHEN is_overdue = 1 THEN 1 ELSE 0 END) as overdue_count, + SUM(CASE WHEN is_bad_debt = 1 THEN 1 ELSE 0 END) as bad_debt_count, + SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as total_amount, + SUM(CASE WHEN is_bad_debt = 1 THEN amount ELSE 0 END) as bad_debt_amount, SUM(CASE WHEN transfer_type = 'initial' AND status = 'confirmed' THEN amount - ELSE 0 END) as initial_amount, + ELSE 0 END) as initial_amount, SUM(CASE WHEN transfer_type = 'return' AND status = 'confirmed' THEN amount - ELSE 0 END) as return_amount, + ELSE 0 END) as return_amount, SUM(CASE WHEN transfer_type = 'user_to_user' AND status = 'confirmed' THEN amount - ELSE 0 END) as user_to_user_amount, + ELSE 0 END) as user_to_user_amount, (SELECT SUM(balance) FROM users WHERE role = 'user' - AND is_system_account = 1) as total_merchant_balance, + AND is_system_account = 1) as total_merchant_balance, (SELECT SUM(balance) FROM users WHERE role = 'user' AND is_system_account != 1) as total_user_balance, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as participated_transfers, - SUM(CASE WHEN source_type IN ('agent') THEN amount END) as agent_total, + SUM(CASE WHEN source_type IN ('system') THEN amount END) as participated_transfers, + SUM(CASE WHEN source_type IN ('agent') THEN amount END) as agent_total, SUM(CASE WHEN source_type IN ('operated_agent') THEN amount END) as operated_agent_total FROM transfers `); @@ -470,26 +512,26 @@ router.get('/stats', authenticateToken, async (req, res) => { ( COALESCE((SELECT SUM(amount) FROM transfers - WHERE DATE(created_at) = ? - AND to_user_id IN (SELECT id FROM users WHERE is_system_account = 1) - AND status = 'received'), 0) - + WHERE DATE (created_at) = ? + AND to_user_id IN (SELECT id FROM users WHERE is_system_account = 1) + AND status = 'received'), 0) - COALESCE((SELECT SUM(amount) FROM transfers - WHERE DATE(created_at) = ? - AND from_user_id IN (SELECT id FROM users WHERE is_system_account = 1) - AND status = 'received'), 0) + WHERE DATE (created_at) = ? + AND from_user_id IN (SELECT id FROM users WHERE is_system_account = 1) + AND status = 'received'), 0) ) as today_amount FROM transfers - WHERE DATE(created_at) = ? + WHERE DATE (created_at) = ? `, [todayStr, todayStr, todayStr]); const [monthlyStats] = await db.execute(` - SELECT COUNT(*) as monthly_transfers, - SUM(CASE WHEN status = 'received' THEN amount ELSE 0 END) as monthly_amount, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as monthly_participated_transfers + SELECT COUNT(*) as monthly_transfers, + SUM(CASE WHEN status = 'received' THEN amount ELSE 0 END) as monthly_amount, + SUM(CASE WHEN source_type IN ('system') THEN amount END) as monthly_participated_transfers FROM transfers - WHERE YEAR(created_at) = ? - AND MONTH(created_at) = ? + WHERE YEAR (created_at) = ? + AND MONTH (created_at) = ? `, [currentYear, currentMonth]); // 获取上月统计数据用于对比 @@ -497,12 +539,12 @@ router.get('/stats', authenticateToken, async (req, res) => { const lastMonthYear = currentMonth === 1 ? currentYear - 1 : currentYear; const [lastMonthStats] = await db.execute(` - SELECT COUNT(*) as last_monthly_transfers, - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as last_monthly_amount, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as last_monthly_participated_transfers + SELECT COUNT(*) as last_monthly_transfers, + SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as last_monthly_amount, + SUM(CASE WHEN source_type IN ('system') THEN amount END) as last_monthly_participated_transfers FROM transfers - WHERE YEAR(created_at) = ? - AND MONTH(created_at) = ? + WHERE YEAR (created_at) = ? + AND MONTH (created_at) = ? `, [lastMonthYear, lastMonth]); stats = { @@ -524,9 +566,9 @@ router.get('/stats', authenticateToken, async (req, res) => { return_amount: parseFloat(totalStats[0].return_amount || 0), user_to_user_amount: parseFloat(totalStats[0].user_to_user_amount || 0), participated_transfers: totalStats[0].participated_transfers || 0, - total_user_balance:totalStats[0].total_user_balance || 0, - agent_total:totalStats[0].agent_total || 0,//代理收入 - operated_agent_total:totalStats[0].operated_agent_total || 0,//直营代理收入 + total_user_balance: totalStats[0].total_user_balance || 0, + agent_total: totalStats[0].agent_total || 0,//代理收入 + operated_agent_total: totalStats[0].operated_agent_total || 0,//直营代理收入 }, today: { transfers: todayStats[0].today_transfers || 0, @@ -565,7 +607,7 @@ router.get('/stats', authenticateToken, async (req, res) => { SUM(CASE WHEN status = 'confirmed' AND to_user_id = ? THEN amount ELSE 0 END) as today_received FROM transfers WHERE (from_user_id = ? OR to_user_id = ?) - AND DATE(created_at) = ? + AND DATE (created_at) = ? `, [userId, userId, userId, userId, todayStr]); stats = { @@ -706,9 +748,9 @@ router.get('/trend', authenticateToken, async (req, res) => { // 首先获取数据库中最早和最晚的转账日期 const [dateRange] = await db.execute(` - SELECT MIN(DATE(created_at)) as min_date, - MAX(DATE(created_at)) as max_date, - COUNT(*) as total_count + SELECT MIN(DATE (created_at)) as min_date, + MAX(DATE (created_at)) as max_date, + COUNT(*) as total_count FROM transfers `); @@ -737,13 +779,13 @@ router.get('/trend', authenticateToken, async (req, res) => { // 获取指定天数内的转账趋势(从最大日期往前推) const [trendData] = await db.execute(` - SELECT DATE(created_at) as date, - COUNT(*) as count, - SUM(amount) as amount + SELECT DATE (created_at) as date, COUNT (*) as count, SUM (amount) as amount FROM transfers - WHERE DATE(created_at) >= DATE_SUB(?, INTERVAL ? DAY) - AND status IN ('confirmed', 'received') - GROUP BY DATE(created_at) + WHERE DATE (created_at) >= DATE_SUB(? + , INTERVAL ? DAY) + AND status IN ('confirmed' + , 'received') + GROUP BY DATE (created_at) ORDER BY date ASC `, [dateRange[0].max_date, daysNum - 1]); @@ -1024,7 +1066,8 @@ router.get('/pending-allocations', JOIN matching_orders mo ON oa.id = mo.id ${whereClause} ORDER BY oa.${sortField} ${sortOrder} - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)} + LIMIT ${parseInt(limit)} + OFFSET ${parseInt(offset)} `; const [allocations] = queryParams.length > 0 @@ -1140,19 +1183,16 @@ router.get('/daily-stats', // 获取所有用户的昨日转出和今日入账统计 let [userStats] = await db.execute(` - SELECT u.id as user_id, + SELECT u.id as user_id, u.username, u.real_name, u.phone, u.balance, - COALESCE(yesterday_out.amount, 0) as yesterday_out_amount, - COALESCE(today_in.amount, 0) as today_in_amount, - COALESCE(confirmed_from.confirmed_amount, 0) as confirmed_from_amount, - CASE - WHEN (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0)) > ABS(u.balance) - THEN ABS(u.balance) - ELSE (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0)) - END as balance_needed + COALESCE(yesterday_out.amount, 0) - + COALESCE(yesterday_system_num.amount, 0) as yesterday_out_amount, + COALESCE(today_in.amount, 0) as today_in_amount, + COALESCE(confirmed_from.confirmed_amount, 0) as confirmed_from_amount, + COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0) as balance_needed FROM users u LEFT JOIN (SELECT from_user_id, SUM(amount) as amount @@ -1161,6 +1201,15 @@ router.get('/daily-stats', AND created_at <= ? AND status IN ('confirmed', 'received') GROUP BY from_user_id) yesterday_out ON u.id = yesterday_out.from_user_id + LEFT JOIN (SELECT to_user_id, + SUM(amount) as amount + FROM transfers + WHERE created_at >= ? + AND created_at <= ? + AND transfer_type != 'user_to_user' + AND status IN ('received') + GROUP BY to_user_id) yesterday_system_num + ON u.id = yesterday_system_num.to_user_id LEFT JOIN (SELECT to_user_id, SUM(amount) as amount FROM transfers @@ -1171,17 +1220,17 @@ router.get('/daily-stats', left join (select from_user_id, sum(amount) as confirmed_amount from transfers - where status = 'received' - and created_at >= ? - and created_at <= ? - group by from_user_id) as confirmed_from on u.id = confirmed_from.from_user_id - WHERE u.role != 'admin' + WHERE status IN ('confirmed', 'received') + AND created_at >= ? + AND created_at <= ? + GROUP BY from_user_id) as confirmed_from on u.id = confirmed_from.from_user_id + WHERE u.role != 'admin' AND u.user_type !='directly_operated' AND u.is_system_account != 1 AND yesterday_out.amount > 0 AND u.balance < 0 ORDER BY balance_needed DESC, yesterday_out_amount DESC - `, [yesterdayStartStr, yesterdayEndStr, todayStartStr, todayEndStr, todayStartStr, todayEndStr]); - // userStats = userStats.filter(item=>item.balance_needed >= 100) + `, [yesterdayStartStr, yesterdayEndStr, yesterdayStartStr, yesterdayEndStr,todayStartStr, todayEndStr, todayStartStr, todayEndStr]); + // userStats = userStats.filter(item=>item.balance_needed > 100) userStats.forEach(item => { item.balance_needed = Math.abs(item.balance_needed) }) diff --git a/routes/upload.js b/routes/upload.js index 2222e0b..d3894ef 100644 --- a/routes/upload.js +++ b/routes/upload.js @@ -51,54 +51,6 @@ const multiUpload = multer({ } }); -/** - * @swagger - * /upload/image: - * post: - * summary: 上传图片 - * tags: [Upload] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * description: 要上传的图片文件 - * type: - * type: string - * enum: [avatar, product, document] - * default: document - * description: 上传文件类型 - * responses: - * 200: - * description: 图片上传成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * url: - * type: string - * description: 上传后的文件URL - * filename: - * type: string - * description: 上传后的文件名 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ router.post('/image', authenticateToken, (req, res) => { upload.single('file')(req, res, async (err) => { if (err instanceof multer.MulterError) { diff --git a/routes/users.js b/routes/users.js index 5b0afbc..dbecd93 100644 --- a/routes/users.js +++ b/routes/users.js @@ -215,7 +215,7 @@ router.get('/', auth, adminAuth, async (req, res) => { // 确保参数为有效数字 const pageNum = Math.max(1, parseInt(page) || 1); - const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); + const limitNum = Math.max(1, parseInt(limit) || 10); const offset = Math.max(0, (pageNum - 1) * limitNum); let whereConditions = []; @@ -504,7 +504,10 @@ router.get('/stats', auth, async (req, res) => { // 活跃用户数(有订单的用户) const [activeUsers] = await db.execute( - 'SELECT COUNT(DISTINCT from_user_id) as count FROM transfers' + `SELECT + COUNT(DISTINCT from_user_id) AS count + FROM transfers + WHERE YEARWEEK(created_at, 1) = YEARWEEK(CURDATE() - INTERVAL 1 WEEK, 1)` ); const [weekUsers] = await db.execute(` SELECT COUNT(DISTINCT from_user_id) as count @@ -1546,20 +1549,52 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { [distribute.inviter] ); distributeUser = distributeUser[0] + //分销是代理 if (distributeUser.user_type == 'agent') { - //给代理添加2980融豆的70% 增加区域保护 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.7, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.7, '用户服务费返现', 'agent'] - ); + let [agentUser] = await db.execute(` + SELECT r.* + FROM regional_agents as r + LEFT JOIN users au on r.user_id = au.id + WHERE au.user_type = 'agent' + AND r.status = 'active' + AND r.region_id = ${user.district_id} + `) + if (agentUser.length > 0 && agentUser[0].user_id !== distributeUser.id) { + //增加区域保护 + await db.execute(` + UPDATE users + SET balance = balance - ? + WHERE id = ? + `, [serviceFee * 0.05, agentUser[0].user_id]) + await db.execute( + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, agentUser[0].user_id, 'user_to_agent', 'received', serviceFee * 0.05, '区域保护服务费返现', 'agent'] + ); + await db.execute( + 'UPDATE users SET balance = balance - ? WHERE id = ?', + [serviceFee * 0.65, distributeUser.id] + ); + //记录转账记录 + await db.execute( + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.65, '用户服务费返现', 'agent'] + ); + } else { + //给代理添加2980融豆的70% 增加区域保护 + await db.execute( + 'UPDATE users SET balance = balance - ? WHERE id = ?', + [serviceFee * 0.7, distributeUser.id] + ); + //记录转账记录 + await db.execute( + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.7, '用户服务费返现', 'agent'] + ); + } + //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system'] ); //记录服务费 @@ -1577,12 +1612,12 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录转账记录 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent_operated'] ); //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] ); //记录服务费 @@ -1615,17 +1650,17 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录转账记录 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'operated_agent'] ); //记录直营利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id, from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.3, '用户服务费返现', 'directly_operated'] ); //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] ); } @@ -1642,17 +1677,17 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录转账记录 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.15, '用户服务费返现', 'agent_operated'] ); //记录直营利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.35, '用户服务费返现', 'directly_operated'] ); //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] ); } @@ -1669,7 +1704,7 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录转账记录 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id ,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.1, '用户服务费返现', 'agent_operated'] ); //记录直营利润 @@ -1679,7 +1714,7 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] ); } @@ -1701,29 +1736,61 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { userUpInfo = userUpInfo[0] //判断用户上级是否是代理 if (userUpInfo && userUpInfo.user_type === 'agent') { + let [agentUser] = await db.execute(` + SELECT r.* + FROM regional_agents as r + LEFT JOIN users au on r.user_id = au.id + WHERE au.user_type = 'agent' + AND r.status = 'active' + AND r.region_id = ${user.district_id} + `) + if (agentUser.length > 0 && agentUser[0].user_id !== distributeUser.id) { + //增加区域保护 + await db.execute(` + UPDATE users + SET balance = balance - ? + WHERE id = ? + `, [serviceFee * 0.05, agentUser[0].user_id]) + await db.execute( + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, agentUser[0].user_id, 'user_to_agent', 'received', serviceFee * 0.05, '区域保护服务费返现', 'agent'] + ); + //给代理分配 + await db.execute( + 'UPDATE users SET balance = balance - ? WHERE id = ?', + [serviceFee * 0.45, userUpInfo.id] + ); + //记录代理转账信息 + await db.execute( + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, userUpInfo.id, 'user_to_agent', 'received', serviceFee * 0.45, '用户服务费返现', 'agent'] + ); + }else { + //给代理分配 + await db.execute( + 'UPDATE users SET balance = balance - ? WHERE id = ?', + [serviceFee * 0.5, userUpInfo.id] + ); + //记录代理转账信息 + await db.execute( + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + [userId, userUpInfo.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent'] + ); + } //给用户分配 await db.execute( 'UPDATE users SET balance = balance - ? WHERE id = ?', [serviceFee * 0.2, distributeUser.id] ); - //给代理分配 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.5, userUpInfo.id] - ); //记录用户转账信息 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id ,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.id, 'user_to_user', 'received', serviceFee * 0.2, '用户服务费返现', 'operated'] ); - //记录代理转账信息 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, userUpInfo.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent'] - ); + //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system'] ); } else { @@ -1734,12 +1801,12 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { ); //记录转账记录 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, distributeUser.id, 'user_to_user', 'received', serviceFee * 0.2, '用户服务费返现', 'operated'] ); //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id ,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.8, '用户服务费返现', 'system'] ); @@ -1754,25 +1821,34 @@ router.post('/:id/deduct-service-fee', auth, async (req, res) => { } else { //判断用户此区域是否有代理 let [agentUser] = await db.execute(` - SELECT user_id - FROM regional_agents - WHERE region_id = ? - `, [users[0].district_id]) + SELECT rg.user_id + FROM regional_agents as rg + LEFT JOIN users ag ON ag.id = rg.user_id + WHERE rg.region_id = ? + AND rg.status = 'active' + AND ag.user_type = 'agent' + `, [user.district_id]) if (agentUser.length > 0) { + //给区域代理分区域保护 + await db.execute(` + UPDATE users + SET balance = balance - ? + WHERE id = ? + `, [serviceFee * 0.05, agentUser[0].user_id]) //给代理分成5% await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id ,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, agentUser[0].user_id, 'user_to_regional', 'received', serviceFee * 0.05, '区域保护服务费返现', 'agent'] ) //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id , from_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee * 0.95, '用户服务费返现', 'system'] ); } else { //记录平台利润 await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', + 'INSERT INTO transfers (to_user_id ,from_user_id , transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', [userId, 3512, 'user_to_system', 'received', serviceFee, '用户服务费返现', 'system'] ); diff --git a/services/matchingService.js b/services/matchingService.js index 380696e..5910bfe 100644 --- a/services/matchingService.js +++ b/services/matchingService.js @@ -1,4 +1,4 @@ -const { getDB } = require('../database'); +const {getDB} = require('../database'); const timeoutService = require('./timeoutService'); const dayjs = require('dayjs'); @@ -9,1425 +9,1441 @@ const dayjs = require('dayjs'); * @returns {string} 格式化的日期字符串 */ function getLocalDateString(date = new Date()) { - return dayjs(date).format('YYYY-MM-DD'); + 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' - AND u.province = ? - 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,currentUserProvince, 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) => { - const getPriority = (user) => { - if (user.district_id === currentUserDistrictId) return 1; // 同区县 - if (user.city === currentUserCity) return 2; // 同城市 - return 3; // 其他 - }; - - const priorityA = getPriority(a); - const priorityB = getPriority(b); - - if (priorityA !== priorityB) { - return priorityA - priorityB; // 优先级小的排前 - } - - // 同优先级里:越接近0(数值越大)排前 -> 使用降序 - return b.has_active_allocations - a.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 + // 创建匹配订单(支持两种模式) + 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] + ); - // 确保不超过最大限制 - 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}元`); - } + if (userResult.length === 0) { + throw new Error('用户不存在'); } - // 更新实际分配的剩余金额 - remainingAmount = remainingToDistribute; + const user = userResult[0]; - if (remainingAmount === 0) { - console.log('剩余金额已全部分配给现有用户'); + // 检查用户余额:只有负余额用户才能发起匹配 + // 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 { - console.log(`部分剩余金额已分配给现有用户,仍有${remainingAmount}元未分配`); + 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); + + return { + orderId, + matchingType, + totalAmount, + allocations: allocations || [], + allocationCount: allocations ? allocations.length : 0 + }; + } catch (error) { + await db.query('ROLLBACK'); + console.error('创建匹配订单失败:', error); + throw error; } - } + } - // 如果仍有剩余金额,检查是否有未分配的用户可以消化剩余金额 - if (remainingAmount > 0) { - // 获取已分配的用户ID列表 - const allocatedUserIds = new Set(allocations.map(a => a.userId)); + /** + * 检查资金平衡并触发系统账户反向匹配 + * 当收款需求大于打款资金时,系统账户主动发起匹配 + */ + async checkAndTriggerSystemMatching() { + const db = getDB(); - // 从原始用户列表中找到未分配的用户 - const unallocatedUsers = userBalanceResult.filter(user => !allocatedUserIds.has(user.user_id)); + try { + // 计算当前待收款总额(负余额用户的资金缺口) + const [negativeBalanceResult] = await db.execute(` + SELECT SUM(ABS(balance)) as total_deficit + FROM users + WHERE is_system_account = FALSE + AND balance < 0 + `); - if (unallocatedUsers.length > 0) { - console.log(`发现${unallocatedUsers.length}个未分配的用户,剩余金额: ${remainingAmount}`); + const totalDeficit = negativeBalanceResult[0].total_deficit || 0; - // 查找可分配金额大于剩余金额的用户 - for (const user of unallocatedUsers) { - const maxSafeAmount = Math.abs(user.has_active_allocations); + // 计算当前待打款总额(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 + `); - 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 - }); + const totalPendingPayments = pendingPaymentsResult[0].total_pending || 0; - console.log(`为未分配用户${user.user_id}分配剩余金额${remainingAmount}元`); - remainingAmount = 0; - break; + console.log(`资金平衡检查: 总资金缺口=${totalDeficit}, 待打款总额=${totalPendingPayments}`); + + // 如果收款需求大于打款资金,触发系统账户反向匹配 + if (totalDeficit > totalPendingPayments) { + const shortfall = totalDeficit - totalPendingPayments; + console.log(`检测到资金缺口: ${shortfall}元,触发系统账户反向匹配`); + + await this.createSystemReverseMatching(shortfall); } - } + } catch (error) { + console.error('检查资金平衡失败:', error); + // 不抛出错误,避免影响主流程 } - } + } - // 如果仍有剩余金额,分配给虚拟用户 - if (remainingAmount > 0 && availableVirtualUsers.length > 0) { - const maxPossibleTransfers = Math.min((minTransfers - allocations.length) <= 0 ? 1 : minTransfers - allocations.length, availableVirtualUsers.length); + /** + * 创建系统账户反向匹配 + * 系统账户作为付款方,向有资金缺口的用户打款 + * @param {number} targetAmount - 目标匹配金额 + */ + async createSystemReverseMatching(targetAmount) { + const db = getDB(); - // 生成随机分配金额数组 - const randomAmounts = this.generateRandomAmounts(remainingAmount, maxPossibleTransfers, minAmount, maxAmountPerTransfer); + 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 + `); - // 为每个随机金额分配虚拟用户 - for (let i = 0; i < randomAmounts.length && availableVirtualUsers.length > 0; i++) { - const randomIndex = Math.floor(Math.random() * availableVirtualUsers.length); - const virtualUser = availableVirtualUsers[randomIndex]; + if (systemAccounts.length === 0) { + console.log('没有可用的系统账户进行反向匹配'); + return; + } - allocations.push({ - userId: virtualUser.id, - username: virtualUser.username, - amount: randomAmounts[i], - userType: 'virtual', - balance: virtualUser.balance - }); + const systemAccount = systemAccounts[0]; - remainingAmount -= randomAmounts[i]; - availableVirtualUsers.splice(randomIndex, 1); + // 确定实际匹配金额(不超过系统账户余额的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); } - } + } - // 检查是否有足够的用户来完成分配 - if (remainingAmount > 0 && allocations.length < minTransfers && availableVirtualUsers.length === 0) { - throw new Error('没有足够的可用用户来完成分配(避免重复分配给同一用户)'); - } + /** + * 为系统反向匹配生成分配 + * @param {number} orderId - 匹配订单ID + * @param {number} totalAmount - 总金额 + * @param {number} systemUserId - 系统账户ID + */ + async generateSystemReverseAllocations(orderId, totalAmount, systemUserId) { + const db = getDB(); - // 确保至少有最小笔数 - // if (allocations.length < minTransfers) { - // throw new Error(`无法生成足够的分配:需要至少${minTransfers}笔,但只能生成${allocations.length}笔`); - // } + 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 + `); - // 精确控制总金额,避免超出预期 - const currentTotal = allocations.reduce((sum, a) => sum + a.amount, 0); - console.log('剩余金额处理前:', remainingAmount, '当前总分配金额:', currentTotal, '期望总金额:', totalAmount); + if (negativeUsers.length === 0) { + console.log('没有负余额用户需要反向匹配'); + return; + } - 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) { + // 按比例分配金额给负余额用户 + const totalDeficit = negativeUsers.reduce((sum, user) => sum + user.deficit, 0); + let remainingAmount = totalAmount; - console.log('调整最后一笔分配,增加', allowedAmount, '元以达到精确总金额'); - allocations[allocations.length - 1].amount += allowedAmount; - } + 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; } - 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 = []; + /** + * 生成大额匹配的随机金额分拆(15000以上) + * @param {number} totalAmount - 总金额 + * @returns {Array} 分拆后的金额数组 + */ + generateRandomLargeAmounts(totalAmount) { + const amounts = []; + let remaining = totalAmount; + const minAmount = 1000; // 最小单笔金额 + const maxAmount = 8000; // 最大单笔金额 - // 首先为每笔分配最小金额 - 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); + 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 (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 (remaining > 0) { + amounts.push(remaining); } - } - // 如果所有位置都已达到最大值,退出循环 - if (perIndexAmount === 0 && remainder === 0) { - break; - } + return amounts; } - // 如果还有剩余金额无法分配,返回空数组表示失败 - if (remainingToDistribute > 0) { - return []; - } + /** + * 生成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(); - return amounts; - } + try { + // 获取昨天的日期(本地时区) + const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); - // 生成3笔随机金额,总计指定金额(保留原方法用于兼容性) - generateThreeRandomAmounts(totalAmount) { - // 确保总金额足够分配三笔最小金额 - const minAmount = 500; - const maxAmount = Math.min(5000, totalAmount - 2 * minAmount); + // 获取前一天所有用户的出款总数(系统总出款) + 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] + ); - // 生成第一笔金额 (500-5000) - const amount1 = Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; + const systemOutbound = systemOutboundResult[0].total_outbound || 0; - // 生成第二笔金额 (500-剩余金额-500) - const remaining1 = totalAmount - amount1; - const maxAmount2 = Math.min(5000, remaining1 - minAmount); - const amount2 = Math.floor(Math.random() * (maxAmount2 - minAmount + 1)) + minAmount; + // 获取前一天所有用户的具体出款金额(用于检查重复) + 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 amount3 = totalAmount - amount1 - amount2; + const yesterdayAmounts = yesterdayAmountsResult.map(row => row.amount); - return [amount1, amount2, amount3]; - } + // 检查每笔金额是否与前一天的金额不同 + const duplicateAmounts = []; + for (const amount of amounts) { + if (yesterdayAmounts.includes(amount)) { + duplicateAmounts.push(amount); + } + } - // 生成随机金额(保留原方法用于其他地方) - - - /** - * 确认分配并创建转账记录 - * @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} 元`); + 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: '验证匹配金额时发生错误' + }; } - } - - // 检查分配状态 - 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(); + /** + * 生成智能分配并创建数据库记录 + * @param {number} orderId - 订单ID + * @param {number} initiatorId - 发起人ID + * @returns {Promise} 返回分配结果数组 + */ + async generateSmartAllocationsWithDB(orderId, initiatorId) { + 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] - ); + try { + await db.query('START TRANSACTION'); + // 获取订单总金额 + const [orderResult] = await db.execute( + 'SELECT amount FROM matching_orders WHERE id = ? FOR UPDATE', + [orderId] + ); + //获取用户余额 + const [user] = await db.execute(` + SELECT balance + FROM users + WHERE id = ${initiatorId} + `) - if (pending[0].count === 0) { - // 当前轮次完成,检查是否需要下一轮 - const [order] = await db.execute( - 'SELECT * FROM matching_orders WHERE id = ?', - [matchingOrderId] - ); + if (orderResult.length === 0) { + await db.query('ROLLBACK'); + throw new Error('匹配订单不存在'); + } - const currentOrder = order[0]; + const totalAmount = orderResult[0].amount; - if (currentOrder.cycle_count + 1 < currentOrder.max_cycles) { - // 开始下一轮 - await db.execute( - 'UPDATE matching_orders SET cycle_count = cycle_count + 1 WHERE id = ?', - [matchingOrderId] - ); + // 使用智能分配算法生成分配方案 + const allocations = await this.generateSmartAllocations(totalAmount, initiatorId); - // 生成下一轮分配 - 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] - ); + if (allocations.length === 0) { + await db.query('ROLLBACK'); + throw new Error('无法生成有效的分配方案'); + } - console.log(`匹配订单 ${matchingOrderId} 已完成所有轮次`); + // 验证总金额(简化版验证) + const totalAllocated = allocations.reduce((sum, allocation) => sum + allocation.amount, 0); + if (Math.abs(totalAllocated - totalAmount) > 0.01) { + await db.query('ROLLBACK'); + throw new Error(`分配金额不匹配:期望${totalAmount}元,实际分配${totalAllocated}元`); + } - // 检查用户是否完成第三次匹配,如果是则给代理分佣 - await this.checkAndProcessAgentCommission(currentOrder.initiator_id); + console.log(`智能分配验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 分配${allocations.length}笔`); + + // 创建分配记录 + const createdAllocations = []; + let from_user_balance = user[0].balance + for (let i = 0; i < allocations.length; i++) { + const allocation = allocations[i]; + from_user_balance -= allocation.amount + // 设置出款日期为今天,可回款时间为明天的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,from_user_balance,to_user_balance) VALUES (?, ?, ?, ?, 1, "pending", CURDATE(), ?,?,?)', + [orderId, initiatorId, allocation.userId, allocation.amount, tomorrow.format('YYYY-MM-DD HH:mm:ss'), from_user_balance, Number(allocation.currentBalance) + Number(allocation.amount)] + ); + + // 添加分配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}]`); + } + await db.query('COMMIT'); + + return createdAllocations; + } catch (error) { + await db.query('ROLLBACK'); + console.error('生成智能分配失败:', error); + throw error; } - } - } catch (error) { - console.error('检查轮次完成情况失败:', error); - throw error; } - } - // 获取用户的匹配订单 - async getUserMatchingOrders(userId, page = 1, limit = 10) { - const db = getDB(); - const offset = (parseInt(page) - 1) * parseInt(limit); + /** + * 生成传统三笔分配(保留原方法用于兼容性) + * @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('匹配订单不存在'); + } - 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 totalAmount = orderResult[0].amount; - // 同时获取系统反向匹配订单(如果用户参与了分配) - 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 validation = await this.validateMatchingAmount(initiatorId, totalAmount, amounts); + if (!validation.isValid) { + throw new Error(`匹配金额不符合业务规则:${validation.message}。建议匹配金额:${validation.suggestedAmount}元`); + } - // 合并订单列表 - const allOrders = [...orders, ...systemOrders]; + // 记录验证信息 + console.log(`匹配金额验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 前一天系统出款${validation.systemOutbound}元`); - // 按创建时间排序 - allOrders.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + const usedTargetUsers = new Set(); // 记录已使用的目标用户 - // 为每个订单获取分配信息 - 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; - } + for (let i = 0; i < amounts.length; i++) { + const amount = amounts[i]; - return allOrders; - } catch (error) { - console.error('获取用户匹配订单失败:', error); - throw error; + // 获取匹配目标,排除已使用的用户 + 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(); - // 获取用户待处理的分配 - async getUserPendingAllocations(userId) { - 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); - try { - const [allocations] = await db.execute( - `(SELECT oa.*, mo.amount as total_amount, mo.status as order_status, u.username as to_user_name, + // 获取前一天打款的用户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' + AND u.province = ? + 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, currentUserProvince, 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] + ); + // 查询用户当天在matching_orders表中打出去的款项 + const today = getLocalDateString(); + const [todayOutflowResult] = await db.execute( + `SELECT SUM(amount) as today_outflow + FROM transfers + WHERE from_user_id = ? + AND DATE(created_at) = ? + AND status != 'cancelled' `, + [user.user_id, today] + ); + + // 添加分配金额信息到用户对象 + const orderStatus = orderStatusResult[0] || {pending_amount: 0}; + const todayOutflow = todayOutflowResult[0] || {today_outflow: 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; + + + // 所有查询到的用户都是负余额用户,直接添加到可用列表 + } + userBalanceResult = userBalanceResult.filter(user => user.has_active_allocations < -100); + userBalanceResult = userBalanceResult.sort((a, b) => { + const getPriority = (user) => { + if (user.district_id === currentUserDistrictId) return 1; // 同区县 + if (user.city === currentUserCity) return 2; // 同城市 + return 3; // 其他 + }; + + const priorityA = getPriority(a); + const priorityB = getPriority(b); + + if (priorityA !== priorityB) { + return priorityA - priorityB; // 优先级小的排前 + } + + // 同优先级里:越接近0(数值越大)排前 -> 使用降序 + return b.has_active_allocations - a.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, + 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, + currentBalance: 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); + console.log(`智能分配完成: 总金额${totalAmount}元,分配${allocations.length}笔`); + console.log('分配详情:', allocations.map(a => + `${a.amount}元 -> 用户${a.userId}(${a.username}) [${a.userType}]` + ).join(', ')); + //检查每个用户的匹配到的数量不能超过自身 + let checking = allocations.filter(item => item.userType !== 'virtual') + let is_checking = true + for (const user of checking) { + if (Math.abs(user.has_active_allocations) < user.amount) { + is_checking = false; + break; + } + } + if (is_checking) { + return allocations; + } + return [] + + + } 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, @@ -1451,122 +1467,123 @@ class MatchingService { 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] - ); + [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}%`] - ); + // 检查每个分配的超时状态,但不过滤掉 + 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; + // 添加超时状态标记 + 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 = ''; + // 计算剩余时间 + 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}分钟`; + 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; } - - 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(); + // 检查并处理代理佣金 + async checkAndProcessAgentCommission(userId) { + const db = getDB(); - try { - // 检查用户是否有代理关系 - const [agentRelation] = await db.execute( - 'SELECT agent_id, created_at FROM agent_merchants WHERE merchant_id = ?', - [userId] - ); + try { + // 检查用户是否有代理关系 + const [agentRelation] = await db.execute( + 'SELECT agent_id, created_at FROM agent_merchants WHERE merchant_id = ?', + [userId] + ); - if (agentRelation.length === 0) { - return; // 用户没有代理,无需处理 - } + if (agentRelation.length === 0) { + return; // 用户没有代理,无需处理 + } - const agentId = agentRelation[0].agent_id; - const agentJoinTime = agentRelation[0].created_at; + 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 [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; + 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 (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; + 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] - ); + // 记录佣金 + 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} 元`); + console.log(`用户 ${userId} 完成第三次转账,为代理 ${agentId} 分佣 ${commissionAmount} 元`); + } + } + } catch (error) { + console.error('处理代理佣金失败:', error); + // 不抛出错误,避免影响主流程 } - } - } catch (error) { - console.error('处理代理佣金失败:', error); - // 不抛出错误,避免影响主流程 } - } } module.exports = new MatchingService(); \ No newline at end of file diff --git a/services/transferService.js b/services/transferService.js index c4e5fb4..5d0ea0b 100644 --- a/services/transferService.js +++ b/services/transferService.js @@ -241,7 +241,7 @@ class TransferService { // 获取转账列表 async getTransfers(filters = {}, pagination = {}, user_type = 'user_to_user') { const db = getDB(); - const {page = 1, limit = 10, sort = 'created_at', order = 'desc'} = pagination; + const {page = 1, limit = 10, sort = 'id', order = 'desc'} = pagination; const pageNum = parseInt(page, 10) || 1; const limitNum = parseInt(limit, 10) || 10; const offset = (pageNum - 1) * limitNum; @@ -283,7 +283,7 @@ class TransferService { // 构建排序子句 const validSortFields = ['id', 'amount', 'created_at', 'updated_at', 'status']; - const sortField = validSortFields.includes(sort) ? sort : 'created_at'; + const sortField = validSortFields.includes(sort) ? sort : 'id'; const sortOrder = order && order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; const orderClause = `ORDER BY t.${sortField} ${sortOrder}`; @@ -304,6 +304,8 @@ class TransferService { `SELECT t.*, fu.username as from_username, fu.real_name as from_real_name, + fu.balance as from_balance, + tu.balance as to_balance, tu.username as to_username, tu.real_name as to_real_name, f_p.name as from_province, @@ -322,7 +324,7 @@ class TransferService { LEFT JOIN china_regions t_c ON t_c.code = tu.city LEFT JOIN china_regions t_d ON t_d.code = tu.district_id ${whereClause} ${orderClause} - LIMIT ${limitNum}`, + LIMIT ${limitNum} OFFSET ${offset}`, params );