2025-08-26 10:06:23 +08:00
const express = require ( 'express' ) ;
const bcrypt = require ( 'bcryptjs' ) ;
const jwt = require ( 'jsonwebtoken' ) ;
const { getDB } = require ( '../database' ) ;
const router = express . Router ( ) ;
const JWT _SECRET = process . env . JWT _SECRET || 'your-secret-key' ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* tags :
* name : Authentication
* description : 用户认证API
* /
/ * *
* @ swagger
* components :
* schemas :
* LoginCredentials :
* type : object
* required :
* - username
* - password
* properties :
* username :
* type : string
* description : 用户名或手机号
* password :
* type : string
* description : 密码
* RegisterRequest :
* type : object
* required :
* - username
* - phone
* - password
* - registrationCode
* - city
* - district _id
* - captchaId
* - captchaText
* - smsCode
* properties :
* username :
* type : string
* description : 用户名
* phone :
* type : string
* description : 手机号
* password :
* type : string
* description : 密码
* registrationCode :
* type : string
* description : 注册激活码
* city :
* type : string
* description : 城市
* district _id :
* type : string
* description : 区域ID
* captchaId :
* type : string
* description : 图形验证码ID
* captchaText :
* type : string
* description : 图形验证码文本
* smsCode :
* type : string
* description : 短信验证码
* role :
* type : string
* description : 用户角色
* default : user
* /
2025-08-26 10:06:23 +08:00
/ * *
2025-08-28 09:14:56 +08:00
* @ swagger
* / a u t h / r e g i s t e r :
* post :
* summary : 用户注册
* description : 需要提供有效的激活码才能注册
* tags : [ Authentication ]
* requestBody :
* required : true
* content :
* application / json :
* schema :
* $ref : '#/components/schemas/RegisterRequest'
* responses :
* 201 :
* description : 用户注册成功
* content :
* application / json :
* schema :
* type : object
* properties :
* success :
* type : boolean
* message :
* type : string
* token :
* type : string
* description : JWT认证令牌
* user :
* type : object
* properties :
* id :
* type : integer
* username :
* type : string
* role :
* type : string
* 400 :
* description : 请求参数错误
* 500 :
* description : 服务器错误
2025-08-26 10:06:23 +08:00
* /
router . post ( '/register' , async ( req , res ) => {
try {
const db = getDB ( ) ;
await db . query ( 'START TRANSACTION' ) ;
const {
username ,
phone ,
password ,
registrationCode ,
city ,
district _id : district ,
captchaId ,
captchaText ,
smsCode , // 短信验证码
role = 'user'
} = req . body ;
if ( ! username || ! phone || ! password || ! registrationCode || ! city || ! district ) {
return res . status ( 400 ) . json ( { success : false , message : '用户名、手机号、密码、激活码、城市和区域不能为空' } ) ;
}
if ( ! captchaId || ! captchaText ) {
return res . status ( 400 ) . json ( { success : false , message : '图形验证码不能为空' } ) ;
}
if ( ! smsCode ) {
return res . status ( 400 ) . json ( { success : false , message : '短信验证码不能为空' } ) ;
}
// 注意:图形验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证
// 验证短信验证码
const smsAPI = require ( './sms' ) ;
const smsValid = smsAPI . verifySMSCode ( phone , smsCode ) ;
if ( ! smsValid ) {
return res . status ( 400 ) . json ( { success : false , message : '短信验证码错误或已过期' } ) ;
}
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/ ;
if ( ! phoneRegex . test ( phone ) ) {
return res . status ( 400 ) . json ( { success : false , message : '手机号格式不正确' } ) ;
}
// 验证激活码
const [ registrationCodes ] = await db . execute (
'SELECT id, is_used, expires_at, agent_id FROM registration_codes WHERE code = ?' ,
[ registrationCode ]
) ;
if ( registrationCodes . length === 0 ) {
return res . status ( 400 ) . json ( { success : false , message : '激活码不存在' } ) ;
}
const regCode = registrationCodes [ 0 ] ;
// 检查激活码是否已使用
if ( regCode . is _used ) {
return res . status ( 400 ) . json ( { success : false , message : '激活码已被使用' } ) ;
}
// 检查激活码是否过期
if ( new Date ( ) > new Date ( regCode . expires _at ) ) {
return res . status ( 400 ) . json ( { success : false , message : '激活码已过期' } ) ;
}
// 检查用户是否已存在
const [ existingUsers ] = await db . execute (
'SELECT id FROM users WHERE username = ? OR phone = ?' ,
[ username , phone ]
) ;
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, phone, password, role, points, audit_status, city, district_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' ,
[ username , phone , hashedPassword , role , 0 , 'pending' , city , district ]
) ;
const userId = result . insertId ;
// 用户余额已在创建用户时设置为默认值0.00,无需额外操作
// 标记激活码为已使用
await db . execute (
'UPDATE registration_codes SET is_used = TRUE, used_at = NOW(), used_by_user_id = ? WHERE id = ?' ,
[ userId , regCode . id ]
) ;
// 如果是代理邀请码,建立代理关系
if ( regCode . agent _id ) {
// 验证agent_id是否存在于regional_agents表中
const [ agentExists ] = await db . execute (
'SELECT id FROM regional_agents WHERE id = ?' ,
[ regCode . agent _id ]
) ;
if ( agentExists . length > 0 ) {
await db . execute (
'INSERT INTO agent_merchants (agent_id, merchant_id, created_at) VALUES (?, ?, NOW())' ,
[ regCode . agent _id , userId ]
) ;
}
} else {
// 如果不是代理邀请码,根据地区自动关联代理
const [ agents ] = await db . execute (
'SELECT ra.id FROM users u INNER JOIN regional_agents ra ON u.id = ra.user_id WHERE ra.region_id = ? AND ra.status = "active" ORDER BY ra.created_at ASC LIMIT 1' ,
[ district ]
) ;
if ( agents . length > 0 ) {
await db . execute (
'INSERT INTO agent_merchants (agent_id, merchant_id, created_at) VALUES (?, ?, NOW())' ,
[ agents [ 0 ] . id , userId ]
) ;
}
}
await db . query ( 'COMMIT' ) ;
// 生成JWT token
const token = jwt . sign (
{ userId : userId , username , role } ,
JWT _SECRET ,
{ expiresIn : '24h' }
) ;
res . status ( 201 ) . json ( {
success : true ,
message : '注册成功' ,
token ,
user : {
id : userId ,
username ,
phone ,
role ,
points : 0 ,
audit _status : 'pending' ,
city ,
district
}
} ) ;
} catch ( error ) {
try {
await getDB ( ) . query ( 'ROLLBACK' ) ;
} catch ( rollbackError ) {
console . error ( '回滚错误:' , rollbackError ) ;
}
console . error ( '注册错误详情:' , error ) ;
console . error ( '错误堆栈:' , error . stack ) ;
res . status ( 500 ) . json ( {
success : false ,
message : '注册失败' ,
error : process . env . NODE _ENV === 'development' ? error . message : undefined
} ) ;
}
} ) ;
2025-08-28 09:14:56 +08:00
/ * *
* @ swagger
* / a u t h / l o g i n :
* post :
* summary : 用户登录
* tags : [ Authentication ]
* requestBody :
* required : true
* content :
* application / json :
* schema :
* $ref : '#/components/schemas/LoginCredentials'
* responses :
* 200 :
* description : 登录成功
* content :
* application / json :
* schema :
* type : object
* properties :
* success :
* type : boolean
* message :
* type : string
* token :
* type : string
* description : JWT认证令牌
* user :
* type : object
* properties :
* id :
* type : integer
* username :
* type : string
* role :
* type : string
* avatar :
* type : string
* points :
* type : integer
* 400 :
* description : 请求参数错误
* 401 :
* description : 用户名或密码错误
* 403 :
* description : 账户审核未通过
* 500 :
* description : 服务器错误
* /
2025-08-26 10:06:23 +08:00
router . post ( '/login' , async ( req , res ) => {
try {
const db = getDB ( ) ;
const { username , password , captchaId , captchaText } = req . body ;
if ( ! username || ! password ) {
return res . status ( 400 ) . json ( { success : false , message : '用户名和密码不能为空' } ) ;
}
2025-08-28 09:14:56 +08:00
if ( ! captchaId || ! captchaText ) {
return res . status ( 400 ) . json ( { success : false , message : '验证码不能为空' } ) ;
}
// 获取存储的验证码
const storedCaptcha = global . captchaStore . get ( captchaId ) ;
console . log ( storedCaptcha ) ;
if ( ! storedCaptcha ) {
return res . status ( 400 ) . json ( {
success : false ,
message : '验证码不存在或已过期'
} ) ;
}
// 检查是否过期
if ( Date . now ( ) > storedCaptcha . expires ) {
global . captchaStore . delete ( captchaId ) ;
return res . status ( 400 ) . json ( {
success : false ,
message : '验证码已过期'
} ) ;
}
// 验证验证码(不区分大小写)
const isValid = storedCaptcha . text === captchaText . toLowerCase ( ) ;
// 删除已验证的验证码
global . captchaStore . delete ( captchaId ) ;
if ( ! isValid ) {
return res . status ( 400 ) . json ( {
success : false ,
message : '验证码错误'
} ) ;
}
2025-08-26 10:06:23 +08:00
// 注意:验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证
// 查找用户
console . log ( '登录尝试 - 用户名:' , username ) ;
const [ users ] = await db . execute (
'SELECT * FROM users WHERE username = ?' ,
[ username ]
) ;
console . log ( '查找到的用户数量:' , users . length ) ;
if ( users . length === 0 ) {
console . log ( '用户不存在:' , username ) ;
return res . status ( 401 ) . json ( { success : false , message : '用户名或密码错误' } ) ;
}
const user = users [ 0 ] ;
console . log ( '找到用户:' , user . username , '密码长度:' , user . password ? user . password . length : 'null' ) ;
// 验证密码
console . log ( '验证密码 - 输入密码:' , password , '数据库密码前10位:' , user . password ? user . password . substring ( 0 , 10 ) : 'null' ) ;
const isValidPassword = await bcrypt . compare ( password , user . password ) ;
console . log ( '密码验证结果:' , isValidPassword ) ;
if ( ! isValidPassword ) {
console . log ( '密码验证失败' ) ;
return res . status ( 401 ) . json ( { success : false , message : '用户名或密码错误' } ) ;
}
// 检查用户审核状态(管理员除外,只阻止被拒绝的用户)
if ( user . role !== 'admin' && user . audit _status === 'rejected' ) {
return res . status ( 403 ) . json ( { success : false , message : '您的账户审核未通过,请联系管理员' } ) ;
}
// 待审核用户可以正常登录使用系统,但匹配功能会有限制
// 生成JWT token
const token = jwt . sign (
{ userId : user . id , username : user . username , role : user . role } ,
JWT _SECRET ,
{ expiresIn : '24h' }
) ;
res . json ( {
success : true ,
message : '登录成功' ,
token ,
user : {
id : user . id ,
username : user . username ,
role : user . role ,
avatar : user . avatar ,
points : user . points
}
} ) ;
} catch ( error ) {
console . error ( '登录错误:' , error ) ;
res . status ( 500 ) . json ( { success : false , message : '登录失败' } ) ;
}
} ) ;
// 验证token中间件
const authenticateToken = ( req , res , next ) => {
const authHeader = req . headers [ 'authorization' ] ;
const token = authHeader && authHeader . split ( ' ' ) [ 1 ] ;
if ( ! token ) {
return res . status ( 401 ) . json ( { success : false , message : '访问令牌缺失' } ) ;
}
jwt . verify ( token , JWT _SECRET , ( err , user ) => {
if ( err ) {
return res . status ( 403 ) . json ( { success : false , message : '访问令牌无效' } ) ;
}
req . user = user ;
next ( ) ;
} ) ;
} ;
// 获取当前用户信息
router . get ( '/me' , authenticateToken , async ( req , res ) => {
try {
const db = getDB ( ) ;
const [ users ] = await db . execute (
'SELECT id, username, role, avatar, points, created_at FROM users WHERE id = ?' ,
[ req . user . 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 ( '/change-password' , authenticateToken , async ( req , res ) => {
try {
const db = getDB ( ) ;
const { currentPassword , newPassword } = req . body ;
if ( ! currentPassword || ! newPassword ) {
return res . status ( 400 ) . json ( { success : false , message : '旧密码和新密码不能为空' } ) ;
}
// 获取用户当前密码
const [ users ] = await db . execute (
'SELECT password FROM users WHERE id = ?' ,
[ req . user . userId ]
) ;
if ( users . length === 0 ) {
return res . status ( 404 ) . json ( { success : false , message : '用户不存在' } ) ;
}
// 验证旧密码
const isValidPassword = await bcrypt . compare ( currentPassword , users [ 0 ] . password ) ;
if ( ! isValidPassword ) {
return res . status ( 400 ) . json ( { success : false , message : '旧密码错误' } ) ;
}
// 加密新密码
const hashedNewPassword = await bcrypt . hash ( newPassword , 10 ) ;
// 更新密码
await db . execute (
'UPDATE users SET password = ? WHERE id = ?' ,
[ hashedNewPassword , req . user . userId ]
) ;
res . json ( { success : true , message : '密码修改成功' } ) ;
} catch ( error ) {
console . error ( '修改密码错误:' , error ) ;
res . status ( 500 ) . json ( { success : false , message : '修改密码失败' } ) ;
}
} ) ;
module . exports = router ;
module . exports . authenticateToken = authenticateToken ;