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(); /** * @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: 是否为系统账户 * is_distribute: * 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: 服务器错误 */ 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: '创建用户失败' }); } }); /** * @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: 服务器错误 */ 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,u.is_distribute, 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 ${limitNum} OFFSET ${offset}`, listParams.slice(0, -2) ); 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 ${limitNum} OFFSET ${offset}`, queryParams ); 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 ${limit} OFFSET ${offset} `, countParams); // 获取总数 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: '获取用户审核详情失败' }); } }); /** * @swagger * /api/users/{id}/distribute: * put: * summary: 设置用户分发状态 * description: 更新指定用户的分发状态 * tags: [Users] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 用户ID * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - is_distribute * properties: * is_distribute: * type: boolean * description: 分发状态,true为启用分发,false为禁用分发 * example: true * responses: * 200: * description: 分发状态更新成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * message: * type: string * example: "分发状态更新成功" * is_distribute: * type: boolean * description: 更新后的分发状态 * example: true * 400: * description: 请求参数错误 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: false * message: * type: string * example: "分发状态无效" * 404: * description: 用户不存在 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: false * message: * type: string * example: "用户不存在" * 500: * description: 服务器内部错误 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: false * message: * type: string * example: "服务器内部错误" */ router.put('/:id/distribute', auth, async (req, res) => { try { const db = getDB(); const userId = req.params.id; const { is_distribute } = req.body; if (typeof is_distribute !== 'boolean') { 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( 'UPDATE users SET is_distribute = ? WHERE id = ?', [is_distribute, userId] ); res.json({ success: true, message: '分发状态更新成功', is_distribute }); } catch (error) { } }) module.exports = router;