目录
1. 实现目标 2. 统一状态码 3. 统一响应体 4. 统一异常 5. 统一入参校验 6. 统一返回结果 7. 统一异常处理 8. 验证
1. 实现目标
优雅校验接口入参 响应体格式统一处理 异常统一处理
2. 统一状态码
创建状态码接口,所有状态码必须实现这个接口,统一标准
package com. example. mavendemo. enums ;
public interface StatusCode {
int getCode ( ) ;
String getMsg ( ) ;
}
package com. example. mavendemo. enums ;
import lombok. AllArgsConstructor ;
import lombok. Getter ;
@Getter
@AllArgsConstructor
public enum ResponseCode implements StatusCode {
SUCCESS ( 0 , "请求成功" ) ,
FAILED ( 1 , "请求失败" ) ,
VALIDATE_ERROR ( 2 , "参数校验失败" ) ,
RESPONSE_ERROR ( 3 , "返回失败" ) ,
APP_ERROR ( 40000 , "服务内部异常" ) ,
BUSINESS_ERROR ( 40001 , "业务异常" ) ;
private int code;
private String msg;
}
3. 统一响应体
响应结果统一使用ResultResponse进行包装,返回给前端
package com. example. mavendemo. dto ;
import com. example. mavendemo. enums. ResponseCode ;
import com. example. mavendemo. enums. StatusCode ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
@Data
@AllArgsConstructor
public class ResultResponse {
private int code;
private String msg;
private Object data;
public static ResultResponse ok ( ) {
return new ResultResponse ( ResponseCode . SUCCESS . getCode ( ) , ResponseCode . SUCCESS . getMsg ( ) , null ) ;
}
public static ResultResponse ok ( Object data) {
return new ResultResponse ( ResponseCode . SUCCESS . getCode ( ) , ResponseCode . SUCCESS . getMsg ( ) , data) ;
}
public static ResultResponse error ( StatusCode statusCode) {
return new ResultResponse ( statusCode. getCode ( ) , statusCode. getMsg ( ) , null ) ;
}
public static ResultResponse error ( StatusCode statusCode, Object data) {
return new ResultResponse ( statusCode. getCode ( ) , statusCode. getMsg ( ) , data) ;
}
}
4. 统一异常
package com. example. mavendemo. exception ;
import com. example. mavendemo. enums. ResponseCode ;
import com. example. mavendemo. enums. StatusCode ;
import lombok. Getter ;
@Getter
public class ApiException extends RuntimeException {
private StatusCode statusCode;
public ApiException ( String message) {
super ( message) ;
this . statusCode = ResponseCode . APP_ERROR ;
}
public ApiException ( StatusCode statusCode, String message) {
super ( message) ;
this . statusCode = statusCode;
}
}
5. 统一入参校验
< dependency>
< groupId> org.hibernate</ groupId>
< artifactId> hibernate-validator</ artifactId>
< version> 6.1.7.Final</ version>
</ dependency>
定义实体类,对于需要校验的字段添加注解,例如@NotBlank,@Max
package com. example. mavendemo. model ;
import javax. validation. constraints. Max ;
import javax. validation. constraints. NotBlank ;
import lombok. Data ;
@Data
public class User {
@NotBlank ( message = "姓名不能为空" )
private String name;
@Max ( value = 18 , message = "年龄不能大于18" )
private Integer age;
private String address;
}
在传参时,使用@Valid,例如@RequestBody @Valid User user
6. 统一返回结果
如果不想统一响应体,可以定义一个注解,标记具体的接口进行排除
package com. example. mavendemo. annotation ;
import java. lang. annotation. ElementType ;
import java. lang. annotation. Retention ;
import java. lang. annotation. RetentionPolicy ;
import java. lang. annotation. Target ;
@Target ( { ElementType . METHOD } )
@Retention ( RetentionPolicy . RUNTIME )
public @interface NotResponseAdvice {
}
实现ResponseBodyAdvice接口,重写supports和beforeBodyWrite方法,完成对response body的增强
package com. example. mavendemo. advice ;
import com. example. mavendemo. annotation. NotResponseAdvice ;
import org. springframework. core. MethodParameter ;
import org. springframework. http. MediaType ;
import org. springframework. http. converter. HttpMessageConverter ;
import org. springframework. http. server. ServerHttpRequest ;
import org. springframework. http. server. ServerHttpResponse ;
import org. springframework. web. bind. annotation. RestControllerAdvice ;
import org. springframework. web. servlet. mvc. method. annotation. ResponseBodyAdvice ;
import com. example. mavendemo. dto. ResultResponse ;
@RestControllerAdvice ( basePackages = { "com.example.mavendemo.controller" } )
public class ControllerResponseAdvice implements ResponseBodyAdvice < Object > {
@Override
public boolean supports ( MethodParameter returnType, Class < ? extends HttpMessageConverter < ? > > converterType) {
return ! ( returnType. getParameterType ( ) . isAssignableFrom ( ResultResponse . class )
|| returnType. hasMethodAnnotation ( NotResponseAdvice . class ) ) ;
}
@Override
public Object beforeBodyWrite ( Object body, MethodParameter returnType, MediaType selectedContentType,
Class < ? extends HttpMessageConverter < ? > > selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
return ResultResponse . ok ( body) ;
}
}
这里需要注意String类型的返回值,使用上面的代码会报错java.lang.ClassCastException: cannot be cast to java.lang.String 需要介绍一下HttpMessageConverter接口,负责将请求信息转换为一个java对象,将java对象输出为响应信息;所以在触发@ResponseBody注解时,Spring都会遍历这个HttpMessageConverter列表,然后选择第一个符合返回值类型的转换器然后进行转换。 HttpMessageConverter如下
String类型会优先使用StringHttpMessageConverter转换器。实际上String类型既可以使用MappingJackson2HttpMessageConverter,也可以使用StringHttpMessageConverter来解析。所以可以将HttpMessageConverter列表反转,调换MappingJackson2HttpMessageConverter和StringHttpMessageConverter的顺序来解决。 实现WebMvcConfigurer,重写configureMessageConverters方法
package com. example. mavendemo. advice ;
import java. util. Collections ;
import java. util. List ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. http. converter. HttpMessageConverter ;
import org. springframework. web. servlet. config. annotation. WebMvcConfigurer ;
@Configuration
public class WebResponseConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters ( List < HttpMessageConverter < ? > > converters) {
Collections . reverse ( converters) ;
}
}
7. 统一异常处理
使用@RestControllerAdvice注解对Controller进行增强,配合@ExceptionHandler使用,统一处理异常
package com. example. mavendemo. advice ;
import com. example. mavendemo. exception. ApiException ;
import org. springframework. web. bind. MethodArgumentNotValidException ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. bind. annotation. RestControllerAdvice ;
import com. example. mavendemo. dto. ResultResponse ;
import com. example. mavendemo. enums. ResponseCode ;
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler ( MethodArgumentNotValidException . class )
public ResultResponse methodArgumentNotValidExceptionHandler ( MethodArgumentNotValidException e) {
return ResultResponse . error ( ResponseCode . VALIDATE_ERROR ,
e. getBindingResult ( ) . getAllErrors ( ) . get ( 0 ) . getDefaultMessage ( ) ) ;
}
@ExceptionHandler ( ApiException . class )
public ResultResponse apiExceptionHandler ( ApiException e) {
return ResultResponse . error ( e. getStatusCode ( ) , e. getMessage ( ) ) ;
}
@ExceptionHandler ( Exception . class )
public ResultResponse exceptionHandler ( Exception e) {
return ResultResponse . error ( ResponseCode . FAILED , e. getMessage ( ) ) ;
}
}
8. 验证
package com. example. mavendemo. controller ;
import javax. validation. Valid ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. PostMapping ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RestController ;
import com. example. mavendemo. annotation. NotResponseAdvice ;
import com. example. mavendemo. enums. ResponseCode ;
import com. example. mavendemo. exception. ApiException ;
import com. example. mavendemo. model. User ;
@RestController
public class HelloController {
@PostMapping ( "/hello" )
public User hello ( @RequestBody @Valid User user) {
return user;
}
@GetMapping ( "/name" )
public String getName ( String name) {
return name;
}
@NotResponseAdvice
@GetMapping ( "/findUser" )
public User getAddress ( String name, Integer age) {
if ( age > 18 ) {
throw new ApiException ( ResponseCode . BUSINESS_ERROR , "查无此人" ) ;
}
User user = new User ( ) ;
user. setName ( name) ;
user. setAge ( age) ;
user. setAddress ( "北京市王府井大街1号" ) ;
return user;
}
}
返回值是java对象
返回值是String类型