欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式

程序员文章站 2022-06-28 17:10:29
一、前言这篇博客主要介绍以下几个方面:通过Validator来方便快捷地完成参数的校验工作;通过全局异常处理来完成异常操作的规范;通过数据统一响应来完成响应数据的规范;通过自定义注解,过滤返回的响应体是否需要进行统一包装。二、搭建spring boot项目项目结构如下:三、导入maven依赖 org.springframework.boot...

一、前言

这篇博客主要介绍以下几个方面:

  • 通过Validator来方便快捷地完成参数的校验工作;
  • 通过全局异常处理来完成异常操作的规范;
  • 通过数据统一响应来完成响应数据的规范;
  • 通过自定义注解,过滤返回的响应体是否需要进行统一包装。

二、搭建spring boot项目

项目结构如下:

spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式

三、导入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;
}

五、定义统一的接口返回响应数据

  1. 简单定义一个返回结果状态码的枚举类:
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;
    }
}
  1. 定义返回结果Vo类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultVo<T> {
    /**
     * 状态响应码
     */
    private int code;

    /**
     * 响应信息
     */
    private String message;

    /**
     * 响应数据
     */
    private T data;
}
  1. 定义一个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报错的情况。

七、自定义注解绕过全局统一响应数据处理

在实际工作中,可能有些接口的返回结果是不需要进行统一响应数据处理的,这种情况,我们可以借助自定义注解实现。

  1. 自定义一个标识注解:
/**
 * 标识注解,使用此注解标识方法返回结果不需要进行统一包装
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //该注解适用于方法上
@Documented
public @interface NoAdapterResult {
}
  1. 修改全局统一处理响应数据的条件的匹配条件:
    @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);
    }
}
  1. 测试saveUser方法——合法参数
    spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
  2. 测试saveUser方法——非法参数
    spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
  3. 测试getUser方法,自动返回统一响应的数据格式
    spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
  4. 测试getOriginalUser方法,使用@NoAdapterResult注解,不会被包装成统一响应格式
    spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式

九、总结

到此,整个体系构建基本完成了,不过案例中的构建代码十分简单,主要是用于记录我自己的一种思路,平常用于开发的体系应该更加完善和健全。比如可以自定义异常,增加响应码枚举类类型等等,都是可以完善的地方。

本文地址:https://blog.csdn.net/C_AJing/article/details/109615005