2025-08-26 10:06:23 +08:00
const express = require ( 'express' ) ;
const router = express . Router ( ) ;
const { getDB } = require ( '../database' ) ;
const { auth , adminAuth } = require ( '../middleware/auth' ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ 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
* / a p i / p o i n t s / b a l a n c e :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 : '获取积分余额失败' } ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a p i / p o i n t s / h i s t o r y :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 : '获取积分历史失败' } ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a p i / p o i n t s / a d j u s t :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 ( ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a p i / p o i n t s / r e c h a r g e :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 ( ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a p i / p o i n t s / l e a d e r b o a r d :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 : '获取积分排行榜失败' } ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a p i / p o i n t s / s t a t s :
* 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 : 服务器错误
* /
2025-08-26 10:06:23 +08:00
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 ;