阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 中间件的基本结构与编写规范

中间件的基本结构与编写规范

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

中间件的基本结构

Koa2中间件本质上是异步函数,接收两个参数:ctx和next。ctx封装了请求和响应对象,next代表下一个中间件的执行权。中间件通过async/await语法处理异步操作,形成洋葱模型调用链。

async function middleware(ctx, next) {
  // 前置操作
  console.log('Before next()');
  
  await next(); // 执行下游中间件
  
  // 后置操作
  console.log('After next()');
}

典型结构包含三个阶段:

  1. 请求预处理:修改ctx状态或拦截请求
  2. next()调用:将控制权移交下游
  3. 响应后处理:修改响应数据或记录日志

编写规范

单一职责原则

每个中间件应只处理特定功能。例如身份验证中间件不应包含业务逻辑:

// 好的实践
async function auth(ctx, next) {
  const token = ctx.headers['authorization'];
  if (!verifyToken(token)) {
    ctx.throw(401, 'Unauthorized');
  }
  await next();
}

// 反模式
async function authAndProcess(ctx, next) {
  // 混合认证与业务处理
  const token = ctx.headers['authorization'];
  if (verifyToken(token)) {
    ctx.user = getUser(token);
    await processBusiness(ctx); // 业务逻辑混入
  }
  await next();
}

错误处理规范

同步错误自动捕获,异步错误需显式处理:

// 错误处理中间件示例
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = { 
      error: err.message,
      code: err.code 
    };
    ctx.app.emit('error', err, ctx); // 触发应用级错误事件
  }
});

// 业务中间件抛出错误
app.use(async ctx => {
  if (!ctx.query.requiredParam) {
    const err = new Error('Missing parameter');
    err.code = 'MISSING_PARAM';
    err.status = 400;
    throw err;
  }
});

性能优化要点

  1. 避免阻塞操作:长时间同步任务应转为异步
  2. 合理使用缓存:高频操作结果可缓存
  3. 控制中间件数量:每个请求都会遍历整个中间件栈
// 缓存示例
const cache = new Map();
app.use(async (ctx, next) => {
  const key = ctx.url;
  if (cache.has(key)) {
    ctx.body = cache.get(key);
    return;
  }
  
  await next();
  
  if (ctx.status === 200) {
    cache.set(key, ctx.body);
  }
});

高级模式

中间件工厂函数

通过闭包创建可配置中间件:

function logger(format = ':method :url') {
  return async (ctx, next) => {
    const start = Date.now();
    await next();
    const duration = Date.now() - start;
    console.log(format
      .replace(':method', ctx.method)
      .replace(':url', ctx.url)
      .replace(':duration', duration)
    );
  };
}

app.use(logger());
app.use(logger(':method :url - :duration ms'));

组合中间件

使用koa-compose创建子中间件栈:

const compose = require('koa-compose');

const middleware1 = async (ctx, next) => {
  console.log('Middleware 1 start');
  await next();
  console.log('Middleware 1 end');
};

const middleware2 = async (ctx, next) => {
  console.log('Middleware 2 start');
  await next();
  console.log('Middleware 2 end');
};

const combined = compose([middleware1, middleware2]);
app.use(combined);

响应时间扩展

通过上下文原型扩展自定义方法:

// 扩展ctx原型
app.context.formatResponse = function(data) {
  this.type = 'application/json';
  this.body = {
    timestamp: Date.now(),
    data: data,
    status: this.status
  };
};

// 使用扩展方法
app.use(async ctx => {
  const users = await User.findAll();
  ctx.formatResponse(users);
});

调试与测试

调试技巧

  1. 使用debug模块标记中间件
  2. 插入调试中间件检查上下文状态
const debug = require('debug')('koa:middleware');

app.use(async (ctx, next) => {
  debug('Request headers:', ctx.headers);
  const start = Date.now();
  await next();
  debug(`Response time: ${Date.now() - start}ms`);
});

单元测试示例

使用supertest测试中间件行为:

const request = require('supertest');
const Koa = require('koa');

describe('Auth Middleware', () => {
  it('should 401 without token', async () => {
    const app = new Koa();
    app.use(authMiddleware);
    app.use(ctx => { ctx.body = 'OK' });
    
    await request(app.callback())
      .get('/')
      .expect(401);
  });
});

最佳实践

配置管理

通过app.context共享配置:

// 初始化时
app.context.config = {
  maxRequests: 1000,
  timeout: 3000
};

// 中间件中使用
app.use(async (ctx, next) => {
  if (ctx.request.count > ctx.config.maxRequests) {
    ctx.throw(429);
  }
  await next();
});

流量控制实现

令牌桶算法中间件示例:

class TokenBucket {
  constructor(rate, capacity) {
    this.tokens = capacity;
    this.rate = rate;
    this.lastFill = Date.now();
  }

  take() {
    this.refill();
    if (this.tokens < 1) return false;
    this.tokens -= 1;
    return true;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastFill) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.rate
    );
    this.lastFill = now;
  }
}

// 使用令牌桶中间件
const bucket = new TokenBucket(10, 20); // 10 tokens/second, 20 max
app.use(async (ctx, next) => {
  if (!bucket.take()) {
    ctx.status = 429;
    ctx.body = 'Too Many Requests';
    return;
  }
  await next();
});

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

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

前端川

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