const express = require('express'); const router = express.Router(); const {getDB} = require('../database'); const QRCode = require('qrcode'); const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const {auth} = require('../middleware/auth'); const dayjs = require('dayjs'); // 获取浙江省所有区域列表 router.get('/regions', async (req, res) => { try { const [regions] = await getDB().execute( 'SELECT * FROM zhejiang_regions ORDER BY city_name, district_name' ); res.json({success: true, data: regions}); } catch (error) { console.error('获取区域列表失败:', error); res.status(500).json({success: false, message: '获取区域列表失败'}); } }); // 申请成为区域代理 router.post('/apply', async (req, res) => { try { const {region_id, real_name, phone, id_card, contact_address} = req.body; if (!region_id || !real_name || !phone || !id_card) { return res.status(400).json({success: false, message: '请填写完整信息'}); } // 检查该区域是否已有代理(包括所有状态,不仅仅是active) const [existingRegionAgent] = await getDB().execute( 'SELECT id, status FROM regional_agents WHERE region_id = ? AND status IN ("pending", "active")', [region_id] ); if (existingRegionAgent.length > 0) { const status = existingRegionAgent[0].status; if (status === 'active') { return res.status(400).json({ success: false, message: '该区域已有激活的代理,每个区域只能有一个代理账号' }); } else if (status === 'pending') { return res.status(400).json({ success: false, message: '该区域已有待审核的代理申请,每个区域只能有一个代理账号' }); } } // 检查手机号是否已存在用户 const [existingUser] = await getDB().execute( 'SELECT id FROM users WHERE phone = ?', [phone] ); let userId; if (existingUser.length > 0) { userId = existingUser[0].id; // 检查该用户是否已申请过代理(包括所有状态) const [existingUserAgent] = await getDB().execute( 'SELECT id, status, region_id FROM regional_agents WHERE user_id = ?', [userId] ); if (existingUserAgent.length > 0) { const agentStatus = existingUserAgent[0].status; if (agentStatus === 'active') { return res.status(400).json({ success: false, message: '该用户已是其他区域的激活代理,一个用户只能申请一个区域的代理' }); } else if (agentStatus === 'pending') { return res.status(400).json({ success: false, message: '该用户已有待审核的代理申请,一个用户只能申请一个区域的代理' }); } else if (agentStatus === 'suspended' || agentStatus === 'terminated') { return res.status(400).json({ success: false, message: '该用户的代理资格已被暂停或终止,无法重新申请' }); } } } else { // 创建新用户(为代理申请用户生成临时密码) const bcrypt = require('bcryptjs'); const tempPassword = Math.random().toString(36).slice(-8); // 生成8位临时密码 const hashedPassword = await bcrypt.hash(tempPassword, 10); const [userResult] = await getDB().execute( 'INSERT INTO users (username, password, phone, real_name, id_card, created_at) VALUES (?, ?, ?, ?, ?, NOW())', [phone, hashedPassword, phone, real_name, id_card] ); userId = userResult.insertId; } // 生成代理编码 const agentCode = 'AG' + Date.now().toString().slice(-8); // 创建代理申请 await getDB().execute( 'INSERT INTO regional_agents (user_id, region_id, agent_code, status, created_at) VALUES (?, ?, ?, "pending", NOW())', [userId, region_id, agentCode] ); res.json({success: true, message: '申请提交成功,请等待审核'}); } catch (error) { console.error('申请代理失败:', error); res.status(500).json({success: false, message: '申请失败'}); } }); // 代理登录 router.post('/login', async (req, res) => { try { const {phone, password} = req.body; if (!phone || !password) { return res.status(400).json({success: false, message: '请输入手机号和密码'}); } // 先查询用户和代理信息(包含密码用于验证) const [agents] = await getDB().execute( `SELECT ra.*, u.id as user_id, u.username, u.phone, u.real_name, u.password, u.role, zr.name as city_name, d.name as district_name FROM regional_agents ra JOIN users u ON ra.user_id = u.id JOIN china_regions d ON d.code = u.district_id JOIN china_regions zr ON ra.region_id = zr.code WHERE u.phone = ? AND ra.status = "active"`, [phone] ); if (agents.length === 0) { return res.status(401).json({success: false, message: '手机号或密码错误,或账户未激活'}); } const agent = agents[0]; // 验证密码 const isPasswordValid = await bcrypt.compare(password, agent.password); if (!isPasswordValid) { return res.status(401).json({success: false, message: '手机号或密码错误,或账户未激活'}); } // 生成JWT token const jwt = require('jsonwebtoken'); const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; const token = jwt.sign( { userId: agent.user_id, username: agent.username || agent.phone, role: agent.role || 'agent', agentId: agent.id }, JWT_SECRET, {expiresIn: '24h'} ); delete agent.password; // 不返回密码 res.json({ success: true, data: { ...agent, token }, message: '登录成功' }); } catch (error) { console.error('代理登录失败:', error); res.status(500).json({success: false, message: '登录失败'}); } }); // 获取代理的商户列表(包含所有商户,标注早期商户状态) router.get('/merchants/:agent_id',auth, async (req, res) => { try { const {agent_id} = req.params; const {page = 1, limit = 10} = req.query; const offset = (page - 1) * limit; // 首先获取代理的注册时间 const [agentInfo] = await getDB().execute( `SELECT ra.created_at as agent_created_at, ra.region_id, ra.user_id FROM regional_agents ra WHERE ra.id = ?`, [parseInt(agent_id)] ); const regionId = agentInfo[0].region_id; const userId = agentInfo[0].user_id; if (!agentInfo || agentInfo.length === 0) { return res.status(404).json({success: false, message: '代理不存在'}); } const agentCreatedAt = agentInfo[0].agent_created_at; // 获取商户列表(包含所有商户,包括agent_merchants表中的和符合条件的早期商户) const [merchants] = await getDB().execute( `SELECT u.id, u.username, u.phone, u.real_name, u.created_at, u.audit_status, IFNULL(u.created_at, '未关联') as joined_at, CASE WHEN u.created_at < ? AND u.district_id = ? THEN 1 ELSE 0 END as is_early_merchant, CASE WHEN u.created_at < ? AND u.district_id = ? THEN '早期商户(不记录佣金)' ELSE '正常商户' END as merchant_status, (SELECT COUNT(*) FROM matching_orders WHERE initiator_id = u.id AND status = 'completed') as completed_matches FROM users u WHERE (u.inviter = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user')) ORDER BY u.created_at DESC LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, [agentCreatedAt, parseInt(regionId), agentCreatedAt, parseInt(regionId), parseInt(userId), agentCreatedAt, parseInt(regionId)] ); // 获取总数(包括代理关联的商户和符合条件的早期商户) const [countResult] = await getDB().execute( `SELECT COUNT(*) as total FROM users u WHERE (u.inviter = ? OR (u.created_at < ? AND u.district_id = ?))`, [parseInt(userId), agentCreatedAt, parseInt(regionId)] ); // 获取早期商户统计(从user表获取所有符合条件的早期商户) // 早期商户的判断条件:1.早期商户注册时间比代理要早。2.代理商代理的区县与商户的区县一致 const [earlyMerchantStats] = await getDB().execute( `SELECT COUNT(*) as early_merchant_count FROM users u WHERE u.created_at < ? AND u.district_id = ? AND u.role = 'user'`, [agentCreatedAt, parseInt(regionId)] ); // 获取正常商户统计(包括代理关联的商户,排除符合条件的早期商户) const [normalMerchantStats] = await getDB().execute( `SELECT COUNT(*) as normal_merchant_count FROM users u WHERE (u.inviter = ? AND (u.created_at >= ? OR u.district_id != ?))`, [parseInt(userId), agentCreatedAt, parseInt(regionId)] ); res.json({ success: true, data: { merchants, total: parseInt(countResult[0].total), page: parseInt(page), limit: parseInt(limit), stats: { total_merchants: parseInt(countResult[0].total), early_merchants: parseInt(earlyMerchantStats[0].early_merchant_count), normal_merchants: parseInt(normalMerchantStats[0].normal_merchant_count) } } }); } catch (error) { console.error('获取商户列表失败:', error); res.status(500).json({success: false, message: '获取商户列表失败'}); } }); // 获取代理的佣金记录 router.get('/commissions/:agent_id',auth, async (req, res) => { try { const {agent_id} = req.params; const {id} = req.user const {page = 1, limit = 10} = req.query; const offset = (page - 1) * limit; // 获取佣金记录 const [commissions] = await getDB().execute( `SELECT acr.*, u.username, u.real_name FROM transfers acr JOIN users u ON acr.from_user_id = u.id WHERE acr.to_user_id = ${parseInt(id)} AND source_type='agent' ORDER BY acr.created_at DESC LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}` ); // 获取总数和总佣金 const [summary] = await getDB().execute( `SELECT COUNT(*) as total_records, COALESCE(SUM(amount), 0) as total_commission FROM transfers WHERE source_type = 'agent' AND to_user_id=${id}` ); // 由于agent_commission_records表没有status字段,设置默认值 summary[0].paid_commission = summary[0].total_commission; summary[0].pending_commission = 0; res.json({ success: true, data: { commissions, summary: summary[0], page: parseInt(page), limit: parseInt(limit) } }); } catch (error) { console.error('获取佣金记录失败:', error); res.status(500).json({success: false, message: '获取佣金记录失败'}); } }); // 获取代理统计信息 router.get('/stats/:agent_id', auth,async (req, res) => { try { const {agent_id} = req.params; // 获取统计数据 const [stats] = await getDB().execute( `SELECT (SELECT COUNT(*) FROM agent_merchants WHERE agent_id = ${parseInt(agent_id)}) as total_merchants, (SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ${parseInt(agent_id)} AND u.audit_status = 'approved') as approved_merchants, (SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ${parseInt(agent_id)}) as total_commission, (SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ${parseInt(agent_id)}) as paid_commission, (SELECT COUNT(*) FROM registration_codes rc JOIN regional_agents ra ON rc.agent_id = ra.user_id WHERE ra.id = ${parseInt(agent_id)} AND rc.is_used = 1) as used_codes, (SELECT COUNT(*) FROM registration_codes rc JOIN regional_agents ra ON rc.agent_id = ra.user_id WHERE ra.id = ${parseInt(agent_id)} AND rc.is_used = 0 AND rc.expires_at > NOW()) as active_codes` ); res.json({success: true, data: stats[0]}); } catch (error) { console.error('获取统计信息失败:', error); res.status(500).json({success: false, message: '获取统计信息失败'}); } }); // 获取代理列表 router.get('/list', auth,async (req, res) => { try { const {page = 1, limit = 10, status, region_id} = req.query; const offset = (page - 1) * limit; let whereClause = '1=1'; let params = []; if (status) { whereClause += ' AND ra.status = ?'; params.push(status); } if (region_id) { whereClause += ' AND ra.region_id = ?'; params.push(region_id); } // 获取代理列表 const [agents] = await getDB().execute( `SELECT ra.*, u.username, u.phone, u.real_name, u.created_at as user_created_at, zr.city_name, zr.district_name, zr.region_code FROM regional_agents ra JOIN users u ON ra.user_id = u.id JOIN zhejiang_regions zr ON ra.region_id = zr.id WHERE ${whereClause} ORDER BY ra.created_at DESC LIMIT ${limit} OFFSET ${offset}` ); // 获取总数 const [countResult] = await getDB().execute( `SELECT COUNT(*) as total FROM regional_agents ra JOIN users u ON ra.user_id = u.id JOIN zhejiang_regions zr ON ra.region_id = zr.id WHERE ${whereClause}` ); const total = countResult[0].total; const totalPages = Math.ceil(total / limit); res.json({ success: true, data: { agents, pagination: { page: parseInt(page), limit: parseInt(limit), total, totalPages } } }); } catch (error) { console.error('获取代理列表失败:', error); res.status(500).json({success: false, message: '获取代理列表失败'}); } }); /** * 获取代理佣金趋势数据 * @route GET /agents/commission-trend/:agent_id * @param {string} agent_id - 代理ID * @param {string} period - 时间周期 (7d, 30d, 3m) * @returns {Object} 佣金趋势数据 */ router.get('/commission-trend/:agent_id',auth, async (req, res) => { try { console.log(req.params, 'req.params') const {agent_id} = req.params; let [agentUserInfo] = await getDB().execute(` SELECT u.* FROM regional_agents as rg LEFT JOIN users u ON rg.user_id = u.id WHERE rg.id = ${agent_id} `) let userId = agentUserInfo[0].id console.log(userId, 'userId') const {period = '7d'} = req.query; let days; switch (period) { case '7d': days = 7; break; case '30d': days = 30; break; case '3m': days = 90; break; default: days = 7; } console.log(period, 'period') const [trendData] = await getDB().execute(` SELECT DATE(created_at) as date, CAST(COALESCE(SUM(amount), 0) AS DECIMAL(10, 2)) as amount FROM transfers WHERE to_user_id = ? AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) AND source_type = 'user_to_agent' GROUP BY DATE(created_at) ORDER BY date ASC `, [userId, parseInt(days)]); // 填充缺失的日期(佣金为0) const result = []; for (let i = days - 1; i >= 0; i--) { const date = dayjs().subtract(i, 'day'); const dateStr = date.format('YYYY-MM-DD'); const existingData = trendData.find(item => { const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); return itemDateStr === dateStr; }); result.push({ date: date.format('MM-DD'), amount: existingData ? parseFloat(existingData.amount) : 0 }); } res.json({ success: true, data: result }); } catch (error) { console.log(error); res.status(500).json({ success: false, message: '获取趋势数据失败' }); } }); /** * 获取代理商户状态分布数据 * @route GET /agents/merchant-status/:agent_id * @param {string} agent_id - 代理ID * @returns {Object} 商户状态分布数据 */ router.get('/merchant-status/:agent_id',auth, async (req, res) => { try { const {agent_id} = req.params; let [agent] = await getDB().execute(` SELECT u.* FROM users as u LEFT JOIN regional_agents as rg ON rg.user_id = u.id WHERE rg.id = ${agent_id} `) let userId = agent[0].id // 获取商户状态分布 const [statusData] = await getDB().execute( `SELECT CASE WHEN am.audit_status = 'approved' THEN '已审核' WHEN am.audit_status = 'pending' THEN '待审核' WHEN am.audit_status = 'rejected' THEN '已拒绝' ELSE '未知状态' END as status, COUNT(*) as count FROM users am WHERE am.inviter = ? GROUP BY am.audit_status ORDER BY count DESC`, [parseInt(userId)] ); res.json({ success: true, data: statusData }); } catch (error) { console.error('获取商户状态分布数据失败:', error); res.status(500).json({success: false, message: '获取商户状态分布数据失败'}); } }); /** * 获取代理详细统计数据(包含更多维度) * @route GET /agents/detailed-stats/:agent_id * @param {string} agent_id - 代理ID * @returns {Object} 详细统计数据 */ router.get('/detailed-stats/:agent_id',auth, async (req, res) => { try { const {agent_id} = req.params; let [agentUserInfo] = await getDB().execute(` SELECT u.* FROM users as u LEFT JOIN regional_agents as ag ON ag.user_id = u.id WHERE ag.id = ${agent_id} `) let agUserInfo = agentUserInfo[0]; // 获取基础统计数据 const [basicStats] = await getDB().execute( `SELECT (SELECT COUNT(*) FROM users WHERE inviter = ?) as total_merchants, (SELECT COUNT(*) FROM users am WHERE am.inviter = ? AND am.audit_status = 'approved') as approved_merchants, (SELECT COUNT(*) FROM users am WHERE am.inviter = ? AND am.audit_status = 'pending') as pending_merchants, (SELECT COUNT(*) FROM users am WHERE am.inviter = ? AND am.audit_status = 'rejected') as rejected_merchants, (SELECT COALESCE(SUM(amount), 0) FROM transfers WHERE transfer_type = 'user_to_agent' AND to_user_id = ?) as total_commission `, [parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id)] ); // 获取本月营收 const [monthlyStats] = await getDB().execute( `SELECT COALESCE(SUM(amount), 0) as monthly_commission, COUNT(*) as monthly_commission_records FROM transfers WHERE to_user_id = ? AND transfer_type = 'user_to_agent' AND YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())`, [parseInt(agUserInfo.id)] ); // 获取今日佣金 const [dailyStats] = await getDB().execute( `SELECT COALESCE(SUM(amount), 0) as daily_commission, COUNT(*) as daily_commission_records FROM transfers WHERE to_user_id = ? AND transfer_type = 'user_to_agent' AND DATE(created_at) = CURDATE()`, [parseInt(agUserInfo.id)] ); // 获取最近7天新增商户数 const [weeklyMerchants] = await getDB().execute( `SELECT COUNT(*) as weekly_new_merchants FROM users am WHERE am.inviter = ? AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`, [parseInt(agUserInfo.id)] ); // 合并所有统计数据 const stats = { ...basicStats[0], ...monthlyStats[0], ...dailyStats[0], ...weeklyMerchants[0] }; res.json({ success: true, data: stats }); } catch (error) { console.error('获取详细统计数据失败:', error); res.status(500).json({success: false, message: '获取详细统计数据失败'}); } }); /** * 获取代理商户的转账记录 * @route GET /agents/merchants/:agent_id/transfers * @param {string} agent_id - 代理ID * @param {string} page - 页码 * @param {string} limit - 每页数量 * @returns {Object} 转账记录列表 */ router.get('/merchants/:agent_id/transfers', auth,async (req, res) => { try { const {agent_id} = req.params; const {page = 1, limit = 10} = req.query; const pageNum = parseInt(page) || 1; const limitNum = parseInt(limit) || 10; const offset = (pageNum - 1) * limitNum; // 检查代理是否存在 const [agentResult] = await getDB().execute( 'SELECT * FROM regional_agents WHERE id = ?', [parseInt(agent_id)] ); if (agentResult.length === 0) { return res.status(404).json({success: false, message: '代理不存在'}); } // 查询商户转账记录 const transferQuery = ` SELECT t.id, t.from_user_id, t.to_user_id, t.amount, t.status, t.transfer_type, t.description, t.created_at, t.confirmed_at, from_user.real_name as from_real_name, from_user.phone as from_phone, to_user.real_name as to_real_name, to_user.phone as to_phone FROM agent_merchants am JOIN transfers t ON am.merchant_id = t.from_user_id LEFT JOIN users from_user ON t.from_user_id = from_user.id LEFT JOIN users to_user ON t.to_user_id = to_user.id WHERE am.agent_id = ? ORDER BY t.created_at DESC LIMIT ${limitNum} OFFSET ${offset} `; const [transfers] = await getDB().execute(transferQuery, [parseInt(agent_id)]); // 查询总数 const [totalResult] = await getDB().execute( `SELECT COUNT(*) as total FROM agent_merchants am JOIN transfers t ON am.merchant_id = t.from_user_id WHERE am.agent_id = ?`, [parseInt(agent_id)] ); const total = totalResult[0].total; res.json({ success: true, data: { transfers, pagination: { page: pageNum, limit: limitNum, total, pages: Math.ceil(total / limitNum) } } }); } catch (error) { console.error('获取代理商户转账记录失败:', error); res.status(500).json({success: false, message: '获取代理商户转账记录失败,请稍后再试'}); } }); /** * 获取分销列表 * @route GET /agents/distribution * @returns {Object} 分销列表 */ router.get('/distribution', auth, async (req, res) => { try { const {page = 1, size = 10, user_id} = req.query; const {id} = user_id || req.user; const pageNum = parseInt(page) || 1; const limitNum = parseInt(size) || 10; const offset = (page - 1) * size; const [result] = await getDB().execute( `SELECT real_name, phone, username, avatar, created_at, id as user_id FROM users WHERE inviter = ? ORDER BY created_at DESC LIMIT ${size} OFFSET ${offset}`, [parseInt(id)] ); const [totalResult] = await getDB().execute( `SELECT COUNT(*) as total FROM users WHERE inviter = ? `, [parseInt(id)] ); result.forEach(item => { item.created_at = dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss'); }) const total = totalResult[0].total; res.json({ success: true, data: result, pagination: { page: pageNum, limit: limitNum, total, pages: Math.ceil(total / limitNum) } }); } catch (error) { console.error('获取分销列表失败:', error); res.status(500).json({success: false, message: '获取分销列表失败'}); } }); module.exports = router;