前言
在之前的文章中我们有介绍过,如何更好、更简单的写好一个接口(接口返回值篇),今天的这篇文章我们主要介绍,怎么统一处理下接口的返回格式问题。
问题分析
我们先来分析下我们所面临的问题在哪里,然后接着给出解决方案。在写一个接口时,我们通常会先统一定义一下接口的返回格式是什么,然后在跟前端去对接,通常的返回格式大体两种(我们以保存用户为例):
1. 成功/失败响应格式不一致(此种方式作为我们默认的接口响应方式)
- 保存用户成功,响应体
{
"id": 10000,
"pwd": "123456",
"nickname": "小竹马",
"img": "http://avatar.youkuaiyun.com/0/E/9/1_aiyaya_.jpg",
"status": "NORMAL",
"createTime": 1515075974540
}
- 失败响应体(下面的格式是spring boot默认的错误响应格式,只不过我们在其基础上增加了一个code字段用于解释更详细的错误码)
{
"status": 400,
"error": "Bad Request",
"message": "参数无效",
"code": 10001,
"path": "/zhuma-demo/users",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"fieldName": "status",
"message": "值是无效的"
}
],
"timestamp": 1515076067369
}
2.成功/失败响应体格式一致
- 保存用户成功,响应体
{
"code": 1,
"msg": "成功",
"data": {
"id": 10000,
"pwd": "123456",
"nickname": "小竹马",
"img": "http://avatar.youkuaiyun.com/0/E/9/1_aiyaya_.jpg",
"status": "NORMAL",
"createTime": 1515076287882
}
}
- 失败响应体
{
"code": 10001,
"msg": "参数无效",
"data": [
{
"fieldName": "status",
"message": "值是无效的"
}
]
}
那么如果我们想要的响应体格式是第二种,我们该如何写我们的代码呢?你可能想是这样么?
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public PlatformResult addUser(@Validated @RequestBody User user) {
user.setId(10000L);
user.setCreateTime(new Date());
return PlatformResult.success(user);
}
}
PlatformResult.success()这段逻辑显然很多余,每个方法都要这样写一遍,所以上述方式并不是我们想要的,我们要的是
@ResponseResult(PlatformResult.class)
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User addUser(@Validated @RequestBody User user) {
user.setId(10000L);
user.setCreateTime(new Date());
return user;
}
}
我们加了一个自定义的注解@ResponseResult(PlatformResult.class),参数PlatformResult.class告诉这个Controller类下的所有方法都以这个类PlatformResult的格式进行返回,这个注解可以标记在类或方法上,好了,我们的目的明朗了许多,要做的就是标记这个注解让它实现接口返回值格式控制这个功能,下面我们给出具体的实现方式。
实现思路
首先介绍下完成我们这次主要功能的几个类:
- Result 是返回格式类的父接口(所有返回格式类都需要继承它)
- PlatformResult 通用返回结果格式(我们上面说的第二种返回结果)
- DefaultErrorResult 全局错误返回结果(我们上面说的第一种错误时的返回结果)
- GlobalExceptionHandler全局异常处理
- ResponseResult 注解类(用于在Controller上指定返回值格式类)
- ResponseResultInterceptor 拦截器(主要用于将ResponseResult注解类的标记信息传入ResponseResultHandler中)
- ResponseResultHandler 响应体格式处理器(主要转换逻辑都在这里)
代码实现
下面将有一大片代码袭来,要顶住!O(∩_∩)O哈哈~
1. Result 接口类
package com.zhuma.demo.comm.result;
import java.io.Serializable;
/**
* @desc 响应格式父接口
*
* @author zhumaer
* @since 4/1/2018 3:00 PM
*/
public interface Result extends Serializable {
}
说明
理论上所有的返回格式类都需要实现该接口才能被使用
2. PlatformResult 通用返回结果
package com.zhuma.demo.comm.result;
import com.zhuma.demo.enums.ResultCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @desc 平台通用返回结果
*
* @author zhumaer
* @since 10/9/2017 3:00 PM
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PlatformResult implements Result {
private static final long serialVersionUID = 874200365941306385L;
private Integer code;
private String msg;
private Object data;
public static PlatformResult success() {
PlatformResult result = new PlatformResult();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static PlatformResult success(Object data) {
PlatformResult result = new PlatformResult();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
public static PlatformResult failure(ResultCode resultCode) {
PlatformResult result = new PlatformResult();
result.setResultCode(resultCode);
return result;
}
public static PlatformResult failure(ResultCode resultCode, Object data) {
PlatformResult result = new PlatformResult();
result.setResultCode(resultCode);
result.setData(data);
return result;
}
public static PlatformResult failure(String message) {
PlatformResult result = new PlatformResult();
result.setCode(ResultCode.PARAM_IS_INVALID.code());
result.setMsg(message);
return result;
}
private void