统一返回格式
定义一个业务 CODE 枚举类
public enum ResultCodeEnum {
SUCCESS(1, "成功"),
//参数错误
ILLEGAL_PARAMETER(10001, "非法参数");
private Integer code;
private String msg;
ResultCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
复制代码
定义统一返回类
public class R<T> implements Serializable {
private T data;
private Integer code;
private String msg;
public static <T> R<T> ok(T data) {
R<T> r = new R<>();
r.setData(data);
return r;
}
public static <T> R<T> ok(ResultCodeEnum codeEnum, T data) {
R<T> r = new R<>();
r.setData(data);
r.setCode(codeEnum.getCode());
r.setMsg(codeEnum.getMsg());
return r;
}
public static <T> R<T> fail(Integer code, String msg) {
R<T> r = new R<>();
r.setCode(code);
r.setMsg(msg);
return r;
}
public static <T> R<T> fail(ResultCodeEnum codeEnum) {
R<T> r = new R<>();
r.setCode(codeEnum.getCode());
r.setMsg(codeEnum.getMsg());
return r;
}
//省略部分代码
}
复制代码
这样每个接口都需要使用 R.ok()包裹返回值,下面我们通过一些配置来实现自动包裹。
定义一个封装注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Result {
}
复制代码
ResponseBodyAdvice 接口
Allows customizing the response after the execution of an @ResponseBody or a ResponseEntity controller method but before the body is written with an HttpMessageConverter.
复制代码
这个接口作用在 Controller 返回结果之后和 written with an HttpMessageConverter 之前。
true if beforeBodyWrite should be invoked; false otherwise
复制代码
重写 supports 方法 ,返回 true 调用 beforeBodyWrite 。
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//方法所在class
Class<?> methodClass = returnType.getContainingClass();
//获取注解
Result r = methodClass.getAnnotation(Result.class);
//如果不存在不处理
if (ObjectUtils.isEmpty(r)) {
return false;
}
//如果已经是R返回false,如果不是R返回true
boolean assignableFrom = returnType.getParameterType().isAssignableFrom(R.class);
return !assignableFrom;
}
复制代码
当接口返回 String 类型报错如下
R cannot be cast to java.lang.String
at org.springframework.http.converter.StringHttpMessageConverter.addDefaultHeaders
复制代码
Invoked after an HttpMessageConverter is selected and just before its write method is invoked.
复制代码
beforeBodyWrite 调用在 HttpMessageConverter 选择后和 write 方法调用之前。
重写 beforeBodyWrite 方法
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof String) {
try {
ObjectMapper objectMapper = new ObjectMapper();
//修改返回类型
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
R<Object> r = R.ok(body);
return objectMapper.writeValueAsString(r);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return R.ok(body);
}
复制代码
到这里我们就不需要使用 R.ok 来包裹返回类型了。
selectedContentType 和 selectedConverterType 是选定的 ContentType 和 ConverterType
打印一下这两个值
logger.info("selectedContentType:" + selectedContentType);
logger.info("selectedConverterType:" + selectedConverterType);
复制代码
输出
//String
selectedConverterType:class org.springframework.http.converter.StringHttpMessageConverter
selectedContentType:text/html
//其他
selectedContentType:application/json
selectedConverterType:class org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
复制代码
那么谁调用的 beforeBodyWrite 这个方法呢 ?
beforeBodyWrite 调用过程
在方法内部打一个断点定位到 RequestResponseBodyAdviceChain,processBody 中 141 行调用 beforeBodyWrite
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
复制代码
beforeBodyWrite 中 116 行调用 processBody
return processBody(body, returnType, contentType, converterType, request, response);
复制代码
AbstractMessageConverterMethodProcessor 中 268 行调用了 beforeBodyWrite
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
复制代码
全局异常处理
迭代到这里,只需要在类上加入 @Result 注解就可以实现接口的封装处理,那么异常的情况呢?
在 ResultBodyAdvice 中加入这一段代码,通过 code 和 msg 来统一处理异常。
@ExceptionHandler(Exception.class)
public R runtimeExceptionHandle(Throwable e) {
if (RuntimeException.class.equals(e.getClass())) {
return R.fail(ResultCodeEnum.SERVER_ERROR);
} else if (IllegalArgumentException.class.equals(e.getClass())) {
return R.fail(ResultCodeEnum.ILLEGAL_PARAMETER);
} else {
return R.fail(3000, "其他错误");
}
}
复制代码
参数校验
目的:消灭 if 条件判断。
约定:Get 请求不传 body,Post 请求通过 body 传参。RequestParam 绑定 url 参数,RequestBody 绑定 body 体中参数。
@RequestParam("id") Integer id
@RequestBody String name
复制代码
@Validated
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
复制代码
public class Hello6VO {
@Min(value = 10, message = "最小值10")
private Integer id;
@Min(value = 5, message = "最小值5")
private Integer id2;
}
复制代码
@GetMapping("hello6")
public void hello6(@Validated Hello6VO vo) {
System.out.println(vo.getId());
}
@PostMapping("hello7")
public void hello7(@RequestBody @Validated Hello6VO vo) {
System.out.println(vo.getId());
}
复制代码
捕获失败异常
else if (e instanceof org.springframework.validation.BindException) {
BindException exception = (org.springframework.validation.BindException) e;
//优先处理字段错误
List<FieldError> fieldErrorsList = exception.getBindingResult().getFieldErrors();
List<String> fieldErrorMessageList = new ArrayList<>(fieldErrorsList.size());
//获取默认提示信息列表
for (FieldError fieldError : fieldErrorsList) {
//字段名
String field = fieldError.getField();
//提示信息
String message = fieldError.getDefaultMessage();
//拒绝的值
Object rejectedValue = fieldError.getRejectedValue();
String str = "字段:" + field + " 提示信息:" + message + " 拒绝的值:" + rejectedValue;
fieldErrorMessageList.add(str);
}
return R.fail(ResultCodeEnum.VALIDATE_FAILED.getCode(), fieldErrorMessageList.toString());
}
复制代码
输出
{
"data": null,
"code": 1002,
"msg": "[字段:id 提示信息:最小值10 拒绝的值:1, 字段:id2 提示信息:最小值5 拒绝的值:2]"
}
复制代码
评论