springboot项目中接口入参的简单校验

 

前言

参数校验在项目中是必不可少的一环,对于一些常规的校验,可以通过注解来开启自动校验,减少重复的校验代码,简化开发。

JSR 303

JSR 303 是Bean Validation验证的规范 ,定义了如下的注解:

8mrUl6.png

Hibernate Validator

Hibernate Validator 是该规范的参考实现,它除了实现规范要求的注解外,还额外添加了一些注解:

8mymrT.png

spring validation

spring validation对hibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中。

8mybLT.png

以springboot工程为例,添加 spring-boot-starter-web 依赖,其自带了spring validation:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>
 
8m6GkQ.png

一个典型springmvc接口如下:

package pers.skindream.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.skindream.entity.SysUser;

@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping
    public void doTest(@Validated SysUser user) {

    }

}

实体类入参

在接口实体入参user前有标有@Validated注解,SysUser类中代码如下:

package pers.skindream.entity;

import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

/**
*
*使用是lombok进行代码简化,下同
*
*/

@Data
public class SysUser {

    private String id;

    @NotBlank(message = "请输入用户名")
    private String username;

    @NotBlank(message = "请输入密码")
    private String password;

    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "请输入邮箱地址")
    private String email;

}

不带如何参数访问接口地址localhost:7001/test,会抛出异常。

org.springframework.validation.BindException

在springboot项目中可以定义一个全局异常处理器捕获并处理异常,自定义的全局异常处理器代码如下:

package pers.skindream.system.handler;

import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.skindream.commons.result.AjaxResult;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = BindException.class)
    public AjaxResult handBindException(BindException exception) {
        String message = exception.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

}

其中AjaxResult为自定义的一个响应实体,代码如下:

package pers.skindream.commons.result;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class AjaxResult {

    private int code;

    private String msg;

    private Object data;

    public static AjaxResult success() {
        return new AjaxResult(200nullnull);
    }

    public static AjaxResult error() {
        return new AjaxResult(0nullnull);
    }

    public static AjaxResult error(String msg) {
        return new AjaxResult(0, msg, null);
    }

    public static AjaxResult success(String msg, Object object) {
        return new AjaxResult(200, msg, object);
    }

}

再次访问接口地址localhost:7001/test,得到:

{
    "code"0,
    "msg""请输入密码",
    "data"null
}

分组校验

对于一个实体,在不同的接口有不同的校验规则,这是可以对校验注解添加分组,groups属性可以接收一个到多个分组接口的class对象。

为上述SysUser类添加分组,代码如下:

package pers.skindream.entity;

import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@Data
public class SysUser {

    private String id;

    @NotBlank(message = "请输入用户名", groups = UnameAndPwd.class)
    private String username;

    @NotBlank(message = "请输入密码", groups = UnameAndPwd.class)
    private String password;

    @Email(message = "邮箱格式不正确", groups = CheckEmail.class)
    @NotBlank(message = "请输入邮箱地址", groups = CheckEmail.class)
    private String email;

    public interface UnameAndPwd{}

    public interface CheckEmail{}

}

其中username和password属于UnameAndPwd分组,email属于CheckEmail分组

修改上述接口,代码如下:

package pers.skindream.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.skindream.entity.SysUser;

@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping
    public void doTest(@Validated(SysUser.UnameAndPwd.class) SysUser user) {

    }

}

这次指定了分组UnameAndPwd,只会去校验username和password了

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""请输入用户名",
    "data"null
}

改为指定CheckEmail分组

package pers.skindream.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.skindream.entity.SysUser;

@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping
    public void doTest(@Validated(SysUser.CheckEmail.class) SysUser user) {

    }

}

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""请输入邮箱地址",
    "data"null
}

非实体类入参

校验非实体类,改造上述接口代码如下:

package pers.skindream.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotBlank;

@Validated
@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping
    public void doTest(@NotBlank(message = "id不能为空") String id,
                       @NotBlank(message = "请输入用户名") String username) 
{

    }

}

注意:方法所在类前需要添加注解@Validated才能生效

访问接口地址localhost:7001/test,抛出异常

javax.validation.ConstraintViolationException: doTest.id: id不能为空, doTest.username: 请输入用户名

在上述全局异常处理类中捕获并处理该异常,改造代码如下:

package pers.skindream.system.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.skindream.commons.result.AjaxResult;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = BindException.class)
    public AjaxResult handBindException(BindException exception) {
        String message = exception.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public AjaxResult handConstraintViolationException(ConstraintViolationException exception) {
        String message = exception.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(","));
        return AjaxResult.error(message);
    }

}

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""id不能为空,请输入用户名",
    "data"null
}

这里会收集到所有错误消息,如果想让他校验到了错误就立即返回,需要设置为快速失败模式,新建

package pers.skindream.system.config;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidatorConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

}

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""id不能为空",
    "data"null
}

自定义校验注解

当已有的校验注解不能满足业务需求时,可以自定义注解实现校验。

现在有需求校验字符串中不能含有空格,自定义注解代码如下:

package pers.skindream.system.validation.constraints;

import pers.skindream.system.validation.validator.NotSpacesValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义校验注解,校验字符串中是否含有空格
 */

@Constraint(
        validatedBy = NotSpacesValidator.class
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotSpaces {

    String message() default "字符串不能含有空格";

    Class<?>[] groups() default {};

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

}

其中指定的校验器为NotSpacesValidator,代码如下:

package pers.skindream.system.validation.validator;

import pers.skindream.system.validation.constraints.NotSpaces;

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

public class NotSpacesValidator implements ConstraintValidator<NotSpacesString{

    @Override
    public void initialize(NotSpaces constraintAnnotation) {

    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (s.contains(" ")) {
            return false;
        }
        return true;
    }

}

改造接口代码如下:

package pers.skindream.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.skindream.system.validation.constraints.NotSpaces;

@Validated
@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping
    public void doTest(@NotSpaces(message = "密码中不能含有非法字符") String password) {

    }

}

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""密码中不能含有非法字符",
    "data"null
}

手动校验

在上述快速失败中,已经定义了一个validator单例对象,现在改造接口代码如下:

package pers.skindream.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.skindream.commons.result.AjaxResult;
import pers.skindream.entity.SysUser;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;

@RestController
@RequestMapping("test")
public class TestController {

    @Autowired
    Validator validator;

    @PostMapping
    public AjaxResult doTest() {
        SysUser user = getUser();
        Set<ConstraintViolation<SysUser>> validate = validator.validate(user, SysUser.UnameAndPwd.class);
        if (!CollectionUtils.isEmpty(validate)){
            ConstraintViolation constraintViolation = (ConstraintViolation) validate.toArray()[0];
            return AjaxResult.error(constraintViolation.getMessage());
        }
        return AjaxResult.success();
    }

    public SysUser getUser() {
        SysUser user = new SysUser();
        user.setUsername("");
        user.setPassword("123456");
        user.setEmail("123456@163.com");
        return user;
    }

}

访问接口地址localhost:7001/test

{
    "code"0,
    "msg""请输入用户名",
    "data"null
}

结语

以上是基于springboot项目接口入参的简单参数校验,对于复杂的入参校验还是需要基于代码实现。

posted @ 2020-03-14 00:27  丶沉梦昂志丶  阅读(...)  评论(...编辑  收藏