Why

在前后端分离的前提下,大部分的数据传输格式为JSON,因此,定义一个统一的响应数据格式更加有利于前后端的交互。

How

需要返回什么

  • 是否响应成功 用于前端判断是否成功
  • 响应状态码
  • 状态码描述
  • 业务响应数据

响应码接口

考虑后期业务优化和拓展,定义返回响应码接口,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 业务响应码接口
*/
public interface IResultCode extends Serializable {

/**
* 消息
*
* @return String
*/
String getMessage();

/**
* 状态码
*
* @return int
*/
int getCode();

}

返回响应码实现

为了后期方便维护,定义枚举维护响应码,此处的响应码使用Java Http中定义的响应码,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 业务代码枚举
*
* @author
*/
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode {

/**
* 操作成功
*/
SUCCESS(HttpServletResponse.SC_OK, "操作成功"),

/**
* 业务异常
*/
FAILURE(HttpServletResponse.SC_BAD_REQUEST, "业务异常"),

/**
* 请求未授权
*/
UN_AUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "请求未授权"),

/**
* 服务器异常
*/
INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器异常"),

/**
* 缺少必要的请求参数
*/
PARAM_MISS(HttpServletResponse.SC_BAD_REQUEST, "缺少必要的请求参数"),

/**
* 请求参数类型错误
*/
PARAM_TYPE_ERROR(HttpServletResponse.SC_BAD_REQUEST, "请求参数类型错误"),

/**
* 请求参数绑定错误
*/
PARAM_BIND_ERROR(HttpServletResponse.SC_BAD_REQUEST, "请求参数绑定错误"),

/**
* 参数校验失败
*/
PARAM_VALID_ERROR(HttpServletResponse.SC_BAD_REQUEST, "参数校验失败"),
;

/**
* code编码
*/
final int code;
/**
* 中文信息描述
*/
final String message;

}

定义统一结果类

注意

  1. 外部只能调用统一返回类的方法,不可以直接创建
  2. 内置静态方法返回对象。
  3. 使用链式编程,方便开发(方法都返回对象本身return this;)。
  4. 业务响应信息类型由实际使用者定义(使用泛型接收)。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Getter
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
private int code; // 响应码
private boolean success; // 是否成功
private T data; // 承载数据
private String message; // 响应码描述
/**
* 构造函数
* @param resultCode 响应码
* @param data 承载数据
* @param message 响应码描述
*/
private R(IResultCode resultCode,T data,String message){
this.code = resultCode.getCode();
this.data = data;
this.message = message==null?resultCode.getMessage():message;
this.success = this.code == ResultCode.SUCCESS.code;
}
private R(IResultCode resultCode,T data){
this(resultCode,data,null);
}
private R(IResultCode resultCode){
this(resultCode,null,null);
}
private R(IResultCode resultCode,String message){
this(resultCode,null,message);
}

// 成功响应方法,成功方法不支持自定义响应码
public static <T> R<T> success(){ return new R<T>(ResultCode.SUCCESS); }
public static <T> R<T> success(T data){ return new R<>(ResultCode.SUCCESS,data); }
public static <T> R<T> success(T data,String message){ return new R<>(ResultCode.SUCCESS,data,message); }

// 失败响应方法
public static <T> R<T> fail(){ return new R<>(ResultCode.FAILURE); }
public static <T> R<T> fail(String message){ return new R<>(ResultCode.FAILURE,message); }
public static <T> R<T> fail(IResultCode resultCode){ return new R<>(resultCode); }
public static <T> R<T> fail(IResultCode resultCode,String message){ return new R<>(resultCode,message); }

// 特殊需求使用方法
public static <T> R<T> instance(ResultCode resultCode,T data,String message){ return new R<>(resultCode,data,message); }

// 处理对象方法
public R message(String message){
this.message = message;
return this;
}
public R success(boolean success){
this.success = success;
return this;
}
public R data(T data){
this.data = data;
return this;
}
}

测试R类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RTest {
public static void main(String[] args) {
System.out.println("\n——————————————————成功————————————————————————");
System.out.println("无承载数据成功__"+ JSON.toJSONString(R.success()));
System.out.println("承载数据成功__"+JSON.toJSONString(R.success(new String[]{"承载数据"})));
System.out.println("承载数据并修改描述成功__"+JSON.toJSONString(R.success(new String[]{"承载数据"},"修改的描述信息")));

System.out.println("\n——————————————————失败————————————————————————");
System.out.println("默认失败__"+JSON.toJSONString(R.fail()));
System.out.println("修改错误信息失败__"+JSON.toJSONString(R.fail("修改后的错误信息")));
System.out.println("修改ResultCode(缺失参数)失败__"+JSON.toJSONString(R.fail(ResultCode.PARAM_MISS)));
System.out.println("修改ResultCode并自定义信息失败__"+JSON.toJSONString(R.fail(ResultCode.PARAM_MISS,"请检查你的请求参数")));

System.out.println("\n——————————————————特殊————————————————————————");
System.out.println("自定义__"+JSON.toJSONString(R.instance(ResultCode.SUCCESS,new String[]{"承载数据"},"自定义的返回信息")));

System.out.println("\n——————————————————创建后修改————————————————————————");
System.out.println("修改消息__"+R.success().message("创建之后修改的消息"));
System.out.println("修改数据__"+R.success("11").data("创建之后修改的承载数据"));
System.out.println("修改成功标识__"+R.success().success(false));
}
}

优化

现存在的问题

每个Controller中的method在处理结束后都需要返回R.success();与实际业务并无关系,属于冗余代码。

问题处理

利用ControllerAdvice重写response,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ControllerAdvice
public class RespReultHandler implements ResponseBodyAdvice<Object> {
// 需要忽略的方法(特殊接口特殊处理)
private static final Class<? extends Annotation> IGNORE_ANNOTATION_TYPE = IgnoreRespResult.class;
@Override
// 是否处理,当类与方法上不存在IgnoreRespResult注解时处理
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return !AnnotatedElementUtils.hasAnnotation(methodParameter.getContainingClass(),IGNORE_ANNOTATION_TYPE)
&& !methodParameter.hasMethodAnnotation(IGNORE_ANNOTATION_TYPE);
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 防止重复包裹的问题出现
if (o instanceof R) {
return o;
}
return R.success(o);
}
}

IgnoreRespResult代码如下

1
2
3
4
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface IgnoreRespResult { }

注意:这里可能会有其他问题
当返回值是String时,会出现java.lang.ClassCastException: com.**.R cannot be cast to java.lang.String,这是由于默认消息处理类不支持,解决方式如下,加入以下配置后可解决此问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 自定义消息处理配置
@Configuration
@AllArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MessageConfiguration implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter messageConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.QuoteFieldNames,
SerializerFeature.WriteMapNullValue,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteDateUseDateFormat,
SerializerFeature.WriteNullStringAsEmpty);
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_JSON);
messageConverter.setSupportedMediaTypes(mediaTypeList);
messageConverter.setFastJsonConfig(fastJsonConfig);
converters.add(0, messageConverter);
}
}

备注

本篇使用到的maven插件有

  • lombok 简化代码
  • javax.servlet-api HTTP 状态码
  • fastjson 测试是输出json格式
  • 参考自MybatisPlus中R的设计

源码链接GitHub