目录
- 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
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.6version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>maven-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>maven-demoname>
<description>maven-demodescription>
<properties>
<java.version>8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
<configuration>
<includeSystemScope>trueincludeSystemScope>
<mainClass>com.example.mavendemo.MavenDemoApplicationmainClass>
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> {
@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类型