定义统一的数据返回格式有利于提高开发效率、降低沟通成本,降低调用方的开发成本。目前比较流行的是基于JSON格式的数据交互。无论是HTTP接口还是RPC接口,保持返回值格式统一很重要。
一般情况下,统一返回数据格式没有固定的规范,只要能描述清楚返回的数据状态以及要返回的具体数据即可,但是一般会包含状态码、消息提示语、具体数据这3部分内容。
{"code": 20000,"message": "成功","data": {"items": [{"id": "1","name": "weiz","intro": "备注"}]}
}
定义的返回值包含4要素:响应结果、响应码、消息、返回数据。
定义的返回值包含如下内容:
{"code": 20000,"message": "成功","data": {"items": [{"id": "1","name": "weiz","intro": "备注"}]}
}
返回的数据中有一个非常重要的字段:状态码。状态码字段能够让服务端、客户端清楚知道操作的结果、业务是否处理成功,如果失败,失败的原因等信息。
状态码 | 含义 | 说明 |
---|---|---|
200 | OK | 请求成功 |
201 | CREATED | 创建成功 |
204 | DELETED | 删除成功 |
400 | BAD REQUEST | 请求的地址不存在或者包含不支持的参数 |
401 | UNAUTHORIZED | 未授权(验证不通过) |
403 | FORBIDDEN | 被禁止访问 |
404 | NOT FOUND | 请求的资源不存在 |
406 | Not acceptable | 错误 – 无法接受 |
422 | Unprocesable entity | [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误 |
500 | INTERNAL 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
结果处理类:
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种比较常用的异常处理解决方案。
推荐使用@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})注解重新定义一个异常处理的方法即可。
//添加一处错误代码
int i = 4/0;
响应内容:
{"code": 555,"msg": "服务器异常,ArithmeticException: / by zero","data": null
}
默认返回的是满屏的错误信息,自定义全局异常处理类之后,返回我们指定格式的信息。
注意:若使用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.boot spring-boot-starter-parent 2.5.2
org.springframework.boot spring-boot-starter-web
org.projectlombok lombok true
org.springframework.boot spring-boot-starter-test test
com.baomidou mybatis-plus-boot-starter 3.5.2
笔记摘自:《Spring Boot从入门到实战》-章为忠