const express = require('express'); const router = express.Router(); const { getDB } = require('../database'); const { auth, adminAuth } = require('../middleware/auth'); /** * @swagger * tags: * name: Points * description: 积分管理相关接口 */ /** * @swagger * components: * schemas: * PointsHistory: * type: object * properties: * id: * type: integer * description: 积分历史记录ID * points_change: * type: integer * description: 积分变动数量 * type: * type: string * description: 积分变动类型(earn-获得, spend-消费, admin_adjust-管理员调整) * description: * type: string * description: 积分变动描述 * created_at: * type: string * format: date-time * description: 创建时间 */ /** * @swagger * /api/points/balance: * get: * summary: 获取用户当前积分余额 * tags: [Points] * security: * - bearerAuth: [] * responses: * 200: * description: 成功获取积分余额 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: object * properties: * points: * type: integer * description: 用户当前积分 * 401: * description: 未授权,需要登录 * 404: * description: 用户不存在 * 500: * description: 服务器错误 */ router.get('/balance', 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, data: { points: users[0].points } }); } catch (error) { console.error('获取积分余额失败:', error); res.status(500).json({ success: false, message: '获取积分余额失败' }); } }); /** * @swagger * /api/points/history: * get: * summary: 获取用户积分历史记录 * tags: [Points] * security: * - bearerAuth: [] * parameters: * - in: query * name: page * schema: * type: integer * default: 1 * description: 页码 * - in: query * name: limit * schema: * type: integer * default: 10 * description: 每页记录数 * - in: query * name: type * schema: * type: string * enum: [earn, spend, admin_adjust] * description: 积分变动类型 * - in: query * name: username * schema: * type: string * description: 用户名(仅管理员可用) * - in: query * name: change * schema: * type: string * enum: [positive, negative] * description: 积分变动方向(仅管理员可用) * - in: query * name: startDate * schema: * type: string * format: date * description: 开始日期(仅管理员可用) * - in: query * name: endDate * schema: * type: string * format: date * description: 结束日期(仅管理员可用) * responses: * 200: * description: 成功获取积分历史 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: object * properties: * records: * type: array * items: * $ref: '#/components/schemas/PointsHistory' * pagination: * type: object * properties: * page: * type: integer * limit: * type: integer * total: * type: integer * totalPages: * type: integer * 401: * description: 未授权,需要登录 * 500: * description: 服务器错误 */ router.get('/history', auth, async (req, res) => { try { const { page = 1, limit = 10, type, username, change, startDate, endDate } = req.query; // 确保参数为有效数字 const pageNum = parseInt(page) || 1; const limitNum = parseInt(limit) || 10; const offset = (pageNum - 1) * limitNum; let whereClause = ''; let queryParams = []; // 如果是管理员,可以查看所有用户的积分历史 if (req.user.role === 'admin') { whereClause = 'WHERE 1=1'; // 按用户名筛选 if (username) { whereClause += ' AND u.username LIKE ?'; queryParams.push(`%${username}%`); } // 按类型筛选 if (type) { whereClause += ' AND ph.type = ?'; queryParams.push(type); } // 按积分变化筛选 if (change === 'positive') { whereClause += ' AND ph.amount > 0'; } else if (change === 'negative') { whereClause += ' AND ph.amount < 0'; } // 按时间范围筛选 if (startDate) { whereClause += ' AND DATE(ph.created_at) >= ?'; queryParams.push(startDate); } if (endDate) { whereClause += ' AND DATE(ph.created_at) <= ?'; queryParams.push(endDate); } } else { // 普通用户只能查看自己的积分历史 whereClause = 'WHERE ph.user_id = ?'; queryParams.push(req.user.id); if (type && ['earn', 'spend'].includes(type)) { whereClause += ' AND ph.type = ?'; queryParams.push(type); } } // 获取总数 const countQuery = req.user.role === 'admin' ? `SELECT COUNT(*) as total FROM points_history ph JOIN users u ON ph.user_id = u.id ${whereClause}` : `SELECT COUNT(*) as total FROM points_history ph ${whereClause}`; const [countResult] = await getDB().execute(countQuery, queryParams); // 获取历史记录 const historyQuery = req.user.role === 'admin' ? `SELECT ph.id, ph.amount as points, ph.type, ph.description, ph.created_at, u.username, (SELECT points FROM users WHERE id = ph.user_id) as balance_after FROM points_history ph JOIN users u ON ph.user_id = u.id ${whereClause} ORDER BY ph.created_at DESC LIMIT ${limitNum} OFFSET ${offset}` : `SELECT id, amount as points_change, type, description, created_at FROM points_history ph ${whereClause} ORDER BY created_at DESC LIMIT ${limitNum} OFFSET ${offset}`; const [records] = await getDB().execute(historyQuery, queryParams); const responseData = req.user.role === 'admin' ? { history: records, total: countResult[0].total } : { records, pagination: { page: pageNum, limit: limitNum, total: countResult[0].total, totalPages: Math.ceil(countResult[0].total / limitNum) } }; res.json({ success: true, data: responseData }); } catch (error) { console.error('获取积分历史失败:', error); res.status(500).json({ success: false, message: '获取积分历史失败' }); } }); /** * @swagger * /api/points/adjust: * post: * summary: 管理员调整用户积分 * tags: [Points] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - userId * - points * - reason * properties: * userId: * type: integer * description: 用户ID * points: * type: integer * description: 调整的积分数量(正数为增加,负数为减少) * reason: * type: string * description: 调整原因 * responses: * 200: * description: 积分调整成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * message: * type: string * example: 积分调整成功 * data: * type: object * properties: * userId: * type: integer * pointsChanged: * type: integer * newBalance: * type: integer * 400: * description: 参数错误或积分不足 * 401: * description: 未授权,需要管理员权限 * 404: * description: 用户不存在 * 500: * description: 服务器错误 */ router.post('/adjust', auth, adminAuth, async (req, res) => { const connection = await getDB().getConnection(); try { await connection.beginTransaction(); const { userId, points, reason } = req.body; if (!userId || points === undefined || points === null || !reason) { await connection.rollback(); return res.status(400).json({ success: false, message: '请提供有效的用户ID、积分数量和调整原因' }); } // 检查用户是否存在 const [users] = await connection.execute( 'SELECT id, username, points FROM users WHERE id = ?', [userId] ); if (users.length === 0) { await connection.rollback(); return res.status(404).json({ success: false, message: '用户不存在' }); } const currentPoints = users[0].points; const newPoints = currentPoints + points; // 检查积分是否会变为负数 if (newPoints < 0) { await connection.rollback(); return res.status(400).json({ success: false, message: '用户积分不足,无法扣除' }); } // 更新用户积分 await connection.execute( 'UPDATE users SET points = ? WHERE id = ?', [newPoints, userId] ); // 记录积分历史 await connection.execute( `INSERT INTO points_history (user_id, amount, type, description, created_at) VALUES (?, ?, 'admin_adjust', ?, NOW())`, [userId, points, reason] ); await connection.commit(); res.json({ success: true, message: '积分调整成功', data: { userId: userId, pointsChanged: points, newBalance: newPoints } }); } catch (error) { await connection.rollback(); console.error('积分调整失败:', error); res.status(500).json({ success: false, message: '积分调整失败' }); } finally { connection.release(); } }); /** * @swagger * /api/points/recharge: * post: * summary: 管理员给用户充值积分 * tags: [Points] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - user_id * - points * properties: * user_id: * type: integer * description: 用户ID * points: * type: integer * description: 充值的积分数量(必须为正数) * description: * type: string * description: 充值描述 * default: 管理员充值 * responses: * 200: * description: 积分充值成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * message: * type: string * example: 积分充值成功 * data: * type: object * properties: * userId: * type: integer * pointsAdded: * type: integer * 400: * description: 参数错误 * 401: * description: 未授权,需要管理员权限 * 404: * description: 用户不存在 * 500: * description: 服务器错误 */ router.post('/recharge', auth, adminAuth, async (req, res) => { const connection = await getDB().getConnection(); try { await connection.beginTransaction(); const { user_id, points, description = '管理员充值' } = req.body; if (!user_id || !points || points <= 0) { await connection.rollback(); return res.status(400).json({ success: false, message: '请提供有效的用户ID和积分数量' }); } // 检查用户是否存在 const [users] = await connection.execute( 'SELECT id, username FROM users WHERE id = ?', [user_id] ); if (users.length === 0) { await connection.rollback(); return res.status(404).json({ success: false, message: '用户不存在' }); } // 增加用户积分 await connection.execute( 'UPDATE users SET points = points + ? WHERE id = ?', [points, user_id] ); // 记录积分历史 await connection.execute( `INSERT INTO points_history (user_id, amount, type, description, created_at) VALUES (?, ?, 'earn', ?, NOW())`, [user_id, points, description] ); await connection.commit(); res.json({ success: true, message: '积分充值成功', data: { userId: user_id, pointsAdded: points } }); } catch (error) { await connection.rollback(); console.error('积分充值失败:', error); res.status(500).json({ success: false, message: '积分充值失败' }); } finally { connection.release(); } }); /** * @swagger * /api/points/leaderboard: * get: * summary: 获取积分排行榜 * tags: [Points] * security: * - bearerAuth: [] * parameters: * - in: query * name: limit * schema: * type: integer * default: 10 * description: 返回的排行榜数量 * responses: * 200: * description: 成功获取积分排行榜 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: object * properties: * leaderboard: * type: array * items: * type: object * properties: * rank: * type: integer * userId: * type: integer * username: * type: string * points: * type: integer * 401: * description: 未授权,需要登录 * 500: * description: 服务器错误 */ router.get('/leaderboard', auth, async (req, res) => { try { const { limit = 10 } = req.query; const [users] = await getDB().execute( `SELECT id, username, points FROM users WHERE points > 0 ORDER BY points DESC LIMIT ?`, [parseInt(limit)] ); res.json({ success: true, data: { leaderboard: users.map((user, index) => ({ rank: index + 1, userId: user.id, username: user.username, points: user.points })) } }); } catch (error) { console.error('获取积分排行榜失败:', error); res.status(500).json({ success: false, message: '获取积分排行榜失败' }); } }); /** * @swagger * /api/points/stats: * get: * summary: 获取积分统计信息(管理员权限) * tags: [Points] * security: * - bearerAuth: [] * responses: * 200: * description: 成功获取积分统计信息 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: object * properties: * stats: * type: object * properties: * totalPoints: * type: integer * description: 系统中总积分数量 * totalEarned: * type: integer * description: 总积分发放量 * totalSpent: * type: integer * description: 总积分消费量 * activeUsers: * type: integer * description: 活跃用户数 * 401: * description: 未授权,需要管理员权限 * 500: * description: 服务器错误 */ router.get('/stats', auth, adminAuth, async (req, res) => { try { // 总积分发放量 const [totalEarned] = await getDB().execute( 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn"' ); // 总积分消费量 const [totalConsumed] = await getDB().execute( 'SELECT SUM(ABS(amount)) as total FROM points_history WHERE type = "spend"' ); // 本月积分发放 const [monthEarned] = await getDB().execute( 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn" AND YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())' ); // 上月积分发放(用于计算增长率) const [lastMonthEarned] = await getDB().execute( 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn" AND YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))' ); // 计算月增长率 const lastMonthTotal = lastMonthEarned[0].total || 0; const currentMonthTotal = monthEarned[0].total || 0; let monthGrowthRate = 0; if (lastMonthTotal > 0) { monthGrowthRate = ((currentMonthTotal - lastMonthTotal) / lastMonthTotal * 100).toFixed(1); } // 活跃用户数(有积分记录的用户) const [activeUsers] = await getDB().execute( 'SELECT COUNT(DISTINCT user_id) as count FROM points_history' ); res.json({ success: true, data: { stats: { totalPoints: totalEarned[0].total || 0, totalEarned: totalEarned[0].total || 0, totalSpent: totalConsumed[0].total || 0, activeUsers: activeUsers[0].count } } }); } catch (error) { console.error('获取积分统计失败:', error); res.status(500).json({ success: false, message: '获取积分统计失败' }); } }); module.exports = router;