十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI陪伴机器人统一响应与全局异常-ApiResponse三段式

AI陪伴机器人统一响应与全局异常-ApiResponse三段式 06-统一响应与全局异常-ApiResponse三段式黒漂技术佬 · AI 伙伴AI-Partner「数据接口部署与二次开发」系列 06上一篇的 19 个接口返回格式全都长一个样{code:0,message:success,data:...}。这套三段式是怎么实现的报错时 HTTP 状态码和业务码怎么配合为什么业务代码里几乎看不到 try-catch这篇把 AI 伙伴的 common 包三个类拆干净——总共不到一百行代码却是整个接口层体验的压舱石。一、ApiResponse三段式统一响应// 项目源码common/ApiResponse.javaDataNoArgsConstructorAllArgsConstructorpublicclassApiResponseT{privateintcode;// 业务码0 成功非 0 失败privateStringmessage;// 提示信息privateTdata;// 业务数据publicstaticTApiResponseTok(Tdata){returnnewApiResponse(0,success,data);}publicstaticTApiResponseTok(){returnnewApiResponse(0,success,null);}publicstaticTApiResponseTfail(intcode,Stringmessage){returnnewApiResponse(code,message,null);}publicstaticTApiResponseTfail(Stringmessage){returnnewApiResponse(500,message,null);}}三个字段各司其职code给程序判断0 成功、非 0 失败message给人看错误提示或固定 “success”data装业务数据成功时装实体失败时为 null。四个静态工厂覆盖了所有出口语义静态工厂codemessagedata使用场景ok(data)0success业务数据查询/创建成功ok()0successnull无返回值的操作fail(code, msg)自定义错误信息null明确知道错误码的失败fail(msg)500错误信息null不细分码的失败单参默认 500二、code0 vs HTTP 200两派的取舍业界对成功怎么表达有两派。一派信 HTTP 语义200 就是成功404/500 各表其义无需业务码。另一派国内后端主流微信/支付宝开放平台都是是本项目的做法HTTP 状态码只表达传输层结果业务结果放进 body 的 code 字段。本项目的组合更微妙两层都在用。业务成功时 HTTP 200 code0业务异常时 HTTP 状态码也会变见下节BusinessException 返回 HTTP 400。也就是说它没有走永远 200、错误全靠 code的极端派而是让 HTTP 状态承担粗分类4xx 客户端的锅、5xx 服务器的锅业务码承担细分类。维度纯 HTTP 派纯业务码派永远 200本项目混合派网关/监控识别错误天然支持按状态码告警失效需解析 body部分支持前端统一处理要枚举各种状态码只判 code状态码粗判 code 细判中间件友好度重试/熔断好差较好取舍本身没有标准答案重要的是全项目一致——这恰恰是三段式包装最大的价值前端只需要写一次拦截器判断 code 是否为 0非 0 弹 message齐活。三、BusinessException把业务错误变成数据// 项目源码common/BusinessException.javaGetterpublicclassBusinessExceptionextendsRuntimeException{privatefinalintcode;publicBusinessException(Stringmessage){super(message);this.code500;}publicBusinessException(intcode,Stringmessage){super(message);this.codecode;}}注意它继承的是RuntimeException非受检异常——业务代码抛它不用层层声明 throws。两个构造函数语义分明单参抛我不关心错误码的通用错误code 默认 500双参抛这个错误值得一个专属码的精确错误。项目里的实际用法比如对话服务里// 项目源码ChatService 内节选thrownewBusinessException(400,大模型 API Key 未配置…);thrownewBusinessException(AI 服务暂时不可用请稍后再试);抛出之后业务代码就撒手不管了——接下来的活全是全局异常处理器的。四、GlobalExceptionHandler三类拦截全项目兜底// 项目源码common/GlobalExceptionHandler.javaSlf4jRestControllerAdvicepublicclassGlobalExceptionHandler{ExceptionHandler(BusinessException.class)publicResponseEntityApiResponseVoidhandleBusiness(BusinessExceptione){returnResponseEntity.badRequest().body(ApiResponse.fail(e.getCode(),e.getMessage()));}ExceptionHandler(MethodArgumentNotValidException.class)publicResponseEntityApiResponseVoidhandleValidation(MethodArgumentNotValidExceptione){FieldErrorfieldErrore.getBindingResult().getFieldError();StringmessagefieldErrornull?参数校验失败:fieldError.getDefaultMessage();returnResponseEntity.badRequest().body(ApiResponse.fail(400,message));}ExceptionHandler(Exception.class)publicResponseEntityApiResponseVoidhandleOther(Exceptione){log.error(系统异常,e);returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.fail(500,系统繁忙请稍后再试));}}RestControllerAdvice让它成为所有 Controller 的常驻保安三类拦截各有讲究**第一类BusinessException → HTTP 400 业务码透传。**业务员主动抛的错设备不存在、提醒创建失败code 从异常里取出来原样进 body。注意 HTTP 状态固定给 400——这类错误的共同点是请求本身有问题客户端改改参数就能重试。第二类MethodArgumentNotValidException → HTTP 400 code 400。DTO 上NotNull/NotBlank校验失败时由Valid触发。消息取首个FieldError 的 defaultMessage比如userId 不能为空一个字段一个字段地修体验友好一个 FieldError 都没有就退回兜底文案参数校验失败。**第三类Exception 兆底 → HTTP 500 固定文案。**任何没被预料到的异常统一返回系统繁忙请稍后再试同时log.error(系统异常, e)把完整堆栈打进日志。这里有个教科书级的细节对外文案模糊、对内日志详尽。错误堆栈里的类名、SQL 片段绝不能透给前端信息泄露风险但日志里必须留全否则排查问题两眼一抹黑。五、为什么参数校验错误要用 400有同学会问反正前端只看 codeHTTP 状态码随便给不行吗行但浪费了。HTTP 4xx 和 5xx 的分工是整个互联网基础设施的共识4xx 客户端的错网关不会告警、重试也没用参数不变结果不变、前端该提示用户改输入5xx 服务器的错监控系统该告警、运维该介入、客户端重试可能恢复。参数校验失败 100% 是客户端请求的问题标 400 后APM 工具、Nginx 日志分析、前端 axios 拦截器都能自动把它归入用户输入问题处理而不是误报服务挂了。一行状态码省掉一整层沟通成本。六、异常交给全局处理业务代码零 try-catch这套机制的最终红利是Controller 和 Service 里几乎看不到 try-catch。对比一下两种写法// 示意没有全局处理器的世界每个接口都要这样包PostMapping(/api/chat)publicApiResponseChatResultchat(ValidRequestBodyChatRequestreq){try{returnApiResponse.ok(chatService.chat(...));}catch(BusinessExceptione){returnApiResponse.fail(e.getCode(),e.getMessage());}catch(Exceptione){log.error(chat error,e);returnApiResponse.fail(500,系统繁忙请稍后再试);}}19 个接口 × 每个都写一遍 维护灾难。而 AI 伙伴的实际 Controller 长这样// 项目源码ChatController节选PostMappingpublicApiResponseChatService.ChatResultchat(ValidRequestBodyChatRequestrequest){returnApiResponse.ok(chatService.chat(request.getUserId(),request.getMessage(),request.getSessionType(),Boolean.TRUE.equals(request.getNeedTts())));}干净得像伪代码。校验失败、业务错误、意外异常各走各的拦截通道横切关注点异常处理被彻底从业务代码里剥离——这就是 AOP 思想在异常处理上的落地。七、当前缺少的异常类型与补齐建议全局处理器三类拦截能兜住大局但有两类异常目前会掉进Exception 兜底体验打折IllegalArgumentExceptionService 里throw new IllegalArgumentException(openId 不能为空)这类参数问题现在会被 500 兜底返回系统繁忙——明明是客户端的错却报成服务器故障。补一个 Handler 返回 400 即可。鉴权异常项目无鉴权体系未来引入 Spring Security 或登录拦截器后AccessDeniedException/401 场景必须有专属处理否则未登录用户会看到系统繁忙而不是请先登录。补齐示例示意// 示意建议新增的两个 HandlerExceptionHandler(IllegalArgumentException.class)publicResponseEntityApiResponseVoidhandleIllegalArgument(IllegalArgumentExceptione){returnResponseEntity.badRequest().body(ApiResponse.fail(400,e.getMessage()));}八、异常 → HTTP 状态 → 业务码 → 前端处理对照表异常来源HTTP 状态业务码 codemessage前端建议处理业务成功2000success渲染 dataBusinessException(双参)400自定义如 400具体业务提示toast 展示 messageBusinessException(单参)400500具体业务提示toast 展示 messageValid 校验失败400400首个字段的校验文案高亮对应表单项未捕获异常500500“系统繁忙请稍后再试”通用错误页引导重试建议补IllegalArgumentException400400参数问题提示按输入错误处理建议补鉴权异常401/403401/403请登录/无权限跳登录页九、合规提醒异常处理是隐私泄露的常见暗门。二次开发时守住三条兜底异常的对外文案保持模糊堆栈、SQL、表结构一律不外泄log.error的日志里如果含用户对话、健康数值等敏感数据要按公司日志规范脱敏并限制留存期健康类业务错误如心率数据格式错误的 message 措辞避免下诊断结论——系统只报数据问题医疗判断永远留给专业医生。小结ApiResponse三段式 BusinessException 三类全局拦截不到一百行代码撑起了 19 个接口的统一出口。HTTP 状态码管粗分类、业务码管细分类、业务代码零 try-catch——这就是小项目也有工程尊严的样子。至此从表设计到接口出口的整条数据链路你都过了一遍接下来无论是把系统部署上线还是动手二次开发心里都有底了。
返回列表