完成v1.1.0
This commit is contained in:
218
website/src/middleware/performance.js
Normal file
218
website/src/middleware/performance.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 性能优化中间件
|
||||
*/
|
||||
|
||||
/**
|
||||
* 响应时间头中间件
|
||||
* 添加X-Response-Time头到响应中
|
||||
*/
|
||||
function responseTime(req, res, next) {
|
||||
const start = Date.now();
|
||||
|
||||
// 保存原始的 end 方法
|
||||
const originalEnd = res.end;
|
||||
|
||||
// 重写 end 方法以在响应发送前设置头部
|
||||
res.end = function(...args) {
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// 只有在头部尚未发送时才能设置
|
||||
if (!res.headersSent) {
|
||||
res.setHeader('X-Response-Time', `${duration}ms`);
|
||||
}
|
||||
|
||||
// 记录慢响应
|
||||
if (duration > 1000) {
|
||||
console.warn(`慢响应: ${req.method} ${req.url} - ${duration}ms`);
|
||||
}
|
||||
|
||||
// 调用原始的 end 方法
|
||||
return originalEnd.apply(this, args);
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩中间件
|
||||
* 已经使用compression,这里添加额外的压缩头
|
||||
*/
|
||||
function compressionHeaders(req, res, next) {
|
||||
// 为文本资源启用压缩
|
||||
if (req.url.match(/\.(html|css|js|json|xml)$/)) {
|
||||
res.setHeader('Vary', 'Accept-Encoding');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存控制中间件
|
||||
*/
|
||||
function cacheControl(req, res, next) {
|
||||
// 静态资源缓存策略
|
||||
if (req.url.match(/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/)) {
|
||||
// 静态资源缓存1年
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
} else if (req.url.match(/\.(html)$/)) {
|
||||
// HTML文件不缓存
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
} else {
|
||||
// 默认缓存策略
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全头中间件
|
||||
*/
|
||||
function securityHeaders(req, res, next) {
|
||||
// 防止MIME类型嗅探
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
// 防止点击劫持
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
|
||||
// XSS保护
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
|
||||
// 推荐使用HTTPS
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
|
||||
// 内容安全策略
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||
"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||
"font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com",
|
||||
"img-src 'self' data: https:",
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'"
|
||||
].join('; ');
|
||||
|
||||
res.setHeader('Content-Security-Policy', csp);
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gzip预压缩中间件
|
||||
* 检查是否存在预压缩的.gz文件
|
||||
*/
|
||||
function gzipStatic(req, res, next) {
|
||||
const acceptEncoding = req.headers['accept-encoding'] || '';
|
||||
|
||||
if (acceptEncoding.includes('gzip') && req.url.match(/\.(js|css|html|json)$/)) {
|
||||
req.url = req.url + '.gz';
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
|
||||
// 设置正确的Content-Type
|
||||
if (req.url.endsWith('.js.gz')) {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
} else if (req.url.endsWith('.css.gz')) {
|
||||
res.setHeader('Content-Type', 'text/css');
|
||||
} else if (req.url.endsWith('.html.gz')) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求限流中间件(简单版本)
|
||||
*/
|
||||
function rateLimiter(maxRequests = 100, windowMs = 15 * 60 * 1000) {
|
||||
const requests = new Map();
|
||||
|
||||
return function(req, res, next) {
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
|
||||
if (!requests.has(ip)) {
|
||||
requests.set(ip, []);
|
||||
}
|
||||
|
||||
const timestamps = requests.get(ip);
|
||||
|
||||
// 清理过期的请求记录
|
||||
const windowStart = now - windowMs;
|
||||
while (timestamps.length && timestamps[0] < windowStart) {
|
||||
timestamps.shift();
|
||||
}
|
||||
|
||||
// 检查是否超过限制
|
||||
if (timestamps.length >= maxRequests) {
|
||||
res.status(429).json({
|
||||
error: '请求过多',
|
||||
message: '请稍后再试'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录当前请求
|
||||
timestamps.push(now);
|
||||
|
||||
// 设置限流头
|
||||
res.setHeader('X-RateLimit-Limit', maxRequests);
|
||||
res.setHeader('X-RateLimit-Remaining', maxRequests - timestamps.length);
|
||||
res.setHeader('X-RateLimit-Reset', Math.ceil((timestamps[0] + windowMs) / 1000));
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库查询优化中间件(示例)
|
||||
*/
|
||||
function queryOptimizer(req, res, next) {
|
||||
// 这里可以添加数据库查询优化逻辑
|
||||
// 例如:限制查询结果数量、添加索引提示等
|
||||
|
||||
// 示例:为API请求添加默认分页
|
||||
if (req.path.startsWith('/api/') && req.method === 'GET') {
|
||||
req.query.limit = req.query.limit || '50';
|
||||
req.query.offset = req.query.offset || '0';
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存使用监控
|
||||
*/
|
||||
function memoryMonitor(req, res, next) {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
// 记录高内存使用
|
||||
if (memoryUsage.heapUsed > 500 * 1024 * 1024) { // 500MB
|
||||
console.warn('高内存使用:', {
|
||||
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
|
||||
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB',
|
||||
rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB',
|
||||
url: req.url
|
||||
});
|
||||
}
|
||||
|
||||
// 添加内存使用头(仅开发环境)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
res.setHeader('X-Memory-Usage', Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
responseTime,
|
||||
compressionHeaders,
|
||||
cacheControl,
|
||||
securityHeaders,
|
||||
gzipStatic,
|
||||
rateLimiter,
|
||||
queryOptimizer,
|
||||
memoryMonitor
|
||||
};
|
||||
Reference in New Issue
Block a user