SpringBoot参数校验

无论在做接口或还是前后端交互时,进行必要的参数校验是很重要的,可有效避免垃圾数据。

这里使用Spring Validation校验器进行说明,导入依赖如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

 

1.RequestBody类型校验

一般主要是POST请求使用RequestBody进行参数接收,具体是使用@RequestBody+对象方式接收。下面一一说明:

1.1单个对象

 实体类(需要注意的是,@NotBlank只能验证String类型的参数)

package com.zxh.bootdemo0705.entity;

import com.zxh.bootdemo0705.validator.DateVerify;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotBlank(message = "性别不能为空")
    private String sex;

    @NotBlank(message = "出生日期不能为空")
    @DateVerify(dateFormat = "yyyy-MM-dd")
    private String birthDay;

}

日期验证注解

package com.zxh.bootdemo0705.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * 日期类型校验注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {DateVerifyValidator.class}) // 指定自定义的校验器
public @interface DateVerify {

    // 提示信息
    String message() default "日期格式不正确";

    // 日期格式
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";

    /**
     * 必须包含以下两个属性
     * 否则会报错 error msg: contains Constraint annotation, but does not contain a groups parameter.
     *
     * @return
     */
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

日期验证器,验证日期格式

package com.zxh.bootdemo0705.validator;

import cn.hutool.core.util.StrUtil;
import com.zxh.bootdemo0705.util.DateUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 日期类型校验器
 */
public class DateVerifyValidator implements ConstraintValidator<DateVerify, String> {

    private String dateFormat;

    @Override
    public void initialize(DateVerify obj) {
        dateFormat = obj.dateFormat();
    }

    /**
     * 对参数进行验证
     *
     * @param value   修饰字段的值
     * @param context 上下文
     * @return true:验证通过, false:验证不通过
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StrUtil.isNotEmpty(value)) {
            return DateUtil.isValidDate(value, dateFormat);
        }
        return true;
    }
}

验证格式的工具类

package com.zxh.bootdemo0705.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {

    /**
     * 验证字符串是否日期格式
     *
     * @param str      日期字符串
     * @param dateFormat 格式
     * @return
     */
    public static boolean isValidDate(String str, String dateFormat) {
        boolean convertSuccess = true;
        if (null != str && null != dateFormat && str.length() == dateFormat.length()) {
            SimpleDateFormat format = new SimpleDateFormat(dateFormat);
            try {
                // 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期,比如2007/02/29会被接受,并转换成2007/03/01
                format.setLenient(false);
                format.parse(str);
            } catch (ParseException e) {
                convertSuccess = false;
            }
        } else {
            convertSuccess = false;
        }

        return convertSuccess;
    }
}

全局异常处理,主要是最后两个异常处理来捕获参数不合法所抛出的异常

package com.zxh.bootdemo0705.exception;


import com.zxh.bootdemo0705.entity.RsData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;


@ControllerAdvice
@Slf4j
@RestController
public class InterfaceExceptionHandler {

    private String paramValid = "参数不合法:";

    // 将Spring DataBinder配置为使用直接字段访问
    @InitBinder
    private void activateDirectFieldAccess(DataBinder dataBinder) {
        dataBinder.initDirectFieldAccess();
    }

    /**
     * 方法不被允许
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public RsData HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error("请求方法异常!原因是:{}", e);
        return RsData.fail("方法不被允许");
    }

    /**
     * 请求体参数为空
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public RsData HttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("参数不合法!原因是:{}", e.getMessage());
        return RsData.fail(paramValid + "请求体为空");
    }

    /**
     * 运行时异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = RuntimeException.class)
    public RsData RuntimeException(RuntimeException e) {
        log.error("服务器异常!原因是:{}", e);
        return RsData.fail("服务器开小差了,请稍后再试");
    }


    /**
     * 用来处理validation异常,捕获get请求Param参数校验异常信息
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public RsData resolveConstraintViolationException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) {
            StringJoiner msgJoiner = new StringJoiner(",");
            Set<String> errors = new HashSet<>(constraintViolations.size() / 2);
            for (ConstraintViolation constraintViolation : constraintViolations) {
                errors.add(constraintViolation.getMessage());
            }
            for (String error : errors) {
                msgJoiner.add(error);
            }
            String errorMessage = msgJoiner.toString();
            return RsData.fail(paramValid + errorMessage);
        }
        return RsData.fail(paramValid + ex.getMessage());
    }

    /**
     * 用来处理validation异常,捕获post请求Body实体属性参数校验异常信息
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RsData resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
        if (!CollectionUtils.isEmpty(objectErrors)) {
            StringJoiner msgJoiner = new StringJoiner(",");
            Set<String> errors = new HashSet<>(objectErrors.size() / 2);
            for (ObjectError objectError : objectErrors) {
                errors.add(objectError.getDefaultMessage());
            }
            for (String error : errors) {
                msgJoiner.add(error);
            }
            String errorMessage = msgJoiner.toString();
            return RsData.fail(paramValid + errorMessage);
        }
        return RsData.fail(paramValid + ex.getMessage());
    }


}

返回对象实体类(可根据实际情况编写)

package com.zxh.bootdemo0705.entity;

import lombok.Data;

@Data
public class RsData {

    private static final String SUCCESS = "0";
    private static final String FAIL = "1";

    private String code;

    private Object result;

    private String message;

    public static RsData success(String msg) {
        RsData rs = new RsData();
        rs.setCode(SUCCESS);
        rs.setMessage(msg);
        return rs;
    }

    public static RsData success(String msg, Object result) {
        RsData rs = new RsData();
        rs.setCode(SUCCESS);
        rs.setMessage(msg);
        rs.setResult(result);
        return rs;
    }

    public static RsData error(String msg) {
        RsData rs = new RsData();
        rs.setCode(FAIL);
        rs.setMessage(msg);
        return rs;
    }

    public static RsData error(String msg, Object result) {
        RsData rs = new RsData();
        rs.setCode(FAIL);
        rs.setMessage(msg);
        rs.setResult(result);
        return rs;
    }

    public static RsData fail(String result) {
        RsData rs = new RsData();
        rs.setCode(FAIL);
        rs.setMessage("失败");
        rs.setResult(result);
        return rs;
    }

}

测试接口,在使用@RequestBody注入对象时添加注解@Valid进行校验,若不加则不进行验证

    @PostMapping("/add")
    public String add(@RequestBody @Valid User user) {
        System.out.println(user);
        return "添加成功";
    }

测试:验证不合法的参数

 测试:输入正确的参数

 常见的约束注解如下表:

注解 功能
@NotNull 验证字符串。不能为null,可以是空字符串
@NotBlank 验证字符串。不能为null或空字符串
@NotEmpty 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于""
@Pattern 必须满足指定的正则表达式
@Length 长度必须在指定范围内
@Range 值必须在指定范围内
@Max 最大不得超过此最大值
@Min 最大不得小于此最小值

 

1.2对象集合

 添加集合验证

package com.zxh.bootdemo0705.validator;

import lombok.experimental.Delegate;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

/***
 * 由于Validator只能验证对象合法性,故数组方式需自定义处理,这是方式一
 * 也可直接在controller类上加@Validated注解
 * @param <E>
 */
public class ListValidation<E> implements List<E> {

    @Delegate
    @Valid
    public List<E> list = new ArrayList<>();

    @Override
    public String toString() {
        return list.toString();
    }
}

添加接口

    @PostMapping("/add2")
    public String add(@RequestBody @Validated ListValidation<User> userList) {
        System.out.println(userList);
        return "添加成功";
    }

注意,这是使用的是ListValidation进行接收,还配合使用了@Validated注解。两者都可以用来校验参数,但区别如下:

@Valid是用Hibernate validation校验机制,而@Validated是用Spring Validator校验机制。

@Valid可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套检测。@Validated可以用在类型、方法和方法参数上,但是不能用在成员属性(字段)上,不支持嵌套检测。

 

测试结果如下:

posted @ 2022-10-17 11:34  钟小嘿  阅读(419)  评论(0编辑  收藏  举报