mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-28 05:33:11 +08:00
217 lines
4.6 KiB
JavaScript
217 lines
4.6 KiB
JavaScript
import { authService } from '../services/authService.js';
|
||
|
||
/**
|
||
* JWT认证中间件
|
||
*/
|
||
export const authenticateToken = (req, res, next) => {
|
||
const authHeader = req.headers['authorization'];
|
||
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
||
|
||
if (!token) {
|
||
return res.status(401).json({
|
||
error: {
|
||
code: 'UNAUTHORIZED',
|
||
message: '缺少访问令牌'
|
||
}
|
||
});
|
||
}
|
||
|
||
try {
|
||
const decoded = authService.verifyToken(token);
|
||
req.user = decoded;
|
||
next();
|
||
} catch (error) {
|
||
return res.status(403).json({
|
||
error: {
|
||
code: 'FORBIDDEN',
|
||
message: '无效的访问令牌'
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 可选认证中间件(用于可选登录的接口)
|
||
*/
|
||
export const optionalAuth = (req, res, next) => {
|
||
const authHeader = req.headers['authorization'];
|
||
const token = authHeader && authHeader.split(' ')[1];
|
||
|
||
if (token) {
|
||
try {
|
||
const decoded = authService.verifyToken(token);
|
||
req.user = decoded;
|
||
} catch (error) {
|
||
// 忽略token验证错误,继续执行
|
||
req.user = null;
|
||
}
|
||
} else {
|
||
req.user = null;
|
||
}
|
||
|
||
next();
|
||
};
|
||
|
||
/**
|
||
* 错误处理中间件
|
||
*/
|
||
export const errorHandler = (err, req, res, next) => {
|
||
console.error('API Error:', err);
|
||
|
||
// 数据库错误
|
||
if (err.code && err.code.startsWith('SQLITE_')) {
|
||
return res.status(500).json({
|
||
error: {
|
||
code: 'DATABASE_ERROR',
|
||
message: '数据库操作失败'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 验证错误
|
||
if (err.name === 'ValidationError') {
|
||
return res.status(400).json({
|
||
error: {
|
||
code: 'VALIDATION_ERROR',
|
||
message: err.message
|
||
}
|
||
});
|
||
}
|
||
|
||
// JWT错误
|
||
if (err.name === 'JsonWebTokenError') {
|
||
return res.status(401).json({
|
||
error: {
|
||
code: 'INVALID_TOKEN',
|
||
message: '无效的访问令牌'
|
||
}
|
||
});
|
||
}
|
||
|
||
if (err.name === 'TokenExpiredError') {
|
||
return res.status(401).json({
|
||
error: {
|
||
code: 'TOKEN_EXPIRED',
|
||
message: '访问令牌已过期'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 默认错误
|
||
res.status(500).json({
|
||
error: {
|
||
code: 'INTERNAL_ERROR',
|
||
message: err.message || '内部服务器错误'
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 请求日志中间件
|
||
*/
|
||
export const requestLogger = (req, res, next) => {
|
||
const start = Date.now();
|
||
const { method, url, ip } = req;
|
||
|
||
res.on('finish', () => {
|
||
const duration = Date.now() - start;
|
||
const { statusCode } = res;
|
||
|
||
console.log(`${new Date().toISOString()} - ${method} ${url} - ${statusCode} - ${duration}ms - ${ip}`);
|
||
});
|
||
|
||
next();
|
||
};
|
||
|
||
/**
|
||
* 输入验证中间件
|
||
*/
|
||
export const validateInput = (schema) => {
|
||
return (req, res, next) => {
|
||
const { error } = schema.validate(req.body);
|
||
|
||
if (error) {
|
||
return res.status(400).json({
|
||
error: {
|
||
code: 'INVALID_INPUT',
|
||
message: error.details[0].message
|
||
}
|
||
});
|
||
}
|
||
|
||
next();
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 速率限制中间件(简单实现)
|
||
*/
|
||
const rateLimitStore = new Map();
|
||
|
||
export const rateLimit = (options = {}) => {
|
||
const {
|
||
windowMs = 15 * 60 * 1000, // 15分钟
|
||
max = 100, // 最大请求数
|
||
message = '请求过于频繁,请稍后再试'
|
||
} = options;
|
||
|
||
return (req, res, next) => {
|
||
const key = req.ip || req.connection.remoteAddress;
|
||
const now = Date.now();
|
||
|
||
if (!rateLimitStore.has(key)) {
|
||
rateLimitStore.set(key, { count: 1, resetTime: now + windowMs });
|
||
return next();
|
||
}
|
||
|
||
const record = rateLimitStore.get(key);
|
||
|
||
if (now > record.resetTime) {
|
||
// 重置计数
|
||
record.count = 1;
|
||
record.resetTime = now + windowMs;
|
||
return next();
|
||
}
|
||
|
||
if (record.count >= max) {
|
||
return res.status(429).json({
|
||
error: {
|
||
code: 'RATE_LIMIT_EXCEEDED',
|
||
message
|
||
}
|
||
});
|
||
}
|
||
|
||
record.count++;
|
||
next();
|
||
};
|
||
};
|
||
|
||
/**
|
||
* CORS中间件配置
|
||
*/
|
||
export const corsOptions = {
|
||
origin: function (origin, callback) {
|
||
// 允许的域名列表
|
||
const allowedOrigins = [
|
||
'http://localhost:5173',
|
||
'http://localhost:3000',
|
||
'http://127.0.0.1:5173',
|
||
'http://127.0.0.1:3000'
|
||
];
|
||
|
||
// 开发环境允许所有来源
|
||
if (process.env.NODE_ENV === 'development') {
|
||
return callback(null, true);
|
||
}
|
||
|
||
if (!origin || allowedOrigins.includes(origin)) {
|
||
callback(null, true);
|
||
} else {
|
||
callback(new Error('不允许的CORS来源'));
|
||
}
|
||
},
|
||
credentials: true,
|
||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||
allowedHeaders: ['Content-Type', 'Authorization']
|
||
}; |