spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
程序员文章站
2022-04-15 19:04:37
一、前言这篇博客主要介绍以下几个方面:通过Validator来方便快捷地完成参数的校验工作;通过全局异常处理来完成异常操作的规范;通过数据统一响应来完成响应数据的规范;通过自定义注解,过滤返回的响应体是否需要进行统一包装。二、搭建spring boot项目项目结构如下:三、导入maven依赖 org.springframework.boot ...
一、前言
这篇博客主要介绍以下几个方面:
- 通过Validator来方便快捷地完成参数的校验工作;
- 通过全局异常处理来完成异常操作的规范;
- 通过数据统一响应来完成响应数据的规范;
- 通过自定义注解,过滤返回的响应体是否需要进行统一包装。
二、搭建spring boot项目
项目结构如下:
三、导入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
注意:spring boot2.3.X版本需要手动导入validation依赖。
四、定义实体类+参数校验
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
@NotNull(message = "用户名不能为空")
@Size(max = 100, message = "用户名过长")
private String username;
@NotNull(message = "用户年龄不能为空")
@Min(value = 0, message = "年龄太小了")
@Max(value = 200, message = "年龄太大了")
private Integer age;
}
五、定义统一的接口返回响应数据
- 简单定义一个返回结果状态码的枚举类:
public enum ResultCode {
/**
* 成功
*/
SUCCESS(200, "success"),
/**
* 失败
*/
FAIL(500, "error");
private int code;
private String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
- 定义返回结果Vo类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultVo<T> {
/**
* 状态响应码
*/
private int code;
/**
* 响应信息
*/
private String message;
/**
* 响应数据
*/
private T data;
}
- 定义一个ResultUtils工具类,方便ResultVo的使用:
public class ResultUtils {
//私有化构造函数
private ResultUtils() {
}
/**
* 返回result信息
*
* @param code
* @param message
* @param data
* @param <T>
* @return
*/
public static <T> ResultVo result(int code, String message, T data) {
return new ResultVo<>(code, message, data);
}
/**
* 返回成功信息
*
* @return
*/
public static ResultVo success() {
return result(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
}
/**
* 返回成功信息
*
* @return
*/
public static ResultVo success(String message) {
return result(ResultCode.SUCCESS.getCode(), message, null);
}
/**
* 返回成功信息
*
* @return
*/
public static <T> ResultVo success(T data) {
return result(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 返回成功信息
*
* @return
*/
public static <T> ResultVo success(String message, T data) {
return result(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 返回失败信息
*
* @return
*/
public static ResultVo fail() {
return result(ResultCode.FAIL.getCode(), ResultCode.FAIL.getMessage(), null);
}
/**
* 返回失败信息
*
* @return
*/
public static ResultVo fail(String message) {
return result(ResultCode.FAIL.getCode(), message, null);
}
/**
* 返回失败信息
*
* @return
*/
public static <T> ResultVo fail(T data) {
return result(ResultCode.FAIL.getCode(), ResultCode.FAIL.getMessage(), data);
}
/**
* 返回失败信息
*
* @return
*/
public static <T> ResultVo fail(String message, T data) {
return result(ResultCode.FAIL.getCode(), message, data);
}
}
六、全局异常处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理Exception异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultVo exceptionHandler(Exception e) {
log.error("异常", e);
return ResultUtils.fail(e.getMessage());
}
/**
* 处理Exception异常
*
* @param e
* @return
*/
@ExceptionHandler(UnexpectedTypeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultVo exceptionHandler(UnexpectedTypeException e) {
log.error("校验异常", e);
return ResultUtils.fail(e.getMessage());
}
}
这里全局异常也只是进行了简单处理,如果想要功能更完善,可以增加更多处理逻辑。
六、全局统一处理响应数据
想一下,平时开发那么多接口,如果没有严格按照ResultVo的格式返回数据,修改的话,只能一个一个去修改,这样十分麻烦,我们可以进行全局处理,如下:
@RestControllerAdvice
@Slf4j
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
//判断是否要执行beforeBodyWrite方法,true为执行,false不执行
public boolean supports(MethodParameter methodParameter, Class aClass) {
// 如果返回本身是ResultVo类型,则没有必要包装,如果不是,才需要包装
return !methodParameter.getParameterType().equals(ResultVo.class);
}
@Override
//对response处理的执行方法
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// String类型不能直接包装,所以要进行些特别的处理
if (methodParameter.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
// 将数据包装在ResultVO里后,再转换为json字符串响应给前端
try {
return objectMapper.writeValueAsString(ResultUtils.success(o));
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("json转换出现异常!", e);
throw new RuntimeException(e.getMessage());
}
}
// 将原本的数据包装在ResultVO里
return ResultUtils.success(o);
}
}
注意:项目中使用ResponseBodyAdvice同一封装返回格式,对于一般的类型都没有问题,但是处理字符串时,会有类型转换的问题,对于字符串的ContentType是“text-plain”,ConverterType是StringHttpMessageConverter这个类型转换,由于将结果封装成了自定义的Result类型,所以会出现转换成String报错的情况。
七、自定义注解绕过全局统一响应数据处理
在实际工作中,可能有些接口的返回结果是不需要进行统一响应数据处理的,这种情况,我们可以借助自定义注解实现。
- 自定义一个标识注解:
/**
* 标识注解,使用此注解标识方法返回结果不需要进行统一包装
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //该注解适用于方法上
@Documented
public @interface NoAdapterResult {
}
- 修改全局统一处理响应数据的条件的匹配条件:
@Override
//判断是否要执行beforeBodyWrite方法,true为执行,false不执行
public boolean supports(MethodParameter methodParameter, Class aClass) {
// 如果返回本身是ResultVo类型,则没有必要包装,如果不是,才需要包装 增加判断是否有@NoAdapterResult注解,没有才进行包装
return !methodParameter.getParameterType().equals(ResultVo.class) && !methodParameter.hasMethodAnnotation(NoAdapterResult.class);
}
八、测试
为了方便测试,定义一个用于测试的UserController类:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/save")
public ResultVo<User> saveUser(@Validated @RequestBody User user) {
return ResultUtils.success(user);
}
@GetMapping("get/{id}")
public User getUser(@PathVariable("id") Long id) {
return new User(id, "jack", 25);
}
/**
* 得到没有经过统一响应结果包装的user
*
* @param id
* @return
*/
@GetMapping("getorg/{id}")
@NoAdapterResult
public User getOriginalUser(@PathVariable("id") Long id) {
return new User(id, "tom", 24);
}
}
- 测试saveUser方法——合法参数
- 测试saveUser方法——非法参数
- 测试getUser方法,自动返回统一响应的数据格式
- 测试getOriginalUser方法,使用@NoAdapterResult注解,不会被包装成统一响应格式
九、总结
到此,整个体系构建基本完成了,不过案例中的构建代码十分简单,主要是用于记录我自己的一种思路,平常用于开发的体系应该更加完善和健全。比如可以自定义异常,增加响应码枚举类类型等等,都是可以完善的地方。
本文地址:https://blog.csdn.net/C_AJing/article/details/109615005