成功响应处理比较简单,正如前面规范提到的,根据不同情况返回对应状态码(200、201)和内容。
针对不同的错误,大致可分为如下几个场景:
服务器错误(500)其它一些业务逻辑错误(422、401、403……)请求路由不存在(404)我们可以通过 koa-ts-controllers 的统一错误处理函数来捕获错误,并同时输出。
// file: backend/src/app.ts import Koa,{Context} from 'koa'; // ... await bootstrapControllers(app, { router: router, basePath: '/api', versions: [1], controllers: [ __dirname + '/controllers/**/*' ], errorHandler: async (err: any, ctx: Context) => { let status = 500; let body: any = { "statusCode": 500, "error": "Internal Server error", "message": "An internal server error occurred" }; ctx.status = status; ctx.body = body; } }); // ...许多时候,我们会对请求携带的数据进行一些必要的验证。
对于 params 数据,我们可以直接通过 path 规则进行验证。
// file: backend/src/controllers/Test.ts //... @Get('/user/:id(\\d+)') //字符串正则,数字才可以进来 public async index( @Params() params: {id: number} ) { console.log(params); } // ...对于 query 和 body 数据,我们可以通过 class-validator 库来进行统一处理。
通过类来定义要验证的数据。其中,属性表示要验证的包含数据名称,配合着 class-validator 提交的装饰器来定义数据验证规则。
class-validator 地址
// file: backend/src/controllers/Test.ts import {IsNumberString} from 'class-validator'; class GetUsersQuery { @IsNumberString() page: number; }把验证类作为要验证的参数(如:query)的类型。当请求该路由以后就会使用验证类对对应的数据进行验证。
// file: backend/src/controllers/Test.ts import {Controller, Get, Params, Query} from "koa-ts-controllers"; import {IsNumberString} from 'class-validator'; class GetUsersQuery { @IsNumberString() page: number; } //... @Get('/users') public async getUsers( @Query() query: GetUsersQuery ) { console.log(query); } // ...为了方便统一管理,我们可以把验证类存放到一个外部文件中,然后进行引入
存放目录:backend/src/validators/[模块,如:User].ts
如果验证失败,返回如下格式:
{ "data": [ { "field": "page", "violations": { "isNumberString": "page must be a number string" } } ], "isBoom": true, "isServer": false, "output": { "statusCode": 422, "payload": { "statusCode": 422, "error": "Unprocessable Entity", "message": "validation error for argument type: query" }, "headers": {} } }为了保证和我们前面定义的响应格式保持一致,我们对这个数据进行一下处理。
// file: backend/src/app.ts // ... await bootstrapControllers(app, { router: router, basePath: '/api', versions: [1], controllers: [ __dirname + '/controllers/**/*' ], errorHandler: async (err: any, ctx: Context) => { let status = 500; let body: any = { "statusCode": 500, "error": "Internal Server error", "message": "An internal server error occurred" }; if (err.output) { status = err.output.statusCode; body = {...err.output.payload}; if (err.data) { body.errorDetails = err.data; } } ctx.status = status; ctx.body = body; } }); // ...有些错误是一些业务逻辑等方面的错误,比如:用户已经被注册,没有该用户等等。
我们这里会用到一个库 @hapi/Boom ,上面的 class-validator 与 koa-ts-controllers 配合验证中,也是使用了它。听过它提供的一些 API,返回对应 HTTP 状态的响应格式。
// file: backend/src/controllers/Test.ts import {Controller, Get, Params, Query} from "koa-ts-controllers"; import {IsNumberString} from 'class-validator'; import Boom from '@hapi/Boom'; class GetUsersQuery { @IsNumberString() page: number; } //... @Get('/users') public async getUsers( @Query() query: GetUsersQuery ) { console.log(query); // 不存在该用户 if (false) { throw Boom.notFound('用户不存在', '这是错误的详细描述'); } } // ...返回的错误格式如下:
{ "data": "这是错误的详细描述", "isBoom": true, "isServer": false, "output": { "statusCode": 404, "payload": { "statusCode": 404, "error": "Not Found", "message": "用户不存在" }, "headers": {} } }通过前面的统一处理,返回统一的错误格式。
最后,我们还要对未命中的路由(不存在的api)进行一个单独的处理。
// file: backend/src/app.ts // ... import Boom from '@hapi/Boom'; await bootstrapControllers(app, { //... }); router.all('*', async ctx => { throw Boom.notFound(); });以上需要注意:
awaitrouter.all 放在 bootstrapControllers 后面否则每次请求都会先处理 '*'。