Node.js中间件开发实战
中间件是Node.js开发中的核心概念,它提供了一种优雅的方式来组织服务器端的处理逻辑。本文将深入探讨Node.js中间件的原理、实现方法和实战应用,帮助你构建更加模块化、可维护的服务器应用。
什么是中间件?
中间件本质上是一个函数,它接收请求对象(req)、响应对象(res)和next函数作为参数。中间件可以在请求-响应周期的特定阶段执行代码,控制流程,并决定是否将请求传递给下一个中间件。
function middleware(req, res, next) { next() }
|
Express中间件基础
Express中间件的工作原理
Express使用一个中间件栈来处理请求,每个中间件都可以:
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应周期
- 调用栈中的下一个中间件
常用中间件类型
const app = express() app.use(middleware)
const router = express.Router() router.use(middleware)
app.use((err, req, res, next) => { })
app.use(express.json()) app.use(express.urlencoded({ extended: true }))
app.use(cors()) app(helmet())
|
自定义中间件开发
基础中间件
function requestLogger(req, res, next) { console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`) next() }
function authenticate(req, res, next) { const token = req.headers.authorization if (!token) { return res.status(401).json({ error: '未提供认证令牌' }) } try { const decoded = jwt.verify(token, process.env.JWT_SECRET) req.user = decoded next() } catch (error) { return res.status(401).json({ error: '无效的认证令牌' }) } }
function authorize(roles = []) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: '请先登录' }) } if (roles.length && !roles.includes(req.user.role)) { return res.status(403).json({ error: '权限不足' }) } next() } }
|
高级中间件模式
异步中间件
async function fetchData(req, res, next) { try { const data = await database.query('SELECT * FROM items') req.data = data next() } catch (error) { next(error) } }
|
条件中间件
function conditionalMiddleware(condition) { return (req, res, next) => { if (condition(req)) { middleware(req, res, next) } else { next() } } }
app.use(conditionalMiddleware(req => req.path.startsWith('/api')))
|
链式中间件
function middlewareChain(middlewares) { return (req, res, next) => { let index = 0 function runNext() { if (index < middlewares.length) { middlewares[index++](req, res, runNext) } else { next() } } runNext() } }
|
实战中间件示例
1. 日志中间件
function logger(options = {}) { const { level = 'info', format = 'combined' } = options return (req, res, next) => { const startTime = Date.now() res.on('finish', () => { const duration = Date.now() - startTime const logData = { method: req.method, url: req.url, status: res.statusCode, duration: `${duration}ms`, userAgent: req.get('User-Agent'), ip: req.ip, timestamp: new Date().toISOString() } if (res.statusCode >= 400) { console.error('ERROR:', logData) } else { console.log('INFO:', logData) } }) next() } }
app.use(logger({ level: 'debug' }))
|
2. 安全中间件
function securityMiddleware(options = {}) { return [ cors({ origin: options.corsOrigin || '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }), helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"] } } }), (req, res, next) => { res.setHeader('X-XSS-Protection', '1; mode=block') next() }, (req, res, next) => { res.setHeader('X-Frame-Options', 'DENY') next() }, express.json({ limit: '10kb' }), rateLimit({ windowMs: 15 * 60 * 1000, max: 100, message: '请求过于频繁,请稍后再试' }) ] }
app.use(securityMiddleware({ corsOrigin: 'https://yourdomain.com' }))
|
3. 数据验证中间件
function validate(schema) { return (req, res, next) => { const { error } = schema.validate(req.body, { abortEarly: false, allowUnknown: true, stripUnknown: true }) if (error) { return res.status(400).json({ error: '数据验证失败', details: error.details.map(detail => ({ field: detail.path.join('.'), message: detail.message })) }) } next() } }
const userSchema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), email: Joi.string().email().required(), password: Joi.string().min(6).required() })
app.post('/api/users', validate(userSchema), createUser)
|
4. 缓存中间件
function cacheMiddleware(options = {}) { const { ttl = 60 * 1000, keyGenerator = req => req.url } = options const cache = new Map() return (req, res, next) => { const key = keyGenerator(req) const cached = cache.get(key) if (cached && Date.now() - cached.timestamp < ttl) { return res.json(cached.data) } const originalEnd = res.end const originalJson = res.json res.json = function(data) { cache.set(key, { data, timestamp: Date.now() }) originalJson.call(this, data) } res.end = function(chunk, encoding) { if (!chunk) { cache.set(key, { data: '', timestamp: Date.now() }) } originalEnd.call(this, chunk, encoding) } next() } }
async function redisCacheMiddleware(client, options = {}) { const { ttl = 60, prefix = 'cache:' } = options return (req, res, next) => { const key = prefix + req.originalUrl client.get(key, (err, cached) => { if (err) return next(err) if (cached) { try { return res.json(JSON.parse(cached)) } catch (parseError) { } } const originalEnd = res.end const originalJson = res.json res.json = function(data) { client.setex(key, ttl, JSON.stringify(data)) originalJson.call(this, data) } res.end = function(chunk, encoding) { if (!chunk) { client.setex(key, ttl, '') } originalEnd.call(this, chunk, encoding) } next() }) } }
|
5. 错误处理中间件
function errorHandler(err, req, res, next) { console.error('Error:', err) if (err.name === 'ValidationError') { return res.status(400).json({ error: '数据验证错误', details: err.message }) } if (err.name === 'CastError') { return res.status(400).json({ error: 'ID格式错误', details: err.message }) } if (err.code === 11000) { return res.status(409).json({ error: '数据冲突', details: '该数据已存在' }) } res.status(500).json({ error: '服务器内部错误', details: process.env.NODE_ENV === 'development' ? err.message : '请稍后重试' }) }
function notFoundHandler(req, res, next) { res.status(404).json({ error: '资源未找到', path: req.path }) }
app.use(notFoundHandler) app.use(errorHandler)
|
中间件最佳实践
1. 中间件顺序
app.use(helmet()) app.use(cors()) app(express.json()) app(express.urlencoded()) app(cookieParser()) app(express.static()) app(requestLogger) app(rateLimit()) app(authenticate)
|
2. 条件性中间件
if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')) } else { app.use(morgan('combined')) }
app.use('/api/admin', isAdmin) app.use('/api/public', rateLimiter)
|
3. 错误处理
asyncFunctionWrapper(req, res, next) { try { await asyncFunction(req, res) next() } catch (error) { next(error) } }
app.get('/api/data', (req, res, next) => { fetchData() .then(data => res.json(data)) .catch(next) })
|
4. 性能优化
app(compression())
app(cacheMiddleware({ ttl: 300 * 1000, keyGenerator: req => req.path + JSON.stringify(req.query) }))
app(express.json({ limit: '10mb' }))
|
中间件测试
Jest测试示例
const request = require('supertest') const app = require('../app') const authenticate = require('../middleware/authenticate')
describe('认证中间件', () => { it('应该拒绝未提供token的请求', async () => { const response = await request(app) .get('/api/protected') .expect(401) expect(response.body.error).toBe('未提供认证令牌') }) it('应该接受有效的token', async () => { const token = generateValidToken() const response = await request(app) .get('/api/protected') .set('Authorization', `Bearer ${token}`) .expect(200) expect(response.body.message).toBe('访问成功') }) })
|
Mock测试
jest.mock('../services/authService')
describe('权限中间件', () => { beforeEach(() => { jest.clearAllMocks() }) it('应该允许管理员访问', async () => { authService.verifyUser.mockResolvedValue({ role: 'admin' }) const req = { headers: { authorization: 'Bearer valid-token' }, user: null } const res = {} const next = jest.fn() await authorize('admin')(req, res, next) expect(next).toHaveBeenCalled() }) })
|
实战项目:完整的中间件架构
项目结构
middleware/ ├── index.js # 导出所有中间件 ├── auth/ │ ├── authenticate.js # 身份验证 │ ├── authorize.js # 权限检查 │ └── jwt.js # JWT处理 ├── security/ │ ├── helmet.js # 安全头设置 │ ├── cors.js # CORS配置 │ └── rateLimit.js # 速率限制 ├── logging/ │ ├── logger.js # 日志记录 │ └── morgan.js # HTTP日志 ├── validation/ │ ├── validator.js # 数据验证 │ └── sanitize.js # 数据清理 └── cache/ ├── memory.js # 内存缓存 └── redis.js # Redis缓存
|
主导出文件
const authenticate = require('./auth/authenticate') const authorize = require('./auth/authorize') const helmetConfig = require('./security/helmet') const corsConfig = require('./security/cors') const rateLimiter = require('./security/rateLimit') const logger = require('./logging/logger') const validator = require('./validation/validator') const memoryCache = require('./cache/memory')
module.exports = { authenticate, authorize, securityMiddleware: () => [ helmetConfig(), corsConfig(), rateLimiter() ], logger, morgan: require('./logging/morgan'), validator, sanitize: require('./validation/sanitize'), memoryCache, redisCache: require('./cache/redis') }
|
应用配置
const express = require('express') const cors = require('cors') const helmet = require('helmet') const morgan = require('morgan') const { authenticate, authorize, securityMiddleware, logger, validator, memoryCache } = require('./middleware')
const app = express()
app.use(...securityMiddleware())
app.use(express.json({ limit: '10mb' })) app.use(express.urlencoded({ extended: true })) app.use(cookieParser())
if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')) } app.use(logger)
app.use(memoryCache({ ttl: 60 * 1000 }))
app.get('/api/public', (req, res) => { res.json({ message: '这是公共接口' }) })
app.get('/api/user', authenticate, (req, res) => { res.json({ user: req.user }) })
app.post('/api/admin', authenticate, authorize('admin'), (req, res) => { res.json({ message: '管理员接口' }) })
app.use(notFoundHandler) app.use(errorHandler)
module.exports = app
|
性能监控中间件
function performanceMiddleware(options = {}) { const { sampleRate = 1, slowThreshold = 1000 } = options const metrics = [] return (req, res, next) => { if (Math.random() > sampleRate) { return next() } const startTime = Date.now() const path = req.path res.on('finish', () => { const duration = Date.now() - startTime const status = res.statusCode if (duration > slowThreshold) { console.warn(`慢请求: ${req.method} ${path} - ${duration}ms`) } metrics.push({ path, method: req.method, status, duration, timestamp: Date.now() }) }) next() } }
function metricsMiddleware() { return (req, res, next) => { const startTime = Date.now() res.on('finish', () => { const duration = Date.now() - startTime sendToMetricsService({ path: req.path, method: req.method, status: res.statusCode, duration, timestamp: Date.now() }) }) next() } }
|
总结
中间件是Node.js和Express框架的强大特性,它让代码变得模块化、可重用且易于维护。通过本文的学习,你应该能够:
- 理解中间件的基本概念和工作原理
- 开发自定义中间件满足业务需求
- 掌握中间件的性能优化技巧
- 实现完整的中间件架构
- 编写测试确保中间件质量
在实际项目中,合理使用中间件可以大大提高代码的可维护性和可扩展性。记住遵循最佳实践,保持中间件简洁专注,你的应用架构将更加优雅和高效。
本文档提供了Node.js中间件开发的全面指南,从基础概念到实战应用,帮助你在实际项目中构建高质量的中间件系统。