栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java > SpringBoot

使用spring-validation和@RequestParam(required = false)字符串默认值的校验问题

SpringBoot 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

使用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[] 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[] 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[] 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[] payload() default {};
}

核心的说明,都在上面的注释代码上了。

自定义使用组合Constraint,在原来@Phone的验证规则上,再添加一个 @Length(max = 0, min = 0)规则。使用@ConstraintComposition描述这两个验证规则的逻辑关系。

ConstraintComposition只有一个枚举属性

@documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ConstraintComposition {
	
	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

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号