在开发中,我经常会因为在制定接口返回数据格式而忧愁,也经历过从混乱的每个接口返回数据格式都不同,到同一一个类的过程。
在这里记录一下,如果简单快捷的统一咱们接口请求的返回数据格式
首先却要确定是的返回的数据格式
{
"success":true,
"code":200,
"data":{
"name":"orange",
"age":18
},
"message":null
}
- success 代表此次请求是否成功
- data 代表此次请求返回的具体数据,可以是任意类型的,例如:对象,列表等
- code 状态码来表示此次请求的具体结果,例如200表示操作成功,非200表示操作失败,可以与前端来共同定义维护code,用来做不同的逻辑判断,例如:code 1001 账号密码错误等
- message 当此次请求为失败时用于展示错误信息
在定义code的枚举
/**
* 返回响应码
* @author orange
*/
public enum ApiCodeEnum {
/**
* 成功
*/
SUCCESS(2000,"ok"),
PARAM_ERROR(3001,"参数错误"),
SERVER_ERROR(5000,"系统错误");
....
@Getter
Integer code;
@Getter
String msg;
ApiCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
所有关于业务逻辑的状态码code及对应message都可以通过定义apicode的枚举来确定,这样虽然用起来比较繁琐,但是对于日积月累的code种类来说方便统一管理。
创建统一返回类
/**
* 接口统一返回实体类
*
* @param <T>
* @author orange
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class APIResponse<T> {
private Boolean success;
private T data;
private Integer code;
private String message;
public static APIResponse<String> error(HttpCodeEnum httpCodeEnum) {
return new APIResponse<>(false, null, httpCodeEnum.getCode(), httpCodeEnum.getMsg());
}
public static APIResponse<String> error(Integer code, String msg) {
return new APIResponse<>(false, null, code, msg);
}
public void setSuccessCode(HttpCodeEnum httpCodeEnum) {
this.code = httpCodeEnum.getCode();
this.message = httpCodeEnum.getMsg();
this.success = true;
}
public void setFalseCode(HttpCodeEnum httpCodeEnum) {
this.code = httpCodeEnum.getCode();
this.message = httpCodeEnum.getMsg();
this.success = false;
}
}
实际使用
只需要在接口方法中直接返回该实体类即可
@GetMapping("/user/{id}")
public APIResponse testResponse(@PathVariable Long id){
return APIResponse.builder(userService.findUserById(id)).success(true).code(ApiCode.SUCCESS.getCode()).message(ApiCode.SUCCESS.getValues()).build();
}
这样就统一了接口的返回数据格式。返回结果为:
{
"success":true,
"code":200,
"data":{
"name":"orange",
"age":18,
"gender":"male"
},
"message":null
}
这是这样的话还是需要在每个接口中去用返回实体类包装,就会产生大量重复的代码及繁琐的包装过程,有没有没法简化这一步骤呢?肯定是有的。
@RestControllerAdvice
@Slf4j
@SuppressWarnings("rawtypes")
public class APIResponseAdvice implements ResponseBodyAdvice<Object> {
/**
* 自动处理APIException,包装为APIResponse
*/
@ExceptionHandler(APIException.class)
public APIResponse handleApiException(HttpServletRequest request, APIException ex) {
log.error("process url {} failed", request.getRequestURL().toString(), ex);
return APIResponse.builder().success(false).code(ex.getErrorCode()).message(ex.getErrorMessage()).build();
}
/**
* 自动处理APIException,包装为APIResponse
*/
@ExceptionHandler(LockedAccountException.class)
public APIResponse handleLockedAccountException(HttpServletRequest request, APIException ex) {
log.error("process url {} failed", request.getRequestURL().toString(), ex);
return APIResponse.builder().success(false).code(HttpCodeEnum.ACCOUNT_LOCKED.getCode()).message(ex.getErrorMessage()).build();
}
/**
* 仅当方法或类没有标记@NoAPIResponse才自动包装
*/
@Override
public boolean supports(MethodParameter returnType, @NotNull Class converterType) {
return returnType.getParameterType() != APIResponse.class &&
AnnotationUtils.findAnnotation(Objects.requireNonNull(returnType.getMethod()), NoAPIResponse.class) == null
&& AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), NoAPIResponse.class) == null;
}
/**
* 自动包装外层APIResposne响应
*/
@Override
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
return APIResponse.builder().success(true).data(body).code(HttpCodeEnum.SUCCESS.getCode()).message(HttpCodeEnum.SUCCESS.getMsg()).build();
}
}
只需要实现ResponseBodyAdvice这个接口同时加上@RestControllerAdvice的注解 @RestControllerAdvice 是一个控制增强器 是@ControllerAdvice + @ResponseBody的结合,主要是用来增强controller的。 在ResponseBodyAdvice中有两个方法 一个 supports 用来判断自动包装响应实体的条件,一个是具体包装响应实体的逻辑。 如果我们不需要某个接口的返回值被包装一层的话。我这里使用的是注解的方式。回头看下上图。在supports方法中,如果接口方法被@NoAPIResponse注解标注,那么这个方法就直接返回。跳过下面的beforeBodyWrite方法。
这个实现也很简单 首先定义这个注解
/**
* 自动包装接口返回值注解
* 标记该注解即返回值不会进行包装
* @author orange
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAPIResponse {
}
然后在使用中用这个注解标注方法即可
@GetMapping("/user/{id}")
@NoAPIResponse
public UserVo testResponse(@PathVariable Long id){
return userService.findUserById(id);
}
这个时候的返回结果就是:
{
"name":"orange",
"age":18,
"gender":"male"
}