Express中间件(Middleware)详解:从零开始掌握(2)

1. 请求耗时中间件的增强版

问题:原版只能记录到控制台,如何记录到文件?

改进点

  1. 使用process.hrtime()是什么?获取更高精度的时间
  2. 支持将日志写入文件
  3. 记录更多信息(IP地址、状态码)
  4. 工厂函数模式使中间件可配置
javascript 复制代码
const fs = require('fs');
const path = require('path');

// 增强版耗时记录中间件
function createRequestLogger(logFilePath) {
  const logStream = fs.createWriteStream(path.join(__dirname, logFilePath), { flags: 'a' });
  
  return (req, res, next) => {
    const start = process.hrtime();
    const startDate = new Date().toISOString();
    
    res.on('finish', () => {
      const duration = process.hrtime(start);
      const durationMs = (duration[0] * 1e3 + duration[1] / 1e6).toFixed(3);
      
      const logEntry = `[${startDate}] ${req.ip} ${req.method} ${req.url} ${res.statusCode} ${durationMs}ms\n`;
      
      logStream.write(logEntry);
      console.log(logEntry.trim());
    });
    
    next();
  };
}

// 使用方式
app.use(createRequestLogger('requests.log'));

2. API密钥验证中间件的进阶版

问题:如何支持多种验证方式?

改进点

  1. 支持多种认证策略(API Key、JWT、Basic Auth)
  2. 异步验证支持
  3. 统一的错误处理
  4. 可扩展的工厂函数设计
javascript 复制代码
// 支持多种验证策略的中间件工厂
function createAuthMiddleware(options = {}) {
  return async (req, res, next) => {
    try {
      // 策略1: API Key验证
      if (options.apiKey) {
        const apiKey = req.headers['x-api-key'] || req.query.apiKey;
        if (!apiKey) throw new Error('Missing API key');
        if (!options.apiKey.keys.includes(apiKey)) throw new Error('Invalid API key');
        req.authType = 'apiKey';
      }
      
      // 策略2: JWT验证
      if (options.jwt) {
        const token = req.headers.authorization?.split(' ')[1];
        if (!token) throw new Error('Missing token');
        
        const decoded = await verifyJWT(token, options.jwt.secret);
        req.user = decoded;
        req.authType = 'jwt';
      }
      
      // 策略3: 基本认证
      if (options.basicAuth) {
        const authHeader = req.headers.authorization;
        if (!authHeader || !authHeader.startsWith('Basic ')) {
          throw new Error('Missing basic auth');
        }
        
        const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
        const [username, password] = credentials.split(':');
        
        if (username !== options.basicAuth.user || password !== options.basicAuth.pass) {
          throw new Error('Invalid credentials');
        }
        
        req.authType = 'basic';
      }
      
      next();
    } catch (error) {
      res.status(401).json({ 
        error: 'Authentication failed',
        message: error.message
      });
    }
  };
}

// 使用示例
app.use(createAuthMiddleware({
  apiKey: {
    keys: ['123-abc', '456-def']
  },
  jwt: {
    secret: 'my-secret-key'
  }
}));

3. 组合中间件的模式进阶

问题:如何更灵活地组合中间件?

改进点

  1. 实现了类似Koa的中间件组合机制
  2. 添加条件中间件支持
  3. 更灵活的路径匹配
  4. 错误处理集成
javascript 复制代码
// 中间件组合工具函数
function composeMiddlewares(...middlewares) {
  return (req, res, next) => {
    const dispatch = (i) => {
      if (i >= middlewares.length) return next();
      
      const middleware = middlewares[i];
      try {
        return middleware(req, res, () => dispatch(i + 1));
      } catch (err) {
        return next(err);
      }
    };
    
    return dispatch(0);
  };
}

// 条件中间件
function conditionalMiddleware(condition, middleware) {
  return (req, res, next) => {
    if (condition(req)) {
      return middleware(req, res, next);
    }
    next();
  };
}

// 使用示例
const isAdminRoute = req => req.path.startsWith('/admin');
const isApiRoute = req => req.path.startsWith('/api');

app.use(composeMiddlewares(
  requestLogger,
  conditionalMiddleware(
    isApiRoute,
    apiKeyValidator
  ),
  conditionalMiddleware(
    isAdminRoute,
    adminCheck
  )
));

// 等价于:
// app.use(requestLogger);
// app.use('/api', apiKeyValidator);
// app.use('/admin', adminCheck);

实用中间件模式

1. 数据转换中间件

javascript 复制代码
function transformRequestBody(fields) {
  return (req, res, next) => {
    if (req.body) {
      for (const [field, transform] of Object.entries(fields)) {
        if (req.body[field] !== undefined) {
          req.body[field] = transform(req.body[field]);
        }
      }
    }
    next();
  };
}

// 使用示例
app.use(express.json());
app.use(transformRequestBody({
  email: v => v.toLowerCase().trim(),
  age: v => parseInt(v, 10),
  isActive: v => v === 'true'
}));

2. 响应包装中间件

javascript 复制代码
function responseWrapper() {
  return (req, res, next) => {
    const originalSend = res.send;
    
    res.send = function(body) {
      if (res.statusCode >= 400) {
        originalSend.call(this, {
          success: false,
          error: body
        });
      } else {
        originalSend.call(this, {
          success: true,
          data: body
        });
      }
    };
    
    next();
  };
}

3. 请求限流中间件

javascript 复制代码
function rateLimiter({ windowMs, maxRequests }) {
  const requests = new Map();
  
  setInterval(() => {
    requests.clear();
  }, windowMs);

  return (req, res, next) => {
    const ip = req.ip;
    const count = requests.get(ip) || 0;
    
    if (count >= maxRequests) {
      return res.status(429).send('Too many requests');
    }
    
    requests.set(ip, count + 1);
    next();
  };
}

最佳实践建议

  1. 单一职责:每个中间件只做一件事
  2. 可重用性:设计为可配置的工厂函数
  3. 错误处理:始终捕获同步和异步错误
  4. 性能考虑:避免在中间件中进行阻塞操作
  5. 文档注释:清晰说明中间件的用途和参数。

本节就到这里,下节将继续深入讨论示例。

Express中间件(Middleware)详解:从零开始掌握(3)-CSDN博客

相关推荐
layman052815 小时前
node.js 实战——餐厅静态主页编写(express+node+ejs+bootstrap)
node.js·bootstrap·express
layman05283 天前
node.js 实战——express图片保存到本地或服务器(七牛云、腾讯云、阿里云)
node.js·express
小妖6665 天前
express 怎么搭建 WebSocket 服务器
websocket·网络协议·express
一袋米扛几楼9813 天前
【前端】从零开始的搭建顺序指南(技术栈:Node.js + Express + MongoDB + React)book-management
前端·node.js·express
layman052813 天前
node.js 实战——从0开始做一个餐厅预订(express+node+ejs+bootstrap)
node.js·express
白昼的星光@14 天前
使用nodeJs的express+axios+cors做代理
express
键盘飞行员15 天前
使用 Node、Express 和 MongoDB 构建一个项目工程
数据库·mongodb·express
烛阴16 天前
Node.js中必备的中间件大全:提升性能、安全与开发效率的秘密武器
javascript·后端·express
kovlistudio20 天前
红宝书第四十七讲:Node.js服务器框架解析:Express vs Koa 完全指南
服务器·前端·javascript·node.js·express
还是鼠鼠24 天前
Node.js 中 Token 原理简单介绍 + 示例代码
linux·vscode·中间件·node.js·编辑器·vim·express