Koa2 响应中间件:从基础到进阶的 API 处理方案

197 阅读5分钟

第一部分:Koa2 响应中间件基础

1.1 什么是响应中间件?

响应中间件是 Koa 应用中处理 HTTP 响应的关键部分。它允许我们定义一种统一的方式来格式化和发送响应,从而确保 API 的一致性和可维护性。通过使用响应中间件,我们可以将响应逻辑集中管理,避免在每个路由中重复代码。

1.2 没有响应中间件的用法

在没有响应中间件的情况下,我们的路由处理器可能会变得冗长且重复。例如,下面是一个简单的 Koa 应用,没有使用响应中间件的情况:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
  if (ctx.path === '/success') {
    ctx.status = 200;
    ctx.body = { code: 0, msg: "ok", data: { message: '这是一个成功的响应!' } };
  } else if (ctx.path === '/error') {
    ctx.status = 400;
    ctx.body = { code: 1001, msg: "请求参数错误", data: null };
  } else {
    ctx.status = 404;
    ctx.body = { code: 404, msg: "未找到api" };
  }
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

在这个例子中,我们可以看到重复的响应格式,这会导致代码难以维护和扩展。每当我们需要修改响应格式时,都必须在每个路由处理器中进行更改,增加了出错的风险。

1.3 简单的响应中间件实现

为了简化我们的代码,我们可以实现一个简单的响应中间件。这个中间件将负责处理不同类型的响应,确保我们在整个应用中使用一致的格式。

const ResponseMW = async (ctx, next) => {
  ctx.API_SUCCESS = (data = '') => {
    ctx.status = 200;
    ctx.body = { code: 0, msg: "ok", data };
  };

  ctx.API_PARAM_ERROR = (msg = "请求参数错误", data = '') => {
    ctx.status = 400;
    ctx.body = { code: 1001, msg, data };
  };

  ctx.API_NOT_FOUND = () => {
    ctx.status = 404;
    ctx.body = { code: 404, msg: "未找到api" };
  };

  await next();
};

app.use(ResponseMW);

在这个中间件中,我们定义了三个方法:API_SUCCESSAPI_PARAM_ERRORAPI_NOT_FOUND,分别用于处理成功、参数错误和未找到的响应。这样,我们就可以在路由处理器中简单地调用这些方法,保持代码的整洁和一致性。

第二部分:进阶使用响应中间件

2.1 封装响应逻辑

为了进一步提升我们的代码质量,我们可以将响应逻辑封装到模型类中。这使得响应的结构更加清晰,并且便于管理和扩展。以下是一个基于你提供的封装示例,展示了如何实现响应模型。

首先,我们需要定义一个基础响应模型类 ResponseModel,它将作为所有响应模型的基类:

import { Context } from 'koa';
import { BaseApiResponse } from '@/types';
import moment from 'moment';

class ResponseModel {
  code: number;
  msg: string;
  statusCode: number;

  constructor({
    statusCode,
    code,
    msg,
  }: {
    statusCode?: number;
    code: number;
    msg: string;
  }) {
    this.statusCode = statusCode || 200;
    this.code = code;
    this.msg = msg;
  }
}

2.2 请求成功工具类

接下来,我们实现一个请求成功的工具类 SuccessModel,它继承自 ResponseModel

class SuccessModel extends ResponseModel {
  constructor({
    ctx,
    data,
  }: {
    ctx: Context;
    data?: unknown;
  }) {
    super({
      statusCode: 200,
      code: 0,
      msg: "ok",
    });
    this.success(ctx, data);
  }

  success(ctx: Context, data?: unknown) {
    const response: BaseApiResponse = {
      code: this.code,
      msg: this.msg,
      data: data ?? '',
    };
    ctx.status = this.statusCode;
    ctx.body = response;
  }
}

SuccessModel 中,我们定义了一个 success 方法,用于格式化成功响应并将其发送到客户端。

2.3 请求错误工具类

同样,我们实现一个请求错误的工具类 ErrorModel

class ErrorModel extends ResponseModel {
  constructor({
    ctx,
    statusCode = 200,
    code = 500,
    msg = '服务器内部错误',
    data,
  }: {
    ctx: Context;
    statusCode?: number;
    code?: number;
    msg?: string;
    data?: unknown;
  }) {
    super({
      statusCode,
      code,
      msg,
    });
    this.error(ctx, data);
  }

  error(ctx: Context, data?: unknown) {
    const response: BaseApiResponse = {
      code: this.code,
      msg: this.msg,
      data: data ?? `${ctx.method} >> ${ctx.url} >> ${JSON.stringify(ctx.request.body)}`,
    };
    ctx.status = this.statusCode;
    ctx.body = response;
  }
}

ErrorModel 中,我们的 error 方法会在响应中包含请求的详细信息,帮助开发者快速定位问题。

SuccessBufferModel 允许我们发送文件下载响应,并设置适当的 HTTP 头信息,以便客户端能够正确处理下载。

2.4 调用封装的响应逻辑

在路由处理器中调用这些封装的响应逻辑将变得更加简单和一致。例如:

app.use(async (ctx) => {
  if (ctx.path === '/success') {
    new SuccessModel({ ctx, data: { message: '这是一个成功的响应!' } });
  } else if (ctx.path === '/error') {
    new ErrorModel({ ctx, code: 1001, msg: '参数缺失' });
  } else {
    new ErrorModel({ ctx, statusCode: 404, msg: '未找到api' });
  }
});

通过使用这些模型类,我们的路由处理器变得更加简洁,逻辑清晰。通过调用封装的方法,我们可以快速响应不同类型的请求,而不必担心响应格式的重复。

2.5 进一步的封装与扩展

你可以根据项目的需求进一步扩展响应中间件。例如,可以添加更多的响应类型,例如未授权错误、验证失败等,或者在响应中包含额外的元数据(如请求处理时间、请求 ID 等)。以下是一个扩展示例,添加了请求处理时间的记录:

const ResponseMW = async (ctx, next) => {
  const start = Date.now();

  ctx.API_SUCCESS = (data = '') => {
    const successModel = new SuccessModel({ ctx, data });
    ctx.body.requestTime = Date.now() - start; // 添加请求处理时间
    return successModel;
  };

  ctx.API_PARAM_ERROR = (msg = "请求参数错误", data = '') => {
    const errorModel = new ErrorModel({ ctx, code: 1001, msg, data });
    ctx.body.requestTime = Date.now() - start; // 添加请求处理时间
    return errorModel;
  };

  ctx.API_NOT_FOUND = () => {
    const errorModel = new ErrorModel({ ctx, statusCode: 404, msg: "未找到api" });
    ctx.body.requestTime = Date.now() - start; // 添加请求处理时间
    return errorModel;
  };

  await next();
};

在这个扩展中,我们在每个响应中添加了 requestTime 字段,记录了请求处理的时间。这可以帮助我们监控 API 的性能,并在需要时进行优化。

小结

通过这篇文章,我们从基础开始,逐步构建了一个功能强大的 Koa2 响应中间件。我们讨论了如何实现简单的响应中间件、如何封装响应逻辑,并展示了如何在路由中调用这些逻辑。此外,我们还探讨了如何扩展响应中间件以满足特定需求。