定义统一的数据返回格式有利于提高开发效率、降低沟通成本,降低调用方的开发成本。目前比较流行的是基于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<Object> {
@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";
@Test
void 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));
}
/**
* 404异常处理
*/
@ExceptionHandler(NoHandlerFoundException.class)
public Object handlerNoFoundException(NoHandlerFoundException e) {
String message = ExceptionUtils.getMessage(e);
// 记录日志
log.error(message);
return JSONResult.build(404,"您访问的api【"+message.substring(message.indexOf("/"))+"】不存在!!!",null);
}
}
上面的示例,处理全部Exception的异常,如果需要处理其他异常,例如NullPointerException异常,则只需要在GlobalException类中使用@ExceptionHandler(value ={NullPointerException.class})注解重新定义一个异常处理的方法即可。
要捕获404异常还需要在springboot配置文件进行如下配置:
#当没有对应处理器时,允许抛出异常(让自定义的异常处理类捕获404)
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
#注意:不能访问static下的前端项目,得前后端分开部署
spring.web.resources.add-mappings可更改为配置spring.mvc.static-path-pattern,详情见:给Swagger换皮肤-Knife4j。
//添加一处错误代码
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依赖如下:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.2version>
<relativePath/>
parent>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.2version>
dependency>
笔记摘自:《Spring Boot从入门到实战》-章为忠