您现在的位置是:网站首页 > 中间件的复用与模块化文章详情

中间件的复用与模块化

中间件的复用与模块化

Express框架的核心优势之一在于其灵活的中间件机制。中间件本质上是一个函数,能够访问请求对象(req)、响应对象(res)以及应用程序的请求-响应循环中的下一个中间件函数(next)。通过合理组织和复用中间件,可以显著提升代码的可维护性和开发效率。

中间件的基本概念

在Express中,中间件函数通常具有以下签名:

function middleware(req, res, next) {
  // 处理逻辑
  next();
}

这个简单的结构允许开发者在请求处理流程的任意位置插入功能模块。例如,一个记录请求时间的中间件可以这样实现:

const requestTime = (req, res, next) => {
  req.requestTime = Date.now();
  next();
};

app.use(requestTime);

中间件的复用策略

1. 通用中间件的封装

将常用的功能抽象为独立的中间件模块是提高复用性的关键。例如,一个处理跨域请求的中间件:

// corsMiddleware.js
module.exports = (options = {}) => {
  return (req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
  };
};

// 使用方式
const cors = require('./corsMiddleware');
app.use(cors({ origin: 'https://example.com' }));

2. 配置化中间件

通过参数化配置,可以使中间件适应不同场景。例如,一个速率限制中间件:

// rateLimit.js
module.exports = ({ windowMs, max }) => {
  const requests = new Map();
  
  return (req, res, next) => {
    const ip = req.ip;
    const now = Date.now();
    
    if (!requests.has(ip)) {
      requests.set(ip, { count: 1, startTime: now });
      return next();
    }
    
    const record = requests.get(ip);
    if (now - record.startTime > windowMs) {
      requests.set(ip, { count: 1, startTime: now });
      return next();
    }
    
    if (record.count >= max) {
      return res.status(429).send('Too many requests');
    }
    
    record.count++;
    next();
  };
};

// 使用方式
const rateLimit = require('./rateLimit');
app.use(rateLimit({ windowMs: 60000, max: 100 }));

模块化组织方案

1. 按功能划分中间件

将相关中间件组织在同一模块中。例如,认证相关的中间件:

// authMiddleware.js
exports.authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).send('Unauthorized');
  // 验证逻辑
  next();
};

exports.authorize = (roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).send('Forbidden');
    }
    next();
  };
};

// 使用方式
const { authenticate, authorize } = require('./authMiddleware');
app.use('/admin', authenticate, authorize(['admin']));

2. 中间件组合

Express允许将多个中间件组合成一个处理链。例如:

const composeMiddlewares = (...middlewares) => {
  return (req, res, next) => {
    const dispatch = (i) => {
      if (i === middlewares.length) return next();
      const fn = middlewares[i];
      try {
        return fn(req, res, dispatch.bind(null, i + 1));
      } catch (err) {
        return next(err);
      }
    };
    dispatch(0);
  };
};

// 使用组合中间件
const validateInput = (req, res, next) => { /*...*/ };
const processData = (req, res, next) => { /*...*/ };
app.post('/data', composeMiddlewares(validateInput, processData));

高级复用模式

1. 中间件工厂

创建可配置的中间件生成器:

// cacheMiddleware.js
module.exports = (options) => {
  const cache = new Map();
  const ttl = options.ttl || 300000; // 5分钟默认
  
  return (req, res, next) => {
    const key = req.originalUrl;
    if (cache.has(key)) {
      const { data, timestamp } = cache.get(key);
      if (Date.now() - timestamp < ttl) {
        return res.send(data);
      }
    }
    
    const originalSend = res.send;
    res.send = (body) => {
      cache.set(key, { data: body, timestamp: Date.now() });
      originalSend.call(res, body);
    };
    
    next();
  };
};

// 使用方式
const cacheMiddleware = require('./cacheMiddleware');
app.use(cacheMiddleware({ ttl: 60000 }));

2. 条件中间件应用

根据特定条件动态应用中间件:

const conditionalMiddleware = (condition, middleware) => {
  return (req, res, next) => {
    if (condition(req)) {
      return middleware(req, res, next);
    }
    next();
  };
};

// 示例使用
const isMobile = (req) => {
  return /mobile/i.test(req.headers['user-agent']);
};

app.use(conditionalMiddleware(
  isMobile,
  (req, res, next) => {
    req.isMobile = true;
    next();
  }
));

错误处理中间件的复用

错误处理中间件需要四个参数,可以创建统一的错误处理机制:

// errorHandlers.js
exports.logErrors = (err, req, res, next) => {
  console.error(err.stack);
  next(err);
};

exports.clientErrorHandler = (err, req, res, next) => {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' });
  } else {
    next(err);
  }
};

exports.errorHandler = (err, req, res, next) => {
  res.status(500);
  res.render('error', { error: err });
};

// 使用方式
const { logErrors, clientErrorHandler, errorHandler } = require('./errorHandlers');
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);

性能优化考虑

复用中间件时需要注意性能影响。例如,避免在热路径中间件中进行昂贵操作:

// 优化前
app.use((req, res, next) => {
  // 每次请求都进行昂贵的计算
  const result = expensiveCalculation();
  req.calculationResult = result;
  next();
});

// 优化后 - 使用缓存
const memoizedCalculation = (() => {
  const cache = new Map();
  return (input) => {
    if (cache.has(input)) return cache.get(input);
    const result = expensiveCalculation(input);
    cache.set(input, result);
    return result;
  };
})();

app.use((req, res, next) => {
  req.calculationResult = memoizedCalculation(req.query.input);
  next();
});

测试中间件模块

可复用的中间件应该易于测试。使用supertest等工具可以方便地测试中间件:

// test/authMiddleware.test.js
const request = require('supertest');
const express = require('express');
const { authenticate } = require('../middleware/authMiddleware');

describe('authenticate middleware', () => {
  let app;
  
  beforeEach(() => {
    app = express();
    app.use(express.json());
    app.get('/protected', authenticate, (req, res) => {
      res.status(200).send('OK');
    });
  });
  
  it('should return 401 without token', async () => {
    const res = await request(app).get('/protected');
    expect(res.statusCode).toEqual(401);
  });
  
  it('should pass with valid token', async () => {
    const res = await request(app)
      .get('/protected')
      .set('Authorization', 'valid-token');
    expect(res.statusCode).toEqual(200);
  });
});

中间件的版本管理

随着应用演进,中间件可能需要版本控制:

// middleware/v1/auth.js
module.exports = function authV1() { /*...*/ };

// middleware/v2/auth.js
module.exports = function authV2() { /*...*/ };

// 路由中使用
const authV1 = require('./middleware/v1/auth');
const authV2 = require('./middleware/v2/auth');

app.use('/api/v1', authV1(), v1Router);
app.use('/api/v2', authV2(), v2Router);

中间件的依赖注入

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

// dbMiddleware.js
module.exports = ({ db }) => {
  return async (req, res, next) => {
    try {
      req.db = db;
      const data = await db.query('SELECT...');
      req.dbData = data;
      next();
    } catch (err) {
      next(err);
    }
  };
};

// 应用配置
const db = require('./db');
const dbMiddleware = require('./dbMiddleware')({ db });
app.use(dbMiddleware);

中间件的执行顺序优化

合理排列中间件顺序可以提升性能。例如,尽早终止无效请求:

// 优化执行顺序
app.use(helmet()); // 安全相关优先
app.use(compression()); // 压缩应该尽早应用
app.use(rateLimiter); // 限流放在业务逻辑前
app.use(bodyParser.json()); // 解析body
app.use(validateApiKey); // 验证API密钥
app.use('/api', apiRouter); // 业务路由

中间件的AOP实践

面向切面编程思想可以应用于中间件设计:

// loggingAspect.js
module.exports = (aspectName) => {
  return (req, res, next) => {
    const start = Date.now();
    const originalEnd = res.end;
    
    res.end = (...args) => {
      const duration = Date.now() - start;
      console.log(`[${aspectName}] ${req.method} ${req.url} - ${duration}ms`);
      originalEnd.apply(res, args);
    };
    
    next();
  };
};

// 使用切面
const loggingAspect = require('./loggingAspect');
app.use(loggingAspect('API Performance'));

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步