SpringBoot 响应结果统一封装:JSON 格式与状态码设计

发布时间:2026/5/19 8:32:01

SpringBoot 响应结果统一封装:JSON 格式与状态码设计 做 SpringBoot 前后端分离开发时你是否遇到过这样的问题接口返回格式混乱有的返回纯 JSON 数据有的返回字符串有的甚至直接抛异常前端对接时需要反复判断返回格式还要处理各种杂乱的状态提示对接效率极低。其实解决这个问题的核心就是统一响应结果封装——规定统一的 JSON 返回格式、规范状态码设计让后端所有接口返回结构一致、状态清晰前端只需按照固定格式解析无需额外适配大幅提升对接效率也让接口更健壮、更易维护。一、为什么要统一封装响应结果在前后端分离项目中统一响应结果不是可选而是必须核心原因有 3 点尤其适合团队开发前端对接更高效固定返回格式前端无需判断不同接口的返回结构一次封装全局复用解析逻辑异常处理更规范接口报错时不会直接返回异常堆栈暴露系统细节而是返回统一的错误提示和状态码便于问题排查通过状态码和提示信息后端能快速定位接口问题前端也能清晰知道报错原因比如参数错误、权限不足。举个反例混乱的返回格式正常返回{id:1,username:张三}直接返回实体类异常返回用户名不能为空纯字符串报错返回org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter username is not present异常堆栈暴露系统信息再看统一封装后的返回规范格式正常返回{code:200,msg:操作成功,data:{id:1,username:张三}}异常返回{code:400,msg:用户名不能为空,data:null}报错返回{code:500,msg:服务器内部异常请联系管理员,data:null}一目了然前端只需判断code状态码就能知道接口是否成功无需处理各种杂乱格式。二、统一响应结果类统一响应结果的核心是创建一个通用的响应类封装 3 个核心字段适配所有接口场景代码极简直接复制到项目中即可使用。1. 引入依赖无需额外依赖SpringBoot 自带如果项目中已引入spring-boot-starter-web无需额外添加依赖若未引入添加以下依赖1dependency 2groupIdorg.springframework.bootgroupId 3artifactIdspring-boot-starter-webartifactId 4dependency 5 6dependency 7groupIdorg.projectlombokgroupId 8artifactIdlombokartifactId 9optionaltrueoptional 10dependency2. 统一响应结果类Result.java推荐使用 Lombok 的Data注解简化代码若不使用 Lombok手动添加 get/set 方法即可。1importlombok.Data; 2 3/** 4* 全局统一响应结果类 5* 适配所有接口返回格式{code:状态码,msg:提示信息,data:返回数据} 6*paramT 返回数据的类型泛型支持任意类型实体类、List、Map、String等 7*/ 8Data 9publicclassResultT{ 10 11/** 12* 状态码核心字段用于前端判断接口是否成功 13*200成功4xx客户端错误5xx服务器错误其他自定义业务错误 14*/ 15privateInteger code; 16 17/** 18* 提示信息用于前端展示成功提示、错误提示 19*/ 20privateString msg; 21 22/** 23* 返回数据接口成功时返回的具体数据如实体类、列表、分页数据等 24* 接口失败时该字段为 null 25*/ 26privateT data; 27 28// 私有构造方法禁止外部直接创建对象只能通过静态方法创建 29privateResult(){} 30 31// 1. 成功返回带数据最常用接口查询、新增、修改成功时使用 32publicstaticTResultTsuccess(T data){ 33ResultT result newResult(); 34 result.setCode(200);// 成功状态码固定为200 35 result.setMsg(操作成功);// 默认成功提示可自定义 36 result.setData(data); 37return result; 38} 39 40// 2. 成功返回不带数据适用于删除、退出登录等无需返回数据的接口 41publicstaticTResultTsuccess(){ 42returnsuccess(null);// 调用带数据的方法data传null 43} 44 45// 3. 成功返回自定义提示信息适用于需要自定义成功提示的场景 46publicstaticTResultTsuccess(String msg,T data){ 47ResultT result newResult(); 48 result.setCode(200); 49 result.setMsg(msg); 50 result.setData(data); 51return result; 52} 53 54// 4. 失败返回自定义提示信息适用于业务异常如参数错误、用户名已存在 55publicstaticTResultTfail(String msg){ 56ResultT result newResult(); 57 result.setCode(500);// 默认失败状态码为500可自定义 58 result.setMsg(msg); 59 result.setData(null); 60return result; 61} 62 63// 5. 失败返回自定义状态码提示信息适用于特定业务错误如权限不足、登录过期 64publicstaticTResultTfail(Integer code,String msg){ 65ResultT result newResult(); 66 result.setCode(code); 67 result.setMsg(msg); 68 result.setData(null); 69return result; 70} 71 72// 6. 失败返回自定义状态码提示信息错误数据适用于需要返回错误详情的场景 73publicstaticTResultTfail(Integer code,String msg,T data){ 74ResultT result newResult(); 75 result.setCode(code); 76 result.setMsg(msg); 77 result.setData(data); 78return result; 79} 80}3. 核心说明泛型 支持任意返回数据类型比如实体类、List、Map、String、Integer 等适配所有接口场景私有构造方法禁止外部直接new Result()创建对象只能通过静态方法success/fail创建保证格式统一静态方法分类覆盖所有接口场景无需额外扩展直接调用即可字段含义code状态码、msg提示、data数据三个字段缺一不可前端可通过这三个字段完成所有解析逻辑。三、规范设计状态码生产级标准状态码是前端判断接口状态的核心不能随意定义建议遵循 HTTP 状态码规范同时扩展业务状态码做到“见码知意”。以下是生产级常用状态码设计直接复用即可无需修改。1. 核心状态码状态码含义适用场景200成功所有接口正常返回查询、新增、修改、删除成功400客户端参数错误前端传递的参数格式错误、必填参数缺失、参数值非法401未登录/登录过期用户未登录、Token 过期、Session 失效403权限不足用户没有该接口的访问权限如普通用户访问管理员接口404资源不存在访问的接口路径错误、查询的资源不存在如根据 ID 查询用户ID 不存在500服务器内部异常后端代码报错如数据库异常、空指针异常2. 扩展业务状态码按需添加根据业务场景扩展专属状态码建议以 6xx、7xx 开头避免与 HTTP 状态码冲突状态码含义适用场景601用户名已存在注册时用户名重复602密码错误登录时密码不正确603验证码过期/错误登录、注册时验证码验证失败701订单状态异常操作订单时订单状态不符合要求如已取消订单无法支付702库存不足下单时商品库存不够3. 状态码使用规范禁止随意定义状态码比如不能用 201 表示参数错误不能用 500 表示用户名已存在前端判断逻辑只需判断code 200即为接口成功其余均为失败失败时直接展示msg提示后端返回规范业务异常返回 4xx、6xx、7xx 状态码系统异常返回 500 状态码且 500 时不返回具体异常信息避免暴露系统细节。四、接口中如何使用封装好响应类和状态码后接口中使用非常简单无需额外处理直接调用Result类的静态方法即可以下是高频场景案例。案例1查询接口带数据成功返回1importorg.springframework.web.bind.annotation.GetMapping; 2importorg.springframework.web.bind.annotation.PathVariable; 3importorg.springframework.web.bind.annotation.RequestMapping; 4importorg.springframework.web.bind.annotation.RestController; 5 6RestController 7RequestMapping(/user) 8publicclassUserController{ 9 10// 根据ID查询用户成功返回用户信息 11GetMapping(/{id}) 12publicResultUsergetUserById(PathVariableInteger id){ 13// 模拟数据库查询实际项目中对接数据库 14User user newUser(); 15 user.setId(id); 16 user.setUsername(张三); 17 user.setAge(20); 18 user.setPhone(13800138000); 19 20// 成功返回带用户数据 21returnResult.success(user); 22} 23}返回结果规范 JSON 格式1{ 2code:200, 3msg:操作成功, 4data:{ 5id:1, 6username:张三, 7age:20, 8phone:13800138000 9} 10}案例2新增接口自定义成功提示1// 新增用户自定义成功提示 2PostMapping 3publicResultUseraddUser(RequestBodyUser user){ 4// 模拟新增逻辑实际项目中保存到数据库 5 user.setId(1001);// 模拟生成ID 6 7// 自定义成功提示带新增后的用户数据 8returnResult.success(用户新增成功, user); 9}返回结果1{ 2code:200, 3msg:用户新增成功, 4data:{ 5id:1001, 6username:李四, 7age:22, 8phone:13900139000 9} 10}案例3删除接口不带数据成功返回1// 删除用户无需返回数据 2DeleteMapping(/{id}) 3publicResultVoiddeleteUser(PathVariableInteger id){ 4// 模拟删除逻辑实际项目中删除数据库数据 5returnResult.success(用户删除成功); 6}返回结果1{ 2code:200, 3msg:用户删除成功, 4data:null 5}案例4业务异常参数错误失败返回1// 登录接口业务异常参数错误、密码错误 2PostMapping(/login) 3publicResultStringlogin(RequestParamString username,RequestParamString password){ 4// 模拟参数校验 5if(username null|| username.isEmpty()){ 6// 客户端参数错误返回400状态码 7returnResult.fail(400,用户名不能为空); 8} 9// 模拟密码校验 10if(!123456.equals(password)){ 11// 业务异常返回602状态码自定义业务状态码 12returnResult.fail(602,密码错误请重新输入); 13} 14// 登录成功返回Token实际项目中生成真实Token 15returnResult.success(登录成功,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...); 16}参数错误返回结果1{ 2code:400, 3msg:用户名不能为空, 4data:null 5}密码错误返回结果1{ 2code:602, 3msg:密码错误请重新输入, 4data:null 5}案例5系统异常服务器错误失败返回1// 模拟系统异常如空指针、数据库异常 2GetMapping(/errorTest) 3publicResultStringerrorTest(){ 4try{ 5// 模拟空指针异常 6String str null; 7 str.length(); 8returnResult.success(测试成功); 9}catch(Exception e){ 10// 系统异常返回500状态码不暴露具体异常信息 11returnResult.fail(500,服务器内部异常请联系管理员); 12} 13}返回结果1{ 2code:500, 3msg:服务器内部异常请联系管理员, 4data:null 5}五、全局异常处理上面的案例中系统异常需要手动 try-catch 处理非常繁琐而且容易遗漏。优化方案添加全局异常处理器自动捕获所有异常统一返回规范格式无需手动处理。全局异常处理器GlobalExceptionHandler.java1importorg.springframework.web.bind.MissingServletRequestParameterException; 2importorg.springframework.web.bind.annotation.ExceptionHandler; 3importorg.springframework.web.bind.annotation.RestControllerAdvice; 4 5/** 6* 全局异常处理器捕获所有Controller层异常统一返回规范响应结果 7*RestControllerAdvice作用于所有RestController注解的类 8*/ 9RestControllerAdvice 10publicclassGlobalExceptionHandler{ 11 12// 1. 捕获参数缺失异常Required String parameter xxx is not present 13ExceptionHandler(MissingServletRequestParameterException.class) 14publicResultVoidhandleMissingParamException(MissingServletRequestParameterException e){ 15// 提示缺少xxx参数 16String msg 缺少必要参数 e.getParameterName(); 17returnResult.fail(400, msg); 18} 19 20// 2. 捕获空指针异常最常见的系统异常 21ExceptionHandler(NullPointerException.class) 22publicResultVoidhandleNullPointerException(NullPointerException e){ 23// 生产环境不返回具体异常信息避免暴露系统细节 24returnResult.fail(500,服务器内部异常请联系管理员); 25// 开发环境可返回异常信息便于调试注释掉上面启用下面 26// return Result.fail(500, 服务器内部异常 e.getMessage()); 27} 28 29// 3. 捕获所有未定义的异常兜底异常处理 30ExceptionHandler(Exception.class) 31publicResultVoidhandleException(Exception e){ 32// 兜底处理避免接口返回异常堆栈 33returnResult.fail(500,服务器内部异常请联系管理员); 34} 35 36// 4. 自定义业务异常按需添加比如用户名已存在、库存不足等 37ExceptionHandler(BusinessException.class) 38publicResultVoidhandleBusinessException(BusinessException e){ 39// 自定义业务异常返回自定义状态码和提示信息 40returnResult.fail(e.getCode(), e.getMsg()); 41} 42}补充自定义业务异常BusinessException.java用于处理业务场景中的异常如用户名已存在、库存不足统一管理业务错误1importlombok.Data; 2 3/** 4* 自定义业务异常用于业务场景中的错误如用户名已存在、库存不足 5*/ 6Data 7publicclassBusinessExceptionextendsRuntimeException{ 8 9// 业务状态码如601、602、701 10privateInteger code; 11 12// 业务错误提示信息 13privateString msg; 14 15// 构造方法传入状态码和提示信息 16publicBusinessException(Integer code,String msg){ 17this.code code; 18this.msg msg; 19} 20}优化后使用示例无需手动 try-catch1// 注册接口使用自定义业务异常 2PostMapping(/register) 3publicResultUserregister(RequestBodyUser user){ 4// 模拟用户名重复校验实际项目中查询数据库判断 5if(张三.equals(user.getUsername())){ 6// 抛出自定义业务异常全局异常处理器会自动捕获 7thrownewBusinessException(601,用户名已存在请更换用户名); 8} 9// 模拟注册逻辑 10 user.setId(1002); 11returnResult.success(注册成功, user); 12}返回结果全局异常处理器自动封装1{ 2code:601, 3msg:用户名已存在请更换用户名, 4data:null 5}六、注意事项禁止在接口中直接返回实体类、字符串、null必须通过Result类返回保证格式统一状态码使用规范严格遵循前面的状态码设计不随意定义避免前端解析混乱生产环境中系统异常如 500禁止返回具体异常信息如空指针堆栈避免暴露系统细节自定义业务异常需统一继承RuntimeException并在全局异常处理器中捕获避免遗漏若接口需要返回分页数据可在Result类中添加分页相关字段如 total、page、size或封装分页响应类下文补充。七、分页响应封装很多接口需要返回分页数据单独封装分页响应类适配分页场景与统一响应类结合使用分页响应类PageResult.java1importlombok.Data; 2importjava.util.List; 3 4/** 5* 分页统一响应类适配分页查询接口 6*/ 7Data 8publicclassPageResultT{ 9 10// 总条数数据库中符合条件的总记录数 11privateLong total; 12 13// 当前页码前端传递的页码默认1 14privateInteger page; 15 16// 每页条数前端传递的每页条数默认10 17privateInteger size; 18 19// 当前页数据列表分页查询到的具体数据 20privateListT list; 21 22// 构造方法快速创建分页响应对象 23publicPageResult(Long total,Integer page,Integer size,ListT list){ 24this.total total; 25this.page page; 26this.size size; 27this.list list; 28} 29}分页接口使用示例1// 分页查询用户列表 2GetMapping(/page) 3publicResultPageResultUsergetUserPage( 4RequestParam(defaultValue 1)Integer page, 5RequestParam(defaultValue 10)Integer size 6){ 7// 模拟分页查询实际项目中使用MyBatis-Plus分页插件 8ListUser userList List.of( 9newUser(1,张三,20,13800138000), 10newUser(2,李四,22,13900139000) 11); 12Long total 100L;// 模拟总条数 13 14// 构建分页响应对象 15PageResultUser pageResult newPageResult(total, page, size, userList); 16 17// 统一返回分页数据 18returnResult.success(pageResult); 19}分页返回结果规范格式1{ 2code:200, 3msg:操作成功, 4data:{ 5total:100, 6page:1, 7size:10, 8list:[ 9{ 10id:1, 11username:张三, 12age:20, 13phone:13800138000 14}, 15{ 16id:2, 17username:李四, 18age:22, 19phone:13900139000 20} 21] 22} 23}八、总结统一响应结果封装核心就 3 步看完直接落地新建Result类封装code、msg、data三个核心字段提供静态方法success/fail遵循状态码规范区分 HTTP 状态码和业务状态码做到“见码知意”添加全局异常处理器自动捕获所有异常无需手动 try-catch统一返回规范格式。这样一套封装下来后端接口返回格式完全统一前端对接效率翻倍后续维护也更简单——不管是新增接口、修改接口还是处理异常都无需额外调整返回格式真正实现“一次封装全局复用”。如果这篇文章帮你搞定了响应结果封装的问题麻烦点个赞、在看关注我后续还有更多 SpringBoot 实操技巧从入门到精通

相关新闻