docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
165
backend/middlewares/auth.js
Normal file
165
backend/middlewares/auth.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
/**
|
||||
* JWT认证中间件
|
||||
* 验证token并附加用户信息到req对象
|
||||
*/
|
||||
const authMiddleware = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '未提供有效的认证token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7); // 移除'Bearer '前缀
|
||||
|
||||
try {
|
||||
// 验证token
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// 查询用户信息
|
||||
const users = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, status FROM users WHERE id = ?',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 1) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户已被禁用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 附加用户信息到请求对象
|
||||
req.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
phone: user.phone,
|
||||
email: user.email,
|
||||
user_type: user.user_type,
|
||||
avatar_url: user.avatar_url
|
||||
};
|
||||
|
||||
next();
|
||||
|
||||
} catch (jwtError) {
|
||||
if (jwtError.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: 'token已过期',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (jwtError.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw jwtError;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('认证中间件错误:', error);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 可选认证中间件
|
||||
* 如果提供了token就验证,不提供也不报错
|
||||
*/
|
||||
const optionalAuth = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
const users = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, status FROM users WHERE id = ? AND status = 1',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (users.length > 0) {
|
||||
const user = users[0];
|
||||
req.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
phone: user.phone,
|
||||
email: user.email,
|
||||
user_type: user.user_type,
|
||||
avatar_url: user.avatar_url
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// token无效,但不阻止请求继续
|
||||
console.warn('可选认证失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('可选认证中间件错误:', error);
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 管理员权限检查中间件
|
||||
*/
|
||||
const adminRequired = (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '需要登录',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (req.user.user_type !== 'admin') {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
message: '需要管理员权限',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
authMiddleware,
|
||||
optionalAuth,
|
||||
adminRequired
|
||||
};
|
||||
124
backend/middlewares/errorHandler.js
Normal file
124
backend/middlewares/errorHandler.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 全局错误处理中间件
|
||||
* 统一处理应用中的各种错误
|
||||
*/
|
||||
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
console.error('错误详情:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 数据库错误
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '数据已存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'ER_NO_REFERENCED_ROW_2') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '关联数据不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'ER_DATA_TOO_LONG') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '数据长度超过限制',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// JWT错误
|
||||
if (err.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: 'token已过期',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证错误
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: err.message || '参数验证失败',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 文件上传错误
|
||||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '文件大小超过限制',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'LIMIT_UNEXPECTED_FILE') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不支持的文件类型',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 默认错误处理
|
||||
const statusCode = err.statusCode || err.status || 500;
|
||||
const message = process.env.NODE_ENV === 'production'
|
||||
? '服务器内部错误'
|
||||
: err.message || '服务器内部错误';
|
||||
|
||||
res.status(statusCode).json({
|
||||
code: statusCode,
|
||||
message,
|
||||
data: null,
|
||||
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 异步错误处理包装器
|
||||
* 用于包装异步路由处理函数,自动捕获错误
|
||||
*/
|
||||
const asyncHandler = (fn) => (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
|
||||
/**
|
||||
* 404错误处理中间件
|
||||
*/
|
||||
const notFoundHandler = (req, res) => {
|
||||
res.status(404).json({
|
||||
code: 404,
|
||||
message: '接口不存在',
|
||||
data: null,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorHandler,
|
||||
asyncHandler,
|
||||
notFoundHandler
|
||||
};
|
||||
Reference in New Issue
Block a user