前言
当我们提供数据接口给前端时,一般需要告知前端该接口的调用情况。
- 如果调用成功,需要提供成功码和数据。
- 如果调用失败,需要提供错误码和失败具体原因。
所以一个返回体至少应该包括 code
、message
、data
3 个字段。
本文使用的返回体在这 3 个基础上增加了 success
、status
、timestamp
,实际使用请根据团队规范的来。
配置
本实验环境为 Spring Boot 2.2.5.RELEASE,也就是 Spring 5.2.4.RELEASE
返回体类
/**
* 响应消息,响应请求结果给客户端。
* @param <T>
* @author luoxc
*/
@Setter
@Getter
public class CommonResult<T> implements Serializable {
/**
* 是否成功:true,成功;false,失败
*/
private Boolean success;
/**
* 状态码
*/
private Integer status;
/**
* 业务代码,失败时返回具体错误码
*/
private Integer code;
/**
* 成功时返回 null,失败时返回具体错误消息
*/
private String message;
/**
* 成功时具体返回值,失败时为 null
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
public CommonResult() {}
public CommonResult<T> success(Boolean success) {
this.success = success;
return this;
}
public CommonResult<T> status(Integer status) {
this.status = status;
return this;
}
public CommonResult<T> code(Integer code) {
this.code = code;
return this;
}
public CommonResult<T> message(String message) {
this.message = message;
return this;
}
public CommonResult<T> data(T data) {
this.data = data;
return this;
}
public CommonResult<T> putTimeStamp() {
this.timestamp = System.currentTimeMillis();
return this;
}
}
异常枚举类
public interface BaseExceptionEnum {
/**
* 获取异常编码
* @return code
*/
Integer getCode();
/**
* 获取异常信息
* @return message
*/
String getMessage();
}
返回体工具类
public class RestHelper {
/**
* 请求处理成功
* @param <T>
* @return
*/
public static <T> CommonResult<T> ok() {
return ok(null);
}
/**
* 请求处理成功
* @param <T>
* @return
*/
public static <T> CommonResult<T> ok(T data) {
return new CommonResult<T>()
.success(Boolean.TRUE)
.status(HttpStatus.OK.value())
.data(data)
.putTimeStamp();
}
/**
* 请求处理失败(默认500错误)
* @param message 调用结果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(String message) {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
}
/**
* 请求处理失败
* @param status 状态码
* @param message 调用结果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(Integer status, String message) {
return error(status, null, message);
}
/**
* 业务处理失败(默认500错误)
* @param code 业务代码
* @param message 调用结果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> bizError(Integer code, String message) {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), code, message);
}
/**
* 业务处理失败(默认500错误)
* @param baseExceptionEnum
* @param <T>
* @return
*/
public static <T> CommonResult<T> bizError(BaseExceptionEnum baseExceptionEnum) {
return bizError(baseExceptionEnum.getCode(), baseExceptionEnum.getMessage());
}
/**
* 请求处理失败
* @param status 状态码
* @param code 业务代码
* @param message 调用结果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(Integer status, Integer code, String message) {
return new CommonResult<T>()
.success(Boolean.FALSE)
.status(status)
.code(code)
.message(message)
.putTimeStamp();
}
/**
* 判断是否是ajax请求
*/
public static boolean isAjax(HttpServletRequest request) {
return (request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
}
}
统一返回体自动包装类
通过实现 ResponseBodyAdvice
接口,并在类上添加 @ControllerAdvice
注解,可以拦截 Handler(Controller) 的返回结果。
@ControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice {
/**
* 这个方法表示对于哪些请求要执行 beforeBodyWrite,返回 true 执行,返回 false 不执行
* @param returnType, converterType
* @return boolean
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回体已经是 CommonResult 类型,则不必再次包装
if ((body instanceof CommonResult)) {
return body;
}
return ok(body);
}
}
可以通过设置
@ControllerAdvice(basePackages="...")
来指定返回体拦截的范围。
supports(MethodParameter returnType, Class converterType)
方法返回 true,表示拦截 Handler(Controller) 的返回结果。
beforeBodyWrite(...)
在这个方法中可对返回体进行逻辑处理。
测试
@RestController
@RequestMapping("/response")
public class ResponseController {
@GetMapping("/test")
public Map test() {
Map map = new HashMap();
map.put("name", "xiaoming");
map.put("age", 18);
return map;
}
@GetMapping("/test1")
public CommonResult test1() {
return RestHelper.ok("成功");
}
@GetMapping(value = "/test2")
public CommonResult test2() {
return RestHelper.bizError(1, "啊,报错了");
}
}
调用 localhost/demo/response/test
接口时的返回体
{
"success": true,
"status": 200,
"code": null,
"message": null,
"data": {
"name": "xiaoming",
"age": 18
},
"timestamp": 1585630308124
}
调用 localhost/demo/response/test1
接口时的返回体
{
"success": true,
"status": 200,
"code": null,
"message": null,
"data": "成功",
"timestamp": 1585630638916
}
调用 localhost/demo/response/test2
接口时的返回体
{
"success": false,
"status": 500,
"code": 1,
"message": "啊,报错了",
"data": null,
"timestamp": 1585630611703
}
可能有些小伙伴获取到的返回体信息与上面有所区别,例如有的调用
localhost/demo/response/test1
请求返回体中的"message": ""
,这是由于 Java 对象与 JSon 消息转换阶段配置的序列化策略不同导致的。
到这一步,返回体统一包装配置就结束了。
下面的是通过注解来开启返回体包装,这样设置不知道有没有实际应用场景。
其他
通过使用 @RestResponseResult
注解类或者使用 @ResponseResult
注解方法来开启返回体自动包装。
ResponseResult 注解
/**
* 自定义标识注解,用于标识该类或方法需要被 CommonResult 包装,
* 常标记于方法上,可代替 @ResponseBody 使用
* @author luoxc
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ResponseBody
public @interface ResponseResult {
}
RestResponseResult 注解
/**
* 自定义标识注解,用于标识该接口需要被 CommonResult 包装
* 常标记于类上,代替 @Controller,@ResponseBody,@ResponseResult
* @author luoxc
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseResult
public @interface RestResponseResult {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
ResponseResultProperties 配置类
/**
* 返回体统一包装配置类
* @author luoxc
*/
@Setter
@Getter
@ConfigurationProperties(prefix = "api.result")
public class ResponseResultProperties {
/**
* 启动注解配置:true,启用注解;false,禁用注解
*/
private boolean ann;
}
统一返回体自动包装类
/**
* 返回体包装
* @author luoxc
*/
@ControllerAdvice
@EnableConfigurationProperties(ResponseResultProperties.class)
public class ResultResponseBodyAdvice implements ResponseBodyAdvice {
@Autowired
private ResponseResultProperties responseResultProperties;
/**
* 这个方法表示对于哪些请求要执行 beforeBodyWrite,返回 true 执行,返回 false 不执行
* @param returnType, converterType
* @return boolean
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 根据配置是否使用 @RestResponseResult 或 @ResponseResult 启用返回体包装
if (responseResultProperties.isAnn()) {
Class clazz = returnType.getContainingClass();
return clazz.isAnnotationPresent(RestResponseResult.class) ||
clazz.isAnnotationPresent(ResponseResult.class) ||
returnType.hasMethodAnnotation(ResponseResult.class);
}
return true;
}
/**
* 重写返回体
* @param body, returnType, selectedContentType, selectedConverterType, request, response
* @return java.lang.Object
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回体已经是 CommonResult 类型,则不必再次包装
if ((body instanceof CommonResult)) {
return body;
}
return ok(body);
}
}
开启配置
在 appliation.yaml
中添加以下内容
api:
result:
ann: true # true,表示开启注解配置;false,表示禁用
测试
@RestResponseResult // 类级别
@RequestMapping("/response")
public class ResponseController {
@ResponseResult // 方法级别
@GetMapping(value = "/test")
public CommonResult test() {
return RestHelper.bizError(1, "啊,报错了");
}
}