目录
- 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. 统一入参校验
- 需要引入spring-boot-starter-validation
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.6</version><relativePath/> </parent><groupId>com.example</groupId><artifactId>maven-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>maven-demo</name><description>maven-demo</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals><goal>repackage</goal></goals></execution></executions><configuration><includeSystemScope>true</includeSystemScope><mainClass>com.example.mavendemo.MavenDemoApplication</mainClass></configuration></plugin></plugins></build>
</project>
- 定义实体类,对于需要校验的字段添加注解,例如@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> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return !(returnType.getParameterType().isAssignableFrom(ResultResponse.class)|| returnType.hasMethodAnnotation(NotResponseAdvice.class));}@Overridepublic 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 {@Overridepublic 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类型