第一部分: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_SUCCESS、API_PARAM_ERROR 和 API_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 响应中间件。我们讨论了如何实现简单的响应中间件、如何封装响应逻辑,并展示了如何在路由中调用这些逻辑。此外,我们还探讨了如何扩展响应中间件以满足特定需求。