重构后端服务架构并优化前端错误处理

This commit is contained in:
ylweng
2025-09-05 01:18:40 +08:00
parent 86322c6f50
commit 5853953f79
20 changed files with 608 additions and 772 deletions

View File

@@ -1,186 +1,172 @@
const express = require('express');
const router = express.Router();
const Joi = require('joi');
const User = require('../models/User');
const { generateToken, refreshToken, authenticateToken } = require('../middleware/auth');
const express = require('express')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const Joi = require('joi')
const router = express.Router()
// 验证schema
// 引入数据库模型
const { ApiUser } = require('../models')
// 登录参数验证
const loginSchema = Joi.object({
username: Joi.string().min(2).max(50).required(),
password: Joi.string().min(6).max(100).required()
});
})
const passwordResetRequestSchema = Joi.object({
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required()
});
const passwordResetConfirmSchema = Joi.object({
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
resetCode: Joi.string().required(),
newPassword: Joi.string().min(6).max(100).required()
});
const changePasswordSchema = Joi.object({
oldPassword: Joi.string().required(),
newPassword: Joi.string().min(6).max(100).required()
});
// 生成JWT token
const generateToken = (user) => {
return jwt.sign(
{
id: user.id,
username: user.username,
role: user.user_type
},
process.env.JWT_SECRET || 'niumall-secret-key',
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
)
}
// 用户登录
router.post('/login', async (req, res) => {
try {
const { error, value } = loginSchema.validate(req.body);
// 参数验证
const { error, value } = loginSchema.validate(req.body)
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
details: error.details[0].message
})
}
const { username, password } = value;
// 查找用户
const user = await User.findByLoginIdentifier(username);
const { username, password } = value
// 查找用户
const user = await ApiUser.findOne({
where: {
[require('sequelize').Op.or]: [
{ username: username },
{ phone: username },
{ email: username }
]
}
});
if (!user) {
return res.status(401).json({
success: false,
message: '用户名或密码错误',
code: 'INVALID_CREDENTIALS'
});
message: '用户名或密码错误'
})
}
// 验证密码
const isValidPassword = await user.validatePassword(password);
if (!isValidPassword) {
const isPasswordValid = await bcrypt.compare(password, user.password_hash)
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: '用户名或密码错误',
code: 'INVALID_CREDENTIALS'
});
message: '用户名或密码错误'
})
}
// 检查用户状态
if (user.status !== 'active') {
return res.status(401).json({
return res.status(403).json({
success: false,
message: '账已被禁用,请联系管理员',
code: 'ACCOUNT_DISABLED'
});
message: '账已被禁用,请联系管理员'
})
}
// 更新登录信息
await user.updateLoginInfo();
// 生成token
const token = generateToken(user)
// 生成JWT token
const tokens = generateToken(user);
res.json({
success: true,
message: '登录成功',
data: {
...tokens,
access_token: token,
token_type: 'Bearer',
expires_in: 86400, // 24小时
user: {
id: user.id,
uuid: user.uuid,
username: user.username,
phone: user.phone,
email: user.email,
real_name: user.real_name,
avatar_url: user.avatar_url,
user_type: user.user_type,
status: user.status,
last_login_at: user.last_login_at,
login_count: user.login_count
role: user.user_type,
status: user.status
}
}
});
})
} catch (error) {
console.error('登录错误:', error);
console.error('登录失败:', error)
res.status(500).json({
success: false,
message: '登录失败',
error: error.message
});
message: '登录失败,请稍后重试'
})
}
});
})
// 获取用户信息
// 获取当前用户信息
router.get('/me', authenticateToken, async (req, res) => {
try {
// req.user 已经通过中间件注入
const user = await ApiUser.findByPk(req.user.id)
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
res.json({
success: true,
data: {
id: req.user.id,
uuid: req.user.uuid,
username: req.user.username,
phone: req.user.phone,
email: req.user.email,
real_name: req.user.real_name,
avatar_url: req.user.avatar_url,
user_type: req.user.user_type,
status: req.user.status,
last_login_at: req.user.last_login_at,
login_count: req.user.login_count,
created_at: req.user.created_at,
updated_at: req.user.updated_at
}
});
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json({
success: false,
message: '获取用户信息失败',
error: error.message
});
}
});
// 用户登出
router.post('/logout', authenticateToken, async (req, res) => {
try {
// TODO: 实际项目中可以将token加入黑名单或Redis
res.json({
success: true,
message: '退出登录成功'
});
} catch (error) {
console.error('退出登录错误:', error);
res.status(500).json({
success: false,
message: '退出登录失败',
error: error.message
});
}
});
// 刷新token
router.post('/refresh', refreshToken);
// 验证token有效性
router.post('/verify', authenticateToken, (req, res) => {
try {
// 如果通过认证中间件说明token有效
res.json({
success: true,
message: 'Token有效',
data: {
valid: true,
user: {
id: req.user.id,
username: req.user.username,
user_type: req.user.user_type
id: user.id,
username: user.username,
email: user.email,
role: user.user_type,
status: user.status
}
}
});
})
} catch (error) {
console.error('Token验证错误:', error);
console.error('获取用户信息失败:', error)
res.status(500).json({
success: false,
message: 'Token验证失败',
error: error.message
});
message: '获取用户信息失败'
})
}
});
})
module.exports = router;
// 用户登出
router.post('/logout', authenticateToken, (req, res) => {
// 在实际项目中可以将token加入黑名单
res.json({
success: true,
message: '登出成功'
})
})
// JWT token验证中间件
function 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, process.env.JWT_SECRET || 'niumall-secret-key', (err, user) => {
if (err) {
return res.status(403).json({
success: false,
message: '访问令牌无效或已过期'
})
}
req.user = user
next()
})
}
module.exports = router

View File

@@ -3,39 +3,9 @@ const bcrypt = require('bcryptjs')
const Joi = require('joi')
const router = express.Router()
// 模拟用户数据
let users = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
phone: '13800138000',
role: 'admin',
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z'
},
{
id: 2,
username: 'buyer01',
email: 'buyer01@example.com',
phone: '13800138001',
role: 'buyer',
status: 'active',
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z'
},
{
id: 3,
username: 'supplier01',
email: 'supplier01@example.com',
phone: '13800138002',
role: 'supplier',
status: 'inactive',
createdAt: '2024-01-03T00:00:00Z',
updatedAt: '2024-01-03T00:00:00Z'
}
]
// 引入数据库模型
const { ApiUser } = require('../models')
const sequelize = require('sequelize')
// 验证模式
const createUserSchema = Joi.object({
@@ -43,60 +13,55 @@ const createUserSchema = Joi.object({
email: Joi.string().email().required(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
password: Joi.string().min(6).max(100).required(),
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver').required(),
status: Joi.string().valid('active', 'inactive').default('active')
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin').required(),
status: Joi.string().valid('active', 'inactive', 'locked').default('active')
})
const updateUserSchema = Joi.object({
username: Joi.string().min(2).max(50),
email: Joi.string().email(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver'),
status: Joi.string().valid('active', 'inactive', 'banned')
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin'),
status: Joi.string().valid('active', 'inactive', 'locked')
})
// 获取用户列表
router.get('/', (req, res) => {
router.get('/', async (req, res) => {
try {
const { page = 1, pageSize = 20, keyword, role, status } = req.query
const { page = 1, pageSize = 20, keyword, user_type, status } = req.query
let filteredUsers = [...users]
// 关键词搜索
// 构建查询条件
const where = {}
if (keyword) {
filteredUsers = filteredUsers.filter(user =>
user.username.includes(keyword) ||
user.email.includes(keyword)
)
where[sequelize.Op.or] = [
{ username: { [sequelize.Op.like]: `%${keyword}%` } },
{ email: { [sequelize.Op.like]: `%${keyword}%` } },
{ phone: { [sequelize.Op.like]: `%${keyword}%` } }
]
}
if (user_type) where.user_type = user_type
if (status) where.status = status
// 角色筛选
if (role) {
filteredUsers = filteredUsers.filter(user => user.role === role)
}
// 状态筛选
if (status) {
filteredUsers = filteredUsers.filter(user => user.status === status)
}
// 分页
const total = filteredUsers.length
const startIndex = (page - 1) * pageSize
const endIndex = startIndex + parseInt(pageSize)
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
// 分页查询
const result = await ApiUser.findAndCountAll({
where,
limit: parseInt(pageSize),
offset: (parseInt(page) - 1) * parseInt(pageSize),
order: [['createdAt', 'DESC']]
})
res.json({
success: true,
data: {
items: paginatedUsers,
total: total,
items: result.rows,
total: result.count,
page: parseInt(page),
pageSize: parseInt(pageSize),
totalPages: Math.ceil(total / pageSize)
totalPages: Math.ceil(result.count / parseInt(pageSize))
}
})
} catch (error) {
console.error('获取用户列表失败:', error)
res.status(500).json({
success: false,
message: '获取用户列表失败'
@@ -105,10 +70,10 @@ router.get('/', (req, res) => {
})
// 获取用户详情
router.get('/:id', (req, res) => {
router.get('/:id', async (req, res) => {
try {
const { id } = req.params
const user = users.find(u => u.id === parseInt(id))
const user = await ApiUser.findByPk(id)
if (!user) {
return res.status(404).json({
@@ -122,6 +87,7 @@ router.get('/:id', (req, res) => {
data: user
})
} catch (error) {
console.error('获取用户详情失败:', error)
res.status(500).json({
success: false,
message: '获取用户详情失败'
@@ -142,37 +108,39 @@ router.post('/', async (req, res) => {
})
}
const { username, email, phone, password, role, status } = value
const { username, email, phone, password, user_type, status } = value
// 检查用户名是否已存在
if (users.find(u => u.username === username)) {
const existingUser = await ApiUser.findOne({
where: {
[sequelize.Op.or]: [
{ username: username },
{ email: email },
{ phone: phone }
]
}
})
if (existingUser) {
return res.status(400).json({
success: false,
message: '用户名已存在'
message: '用户名、邮箱或手机号已存在'
})
}
// 检查邮箱是否已存在
if (users.find(u => u.email === email)) {
return res.status(400).json({
success: false,
message: '邮箱已存在'
})
}
// 密码加密
const saltRounds = 10
const password_hash = await bcrypt.hash(password, saltRounds)
// 创建新用户
const newUser = {
id: Math.max(...users.map(u => u.id)) + 1,
const newUser = await ApiUser.create({
username,
email,
phone: phone || '',
role,
password_hash,
user_type,
status,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
users.push(newUser)
})
res.status(201).json({
success: true,
@@ -180,6 +148,7 @@ router.post('/', async (req, res) => {
data: newUser
})
} catch (error) {
console.error('创建用户失败:', error)
res.status(500).json({
success: false,
message: '创建用户失败'
@@ -188,12 +157,12 @@ router.post('/', async (req, res) => {
})
// 更新用户
router.put('/:id', (req, res) => {
router.put('/:id', async (req, res) => {
try {
const { id } = req.params
const userIndex = users.findIndex(u => u.id === parseInt(id))
const user = await ApiUser.findByPk(id)
if (userIndex === -1) {
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
@@ -211,18 +180,15 @@ router.put('/:id', (req, res) => {
}
// 更新用户信息
users[userIndex] = {
...users[userIndex],
...value,
updatedAt: new Date().toISOString()
}
await user.update(value)
res.json({
success: true,
message: '用户更新成功',
data: users[userIndex]
data: user
})
} catch (error) {
console.error('更新用户失败:', error)
res.status(500).json({
success: false,
message: '更新用户失败'
@@ -231,25 +197,26 @@ router.put('/:id', (req, res) => {
})
// 删除用户
router.delete('/:id', (req, res) => {
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params
const userIndex = users.findIndex(u => u.id === parseInt(id))
const user = await ApiUser.findByPk(id)
if (userIndex === -1) {
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
users.splice(userIndex, 1)
await user.destroy()
res.json({
success: true,
message: '用户删除成功'
})
} catch (error) {
console.error('删除用户失败:', error)
res.status(500).json({
success: false,
message: '删除用户失败'
@@ -258,7 +225,7 @@ router.delete('/:id', (req, res) => {
})
// 批量删除用户
router.delete('/batch', (req, res) => {
router.delete('/batch', async (req, res) => {
try {
const { ids } = req.body
@@ -269,13 +236,18 @@ router.delete('/batch', (req, res) => {
})
}
users = users.filter(user => !ids.includes(user.id))
await ApiUser.destroy({
where: {
id: ids
}
})
res.json({
success: true,
message: `成功删除 ${ids.length} 个用户`
})
} catch (error) {
console.error('批量删除用户失败:', error)
res.status(500).json({
success: false,
message: '批量删除用户失败'
@@ -289,8 +261,8 @@ router.put('/:id/password', async (req, res) => {
const { id } = req.params
const { password } = req.body
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
const user = await ApiUser.findByPk(id)
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
@@ -304,14 +276,19 @@ router.put('/:id/password', async (req, res) => {
})
}
// 在实际项目中,这里会对密码进行加密
users[userIndex].updatedAt = new Date().toISOString()
// 密码加密
const saltRounds = 10
const password_hash = await bcrypt.hash(password, saltRounds)
// 更新密码
await user.update({ password_hash })
res.json({
success: true,
message: '密码重置成功'
})
} catch (error) {
console.error('重置密码失败:', error)
res.status(500).json({
success: false,
message: '重置密码失败'
@@ -320,35 +297,35 @@ router.put('/:id/password', async (req, res) => {
})
// 更新用户状态
router.put('/:id/status', (req, res) => {
router.put('/:id/status', async (req, res) => {
try {
const { id } = req.params
const { status } = req.body
const userIndex = users.findIndex(u => u.id === parseInt(id))
if (userIndex === -1) {
const user = await ApiUser.findByPk(id)
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
})
}
if (!['active', 'inactive', 'banned'].includes(status)) {
if (!['active', 'inactive', 'locked'].includes(status)) {
return res.status(400).json({
success: false,
message: '无效的用户状态'
})
}
users[userIndex].status = status
users[userIndex].updatedAt = new Date().toISOString()
await user.update({ status })
res.json({
success: true,
message: '用户状态更新成功',
data: users[userIndex]
data: user
})
} catch (error) {
console.error('更新用户状态失败:', error)
res.status(500).json({
success: false,
message: '更新用户状态失败'