Files
jurong_circle_black/routes/agents.js
2025-09-10 18:10:40 +08:00

719 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.city_name, zr.district_name
FROM regional_agents ra
JOIN users u ON ra.user_id = u.id
JOIN zhejiang_regions zr ON ra.region_id = zr.id
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', 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 FROM regional_agents ra WHERE ra.id = ?`,
[parseInt(agent_id)]
);
const regionId = agentInfo[0].region_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(am.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,
(SELECT COUNT(*) FROM agent_commission_records WHERE merchant_id = u.id AND agent_id = ?) as commission_count
FROM users u
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
WHERE (am.agent_id = ? 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(agent_id), parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
);
// 获取总数(包括代理关联的商户和符合条件的早期商户)
const [countResult] = await getDB().execute(
`SELECT COUNT(*) as total
FROM users u
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
WHERE (am.agent_id = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user'))`,
[parseInt(agent_id), parseInt(agent_id), 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
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
WHERE (am.agent_id = ? AND (u.created_at >= ? OR u.district_id != ?))`,
[parseInt(agent_id), parseInt(agent_id), 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', async (req, res) => {
try {
const { agent_id } = req.params;
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 agent_commission_records acr
JOIN users u ON acr.merchant_id = u.id
WHERE acr.agent_id = ${parseInt(agent_id)}
ORDER BY acr.created_at DESC
LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`
);
// 获取总数和总佣金
const [summary] = await getDB().execute(
`SELECT COUNT(*) as total_records,
COALESCE(SUM(commission_amount), 0) as total_commission
FROM agent_commission_records
WHERE agent_id = ${parseInt(agent_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', 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', 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', async (req, res) => {
try {
const { agent_id } = req.params;
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;
}
// 获取指定时间范围内的佣金趋势数据
const [trendData] = await getDB().execute(
`SELECT
DATE(created_at) as date,
COALESCE(SUM(commission_amount), 0) as amount
FROM agent_commission_records
WHERE agent_id = ?
AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
GROUP BY DATE(created_at)
ORDER BY date ASC`,
[parseInt(agent_id), days]
);
// 填充缺失的日期(确保每天都有数据点)
const filledData = [];
const today = new Date();
for (let i = days - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
// 修复日期比较将数据库返回的Date对象转换为字符串进行比较
const existingData = trendData.find(item => {
const itemDateStr = item.date instanceof Date ?
item.date.toISOString().split('T')[0] :
item.date;
return itemDateStr === dateStr;
});
filledData.push({
date: dateStr,
amount: existingData ? parseFloat(existingData.amount) : 0
});
}
res.json({
success: true,
data: filledData
});
} catch (error) {
console.error('获取佣金趋势数据失败:', 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', async (req, res) => {
try {
const { agent_id } = req.params;
// 获取商户状态分布
const [statusData] = await getDB().execute(
`SELECT
CASE
WHEN u.audit_status = 'approved' THEN '已审核'
WHEN u.audit_status = 'pending' THEN '待审核'
WHEN u.audit_status = 'rejected' THEN '已拒绝'
ELSE '未知状态'
END as status,
COUNT(*) as count
FROM agent_merchants am
JOIN users u ON am.merchant_id = u.id
WHERE am.agent_id = ?
GROUP BY u.audit_status
ORDER BY count DESC`,
[parseInt(agent_id)]
);
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', async (req, res) => {
try {
const { agent_id } = req.params;
// 获取基础统计数据
const [basicStats] = await getDB().execute(
`SELECT
(SELECT COUNT(*) FROM agent_merchants WHERE agent_id = ?) as total_merchants,
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'approved') as approved_merchants,
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'pending') as pending_merchants,
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'rejected') as rejected_merchants,
(SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ?) as total_commission,
(SELECT COUNT(*) FROM agent_commission_records WHERE agent_id = ?) as total_commission_records`,
[parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id)]
);
// 获取本月佣金
const [monthlyStats] = await getDB().execute(
`SELECT
COALESCE(SUM(commission_amount), 0) as monthly_commission,
COUNT(*) as monthly_commission_records
FROM agent_commission_records
WHERE agent_id = ?
AND YEAR(created_at) = YEAR(CURDATE())
AND MONTH(created_at) = MONTH(CURDATE())`,
[parseInt(agent_id)]
);
// 获取今日佣金
const [dailyStats] = await getDB().execute(
`SELECT
COALESCE(SUM(commission_amount), 0) as daily_commission,
COUNT(*) as daily_commission_records
FROM agent_commission_records
WHERE agent_id = ?
AND DATE(created_at) = CURDATE()`,
[parseInt(agent_id)]
);
// 获取最近7天新增商户数
const [weeklyMerchants] = await getDB().execute(
`SELECT COUNT(*) as weekly_new_merchants
FROM agent_merchants am
JOIN users u ON am.merchant_id = u.id
WHERE am.agent_id = ?
AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`,
[parseInt(agent_id)]
);
// 获取提现相关统计数据
const [withdrawalStats] = await getDB().execute(
`SELECT
CAST(COALESCE(commission_sum.total_commission, 0) AS DECIMAL(10,2)) as total_commission_calc,
CAST(COALESCE(ra.withdrawn_amount, 0) AS DECIMAL(10,2)) as withdrawn_amount,
CAST(COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as pending_withdrawal,
CAST(COALESCE(commission_sum.total_commission, 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount
FROM regional_agents ra
LEFT JOIN (
SELECT agent_id, SUM(commission_amount) as total_commission
FROM agent_commission_records
WHERE agent_id = ?
GROUP BY agent_id
) commission_sum ON ra.id = commission_sum.agent_id
WHERE ra.id = ?`,
[parseInt(agent_id), parseInt(agent_id)]
);
// 合并所有统计数据
const stats = {
...basicStats[0],
...monthlyStats[0],
...dailyStats[0],
...weeklyMerchants[0],
...(withdrawalStats[0] || {
withdrawn_amount: 0,
pending_withdrawal: 0,
available_amount: 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', 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 { id } = req.user;
const { page = 1, size = 10 } = req.query;
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 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;