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項目接口入參的簡單參數校驗,對於復雜的入參校驗還是需要基於代碼實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM