实现优雅的数据返回 - springboot
迪丽瓦拉
2024-02-18 04:00:23
0

为什么要统一返回值

定义统一的数据返回格式有利于提高开发效率、降低沟通成本,降低调用方的开发成本。目前比较流行的是基于JSON格式的数据交互。无论是HTTP接口还是RPC接口,保持返回值格式统一很重要。

一般情况下,统一返回数据格式没有固定的规范,只要能描述清楚返回的数据状态以及要返回的具体数据即可,但是一般会包含状态码、消息提示语、具体数据这3部分内容。

{"code": 20000,"message": "成功","data": {"items": [{"id": "1","name": "weiz","intro": "备注"}]}
}

定义的返回值包含4要素:响应结果、响应码、消息、返回数据。


统一数据返回

数据格式

定义的返回值包含如下内容:

  • Integer code:成功时返回0,失败时返回具体错误码。
  • String message:成功时返回null,失败时返回具体错误消息。
  • T data:成功时返回具体值,失败时为null。
    • data字段为泛型字段,根据实际的业务返回前端需要的数据类型。
{"code": 20000,"message": "成功","data": {"items": [{"id": "1","name": "weiz","intro": "备注"}]}
}

状态码

返回的数据中有一个非常重要的字段:状态码。状态码字段能够让服务端、客户端清楚知道操作的结果、业务是否处理成功,如果失败,失败的原因等信息。

状态码含义说明
200OK请求成功
201CREATED创建成功
204DELETED删除成功
400BAD REQUEST请求的地址不存在或者包含不支持的参数
401UNAUTHORIZED未授权(验证不通过)
403FORBIDDEN被禁止访问
404NOT FOUND请求的资源不存在
406Not acceptable错误 – 无法接受
422Unprocesable entity[POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误
500INTERNAL SERVER ERROR内部错误

其他的业务相关状态码需要根据实际业务定义。

定义数据处理类

视图对象(响应数据结构):

package com.qsdbl.malldemo.entity.vo;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** @description vo - 视图对象* @author: 轻率的保罗* @since: 2022-11-26* @version V1.0*/
@Data
@ApiModel("响应数据结构")
public class DataVo {@ApiModelProperty("响应业务状态")private Integer code;@ApiModelProperty("响应消息。一般为请求处理失败后返回的错误提示。")private String msg;@ApiModelProperty("响应中的数据")private Object data;public DataVo(Integer code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public DataVo(Object data) {this.code = 200;this.msg = "OK";this.data = data;}public DataVo() {}
}
 

结果处理类:

package com.qsdbl.malldemo.utils;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qsdbl.malldemo.entity.vo.DataVo;
import java.util.List;/*** @Description:* 状态码说明*    200:表示成功*    500:表示错误,错误信息在msg字段中*    501:bean验证错误,无论多少个错误都以map形式返回*    502:拦截器拦截到用户token出错*    555:异常抛出信息** @author: 轻率的保罗* @since: 2022-11-26* @version V1.0*/
public class JSONResult {// 定义jackson对象private static final ObjectMapper MAPPER = new ObjectMapper();/*** 自定义状态信息*/public static DataVo build(Integer code, String msg, Object data) {return new DataVo(code, msg, data);}/*** 正常 代码200*/public static DataVo ok(Object data) {return new DataVo(data);}/*** 正常 代码200*/public static DataVo ok() {return new DataVo(null);}/*** 异常 代码500* 普通异常,返回错误信息*/public static DataVo errorMsg(String msg) {return new DataVo(500, msg, null);}/*** 异常 代码501* 参数异常,map中放具体的异常参数(key/value)。例如 email = 邮箱格式不正确!*/public static DataVo errorMap(Object data) {return new DataVo(501, "error", data);}/*** Token异常 代码502*/public static DataVo errorTokenMsg(String msg) {return new DataVo(502, msg, null);}/*** 异常 代码555* 系统Exception异常,在try-catch中使用*/public static DataVo errorException(String msg) {return new DataVo(555, msg, null);}/*** 将json字符串转化为DataVo对象。* (data字段的值为对象)需要转换的对象是一个类* @param jsonData json字符串* @param clazz data的值对应的实体类(例如:用户实体类,SysUserEntity.class)*/public static DataVo formatToPojo(String jsonData, Class clazz) {try {if (clazz == null) {return MAPPER.readValue(jsonData, DataVo.class);}JsonNode jsonNode = MAPPER.readTree(jsonData);JsonNode data = jsonNode.get("data");Object obj = null;if (clazz != null) {if (data.isObject()) {obj = MAPPER.readValue(data.traverse(), clazz);} else if (data.isTextual()) {obj = MAPPER.readValue(data.asText(), clazz);}}return build(jsonNode.get("code").intValue(), jsonNode.get("msg").asText(), obj);} catch (Exception e) {return null;}}/*** 将json字符串转化为DataVo对象。* data字段的值为空,无该字段、为空字符串、空对象、空数组等* @param json json字符串*/public static DataVo format(String json) {try {return MAPPER.readValue(json, DataVo.class);} catch (Exception e) {e.printStackTrace();}return null;}/*** 将json字符串转化为DataVo对象。* (data字段的值为数组)需要转换的对象是一个list* @param jsonData json字符串* @param clazz 数组中数据对应的实体类(例如:数组中保存多个用户数据,用户实体类,SysUserEntity.class)*/public static DataVo formatToList(String jsonData, Class clazz) {try {JsonNode jsonNode = MAPPER.readTree(jsonData);JsonNode data = jsonNode.get("data");Object obj = null;if (data.isArray() && data.size() > 0) {obj = MAPPER.readValue(data.traverse(),MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));}return build(jsonNode.get("code").intValue(), jsonNode.get("msg").asText(), obj);} catch (Exception e) {return null;}}
}

测试

返回数据

定义数据处理类后,在控制器中将返回的数据统一加上数据处理。调用如下:

/*** 用户表 Mapper对象*/
@Autowired
private SysUserMapper userMapper;@ApiOperation("查询所有用户数据")
@GetMapping("/all")
public DataVo queryAll(){return JSONResult.ok(userMapper.selectList(null));
}

响应内容:

{"code": 200,"msg": "OK","data": [...]
}

其他方法使用示例

(返回数据)json字符串 转换成 DataVo对象:

package com.qsdbl.malldemo.resultTest;import com.qsdbl.malldemo.entity.SysUserEntity;
import com.qsdbl.malldemo.entity.vo.DataVo;
import com.qsdbl.malldemo.utils.JSONResult;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;/*** @author: 轻率的保罗* @since: 2022-11-26* @Description: 测试 json字符串 转换成 DataVo对象*/
public class MyTest01 {//data字段值为 数组String list1 = "{\n" +"  \"code\": 200,\n" +"  \"msg\": \"OK\",\n" +"  \"data\": [\n" +"    {\n" +"      \"userCode\": \"admin\",\n" +"      \"userName\": \"管理员\",\n" +"      \"userPwd\": \"12345\",\n" +"      \"memo\": \"测试数据!\",\n" +"      \"deleted\": 0\n" +"    }\n" +"  ]\n" +"}";//data字段值为 对象String obj = "{\n" +"\t\"code\": 200,\n" +"\t\"msg\": \"OK\",\n" +"\t\"data\": {\n" +"\t\t\"userCode\": \"admin\",\n" +"\t\t\"userName\": \"管理员\",\n" +"\t\t\"userPwd\": \"12345\",\n" +"\t\t\"memo\": \"测试数据!\",\n" +"\t\t\"deleted\": 0\n" +"\t}\n" +"}\n";//data字段值无数据://data字段值为 空字符串String datanull = "{\n" +"\t\"code\": 200,\n" +"\t\"msg\": \"OK\",\n" +"\t\"data\": \"\"\n" +"}\n";//data字段值为 空对象String datanull2 = "{\n" +"\t\"code\": 200,\n" +"\t\"msg\": \"OK\",\n" +"\t\"data\": {}\n" +"}\n";//没有data字段String datanull3 = "{\n" +"\t\"code\": 200,\n" +"\t\"msg\": \"OK\"\n" +"}\n";@Testvoid test01(){System.out.println("---json字符串 转换成 DataVo对象:\n");System.out.println("---data的值为一个数组,数组中存放的是用户数据(用户实体类)");DataVo dataVo = JSONResult.formatToList(list1,SysUserEntity.class);System.out.println(dataVo);System.out.println("用户数量:"+((ArrayList)dataVo.getData()).size());
//        System.out.println("用户1:"+((ArrayList)dataVo.getData()).get(0));System.out.println("\n---data的值为一个对象,用户数据(用户实体类)");DataVo dataVo_obj = JSONResult.formatToPojo(obj,SysUserEntity.class);System.out.println(dataVo_obj);System.out.println("\n---data的值为空(或空对象、空数组等)");//方式一:DataVo dataVo_null = JSONResult.format(datanull);System.out.println(dataVo_null);//方式二:DataVo dataVo_null2 = JSONResult.formatToPojo(datanull2,null);System.out.println(dataVo_null2);DataVo dataVo_null3 = JSONResult.formatToPojo(datanull3,null);System.out.println(dataVo_null3);}
}

运行结果:

---json字符串 转换成 DataVo对象:---data的值为一个数组,数组中存放的是用户数据(用户实体类)
DataVo(code=200, msg=OK, data=[SysUserEntity{userCode='admin', userName='管理员', userPwd='12345', memo='测试数据!', deleted=0}])
用户数量:1---data的值为一个对象,用户数据(用户实体类)
DataVo(code=200, msg=OK, data=SysUserEntity{userCode='admin', userName='管理员', userPwd='12345', memo='测试数据!', deleted=0})---data的值为空(或空对象、空数组等)
DataVo(code=200, msg=OK, data=)
DataVo(code=200, msg=OK, data={})
DataVo(code=200, msg=OK, data=null)进程已结束,退出代码0

全局异常处理

Spring Boot框架的异常处理有多种方式,从范围来说,包括全局异常捕获处理方式局部异常捕获处理方式。下面介绍3种比较常用的异常处理解决方案。

  • (1)使用@ExceptionHandler处理局部异常
    • 在控制器中通过加入@ExceptionHandler注解的方法来实现异常的处理。这种方式非常容易实现,但是只能处理使用@ExceptionHandler注解方法的控制器异常,而无法处理其他控制器的异常,所以不推荐使用。
  • (2)配置SimpleMappingExceptionResolver类来处理异常
    • 通过配置SimpleMappingExceptionResolver类实现全局异常的处理,但是这种方式不能针对特定的异常进行特殊处理,所有的异常都按照统一的方式处理。
  • (3)使用RestControllerAdvice注解处理全局异常
    • 使用@RestControllerAdvice、@ExceptionHandler注解实现全局异常处理,@RestControllerAdvice定义全局异常处理类,@ExceptionHandler指定自定义错误处理方法拦截的异常类型。实现全局异常捕获,并针对特定的异常进行特殊处理。

推荐使用@RestControllerAdvice注解方式处理全局异常,这样可以针对不同的异常分开处理。

示例

package com.qsdbl.malldemo.configuration;import com.qsdbl.malldemo.utils.JSONResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author: 轻率的保罗* @since: 2022-11-26* @Description: 自定义异常处理类*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler  {/*** 处理全部Exception的异常(代码中try-catch捕获处理了的此处不会处理)* 如果需要处理其他异常,例如NullPointerException异常,则只需要在GlobalException类中使用@ExceptionHandler(value ={NullPointerException.class})注解重新定义一个异常处理的方法即可。*/@ExceptionHandler(value = {Exception.class })public Object errorHandler(HttpServletRequest reqest,HttpServletResponse response, Exception e) throws Exception {//e.printStackTrace();// 记录日志log.error(ExceptionUtils.getMessage(e));return JSONResult.errorException("服务器异常,"+ExceptionUtils.getMessage(e));}
}

上面的示例,处理全部Exception的异常,如果需要处理其他异常,例如NullPointerException异常,则只需要在GlobalException类中使用@ExceptionHandler(value ={NullPointerException.class})注解重新定义一个异常处理的方法即可。


测试1

//添加一处错误代码
int i = 4/0;

响应内容:

{"code": 555,"msg": "服务器异常,ArithmeticException: / by zero","data": null
}

默认返回的是满屏的错误信息,自定义全局异常处理类之后,返回我们指定格式的信息。


测试2

注意:若使用try-catch捕获处理了,则上边定义的全局异常处理类不会处理。

try{int i = 4/0;
}catch (Exception e){return JSONResult.errorException("算术异常!!!");
}

响应内容:

{"code": 555,"msg": "算术异常!!!","data": null
}

响应内容是try-catch中使用JSONResult.errorException返回的错误信息!


说明

本博客中的案例,使用的maven依赖如下:

org.springframework.bootspring-boot-starter-parent2.5.2 

org.springframework.bootspring-boot-starter-web

org.projectlomboklomboktrue

org.springframework.bootspring-boot-starter-testtest

com.baomidoumybatis-plus-boot-starter3.5.2



笔记摘自:《Spring Boot从入门到实战》-章为忠

相关内容