史上最简单 springboot @Body Bean 整合 validation 入参校验框架 统一结果返回 异常处理(优雅)

话不多说,直接上代码,优雅就完事儿了

一、依赖:



    org.projectlombok
    lombok
    1.18.10



    javax.validation
    validation-api
    2.0.1.Final


    org.hibernate
    hibernate-validator
    6.0.1.Final

二、代码:

1、controller 代码:

在入参 Bean 前添加此注解 @Validated

import com.pph.model.request.ValidatedDemoReq;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author pph
 * @date 2020/5/12 17:45
 * @description
 */
@RestController
@RequestMapping("/validation")
public class ValidationTestController {

    /**
     * Bean 入参校验框架测试
     *
     * @param req 入参
     * @return 结果
     */
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public Object test(@RequestBody @Validated ValidatedDemoReq req) {
        return req.toString();
    }

    /**
     * Bean 入参校验框架测试(分组)
     *
     * @param req 入参
     * @return 结果
     */
    @RequestMapping(value = "/test/group", method = RequestMethod.GET)
    public Object test(@RequestBody @Validated(value = GroupTest.class) ValidatedDemoReq req) {
        return req.toString();
    }
}

2、自定义注解:

实现 ConstraintValidator 接口并引用到注解中

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static java.util.stream.Collectors.toList;

/**
 * @author pph
 * @date 2020/5/12 13:57
 * @description 校验只能为指定的某些值
 */
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = Contains.ContainsValidator.class)
public @interface Contains {

    String message() default "必须传入 contains 指定其中的某个值";

    Class[] groups() default {};

    Class[] payload() default {};

    /**
     * 指定值数值
     *
     * @return 数组结果
     */
    String[] contains() default {};

    /**
     * 是否区分大小写
     *
     * @return 结果
     */
    boolean isCaseSensitive() default true;

    /**
     * 是否必传
     *
     * @return 结果
     */
    boolean isMust() default true;

    final class ContainsValidator implements ConstraintValidator {
        /**
         * 指定值集合
         */
        private List contains;
        /**
         * 是否区分大小写
         */
        private boolean isCaseSensitive = true;
        /**
         * 是否必传
         */
        private boolean isMust = true;

        @Override
        public void initialize(Contains constraintAnnotation) {
            // 数组去重转集合
            this.contains = Arrays.stream(constraintAnnotation.contains())
                    .distinct()
                    .collect(toList());
            this.isCaseSensitive = constraintAnnotation.isCaseSensitive();
            this.isMust = constraintAnnotation.isMust();
        }

        @Override
        public boolean isValid(String str, ConstraintValidatorContext context) {
            // 非必传时 str 为 null 默认通过
            if (!isMust
                    && Objects.isNull(str)) {
                return true;
            }
            // contains 不能指定 null,当 str 为 null 直接返回异常信息
            if (Objects.isNull(str)) {
                return false;
            }
            // 未指定值时,默认通过
            if (contains.isEmpty()) {
                return true;
            }
            // 不区分大小写,将所有字符串转大写再作比较
            if (!isCaseSensitive) {
                contains = contains.stream()
                        .map(String::toUpperCase)
                        .collect(toList());
                return contains.contains(str.toUpperCase());
            }
            return contains.contains(str);
        }
    }
}

3、model 使用:

import com.pph.model.annotation.validation.Contains;
import lombok.Data;
import org.hibernate.validator.constraints.Range;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;

/**
 * @author pph
 * @date 2020/5/11 15:13
 * @description Validated 基本使用介绍
 */
@Data
public class ValidatedDemoReq {
    /**
     * 坑:不要跨类型使用注解,否则异常信息无法正常被显示,错误难以排查
     * 

* 常用自带注解与描述 * 1 | @Null | 被注释的元素必须为 null * 2 | @NotNull | 被注释的元素必须不为 null(必传字段) * 3 | @AssertTrue | 被注释的元素必须为 true * 4 | @AssertFalse | 被注释的元素必须为 false * 5 | @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 * 6 | @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 * 7 | @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 * 8 | @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 * 9 | @Size(max, min) | 被注释的元素的大小必须在指定的范围内 * 10 | @Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 * 11 | @Past | 被注释的元素必须是一个过去的日期 * 12 | @Future | 被注释的元素必须是一个将来的日期 * 13 | @Pattern(value) | 被注释的元素必须符合指定的正则表达式 * 14 | @Email | 被注释的元素必须是电子邮箱地址 * 15 | @Length | 被注释的字符串的大小必须在指定的范围内 * 16 | @NotEmpty | 被注释的字符串的必须非空 * 17 | @Email | 被注释的元素必须是电子邮箱地址 * 18 | @Range | 被注释的元素必须在合适的范围内 * 19 | @NotBlank | 验证字符串非null,且长度必须大于 0 * 20 * | @Valid | 注意: 集合嵌套对象必须这样 List<@Valid Object>, 否则 Object 里的注解不生效 *

* 自定义注解(详情可以(ctrl + 左键) 点击跳转了解): * * @see com.pph.model.annotation.validation.Contains */ @Null(message = "id must be null!") private Long id; @NotBlank(message = "name can not be blank!") @Pattern(regexp = "^[a-zA-Z]\\w{5,31}", message = "name 只能为英文或数字,且不能以数字开头,长度为 6-32 个字符") private String name; @NotNull(message = "手机号为必传字段") @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^[1][3-9][0-9]{9}$", message = "手机号格式有误") private String mobile; /** * 这是一个坑坑坑,注意 * 不要将 @Size 作用于 Long 等数值类型包装类, * 否则框架捕获不到参数异常 MethodArgumentNotValidException.class, * 从而走 Exception.class 异常信息返回 * 如果要指定数值类型大小请使用 @Range(min = 1, max = 100, message = "range 只能取 1-100") */ @Size(min = 6, max = 20, message = "size 只能取 6-20 个字符") private String size; @Range(min = 1, max = 100, message = "range 只能取 1-100") private Long range; @AssertTrue(message = "assertTrue must be true!") private Boolean assertTrue; @AssertFalse(message = "assertFalse must be true!") private Boolean assertFalse; @Min(value = 1, message = "min minimum must be 1!") private Long min; @Max(value = 100, message = "max maximum must be 1!") private Long max; @Digits(integer = 2, fraction = 2, message = "只能为 2 位数, 小数点只能保留 2 位!") private Double digits; /** * 集合中嵌套对象时使用此注解,否则集合中的对象不会校验 */ private List<@Valid ValidatedDemoReq> lists; @Contains(contains = {"str1", "str2", "str3"}, isCaseSensitive = false, isMust = false, message = "contains 必须为指定的值") private String contains; // ------------------------- group 分组使用 @NotBlank(message = "group1 can not be blank!", groups = GroupTest.class) private String group1; @NotBlank(message = "group2 can not be blank!", groups = GroupTest.class) private String group2; }

创建分组接口(只需要继承Default 接口即可)

import javax.validation.groups.Default;

/**
 * @author pph
 * @date 2020/5/12 17:07
 * @description 入参校验分组测试
 */
public interface GroupTest extends Default {
}

一、统一结果处理:

1、工具类

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author pph
 * @date 2020/5/11 10:08
 * @description API 统一结果返回
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Results implements Serializable {
    /**
     * 请求返回码
     */
    private Integer code;
    /**
     * 请求返回信息
     */
    private String message;
    /**
     * 请求是否成功
     */
    private Boolean success;
    /**
     * 请求返回数据
     */
    private T data;
    /**
     * 当前时间戳
     */
    private Long timestamp = System.currentTimeMillis();

    public Results(Integer code, String message, Boolean success, T data) {
        this.code = code;
        this.message = message;
        this.success = success;
        this.data = data;
    }

    /**
     * 正常结果返回
     *
     * @param data 返回数据
     * @param   返回结果泛型
     * @return result
     */
    public static  Results success(T data) {
        return new Results<>(200, "请求成功!", true, data);
    }

    /**
     * 异常结果返回
     *
     * @param message 异常信息
     * @return result
     */
    public static Results error(String message) {
        return new Results<>(500, message, false, null);
    }

    /**
     * 异常结果返回
     *
     * @param code    结果码
     * @param message 异常信息
     * @return result
     */
    public static Results error(Integer code, String message) {
        return new Results<>(code, message, false, null);
    }
}
 
  

2、正常结果返回

import com.pph.utils.Results;
import org.apache.commons.lang.StringUtils;
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;

/**
 * @author pph
 * @date 2020/5/11 14:14
 * @description 统一结果返回
 */
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    // public ResponseAdvice(SkipResultsProcessing uris) {
    //     this.uris = uris;
    // }

    // private final SkipResultsProcessing uris;

    @Override
    public boolean supports(MethodParameter returnType, Class> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class> selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {

        // application-skip-results-processing.yml 配置跳过后置统一结果处理则直接返回
        // if (uris.getUris()
        //         .stream()
        //         .anyMatch(u -> StringUtils.equals(u, request.getURI().getPath()))) {
        //     return body;
        // }
        // 如果结果已经是 Results 对象类型则直接返回
        if (body instanceof Results) {
            return body;
        }
        // 返回统一结果
        return Results.success(body);
    }
}
 
  

3、异常结果返回

import com.pph.utils.Results;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author pph
 * @date 2020/5/11 11:27
 * @description 统一异常结果处理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * Controller Bean 入参校验异常结果处理
     *
     * @param e 异常
     * @return result
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Results handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return Results.error(e.getBindingResult()
                .getFieldError()
                .getDefaultMessage());
    }

    /**
     * 统一异常结果处理
     *
     * @return result
     */
    @ExceptionHandler(Exception.class)
    public Results handleException(Exception e) {
        return Results.error(e.getMessage());
    }
}

三、测试

1、异常失败

史上最简单 springboot @Body Bean 整合 validation 入参校验框架 统一结果返回 异常处理(优雅)_第1张图片

2、正常通过

史上最简单 springboot @Body Bean 整合 validation 入参校验框架 统一结果返回 异常处理(优雅)_第2张图片

你可能感兴趣的:(优雅,java,spring,spring,boot)