基于Koa与routing-controllers的通用错误处理方案问询
搞定routing-controllers通用自定义错误处理方案
刚好我之前做过类似的需求,给你一套完整的实现方案,完美匹配你的要求——捕获控制器里所有被拒绝的Promise,返回自定义响应体,根据错误类型设置HTTP状态码,彻底抛弃Koa默认的错误处理(再也不会返回堆栈跟踪这类对用户不友好的内容了)。
第一步:定义自定义错误类
首先得创建我们自己的错误基类,继承原生Error,这样能方便区分不同错误类型,还能携带状态码和额外信息:
export class CustomError extends Error { public statusCode: number; public message: string; public details?: Record<string, any>; constructor(statusCode: number, message: string, details?: Record<string, any>) { super(message); this.statusCode = statusCode; this.details = details; // 修复原型链,保证instanceof判断正常工作 Object.setPrototypeOf(this, CustomError.prototype); } } // 还可以扩展具体的业务错误类型,比如: export class NotFoundError extends CustomError { constructor(message = "资源未找到", details?: Record<string, any>) { super(404, message, details); } } export class ValidationError extends CustomError { constructor(message = "参数验证失败", details?: Record<string, any>) { super(400, message, details); } }
第二步:实现全局错误拦截器
routing-controllers提供了@ErrorHandler()装饰器,用来定义全局错误处理类。我们用它来捕获所有未处理的错误(包括控制器里被拒绝的Promise):
import { ErrorHandler, KoaErrorMiddlewareInterface, HttpError } from "routing-controllers"; import { Context } from "koa"; // 用Koa的话用这个类型,Express的话换成对应的Request/Response import { CustomError } from "./CustomError"; @ErrorHandler() export class CustomErrorHandler implements KoaErrorMiddlewareInterface { async error(error: any, ctx: Context) { // 优先处理我们自定义的错误 if (error instanceof CustomError) { ctx.status = error.statusCode; ctx.body = { success: false, message: error.message, details: error.details || null, timestamp: new Date().toISOString() }; return; } // 处理routing-controllers自带的HttpError(比如路由不存在、方法不允许等) if (error instanceof HttpError) { ctx.status = error.httpCode; ctx.body = { success: false, message: error.message || "服务器错误", details: null, timestamp: new Date().toISOString() }; return; } // 处理其他未知错误(比如Promise拒绝但没抛出自定义错误) console.error("服务器未知错误:", error); // 服务器端打日志方便排查,不要返回给前端 ctx.status = 500; ctx.body = { success: false, message: "服务器内部错误", details: null, timestamp: new Date().toISOString() }; } }
如果是用Express的话,只需要把KoaErrorMiddlewareInterface换成ExpressErrorMiddlewareInterface,参数换成request、response、next即可,核心逻辑完全一致。
第三步:注册错误处理器到routing-controllers
初始化routing-controllers的时候,一定要把我们的错误处理器加进去,同时关闭默认错误处理:
import { createKoaServer } from "routing-controllers"; import { CustomErrorHandler } from "./CustomErrorHandler"; import { UserController } from "./controllers/UserController"; const app = createKoaServer({ controllers: [UserController], // 你的控制器列表 errorHandler: CustomErrorHandler, // 注册自定义错误处理器 defaultErrorHandler: false, // 关闭默认错误处理,完全用我们自己的逻辑 }); app.listen(3000, () => { console.log("服务器启动在3000端口"); });
第四步:在控制器中使用自定义错误
现在不管你在控制器里是同步抛错,还是异步Promise被拒绝,都会被我们的处理器捕获并返回自定义响应:
import { Controller, Get, Param } from "routing-controllers"; import { NotFoundError } from "./CustomError"; import { UserService } from "../services/UserService"; @Controller("/users") export class UserController { constructor(private userService: UserService) {} @Get("/:id") async getUser(@Param("id") id: string) { const user = await this.userService.getUserById(id); if (!user) { // 抛出自定义404错误,携带额外信息 throw new NotFoundError(`ID为${id}的用户不存在`, { userId: id }); } return { success: true, data: user }; } @Get("/test-error") async testPromiseReject() { // 模拟Promise被拒绝的场景,会被错误处理器捕获 await Promise.reject(new ValidationError("参数格式错误", { param: "test" })); } }
核心要点说明
- 全覆盖捕获:不管是控制器里同步抛出的错误,还是异步函数中被拒绝的Promise,这个处理器都能完美捕获,因为routing-controllers会自动处理异步函数的Promise结果。
- 自定义响应体:响应体的格式完全由你掌控,比如
success、message、details这些字段都可以根据业务需求调整。 - 关闭默认处理:
defaultErrorHandler: false确保不会触发Koa的默认错误处理,不会把堆栈跟踪这类敏感信息返回给前端。 - 日志留存:对于未知错误,我们在服务器端保留日志方便排查,但返回给用户的是友好的通用提示,兼顾调试和用户体验。
内容的提问来源于stack exchange,提问作者peterschrott
相关产品推荐
相关产品推荐

