Files
jurong_circle_black/routes/users.js

1726 lines
51 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
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();
2025-08-28 09:14:56 +08:00
/**
* @swagger
* tags:
* name: Users
* description: 用户管理API
*/
/**
* @swagger
* components:
* schemas:
* User:
* type: object
* required:
* - username
* - password
* - real_name
* - id_card
* properties:
* id:
* type: integer
* description: 用户ID
* username:
* type: string
* description: 用户名
* role:
* type: string
* description: 用户角色
* enum: [user, admin, merchant]
* avatar:
* type: string
* description: 用户头像URL
* points:
* type: integer
* description: 用户积分
* real_name:
* type: string
* description: 真实姓名
* id_card:
* type: string
* description: 身份证号
* phone:
* type: string
* description: 手机号
* is_system_account:
* type: boolean
* description: 是否为系统账户
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
*/
/**
* @swagger
* /users:
* post:
* summary: 创建用户管理员权限
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* - real_name
* - id_card
* properties:
* username:
* type: string
* password:
* type: string
* role:
* type: string
* enum: [user, admin, merchant]
* default: user
* is_system_account:
* type: boolean
* default: false
* real_name:
* type: string
* id_card:
* type: string
* wechat_qr:
* type: string
* alipay_qr:
* type: string
* bank_card:
* type: string
* unionpay_qr:
* type: string
* phone:
* type: string
* responses:
* 201:
* description: 用户创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* user:
* $ref: '#/components/schemas/User'
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器错误
*/
2025-08-26 10:06:23 +08:00
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: '创建用户失败' });
}
});
/**
2025-08-28 09:14:56 +08:00
* @swagger
* /users/pending-audit:
* get:
* summary: 获取待审核用户列表管理员权限
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: 每页数量
* responses:
* 200:
* description: 成功获取待审核用户列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* users:
* type: array
* items:
* $ref: '#/components/schemas/User'
* pagination:
* type: object
* properties:
* page:
* type: integer
* limit:
* type: integer
* total:
* type: integer
* pages:
* type: integer
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器错误
2025-08-26 10:06:23 +08:00
*/
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;