尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
NestJS 入门(4):统一响应与异常处理
上一篇NestJS 入门3Guard 如何挡住未登录请求 讲了鉴权门槛。业务代码里常见这样写thrownewUnauthorizedException(Invalid credentials);但前端拿到的往往不是 Nest 默认的异常结构而是统一信封例如{code:1001,msg:Invalid credentials,data:null}成功时则是{code:0,msg:success,data:{/* 业务数据 */}}这篇文章只讲清楚一件事Interceptor 负责成功包装Exception Filter 负责失败整形——两边约定同一套信封前端才能稳定解析。1. 为什么要统一信封如果每个接口自己返回{ok:true,result:...}{success:1,payload:...}{error:xxx}前端就要写一堆特殊判断。统一成字段含义code业务错误码0表示成功msg给人看的说明data成功时的业务数据失败常为null前端只需if(data.code0){returndata.data;}// 否则按 data.code / data.msg 提示用户HTTP 状态码仍然有用401/403/404但业务语义优先看code。例如同是 401可以细分成「未登录 / token 无效 / 凭证错误」。2. 成功路径全局 Response InterceptorNest 启动时挂上全局拦截器asyncfunctionbootstrap(){constappawaitNestFactory.create(AppModule);app.useGlobalInterceptors(newResponseInterceptor());app.useGlobalFilters(newHttpExceptionFilter());awaitapp.listen(3000);}拦截器大致是这样Injectable()exportclassResponseInterceptorimplementsNestInterceptor{intercept(context:ExecutionContext,next:CallHandler):Observableunknown{constresponsecontext.switchToHttp().getResponse();if(this.shouldSkip(response)){returnnext.handle();// SSE / 文件流不要包}returnnext.handle().pipe(map((data)({code:0,msg:success,data:data??null,})));}privateshouldSkip(response:Recordstring,unknown):boolean{constcontentTypetypeofresponse.getHeaderfunction?response.getHeader(Content-Type):response.contentType;if(typeofcontentTypestring){if(contentType.includes(text/event-stream))returntrue;if(contentType.includes(application/octet-stream))returntrue;}returnfalse;}}Controller 仍然可以「直接 return 业务对象」Get()findAll(){returnthis.projectsService.findAll(userId);// 实际响应会被包成 { code: 0, msg: success, data: [...] }}你不用在每个方法里手写信封。为什么 SSE 要跳过SSE 要持续写data: {event:content,data:你好}\n\n如果也走成功拦截器可能被一次性包成 JSON 信封流就坏了。所以看到text/event-stream或文件下载时直接next.handle()不做map。3. 失败路径全局 Exception Filter业务里抛thrownewUnauthorizedException(Invalid credentials);若没有过滤器Nest 默认也会返回 JSON但字段名、结构和成功信封往往不一致。全局过滤器把所有异常收口成同一形状Catch()exportclassHttpExceptionFilterimplementsExceptionFilter{catch(exception:unknown,host:ArgumentsHost){constctxhost.switchToHttp();constresponsectx.getResponseResponse();letstatusHttpStatus.INTERNAL_SERVER_ERROR;letmessageInternal server error;if(exceptioninstanceofHttpException){statusexception.getStatus();constexceptionResponseexception.getResponse();if(typeofexceptionResponsestring){messageexceptionResponse;}elseif(typeofexceptionResponseobjectexceptionResponse!null){message((exceptionResponseasRecordstring,unknown).messageasstring)||message;}}elseif(exceptioninstanceofError){// 也可把 JWT 相关 Error 映射成 401messageexception.message;}constcodethis.mapStatusToErrorCode(status);response.status(status).json({code,msg:message,data:null,});}privatemapStatusToErrorCode(status:number):number{switch(status){caseHttpStatus.UNAUTHORIZED:return1001;caseHttpStatus.FORBIDDEN:return1003;caseHttpStatus.NOT_FOUND:return1100;caseHttpStatus.BAD_REQUEST:return1503;caseHttpStatus.CONFLICT:return1102;caseHttpStatus.SERVICE_UNAVAILABLE:return1502;default:return1500;}}}关键点Catch()不传参数 抓住所有异常不只是HttpException从异常里取出 HTTP status 与可读 message映射成业务code永远返回{ code, msg, data }这样前端无论成功失败解析路径都一样。4. 请求链路对照成功 Controller return data → ResponseInterceptor map 成 { code:0, msg:success, data } → 前端拿到统一成功包 失败 Service throw UnauthorizedException(...) → 不走成功拦截器的 map异常打断 Observable → HttpExceptionFilter catch → { code:1001, msg:..., data:null } → 前端拿到统一错误包可以记成正常 return 走 Interceptor抛异常走 Filter。两边约定同一信封字段前端只认这一套。5. HTTP 状态码 vs 业务错误码两者分工不同维度HTTP status业务code给谁看网关、浏览器、通用客户端业务前端、运营排障粒度粗401/404/500细1001/1100/1503…例子401 Unauthorized1001 InvalidToken / 1004 InvalidCredentials常见分段示例区间含义0成功1000–1099认证鉴权1100–1199项目相关1200–1299文档相关1500系统 / 校验类Filter 里用mapStatusToErrorCode做「粗映射」够入门更精细时可以在抛异常时直接带业务码自定义异常类Filter 优先读业务码。6. 业务代码怎么写才干净Service抛语义清晰的异常asynclogin(email:string,password:string){constuserthis.findUserByEmail(email);if(!user){thrownewUnauthorizedException(Invalid credentials);}// ...}不要在 Service 里手动拼return{code:1001,msg:...,data:null};// 不推荐和拦截器职责打架Controller继续薄Post(login)login(Body()body:{email:string;password:string}){returnthis.authService.login(body.email,body.password);}成功自动包失败自动整形。前端按信封解包http.interceptors.response.use((response){constdataresponse.data;if(datatypeofdataobjectcodeindata){if(data.code0){returndata.data;// 业务层只看到真正的 data}returnPromise.reject(newError(data.msg||请求失败));}returnresponse;});7. 自定义异常信息时注意getResponse()形态UnauthorizedException(Invalid credentials)时getResponse()可能是字符串也可能是{statusCode:401,message:Invalid credentials,error:Unauthorized}所以 Filter 里要同时处理string和object否则msg可能变成[object Object]或拿不到可读文案。校验类异常如 ValidationPipe的message还可能是字符串数组进阶时可以再归一成一句或列表。8. 和 Guard / JWT 的关系Guard 鉴权失败时底层同样会抛出 HTTP 异常常见 401。只要全局 Filter 在Guard 挡下的请求也会变成统一错误包而不是「有的接口结构不一样」。这正是系列串起来的好处Module / Controller / Service 分层DI 接线Guard 守门Interceptor Filter 统一出口前端感知到的 API始终是同一套语言。9. 小结统一信封{ code, msg, data }成功code 0ResponseInterceptor包装成功返回SSE/文件流要跳过ExceptionFilter把HttpException/ 普通Error收口成同一错误包业务层优先throw new UnauthorizedException(...)不要手写两套返回结构HTTP status 表达传输层语义业务code表达产品语义对照前几篇可以再多一句哪个 Controller 接请求哪个 Service 做业务哪个 Module 组装依赖从哪注入有没有 Guard成功谁包装、失败谁整形前端拿到的信封长什么样下一篇会讲Pipe 与 DTO 校验——为什么Body()进来的脏数据可以在进 Controller 之前就被拦下。系列导航上一篇NestJS 入门3Guard 如何挡住未登录请求第二篇NestJS 入门2依赖注入到底解决了什么问题第一篇NestJS 入门1先搞懂 Module、Controller、Service
RELATED

相关推荐

合同智能起草生成系统法律工具怎么选?实测思路梳理了4款,适合自己的才重要

合同智能起草生成系统法律工具怎么选?实测思路梳理了4款,适合自己的才重要

这两年法律AI工具明显多了起来。 以前写一份合同,常见的流程是“找模板—改主体—补条款—查风险”;现在不少法律AI已经可以直接根据交易背景生成合同初稿,甚至把合同起草、审查、修改放到一个流程里完成。 但我在了解这类产品时发现一个问…

📅 2026/9/1 19:09:49
终极Android投屏指南:3分钟学会用scrcpy在电脑上控制手机

终极Android投屏指南:3分钟学会用scrcpy在电脑上控制手机

终极Android投屏指南:3分钟学会用scrcpy在电脑上控制手机 【免费下载链接】scrcpy Display and control your Android device 项目地址: https://gitcode.com/GitHub_Trending/sc/scrcpy 你是否曾经希望在电脑大屏幕上流畅操作手机,却苦于找不到合…

📅 2026/9/9 20:14:55
如何彻底解决Anki同步限制:5种高效方案对比指南

如何彻底解决Anki同步限制:5种高效方案对比指南

如何彻底解决Anki同步限制:5种高效方案对比指南 【免费下载链接】anki Anki is a smart spaced repetition flashcard program 项目地址: https://gitcode.com/GitHub_Trending/an/anki Anki作为智能间隔重复记忆卡片程序,其同步功能是跨设备学习…

📅 2026/9/5 7:17:38
MORE NEWS

更多资讯

📰

OpenClaw安全部署指南:从裸奔到加固的完整实践

OpenClaw 最近确实火得不行,我身边好几个圈子都在聊它,GitHub 上的 star 涨得飞快,各种“AI Agent 自主干活”的演示视频看得人热血沸腾。我自己也跟进部署过几轮,从 Ubuntu 到 Docker,从接模型到接 Teams、Obsidian&a…

📰

基于BGA-PSO混合算法的热电联产经济调度与Matlab实现

搞过电力系统经济调度的人应该都有体会:如果只是做纯火电机组的负荷分配,那是个经典凸优化问题,很多现成算法都能解;但一旦把热电联产机组加进来,问题立刻变味了。热电联产机组的电出力和热出力之间存在强耦合&#xf…

📰

2026远程工具横评:ToDesk低延迟夺冠,六大主流软件深度对比

1. 先说清楚:我在什么环境下测的要说2026年这波远程工具的市场竞争,绕不开一个词:低延迟。我花了两周多时间,把市面上被讨论最多的六款主流远程工具拉到同一批设备、同一根宽带、差不多的使用场景里做了横向对比,最终结…

📰

多代理AI选股筛选器:从架构设计到投资组合构建的工程实践

1. 从零拆解一个AI选股筛选器的设计思路 第一次看到“设计基于代理的人工智能筛选用于投资组合投资”这个题目,很多人脑子里蹦出来的第一反应是“用AI炒股”。这个理解方向没错,但太粗糙了。真正落到工程实现上,它要解决的核心问题其实是&…

📰

含分布式光伏的配电网无功补偿优化:Matlab粒子群算法实现

分布式光伏大面积接入之后,配电网的运行方式跟以前完全不一样了。过去我们习惯按“单电源、单向潮流”来设计补偿方案,固定电容器组加有载调压就能应付大部分场景。但分布式电源出力波动大、接入位置分散,传统手段经常出现两个极端&#xff1…

📰

DuoPlus更新解析:代理批量检测与RPA自动化如何赋能多账号运营

这款工具把"多账号隔离"和"RPA自动化"焊在同一个工作流里,是我拿到更新日志后最直观的感受。过去做批量登录、批量采集,代理和自动化脚本是两套系统,代理挂了你根本不知道,脚本跑了一小时全在做无用功。这次D…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬