统一返回格式
定义一个业务 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)@Documentedpublic @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);
复制代码
输出
//StringselectedConverterType:class org.springframework.http.converter.StringHttpMessageConverterselectedContentType:text/html//其他selectedContentType:application/jsonselectedConverterType: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]"}
复制代码
评论