阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 中间件的复用与模块化

中间件的复用与模块化

作者:陈川 阅读数:46755人阅读 分类: Node.js

中间件的复用与模块化

Express中间件是处理HTTP请求的核心机制,通过将功能拆分为可复用的模块,能够显著提升代码的可维护性和开发效率。合理的中间件设计能让路由逻辑更清晰,避免重复代码,同时便于团队协作。

中间件的基本复用模式

Express中间件本质上是具有特定签名的函数,最简单的复用方式是将中间件函数提取为独立模块。例如,一个记录请求时间的中间件可以这样封装:

// middleware/requestTime.js
module.exports = function requestTime(req, res, next) {
  req.requestTime = Date.now()
  next()
}

// app.js
const requestTime = require('./middleware/requestTime')
app.use(requestTime)

这种模式特别适合需要在多个路由中重复使用的逻辑,如身份验证、数据预处理等。更复杂的中间件可以通过工厂函数实现配置化:

// middleware/logger.js
module.exports = function(format) {
  return function(req, res, next) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
    next()
  }
}

// app.js
const logger = require('./middleware/logger')
app.use(logger('combined'))

模块化中间件组合

多个相关中间件可以组合成模块包,通过index.js统一导出。例如构建API安全相关的中间件集合:

// middleware/security/index.js
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')

module.exports = {
  basicProtection: helmet(),
  apiLimiter: rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100
  }),
  cors: require('./cors')
}

使用时可以整体引入或按需选择:

const { basicProtection, apiLimiter } = require('./middleware/security')
app.use(basicProtection)
app.use('/api/', apiLimiter)

路由级别的中间件封装

对于特定路由组的中间件,可以结合Router实现更精细的模块化。例如用户系统的路由模块:

// routes/users.js
const router = require('express').Router()
const auth = require('../middleware/auth')
const validator = require('../middleware/userValidator')

router.use(auth.required)
router.get('/profile', validator.getProfile, getUserProfile)
router.post('/update', validator.updateUser, updateUser)

module.exports = router

主应用文件只需挂载路由模块:

// app.js
app.use('/users', require('./routes/users'))

动态中间件加载机制

通过配置文件动态加载中间件可以实现环境差异化管理。创建middleware-loader.js:

const fs = require('fs')
const path = require('path')

module.exports = function(app) {
  const env = process.env.NODE_ENV || 'development'
  const config = require(`./config/${env}.json`)

  config.middlewares.forEach(mw => {
    const middleware = require(path.join(__dirname, mw.path))
    app.use(middleware(config.options[mw.name]))
  })
}

对应的配置文件示例:

// config/development.json
{
  "middlewares": [
    { "name": "logger", "path": "./middleware/logger" },
    { "name": "debug", "path": "./middleware/debugTool" }
  ],
  "options": {
    "logger": { "level": "verbose" }
  }
}

中间件的测试策略

可复用的中间件应当具备独立的可测试性。使用supertest进行HTTP层测试的示例:

// test/authMiddleware.test.js
const request = require('supertest')
const express = require('express')
const auth = require('../middleware/auth')

test('auth middleware rejects missing token', async () => {
  const app = express()
  app.get('/protected', auth.required, (req, res) => res.sendStatus(200))
  
  const res = await request(app)
    .get('/protected')
    .expect(401)

  expect(res.body.error).toBe('Unauthorized')
})

对于纯函数型中间件,可以直接调用测试:

// test/queryParser.test.js
const queryParser = require('../middleware/queryParser')

test('transforms comma-separated strings to arrays', () => {
  const req = { query: { tags: 'js,node,express' } }
  const next = jest.fn()
  
  queryParser(req, {}, next)
  
  expect(req.query.tags).toEqual(['js', 'node', 'express'])
  expect(next).toHaveBeenCalled()
})

中间件的错误处理模式

错误处理中间件需要特殊的设计考量。创建可复用的错误处理器:

// middleware/errorHandler.js
module.exports = function(options = {}) {
  return function(err, req, res, next) {
    if (options.log) {
      console.error('[ERROR]', err.stack)
    }

    res.status(err.status || 500).json({
      error: options.expose ? err.message : 'Internal Server Error'
    })
  }
}

// app.js
const errorHandler = require('./middleware/errorHandler')
app.use(errorHandler({ 
  expose: process.env.NODE_ENV === 'development'
}))

对于异步中间件,需要包装处理:

// middleware/asyncHandler.js
module.exports = function(handler) {
  return function(req, res, next) {
    Promise.resolve(handler(req, res, next))
      .catch(next)
  }
}

// routes/users.js
router.get('/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id)
  if (!user) throw new Error('Not found')
  res.json(user)
}))

中间件的性能优化

高频使用的中间件需要考虑性能优化。例如缓存静态资源的ETag计算:

// middleware/staticCache.js
const crypto = require('crypto')
const fs = require('fs').promises

module.exports = function(root) {
  const cache = new Map()

  return async function(req, res, next) {
    if (req.method !== 'GET') return next()
    
    const filePath = path.join(root, req.path)
    try {
      let etag = cache.get(filePath)
      const stats = await fs.stat(filePath)
      
      if (!etag || etag.mtime < stats.mtimeMs) {
        const content = await fs.readFile(filePath)
        etag = {
          value: crypto.createHash('md5').update(content).digest('hex'),
          mtime: stats.mtimeMs
        }
        cache.set(filePath, etag)
      }
      
      res.set('ETag', etag.value)
      if (req.headers['if-none-match'] === etag.value) {
        return res.sendStatus(304)
      }
    } catch (err) {
      // 文件不存在等错误继续传递
    }
    next()
  }
}

中间件的类型安全

在TypeScript项目中,可以定义中间件类型约束:

// types/middleware.ts
import { Request, Response, NextFunction } from 'express'

export interface Middleware {
  (req: Request, res: Response, next: NextFunction): void
}

export interface AsyncMiddleware {
  (req: Request, res: Response, next: NextFunction): Promise<void>
}

// middleware/auth.ts
const authMiddleware: Middleware = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: 'Unauthorized' })
  }
  next()
}

中间件的依赖注入

对于需要外部依赖的中间件,可以采用依赖注入模式:

// middleware/dbContext.js
module.exports = function(db) {
  return function(req, res, next) {
    req.db = {
      users: db.collection('users'),
      posts: db.collection('posts')
    }
    next()
  }
}

// app.js
const MongoClient = require('mongodb').MongoClient
const dbContext = require('./middleware/dbContext')

MongoClient.connect(uri, (err, client) => {
  const db = client.db('myapp')
  app.use(dbContext(db))
})

中间件的AOP实践

面向切面编程在中间件中尤为适用。例如实现方法耗时统计:

// middleware/benchmark.js
module.exports = function(label) {
  return function(req, res, next) {
    const start = process.hrtime()
    
    res.on('finish', () => {
      const diff = process.hrtime(start)
      console.log(`${label} took ${diff[0] * 1e3 + diff[1] / 1e6}ms`)
    })
    
    next()
  }
}

// routes/api.js
router.use(require('../middleware/benchmark')('API'))
router.get('/data', /* ... */)

中间件的版本兼容

处理API版本兼容的中间件设计:

// middleware/versioning.js
module.exports = function(options) {
  return function(req, res, next) {
    const version = req.get('X-API-Version') || options.default
    if (!options.supported.includes(version)) {
      return res.status(406).json({
        error: `Unsupported API version. Supported: ${options.supported.join(', ')}`
      })
    }
    
    req.apiVersion = version
    res.set('X-API-Version', version)
    next()
  }
}

// app.js
app.use(require('./middleware/versioning')({
  default: 'v1',
  supported: ['v1', 'v2']
}))

中间件的元编程应用

利用Proxy实现动态中间件属性:

// middleware/featureToggle.js
module.exports = function(features) {
  return function(req, res, next) {
    req.features = new Proxy({}, {
      get(target, name) {
        return features.includes(name) 
          ? require(`./features/${name}`)
          : null
      }
    })
    next()
  }
}

// routes/admin.js
router.post('/audit', (req, res) => {
  if (req.features.advancedAudit) {
    req.features.advancedAudit.log(req.body)
  }
  // ...
})

中间件的生命周期扩展

为中间件添加生命周期钩子:

// middleware/lifecycle.js
module.exports = function() {
  const hooks = {
    pre: [],
    post: []
  }

  const middleware = function(req, res, next) {
    runHooks('pre', req)
      .then(() => next())
      .then(() => runHooks('post', req))
      .catch(next)
  }

  middleware.hook = function(type, fn) {
    hooks[type].push(fn)
    return this
  }

  async function runHooks(type, req) {
    for (const hook of hooks[type]) {
      await hook(req)
    }
  }

  return middleware
}

// app.js
const lifecycle = require('./middleware/lifecycle')()
lifecycle.hook('pre', req => console.log('Request started'))
lifecycle.hook('post', req => console.log('Request completed'))
app.use(lifecycle)

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌