SpringBoot2.0實戰 | 第八章:整合hibernate-validator優雅表單校驗

相關知識

spring-boot-starter-web 項目中默認已經集成了 hibernate-validator

常用註解

JSR 303 Bean Validation

註解 說明 數據類型
AssertTrue 標註元素必須爲true Boolean
AssertFalse 標註元素必須爲false Boolean
DecimalMax(value,isclusive) 標註元素必須小於等於指定值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
DecimalMin(value,isclusive) 標註元素必須大於等於指定值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
Digits(integer,fraction) 標註元素必須位於指定位數之內 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
Email(regexp,flags) 標註元素必須爲格式正確的郵件地址 CharSequence
Future 標註元素必須爲將來的日期 Date,Calendar,Instant, LocalDate,LocalDateTime, LocalTime,MonthDay, OffsetDateTime,OffsetTime, Year,YearMonth, ZonedDateTime,HijrahDate, JapaneseDate,MinguoDate, ThaiBuddhistDate
FutureOrPresent 標註元素必須爲現在或將來的日期 同Future
Max(value) 標註元素必須小於等於指定值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
Min(value) 標註元素必須大於等於指定值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
Negative 標註元素必須爲嚴格負值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
NegativeOrZero 標註元素必須爲嚴格的負值或者0值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
NotBlank 標註元素必須不爲null,且必須包含至少一個非空字符 CharSequence
NotEmpty 標註元素必須不爲null,且必須包含至少一個子元素 CharSequence,Collection,Map,Array
NotNull 標註元素必須不爲null all
Null 標註元素必須爲null all
Past 標註元素必須爲過去的日期 同Future
PastOrPresent 標註元素必須爲過去的或者現在的日期 同Future
Pattern(regexp,flags) 標註元素必須匹配給定的正則表達式 CharSequence
Positive 標註元素必須爲嚴格的正值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
PositiveOrZero 標註元素必須爲嚴格的正值或者0值 BigDecimal,BigInteger, CharSequence,byte,short, int, long,Byte,Short, Integer,Long
Size(min,max) 標註元素必須在指定範圍之內 CharSequence,Collection,Map,Array

Hibernate Validation

註解 說明 備註
Length(min,max) 標註元素的長度必須在指定範圍之內,包含最大值 字符串
Range(min,max) 標註元素值必須在指定範圍之內 數字值,或者其字符串形式
URL(regexp,flags) 標註元素必須爲格式正確的URL 字符串
URL(protocol,host,port) 標註元素必須滿足給定的協議主機和端口號 字符串

目標

整合 hibernate-validator,使用註解的方式對接口入參進行校驗。

操作步驟

添加依賴

spring-boot-starter-web 已經默認添加對 hibernate-validator 的依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

編碼

  1. 添加校驗規則
@Getter
@Setter
public class User {

    @NotBlank(message = "用戶名不能爲空")
    @Length(min = 6, max = 16, message = "用戶名長度必須在6-16個字符之間")
    private String username;

    @Past(message = "出生日期必須早於當前日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    private LocalDate birthday;

    @NotNull(message = "用戶等級不能爲空")
    @Min(value = 0, message = "用戶等級最小爲0")
    @Max(value = 5, message = "用戶等級最大爲5")
    @Digits(integer = 1, fraction = 0, message = "用戶等級必須爲整數")
    private Integer level;

}
  1. 使用校驗

在 Controller 接口的參數前面添加 @Valid 註解,入參注入時便會自動進行規則校驗,如果校驗成功,則執行方法體,如果校驗失敗,有兩種處理方法。

  • 在參數列表的最後面添加一個 BindingResult 對象獲取校驗結果,自行組織輸出內容。
  • 不使用 BindingResult 對象,則框架拋出異常,通過異常處理機制可以進行捕獲,組織輸出內容。
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    /**
     * 不使用 BindingResult 接收校驗結果,Spring 將拋出異常
     */
    @PostMapping("/add1")
    public User add1(@Valid @RequestBody User user) {
        return user;
    }

    /**
     * 使用 BindingResult 接收校驗結果,自行組織輸出內容
     */
    @PostMapping("/add2")
    public User add2(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            StringBuilder sb = new StringBuilder();
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
                sb.append(",");
            }
            log.debug(sb.toString());
            return null;
        }
        return user;
    }

}
  1. 啓動類
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

驗證結果

編寫測試用例

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = Application.class)
public class UserTest {

    private MockMvc mvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void test1() throws Exception {
        MvcResult mvcResult = mvc.perform(
                MockMvcRequestBuilders
                .post("/user/add1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content("{\"name\":\"user1\",\"sex\":1,\"birthday\":\"2030-05-21\"}")
        )
//        .andExpect(status().isOk());
//        .andExpect(content().string("hello"))
        .andDo(MockMvcResultHandlers.print())
        .andReturn();

        Assert.assertEquals(200, mvcResult.getResponse().getStatus());
    }

    @Test
    public void test2() throws Exception {
        MvcResult mvcResult = mvc.perform(
                MockMvcRequestBuilders
                        .post("/user/add2")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content("{\"name\":\"user1\",\"sex\":1,\"birthday\":\"2030-05-21\"}")
        )
//        .andExpect(status().isOk());
//        .andExpect(content().string("hello"))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();

        Assert.assertEquals(200, mvcResult.getResponse().getStatus());
    }

}

源碼地址

本章源碼 : https://gitee.com/gongm_24/spring-boot-tutorial.git

結束語

一個安全的接口需要對每一個入參進行校驗,以保證參數合法性。

參考資料

擴展

自定義校驗

  1. 創建一個註解,其中 @Constraint 用於指向該註冊將使用的自定義校驗類
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = ValueRangeValidator.class)
public @interface ValueRange {

    String[] values();

    String message() default "值不正確";

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

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

}
  1. 創建自定義校驗類
public class ValueRangeValidator implements ConstraintValidator<ValueRange, Object> {

    private String[] values;

    @Override
    public void initialize(ValueRange constraintAnnotation) {
        values = constraintAnnotation.values();
    }

    /**
     * 校驗函數
     */
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        for (String s : values) {
            if (Objects.equals(s, value)) {
                return true;
            }
        }
        return false;
    }

}
  1. 使用自定義校驗
@Getter
@Setter
public class UserBO {

    /**
     * 用戶名,長度在6-16個字符之間,必須參數
     */
    @NotBlank(message = "用戶名不能爲空")
    @Length(min = 6, max = 16, message = "用戶名長度必須在6-16個字符之間")
    private String username;

    /**
     * 出生日期,格式爲 yyyy-MM-dd,必須爲過去的日期,不必須參數
     */
    @Past(message = "出生日期必須早於當前日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthday;

    /**
     * 等級,整數,0-5之間,必須參數
     */
    @NotNull(message = "用戶等級不能爲空")
    @Min(value = 0, message = "用戶等級最小爲0")
    @Max(value = 5, message = "用戶等級最大爲5")
    @Digits(integer = 1, fraction = 0, message = "用戶等級必須爲整數")
    private Integer level;

    /**
     * 限定性別的值只能是 0、1、2
     */
    @ValueRange(values = {"0", "1", "2"})
    private Integer sex;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章