2025-09-05 01:18:40 +08:00
|
|
|
|
const express = require('express')
|
|
|
|
|
|
const bcrypt = require('bcryptjs')
|
|
|
|
|
|
const jwt = require('jsonwebtoken')
|
|
|
|
|
|
const Joi = require('joi')
|
|
|
|
|
|
const router = express.Router()
|
2025-09-02 21:59:27 +08:00
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
// 引入数据库模型
|
2025-09-19 00:42:14 +08:00
|
|
|
|
const { Admin } = require('../models')
|
2025-09-05 01:18:40 +08:00
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
// 引入认证中间件
|
|
|
|
|
|
const { authenticateJWT } = require('../middleware/auth')
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* components:
|
|
|
|
|
|
* schemas:
|
|
|
|
|
|
* LoginRequest:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - username
|
|
|
|
|
|
* - password
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 用户名
|
|
|
|
|
|
* password:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 密码
|
|
|
|
|
|
* LoginResponse:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* token:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* user:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* email:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* user_type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* status:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
// 从环境变量或配置中获取JWT密钥
|
|
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_key'
|
|
|
|
|
|
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h'
|
|
|
|
|
|
|
|
|
|
|
|
// 验证模式
|
2025-09-02 21:59:27 +08:00
|
|
|
|
const loginSchema = Joi.object({
|
2025-09-12 13:15:03 +08:00
|
|
|
|
username: Joi.string().required(),
|
|
|
|
|
|
password: Joi.string().required()
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
// 生成JWT令牌
|
2025-09-05 01:18:40 +08:00
|
|
|
|
const generateToken = (user) => {
|
|
|
|
|
|
return jwt.sign(
|
|
|
|
|
|
{
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
2025-09-12 13:15:03 +08:00
|
|
|
|
email: user.email,
|
|
|
|
|
|
user_type: user.user_type
|
2025-09-05 01:18:40 +08:00
|
|
|
|
},
|
2025-09-12 13:15:03 +08:00
|
|
|
|
JWT_SECRET,
|
|
|
|
|
|
{
|
|
|
|
|
|
expiresIn: JWT_EXPIRES_IN
|
|
|
|
|
|
}
|
2025-09-05 01:18:40 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-02 21:59:27 +08:00
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/auth/login:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 用户登录
|
|
|
|
|
|
* tags: [认证管理]
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* $ref: '#/components/schemas/LoginRequest'
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 登录成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* $ref: '#/components/schemas/LoginResponse'
|
|
|
|
|
|
* 400:
|
|
|
|
|
|
* description: 参数验证失败或用户名密码错误
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权或用户被禁用
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器内部错误
|
|
|
|
|
|
*/
|
2025-09-02 21:59:27 +08:00
|
|
|
|
// 用户登录
|
|
|
|
|
|
router.post('/login', async (req, res) => {
|
|
|
|
|
|
try {
|
2025-09-05 01:18:40 +08:00
|
|
|
|
// 参数验证
|
|
|
|
|
|
const { error, value } = loginSchema.validate(req.body)
|
2025-09-02 21:59:27 +08:00
|
|
|
|
if (error) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '参数验证失败',
|
2025-09-05 01:18:40 +08:00
|
|
|
|
details: error.details[0].message
|
|
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
const { username, password } = value
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
// 查找用户
|
2025-09-19 00:42:14 +08:00
|
|
|
|
const user = await Admin.findOne({
|
2025-09-05 01:18:40 +08:00
|
|
|
|
where: {
|
2025-09-19 00:42:14 +08:00
|
|
|
|
[Admin.sequelize.Op.or]: [
|
2025-09-12 13:15:03 +08:00
|
|
|
|
{ username },
|
2025-09-05 01:18:40 +08:00
|
|
|
|
{ email: username }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
})
|
2025-09-05 01:18:40 +08:00
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
// 检查用户是否存在以及密码是否正确
|
|
|
|
|
|
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
|
2025-09-02 21:59:27 +08:00
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
success: false,
|
2025-09-05 01:18:40 +08:00
|
|
|
|
message: '用户名或密码错误'
|
|
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
// 检查用户状态
|
2025-09-02 21:59:27 +08:00
|
|
|
|
if (user.status !== 'active') {
|
2025-09-12 13:15:03 +08:00
|
|
|
|
return res.status(401).json({
|
2025-09-02 21:59:27 +08:00
|
|
|
|
success: false,
|
2025-09-12 13:15:03 +08:00
|
|
|
|
message: '用户账号已被禁用'
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 生成JWT令牌
|
2025-09-05 01:18:40 +08:00
|
|
|
|
const token = generateToken(user)
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 准备返回的用户信息(不包含敏感数据)
|
|
|
|
|
|
const userInfo = {
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
email: user.email,
|
|
|
|
|
|
user_type: user.user_type,
|
|
|
|
|
|
status: user.status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 21:59:27 +08:00
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '登录成功',
|
2025-09-12 13:15:03 +08:00
|
|
|
|
token,
|
|
|
|
|
|
user: userInfo
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
} catch (error) {
|
2025-09-12 13:15:03 +08:00
|
|
|
|
console.error('用户登录失败:', error)
|
2025-09-02 21:59:27 +08:00
|
|
|
|
res.status(500).json({
|
|
|
|
|
|
success: false,
|
2025-09-12 13:15:03 +08:00
|
|
|
|
message: '登录失败,请稍后再试'
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/auth/me:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取当前用户信息
|
|
|
|
|
|
* tags: [认证管理]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 获取成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* email:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* user_type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* status:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* createdAt:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* format: date-time
|
|
|
|
|
|
* updatedAt:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* format: date-time
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器内部错误
|
|
|
|
|
|
*/
|
2025-09-05 01:18:40 +08:00
|
|
|
|
// 获取当前用户信息
|
2025-09-12 13:15:03 +08:00
|
|
|
|
router.get('/me', authenticateJWT, async (req, res) => {
|
2025-09-04 09:04:58 +08:00
|
|
|
|
try {
|
2025-09-12 13:15:03 +08:00
|
|
|
|
const userId = req.user.id
|
|
|
|
|
|
|
|
|
|
|
|
// 根据ID查找用户
|
2025-09-19 00:42:14 +08:00
|
|
|
|
const user = await Admin.findByPk(userId, {
|
2025-09-12 13:15:03 +08:00
|
|
|
|
attributes: {
|
|
|
|
|
|
exclude: ['password_hash'] // 排除密码哈希等敏感信息
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
if (!user) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '用户不存在'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
|
2025-09-04 09:04:58 +08:00
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
2025-09-12 13:15:03 +08:00
|
|
|
|
data: user
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-04 09:04:58 +08:00
|
|
|
|
} catch (error) {
|
2025-09-05 01:18:40 +08:00
|
|
|
|
console.error('获取用户信息失败:', error)
|
2025-09-04 09:04:58 +08:00
|
|
|
|
res.status(500).json({
|
2025-09-02 21:59:27 +08:00
|
|
|
|
success: false,
|
2025-09-05 01:18:40 +08:00
|
|
|
|
message: '获取用户信息失败'
|
|
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
|
2025-09-12 13:15:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/auth/logout:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 用户登出
|
|
|
|
|
|
* tags: [认证管理]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 登出成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器内部错误
|
|
|
|
|
|
*/
|
2025-09-02 21:59:27 +08:00
|
|
|
|
// 用户登出
|
2025-09-12 13:15:03 +08:00
|
|
|
|
router.post('/logout', authenticateJWT, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 注意:JWT是无状态的,服务器端无法直接使token失效
|
|
|
|
|
|
// 登出操作主要由客户端完成,如删除本地存储的token
|
|
|
|
|
|
// 这里只返回成功信息
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '登出成功'
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('用户登出失败:', error)
|
|
|
|
|
|
res.status(500).json({
|
2025-09-02 21:59:27 +08:00
|
|
|
|
success: false,
|
2025-09-12 13:15:03 +08:00
|
|
|
|
message: '登出失败,请稍后再试'
|
2025-09-05 01:18:40 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
}
|
2025-09-12 13:15:03 +08:00
|
|
|
|
})
|
2025-09-02 21:59:27 +08:00
|
|
|
|
|
2025-09-05 01:18:40 +08:00
|
|
|
|
module.exports = router
|