使用spring-validation和@RequestParam(required = false)字符串默認值的校驗問題


眾所周知,使用@RequestParam(required = false) 封裝請求參數的時候,如果客戶端不提交參數,或者是只聲明參數,並不賦值。那么方法的形參值,默認為null(基本數據類型除外)。

一個Controller方法,有2個參數

@GetMapping
public Object update(@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}

很簡單的一個Controller方法。有兩個參數,都不是必須的。只是這倆參數的數據類型不同。

// 都不聲明參數
http://localhost:8080/test
日志輸出:number=null, phone=null

// 都只聲明參數,但是不賦值
http://localhost:8080/test?number=&phone=
日志輸出出:number=null, phone=

這里可以看出,String類型的參數。在聲明,不賦值的情況下。默認值為空字符串。

使用spring-validation遇到@RequestParam(required = false)字符串參數的問題

一個驗證手機號碼的注解

極其簡單,通過正則驗證字符串是否是手機號碼

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;


@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface Phone {
	String message() default "手機號碼不正確,只支持大陸手機號碼";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

一般這樣使用

private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);

@GetMapping
public Object update(@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) @Phone String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}

這是一個修改接口,允許用戶修改自己的手機號碼,但手機號碼並不是必須的,允許以空字符串的形式存儲在數據庫。通俗的說就是,phone參數,要么是一個合法的手機號碼。要么是空字符串,或者null。

客戶端發起了請求

// 假如用戶什么也不輸入,清空了 phone 輸入框,客戶端js序列化表單后提交。
http://localhost:8080/test?number=&phone=

果然得到了異常:
javax.validation.ConstraintViolationException: update.phone: 手機號碼不正確,只支持大陸手機號碼

很顯然,空字符串 "",並不符合手機號碼的正則校驗。

這種情況就是,在校驗規則,和默認值之間,出現了一點點沖突

解決辦法

求前端大哥改巴改巴

提交之前,遍歷一下請求參數。把空值參數,從請求體中移除。那么后端接收到的形參就是,null。是業務可以接受的數據類型。

修改驗證規則

這個也不算難,自己修改一下驗證的正則,或者重新實現一個自定義的 ConstraintValidator,允許手機號碼為空字符串。但是,也有一個問題,這個注解就不能用在必填的手機號碼參數上了。例如:注冊業務,因為它的規則是允許空字符串的。

當然,也可以維護多個不同的驗證規則注解。

@Phone 驗證必須是標准手機號碼
@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface Phone {
	String message() default "手機號碼不正確,只支持大陸手機號碼";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}
@PhoneOrEmpty 可以是空字符串或者標准的手機號碼
@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^(1[3-9]\\d{9})|(.{0})$")  
public @interface PhoneOrEmpty {
	String message() default "手機號碼不正確,只支持大陸手機號碼";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

好了,這倆可以用在不同的驗證地方。唯一的不同就是驗證的正則不同。這也是讓我覺得不舒服的地方。需要維護兩個正則表達式

一種我認為比較"優雅"的方式

還是一樣,定義不同的注解來處理不同的驗證場景。但是,我並不選擇自立門戶(單獨維護一個正則),而是在@Phone的基礎上,進行一個加強

@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER })
@Constraint(validatedBy = {})
@ReportAsSingleViolation

@Phone						// 使用已有的@Phone作為校驗規則,參數必須是一個合法的手機號碼
@Length(max = 0, min = 0)	// 使用Hiberante提供的字符串長度校驗規則,在這里,表示慘參數字符串的長度必須:最短0,最長0(就是空字符串)

@ConstraintComposition(CompositionType.OR)	// 核心的來了,這個注解表示“多個驗證注解之間的邏輯關系”,這里使用“or”,滿足任意即可
public @interface PhoneOrEmpty {
	String message() default "手機號碼不正確,只支持大陸手機號碼";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

核心的說明,都在上面的注釋代碼上了。

自定義使用組合Constraint,在原來@Phone的驗證規則上,再添加一個 @Length(max = 0, min = 0)規則。使用@ConstraintComposition描述這兩個驗證規則的邏輯關系。

ConstraintComposition只有一個枚舉屬性

@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ConstraintComposition {
	/**
	 * The value of this element specifies the boolean operator,
	 * namely disjunction (OR), negation of the conjunction (ALL_FALSE),
	 * or, the default, simple conjunction (AND).
	 *
	 * @return the {@code CompositionType} value
	 */
	CompositionType value() default AND;
}
public enum CompositionType {
	OR,   // 多個驗證規則中,只要有一個通過就算驗證成功
	AND,  // 多個驗證規則中,必須全部通過才算成功(默認)
	ALL_FALSE // 多個驗證規則中,必須全部失敗,才算通過(少見)
}

試試看

Controller

@GetMapping
public Object update (@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) @PhoneOrEmpty String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}

空參數:空字符串,是合法的

合法參數:更是合法

非法參數:驗證失敗

原文:https://springboot.io/t/topic/2312


免責聲明!

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



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