Files
jurong_circle_black/routes/agents.js
2025-09-17 14:00:46 +08:00

777 lines
28 KiB
JavaScript
Raw Permalink 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.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;