Files
jurong_circle_black/routes/users.js
2025-08-26 10:06:23 +08:00

1549 lines
47 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 bcrypt = require('bcryptjs');
const { getDB } = require('../database');
const { auth, adminAuth } = require('../middleware/auth');
const dayjs = require('dayjs');
const router = express.Router();
// 创建用户(管理员权限)
router.post('/', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
await db.query('START TRANSACTION');
const {
username,
password,
role = 'user',
isSystemAccount = false, // 是否为虚拟商户
realName,
idCard,
wechatQr,
alipayQr,
bankCard,
unionpayQr,
phone
} = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
}
if (!realName || !idCard) {
return res.status(400).json({ success: false, message: '姓名和身份证号不能为空' });
}
// 验证身份证号格式
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idCardRegex.test(idCard)) {
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
}
// 检查用户是否已存在
const [existingUsers] = await db.execute(
'SELECT id FROM users WHERE username = ? OR id_card = ? OR (phone IS NOT NULL AND phone = ?)',
[username, idCard, phone || null]
);
if (existingUsers.length > 0) {
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已存在' });
}
// 加密密码
const hashedPassword = await bcrypt.hash(password, 10);
// 创建用户
const [result] = await db.execute(
'INSERT INTO users (username, password, role, is_system_account, points, real_name, id_card, wechat_qr, alipay_qr, bank_card, unionpay_qr, phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone]
);
const userId = result.insertId;
// 用户余额已在创建用户时设置为默认值0.00,无需额外操作
await db.query('COMMIT');
// 返回创建的用户信息(不包含密码)
const [newUser] = await db.execute(
'SELECT id, username, role, avatar, points, real_name, phone, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
res.status(201).json({
success: true,
message: '用户创建成功',
user: newUser[0]
});
} catch (error) {
try {
await getDB().query('ROLLBACK');
} catch (rollbackError) {
console.error('回滚错误:', rollbackError);
}
console.error('创建用户错误:', error);
res.status(500).json({ success: false, message: '创建用户失败' });
}
});
/**
* 获取待审核用户列表(管理员权限)
*/
router.get('/pending-audit', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const { page = 1, limit = 10 } = req.query;
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const offset = (pageNum - 1) * limitNum;
// 获取待审核用户总数
const [countResult] = await db.execute(
'SELECT COUNT(*) as total FROM users WHERE audit_status = ?',
['pending']
);
const total = countResult[0].total;
// 获取待审核用户列表
const [users] = await db.execute(
`SELECT id, username, phone, real_name, business_license, id_card_front, id_card_back,
wechat_qr, alipay_qr, unionpay_qr, bank_card, audit_status, created_at
FROM users
WHERE audit_status = ?
ORDER BY created_at ASC
LIMIT ${limitNum} OFFSET ${offset}`,
['pending']
);
res.json({
success: true,
data: {
users,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
}
});
} catch (error) {
console.error('获取待审核用户列表错误:', error);
res.status(500).json({ success: false, message: '获取待审核用户列表失败' });
}
});
// 获取用户列表用于转账(普通用户权限)
router.get('/for-transfer', auth, async (req, res) => {
try {
const db = getDB();
// 获取所有用户的基本信息(用于转账选择)
const [users] = await db.execute(
'SELECT id, username, real_name FROM users WHERE id != ? ORDER BY username',
[req.user.id]
);
res.json({
success: true,
data: users
});
} catch (error) {
console.error('获取转账用户列表错误:', error);
res.status(500).json({ success: false, message: '获取用户列表失败' });
}
});
// 获取用户列表(管理员权限)
router.get('/', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const { page = 1, limit = 10, search = '', role = '', city = '', district = '', sort = 'created_at', order = 'desc' } = req.query;
// 确保参数为有效数字
const pageNum = Math.max(1, parseInt(page) || 1);
const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10));
const offset = Math.max(0, (pageNum - 1) * limitNum);
let whereConditions = [];
let countParams = [];
let listParams = [];
// 构建查询条件
if (search) {
whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ?)');
countParams.push(`%${search}%`, `%${search}%`);
listParams.push(`%${search}%`, `%${search}%`);
}
if (role && role !== 'all') {
whereConditions.push('u.role = ?');
countParams.push(role);
listParams.push(role);
}
if (city) {
whereConditions.push('u.city = ?');
countParams.push(city);
listParams.push(city);
}
if (district) {
whereConditions.push('u.district_id = ?');
countParams.push(district);
listParams.push(district);
}
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
// 添加分页参数
listParams.push(limitNum.toString(), offset.toString());
// 获取总数
const [countResult] = await db.execute(
`SELECT COUNT(*) as total FROM users u
LEFT JOIN zhejiang_regions r ON u.district_id = r.id
${whereClause}`,
countParams
);
// 验证排序字段防止SQL注入
const validSortFields = ['id', 'username', 'role', 'points', 'balance', 'created_at', 'updated_at'];
const sortField = validSortFields.includes(sort) ? sort : 'created_at';
// 验证排序方向
const sortOrder = (order && (order.toUpperCase() === 'ASC' || order.toUpperCase() === 'DESC'))
? order.toUpperCase()
: 'DESC';
// 获取用户列表,关联地区信息和转账统计
const [users] = await db.execute(
`SELECT u.id, u.username, u.role, u.avatar, u.points, u.balance, u.real_name, u.id_card, u.phone,
u.wechat_qr, u.alipay_qr, u.bank_card, u.unionpay_qr, u.audit_status, u.is_system_account,
u.created_at, u.updated_at, u.city, u.district_id,u.id_card_front,u.id_card_back,
u.business_license,
r.city_name, r.district_name,
COALESCE(yesterday_out.amount, 0) as yesterday_transfer_amount,
COALESCE(today_in.amount, 0) as today_received_amount
FROM users u
LEFT JOIN zhejiang_regions r ON u.district_id = r.id
LEFT JOIN (
SELECT from_user_id, SUM(amount) as amount
FROM transfers
WHERE created_at >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
AND created_at < DATE(NOW())
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 >= DATE(NOW())
AND created_at < DATE(DATE_ADD(NOW(), INTERVAL 1 DAY))
AND status IN ('confirmed', 'received')
GROUP BY to_user_id
) today_in ON u.id = today_in.to_user_id
${whereClause}
ORDER BY u.${sortField} ${sortOrder}
LIMIT ? OFFSET ?`,
listParams
);
res.json({
success: true,
users,
total: countResult[0].total,
page: pageNum,
limit: limitNum,
totalPages: Math.ceil(countResult[0].total / limitNum)
});
} catch (error) {
console.error('获取用户列表错误:', error);
res.status(500).json({ success: false, message: '获取用户列表失败' });
}
});
// 获取当前用户的个人资料
router.get('/profile', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.user.id;
const [users] = await db.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
const profile = {
...users[0],
nickname: users[0].username, // 添加nickname字段映射到username
realName: users[0].real_name,
idCard: users[0].id_card,
wechatQr: users[0].wechat_qr,
alipayQr: users[0].alipay_qr,
bankCard: users[0].bank_card,
unionpayQr: users[0].unionpay_qr,
businessLicense: users[0].business_license,
idCardFront: users[0].id_card_front,
idCardBack: users[0].id_card_back
};
res.json({ success: true, user: profile });
} catch (error) {
console.error('获取用户资料错误:', error);
res.status(500).json({ success: false, message: '获取用户资料失败' });
}
});
/**
* 获取当前用户的收款码状态
* 用于检查用户是否已上传微信、支付宝、云闪付收款码
*/
router.get('/payment-codes-status', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.user.id;
const [users] = await db.execute(
'SELECT wechat_qr, alipay_qr, unionpay_qr FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
const paymentCodes = users[0];
res.json({
success: true,
data: {
wechat_qr: paymentCodes.wechat_qr || '',
alipay_qr: paymentCodes.alipay_qr || '',
unionpay_qr: paymentCodes.unionpay_qr || ''
}
});
} catch (error) {
console.error('获取收款码状态错误:', error);
res.status(500).json({ success: false, message: '获取收款码状态失败' });
}
});
// 获取用户收款信息
router.get('/payment-info/:userId', auth, async (req, res) => {
try {
const db = getDB();
const targetUserId = req.params.userId;
const [users] = await db.execute(
'SELECT id, username, wechat_qr, alipay_qr, unionpay_qr, bank_card FROM users WHERE id = ?',
[targetUserId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
const user = users[0];
res.json({
success: true,
data: {
id: user.id,
username: user.username,
wechat_qr: user.wechat_qr,
alipay_qr: user.alipay_qr,
unionpay_qr: user.unionpay_qr,
bank_card: user.bank_card
}
});
} catch (error) {
console.error('获取用户收款信息错误:', error);
res.status(500).json({ success: false, message: '获取用户收款信息失败' });
}
});
// 获取当前用户的统计信息
router.get('/stats', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.user.id;
// 如果是管理员,返回全局统计
if (req.user.role === 'admin') {
// 总用户数
const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users');
// 管理员数量
const [adminUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "admin"');
// 普通用户数量
const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "user"');
// 本月新增用户
const [monthUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
);
// 上月新增用户
const [lastMonthUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
);
// 计算月增长率
const monthGrowthRate = lastMonthUsers[0].count > 0
? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2)
: 0;
// 用户总积分
const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users');
// 今日新增用户
const [todayUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()'
);
// 昨日新增用户
const [yesterdayUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)'
);
// 活跃用户数(有订单的用户)
const [activeUsers] = await db.execute(
'SELECT COUNT(DISTINCT user_id) as count FROM orders'
);
res.json({
success: true,
stats: {
totalUsers: totalUsers[0].count,
adminUsers: adminUsers[0].count,
regularUsers: regularUsers[0].count,
monthNewUsers: monthUsers[0].count,
todayUsers: todayUsers[0].count,
yesterdayUsers: yesterdayUsers[0].count,
monthlyGrowth: parseFloat(monthGrowthRate),
totalPoints: totalPoints[0].total,
activeUsers: activeUsers[0].count
}
});
} else {
// 普通用户返回个人统计
// 用户订单数
const [orderCount] = await db.execute(
'SELECT COUNT(*) as count FROM orders WHERE user_id = ?',
[userId]
);
// 用户总消费
const [totalSpent] = await db.execute(
'SELECT COALESCE(SUM(total_amount), 0) as total FROM orders WHERE user_id = ? AND status = "completed"',
[userId]
);
// 用户积分历史
const [pointsEarned] = await db.execute(
'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "earn"',
[userId]
);
const [pointsSpent] = await db.execute(
'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "spend"',
[userId]
);
res.json({
success: true,
stats: {
orderCount: orderCount[0].count,
totalSpent: totalSpent[0].total,
pointsEarned: pointsEarned[0].total,
pointsSpent: pointsSpent[0].total,
currentPoints: req.user.points || 0
}
});
}
} catch (error) {
console.error('获取用户统计错误:', error);
res.status(500).json({ success: false, message: '获取用户统计失败' });
}
});
// 获取用户统计信息(管理员权限)- 必须在/:id路由之前定义
router.get('/admin/stats', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
// 总用户数
const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users');
// 管理员数量
const [adminUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "admin"');
// 普通用户数量
const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "user"');
// 本月新增用户
const [monthUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
);
// 上月新增用户
const [lastMonthUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
);
// 计算月增长率
const monthGrowthRate = lastMonthUsers[0].count > 0
? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2)
: 0;
// 用户总积分
const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users');
// 今日新增用户
const [todayUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()'
);
// 昨日新增用户
const [yesterdayUsers] = await db.execute(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)'
);
// 活跃用户数(有订单的用户)
const [activeUsers] = await db.execute(
'SELECT COUNT(DISTINCT user_id) as count FROM orders'
);
res.json({
success: true,
stats: {
totalUsers: totalUsers[0].count,
adminUsers: adminUsers[0].count,
regularUsers: regularUsers[0].count,
monthNewUsers: monthUsers[0].count,
todayUsers: todayUsers[0].count,
yesterdayUsers: yesterdayUsers[0].count,
monthlyGrowth: parseFloat(monthGrowthRate),
totalPoints: totalPoints[0].total,
activeUsers: activeUsers[0].count
}
});
} catch (error) {
console.error('获取用户统计错误:', error);
res.status(500).json({ success: false, message: '获取用户统计失败' });
}
});
// 获取当前用户积分
router.get('/points', auth, async (req, res) => {
try {
const userId = req.user.id;
const [users] = await getDB().execute(
'SELECT points FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
res.json({
success: true,
points: users[0].points
});
} catch (error) {
console.error('获取用户积分错误:', error);
res.status(500).json({ success: false, message: '获取用户积分失败' });
}
});
// 获取用户积分历史记录
router.get('/points/history', auth, async (req, res) => {
try {
const userId = req.user.id;
const { page = 1, limit = 20, type } = req.query;
// 确保参数为有效数字
const pageNum = Math.max(1, parseInt(page) || 1);
const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 20));
const offset = Math.max(0, (pageNum - 1) * limitNum);
let whereClause = 'WHERE user_id = ?';
let queryParams = [userId];
if (type && ['earn', 'spend'].includes(type)) {
whereClause += ' AND type = ?';
queryParams.push(type);
}
// 获取总数
const [countResult] = await getDB().execute(
`SELECT COUNT(*) as total FROM points_history ${whereClause}`,
queryParams
);
// 获取历史记录
const [records] = await getDB().execute(
`SELECT id, type, amount, description, order_id, created_at
FROM points_history
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[...queryParams, limitNum.toString(), offset.toString()]
);
res.json({
success: true,
data: {
records,
pagination: {
page: pageNum,
limit: limitNum,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limitNum)
}
}
});
} catch (error) {
console.error('获取积分历史失败:', error);
res.status(500).json({ success: false, message: '获取积分历史失败' });
}
});
// 获取用户增长趋势数据(管理员权限)
router.get('/growth-trend', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const { days = 7 } = req.query;
const daysNum = Math.min(90, Math.max(1, parseInt(days) || 7));
// 获取指定天数内的用户注册趋势
const [trendData] = await db.execute(`
SELECT
DATE(created_at) as date,
COUNT(*) as count
FROM users
WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY DATE(created_at)
ORDER BY date ASC
`, [daysNum]);
// 填充缺失的日期注册数为0
const result = [];
for (let i = daysNum - 1; i >= 0; i--) {
const date = dayjs().subtract(i, 'day');
const dateStr = date.format('YYYY-MM-DD');
// 修复日期比较将数据库返回的Date对象转换为字符串进行比较
const existingData = trendData.find(item => {
const itemDateStr = dayjs(item.date).format('YYYY-MM-DD');
return itemDateStr === dateStr;
});
result.push({
date: date.format('MM-DD'),
count: existingData ? existingData.count : 0
});
}
res.json({
success: true,
data: result
});
} catch (error) {
console.error('获取用户增长趋势错误:', error);
res.status(500).json({ success: false, message: '获取用户增长趋势失败' });
}
});
// 获取日收入统计数据(管理员权限)
router.get('/daily-revenue', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const { days = 30 } = req.query;
const daysNum = Math.min(90, Math.max(1, parseInt(days) || 30));
// 获取指定天数内的用户注册数据,按天统计
const [dailyData] = await db.execute(`
SELECT
DATE(created_at) as date,
COUNT(*) as user_count
FROM users
WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY DATE(created_at)
ORDER BY date ASC
`, [daysNum]);
// 填充缺失的日期注册数为0
const result = [];
for (let i = daysNum - 1; i >= 0; i--) {
const date = dayjs().subtract(i, 'day');
const dateStr = date.format('YYYY-MM-DD'); // YYYY-MM-DD格式
const dateDisplay = date.format('M/D'); // 显示格式
const existingData = dailyData.find(item => {
const itemDateStr = dayjs(item.date).format('YYYY-MM-DD');
return itemDateStr === dateStr;
});
const userCount = existingData ? existingData.user_count : 0;
const revenue = userCount * 398; // 每个用户398元收入
result.push({
date: dateDisplay,
userCount: userCount,
amount: revenue
});
}
res.json({
success: true,
data: result
});
} catch (error) {
console.error('获取日收入统计错误:', error);
res.status(500).json({ success: false, message: '获取日收入统计失败' });
}
});
// 生成注册码(管理员权限)==================== 激活码管理 ====================
/**
* 生成激活码(管理员权限)
*/
router.post('/registration-codes', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const adminId = req.user.id;
// 生成6位随机激活码
const crypto = require('crypto');
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let code = '';
for (let i = 0; i < 6; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
// 设置过期时间为1小时后
const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
// 插入激活码
const [result] = await db.execute(
'INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES (?, ?, ?)',
[code, expiresAt, adminId]
);
res.status(201).json({
success: true,
message: '激活码生成成功',
data: {
id: result.insertId,
code,
expiresAt,
createdAt: new Date()
}
});
} catch (error) {
console.error('生成激活码错误:', error);
res.status(500).json({ success: false, message: '生成激活码失败' });
}
});
/**
* 批量生成激活码(管理员权限)
*/
router.post('/registration-codes/batch', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const adminId = req.user.id;
const { count = 1 } = req.body;
// 验证参数
const codeCount = Math.max(1, Math.min(100, parseInt(count) || 1));
const crypto = require('crypto');
const codes = [];
const values = [];
const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
// 生成指定数量的激活码
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for (let i = 0; i < codeCount; i++) {
let code = '';
for (let j = 0; j < 6; j++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
codes.push(code);
values.push(code, expiresAt, adminId);
}
// 批量插入数据库
const placeholders = Array(codeCount).fill('(?, ?, ?)').join(', ');
await db.execute(
`INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES ${placeholders}`,
values
);
res.status(201).json({
success: true,
message: `成功生成 ${codeCount} 个激活码`,
data: {
codes,
count: codeCount,
expiresAt,
}
});
} catch (error) {
console.error('批量生成激活码错误:', error);
res.status(500).json({ success: false, message: '批量生成激活码失败' });
}
});
/**
* 获取激活码列表(管理员权限)
*/
router.get('/registration-codes', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const { page = 1, limit = 20, status, keyword, sort = 'created_at', order = 'desc' } = req.query;
const offset = (page - 1) * limit;
let whereClause = '';
let whereConditions = [];
let countParams = [];
let listParams = [];
// 根据状态筛选
if (status === 'unused') {
whereConditions.push('rc.is_used = FALSE AND rc.expires_at > NOW()');
} else if (status === 'used') {
whereConditions.push('rc.is_used = TRUE');
} else if (status === 'expired') {
whereConditions.push('rc.is_used = FALSE AND rc.expires_at <= NOW()');
}
// 关键词搜索
if(keyword){
whereConditions.push(`rc.code LIKE '%${keyword}%'`);
}
// 构建WHERE子句
if (whereConditions.length > 0) {
whereClause = 'WHERE ' + whereConditions.join(' AND ');
}
// 处理排序参数
const allowedSortFields = ['created_at', 'expires_at', 'used_at', 'code', 'status'];
const allowedOrders = ['asc', 'desc'];
let sortField = 'rc.created_at';
let sortOrder = 'DESC';
if (allowedSortFields.includes(sort)) {
if (sort === 'status') {
// 状态字段需要使用CASE表达式
sortField = `CASE
WHEN rc.is_used = TRUE THEN 'used'
WHEN rc.expires_at <= NOW() THEN 'expired'
ELSE 'unused'
END`;
} else {
sortField = `rc.${sort}`;
}
}
if (allowedOrders.includes(order.toLowerCase())) {
sortOrder = order.toUpperCase();
}
// 设置查询参数MySQL驱动需要字符串形式的LIMIT和OFFSET
const limitStr = String(parseInt(limit));
const offsetStr = String(parseInt(offset));
listParams = [limitStr, offsetStr];
countParams = [];
// 获取激活码列表
const [codes] = await db.execute(`
SELECT
rc.id,
rc.code,
rc.created_at,
rc.expires_at,
rc.used_at,
rc.is_used,
admin.username as created_by_admin,
user.username as used_by_user,
CASE
WHEN rc.is_used = TRUE THEN 'used'
WHEN rc.expires_at <= NOW() THEN 'expired'
ELSE 'unused'
END as status
FROM registration_codes rc
LEFT JOIN users admin ON rc.created_by_admin_id = admin.id
LEFT JOIN users user ON rc.used_by_user_id = user.id
${whereClause}
ORDER BY ${sortField} ${sortOrder}
LIMIT ? OFFSET ?
`, listParams);
// 获取总数
const [countResult] = await db.execute(`
SELECT COUNT(*) as total
FROM registration_codes rc
${whereClause}
`, countParams);
const total = countResult[0].total;
res.json({
success: true,
data: {
codes,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取激活码列表错误:', error);
res.status(500).json({ success: false, message: '获取激活码列表失败' });
}
});
/**
* 删除激活码(管理员权限)
*/
router.delete('/registration-codes/:id', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const codeId = req.params.id;
// 检查激活码是否存在
const [codes] = await db.execute(
'SELECT id, is_used FROM registration_codes WHERE id = ?',
[codeId]
);
if (codes.length === 0) {
return res.status(404).json({ success: false, message: '激活码不存在' });
}
// 不能删除已使用的激活码
if (codes[0].is_used) {
return res.status(400).json({ success: false, message: '不能删除已使用的激活码' });
}
// 删除激活码
await db.execute('DELETE FROM registration_codes WHERE id = ?', [codeId]);
res.json({ success: true, message: '激活码删除成功' });
} catch (error) {
console.error('删除激活码错误:', error);
res.status(500).json({ success: false, message: '删除激活码失败' });
}
});
// 获取当前用户个人资料
router.get('/profile', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.user.id;
const [users] = await db.execute(
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
// 转换字段名以匹配前端
const user = users[0];
const profile = {
...user,
nickname: user.username, // 添加nickname字段映射到username
realName: user.real_name,
idCard: user.id_card,
wechatQr: user.wechat_qr,
alipayQr: user.alipay_qr,
bankCard: user.bank_card,
unionpayQr: user.unionpay_qr,
businessLicense: user.business_license,
idCardFront: user.id_card_front,
idCardBack: user.id_card_back,
auditStatus: user.audit_status
};
res.json({ success: true, user: profile });
} catch (error) {
console.error('获取用户个人资料错误:', error);
res.status(500).json({ success: false, message: '获取用户个人资料失败' });
}
});
// 更新当前用户个人资料
router.put('/profile', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.user.id;
const {
username,
nickname,
avatar,
realName,
idCard,
phone,
wechatQr,
alipayQr,
bankCard,
unionpayQr,
businessLicense,
idCardFront,
idCardBack,
city,
districtId
} = req.body;
// 处理nickname字段如果提供了nickname则使用nickname作为username
const finalUsername = nickname || username;
// 检查用户名、身份证号和手机号是否已被其他用户使用
if (finalUsername || idCard || phone) {
const conditions = [];
const checkValues = [];
if (finalUsername) {
conditions.push('username = ?');
checkValues.push(finalUsername);
}
if (idCard) {
conditions.push('id_card = ?');
checkValues.push(idCard);
}
if (phone) {
conditions.push('phone = ?');
checkValues.push(phone);
}
if (conditions.length > 0) {
const [existingUsers] = await db.execute(
`SELECT id FROM users WHERE (${conditions.join(' OR ')}) AND id != ?`,
[...checkValues, userId]
);
if (existingUsers.length > 0) {
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已被使用' });
}
}
}
// 验证身份证号格式
if (idCard) {
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idCardRegex.test(idCard)) {
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
}
}
// 构建更新字段
const updateFields = [];
const updateValues = [];
if (finalUsername !== undefined) {
updateFields.push('username = ?');
updateValues.push(finalUsername);
}
if (avatar !== undefined) {
updateFields.push('avatar = ?');
updateValues.push(avatar);
}
if (realName !== undefined) {
updateFields.push('real_name = ?');
updateValues.push(realName);
}
if (idCard !== undefined) {
updateFields.push('id_card = ?');
updateValues.push(idCard);
}
if (phone !== undefined) {
updateFields.push('phone = ?');
updateValues.push(phone);
}
// 添加城市和地区字段更新
if (city !== undefined) {
updateFields.push('city = ?');
updateValues.push(city);
}
if (districtId !== undefined) {
updateFields.push('district_id = ?');
updateValues.push(districtId);
}
// 检查是否更新了需要重新审核的关键信息
let needsReaudit = false;
if (wechatQr !== undefined) {
updateFields.push('wechat_qr = ?');
updateValues.push(wechatQr);
needsReaudit = true;
}
if (alipayQr !== undefined) {
updateFields.push('alipay_qr = ?');
updateValues.push(alipayQr);
needsReaudit = true;
}
if (bankCard !== undefined) {
updateFields.push('bank_card = ?');
updateValues.push(bankCard);
needsReaudit = true;
}
if (unionpayQr !== undefined) {
updateFields.push('unionpay_qr = ?');
updateValues.push(unionpayQr);
needsReaudit = true;
}
if (city !== undefined) {
updateFields.push('city = ?');
updateValues.push(city);
}
if (districtId !== undefined) {
updateFields.push('district_id = ?');
updateValues.push(districtId);
}
if (businessLicense !== undefined) {
updateFields.push('business_license = ?');
updateValues.push(businessLicense);
needsReaudit = true;
}
if (idCardFront !== undefined) {
updateFields.push('id_card_front = ?');
updateValues.push(idCardFront);
needsReaudit = true;
}
if (idCardBack !== undefined) {
updateFields.push('id_card_back = ?');
updateValues.push(idCardBack);
needsReaudit = true;
}
// 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核
if (needsReaudit && req.user.role !== 'admin') {
updateFields.push('audit_status = ?');
updateValues.push('pending');
}
if (updateFields.length === 0) {
return res.status(400).json({ success: false, message: '没有要更新的字段' });
}
updateValues.push(userId);
await db.execute(
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
updateValues
);
// 返回更新后的用户信息
const [updatedUsers] = await db.execute(
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, is_system_account, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
// 转换字段名以匹配前端
const user = updatedUsers[0];
const profile = {
...user,
nickname: user.username, // 添加nickname字段映射到username
realName: user.real_name,
idCard: user.id_card,
wechatQr: user.wechat_qr,
alipayQr: user.alipay_qr,
bankCard: user.bank_card,
unionpayQr: user.unionpay_qr,
businessLicense: user.business_license,
idCardFront: user.id_card_front,
idCardBack: user.id_card_back,
auditStatus: user.audit_status
};
res.json({
success: true,
message: '个人资料更新成功',
data: profile
});
} catch (error) {
console.error('更新个人资料错误:', error);
res.status(500).json({ success: false, message: '更新个人资料失败' });
}
});
// 获取用户详情
router.get('/:id', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.params.id;
// 只有管理员或用户本人可以查看详情
if (req.user.role !== 'admin' && req.user.id != userId) {
return res.status(403).json({ success: false, message: '权限不足' });
}
const [users] = await db.execute(
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
res.json({ success: true, user: users[0] });
} catch (error) {
console.error('获取用户详情错误:', error);
res.status(500).json({ success: false, message: '获取用户详情失败' });
}
});
// 更新用户信息
router.put('/:id', auth, async (req, res) => {
try {
const db = getDB();
const userId = req.params.id;
const {
username,
password,
role,
isSystemAccount,
avatar,
realName,
idCard,
phone,
wechatQr,
alipayQr,
bankCard,
unionpayQr,
city,
districtId,
idCardFront,
idCardBack,
businessLicense,
} = req.body;
// 只有管理员或用户本人可以更新信息
if (req.user.role !== 'admin' && req.user.id != userId) {
return res.status(403).json({ success: false, message: '权限不足' });
}
// 非管理员不能修改角色
if (req.user.role !== 'admin' && role) {
return res.status(403).json({ success: false, message: '无权限修改用户角色' });
}
// 检查用户名、身份证号和手机号是否已被其他用户使用
if (username || idCard || phone) {
const conditions = [];
const checkValues = [];
if (username) {
conditions.push('username = ?');
checkValues.push(username);
}
if (idCard) {
conditions.push('id_card = ?');
checkValues.push(idCard);
}
if (phone) {
conditions.push('phone = ?');
checkValues.push(phone);
}
if (conditions.length > 0) {
const [existingUsers] = await db.execute(
`SELECT id FROM users WHERE (${conditions.join(' OR ')}) AND id != ?`,
[...checkValues, userId]
);
if (existingUsers.length > 0) {
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已被使用' });
}
}
}
// 验证身份证号格式
if (idCard) {
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idCardRegex.test(idCard)) {
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
}
}
// 构建更新字段
const updateFields = [];
const updateValues = [];
if (username) {
updateFields.push('username = ?');
updateValues.push(username);
}
// 处理密码更新
if (password && password.trim() !== '') {
const hashedPassword = await bcrypt.hash(password, 10);
updateFields.push('password = ?');
updateValues.push(hashedPassword);
}
if (role && req.user.role === 'admin') {
updateFields.push('role = ?');
updateValues.push(role);
}
// 只有管理员可以修改账户类型
if (isSystemAccount !== undefined && req.user.role === 'admin') {
updateFields.push('is_system_account = ?');
updateValues.push(isSystemAccount);
}
if (avatar !== undefined) {
updateFields.push('avatar = ?');
updateValues.push(avatar);
}
if (realName !== undefined) {
updateFields.push('real_name = ?');
updateValues.push(realName);
}
if (idCard !== undefined) {
updateFields.push('id_card = ?');
updateValues.push(idCard);
}
if (phone !== undefined) {
updateFields.push('phone = ?');
updateValues.push(phone);
}
if (city !== undefined) {
updateFields.push('city = ?');
updateValues.push(city);
}
if (districtId !== undefined) {
updateFields.push('district_id = ?');
updateValues.push(districtId);
}
// 检查是否更新了需要重新审核的关键信息
let needsReaudit = false;
if (wechatQr !== undefined) {
updateFields.push('wechat_qr = ?');
updateValues.push(wechatQr);
needsReaudit = true;
}
if (alipayQr !== undefined) {
updateFields.push('alipay_qr = ?');
updateValues.push(alipayQr);
needsReaudit = true;
}
if (bankCard !== undefined) {
updateFields.push('bank_card = ?');
updateValues.push(bankCard);
needsReaudit = true;
}
if (unionpayQr !== undefined) {
updateFields.push('unionpay_qr = ?');
updateValues.push(unionpayQr);
needsReaudit = true;
}
if (idCardFront !== undefined) {
updateFields.push('id_card_front = ?');
updateValues.push(idCardFront);
needsReaudit = true;
}
if (idCardBack !== undefined) {
updateFields.push('id_card_back = ?');
updateValues.push(idCardBack);
needsReaudit = true;
}
if (businessLicense !== undefined) {
updateFields.push('business_license = ?');
updateValues.push(businessLicense);
needsReaudit = true;
}
// 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核
if (needsReaudit && req.user.role !== 'admin') {
updateFields.push('audit_status = ?');
updateValues.push('pending');
}
if (updateFields.length === 0) {
return res.status(400).json({ success: false, message: '没有要更新的字段' });
}
updateValues.push(userId);
await db.execute(
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
updateValues
);
// 返回更新后的用户信息
const [updatedUsers] = await db.execute(
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, city, district_id, created_at, updated_at FROM users WHERE id = ?',
[userId]
);
res.json({
success: true,
message: '用户信息更新成功',
user: updatedUsers[0]
});
} catch (error) {
console.error('更新用户信息错误:', error);
res.status(500).json({ success: false, message: '更新用户信息失败' });
}
});
// 删除用户(管理员权限)
router.delete('/:id', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const userId = req.params.id;
// 不能删除自己
if (req.user.id == userId) {
return res.status(400).json({ success: false, message: '不能删除自己的账户' });
}
// 检查用户是否存在
const [users] = await db.execute(
'SELECT id FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
// 删除用户
await db.execute('DELETE FROM users WHERE id = ?', [userId]);
res.json({ success: true, message: '用户删除成功' });
} catch (error) {
console.error('删除用户错误:', error);
res.status(500).json({ success: false, message: '删除用户失败' });
}
});
/**
* 审核用户(管理员权限)
*/
router.put('/:id/audit', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const userId = req.params.id;
const { action, note } = req.body; // action: 'approve' 或 'reject'
if (!action || !['approve', 'reject'].includes(action)) {
return res.status(400).json({ success: false, message: '审核操作无效' });
}
// 检查用户是否存在且为待审核状态
const [users] = await db.execute(
'SELECT id, username, audit_status FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
const user = users[0];
if (user.audit_status !== 'pending') {
return res.status(400).json({ success: false, message: '该用户不是待审核状态' });
}
// 更新审核状态
const auditStatus = action === 'approve' ? 'approved' : 'rejected';
await db.execute(
`UPDATE users SET
audit_status = ?,
audit_note = ?,
audited_by = ?,
audited_at = NOW()
WHERE id = ?`,
[auditStatus, note || null, req.user.id, userId]
);
const message = action === 'approve' ? '用户审核通过' : '用户审核拒绝';
res.json({ success: true, message });
} catch (error) {
console.error('审核用户错误:', error);
res.status(500).json({ success: false, message: '审核用户失败' });
}
});
/**
* 获取用户审核详情(管理员权限)
*/
router.get('/:id/audit-detail', auth, adminAuth, async (req, res) => {
try {
const db = getDB();
const userId = req.params.id;
// 获取用户详细信息
const [users] = await db.execute(
`SELECT u.id, u.username, u.phone, u.real_name, u.business_license,
u.id_card_front, u.id_card_back, u.audit_status, u.audit_note,
u.audited_at, u.created_at,
auditor.username as auditor_name
FROM users u
LEFT JOIN users auditor ON u.audited_by = auditor.id
WHERE u.id = ?`,
[userId]
);
if (users.length === 0) {
return res.status(404).json({ success: false, message: '用户不存在' });
}
res.json({
success: true,
data: users[0]
});
} catch (error) {
console.error('获取用户审核详情错误:', error);
res.status(500).json({ success: false, message: '获取用户审核详情失败' });
}
});
module.exports = router;