什么是不优雅的参数校验后端对前端传过来的参数也是需要进行校验的,如果在controller中直接校验需要用大量的if else做判断
以添加用户的接口为例,需要对前端传过来的参数进行校验, 如下的校验就是不优雅的:
@restcontroller@requestmapping("/user")public class usercontroller {@postmapping("add")public responseentity<string> add(user user){if(user.getname()==null) {return responseresult.fail("user name should not be empty");} else if(user.getname().length()<5 || user.getname().length()>50){return responseresult.fail("user name length should between 5-50");}if(user.getage()< 1 || user.getage()> 150) {return responseresult.fail("invalid age");}// ...return responseentity.ok("success");}}
针对这个普遍的问题,java开者在java api规范 (jsr303) 定义了bean校验的标准validation-api,但没有提供实现。
hibernate validation是对这个规范的实现,并增加了校验注解如@email、@length等。
spring validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。
接下来,我们以springboot项目为例,介绍spring validation的使用。
实现案例本案例使用了 spring validation 对参数绑定进行了验证,主要为您提供参数验证的思路。请参考springboot如何封装接口统一错误信息处理,其中包括针对绑定参数检查错误的处理
pom添加pom依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation --><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-validation</artifactid></dependency>
请求参数封装单一职责,所以将查询用户的参数封装到userparam中, 而不是user(数据库实体)本身。
对每个参数字段添加validation注解约束和message。
/*** user.** @author pdai*/@data@builder@apimodel(value = "user", subtypes = {addressparam.class})public class userparam implements serializable {private static final long serialversionuid = 1l;@notempty(message = "could not be empty")private string userid;@notempty(message = "could not be empty")@email(message = "invalid email")private string email;@notempty(message = "could not be empty")@pattern(regexp = "^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|x)$", message = "invalid id")private string cardno;@notempty(message = "could not be empty")@length(min = 1, max = 10, message = "nick name should be 1-10")private string nickname;@notempty(message = "could not be empty")@range(min = 0, max = 1, message = "sex should be 0-1")private int sex;@max(value = 100, message = "please input valid age")private int age;@validprivate addressparam address;}
controller中获取参数绑定结果使用@valid或者@validate注解,参数校验的值放在bindingresult中
/*** @author pdai*/@slf4j@api(value = "user interfaces", tags = "user interfaces")@restcontroller@requestmapping("/user")public class usercontroller {/*** http://localhost:8080/user/add .** @param userparam user param* @return user*/@apioperation("add user")@apiimplicitparam(name = "userparam", type = "body", datatypeclass = userparam.class, required = true)@postmapping("add")public responseentity<string> add(@valid @requestbody userparam userparam, bindingresult bindingresult){if (bindingresult.haserrors()) {list<objecterror> errors = bindingresult.getallerrors();errors.foreach(p -> {fielderror fielderror = (fielderror) p;log.error("invalid parameter : object - {},field - {},errormessage - {}", fielderror.getobjectname(), fielderror.getfield(), fielderror.getdefaultmessage());});return responseentity.badrequest().body("invalid parameter");}return responseentity.ok("success");}}
校验结果post访问添加user的请求
后台输出参数绑定错误信息:(包含哪个对象,哪个字段,什么样的错误描述)
2021-09-16 10:37:05.173 error 21216 --- [nio-8080-exec-8] t.p.s.v.controller.usercontroller : invalid parameter : object - userparam,field - nickname,errormessage - could not be empty
2021-09-16 10:37:05.176 error 21216 --- [nio-8080-exec-8] t.p.s.v.controller.usercontroller : invalid parameter : object - userparam,field - email,errormessage - could not be empty
2021-09-16 10:37:05.176 error 21216 --- [nio-8080-exec-8] t.p.s.v.controller.usercontroller : invalid parameter : object - userparam,field - cardno,errormessage - could not be empty
(本例只是springboot-validation的简单用例,针对接口统一的错误信息封装请看springboot接口如何统一异常处理
进一步理解我们再通过一些问题来帮助你更深入理解validation校验。@pdai
validation分组校验?上面的例子中,其实存在一个问题,userparam既可以作为adduser的参数(id为空),又可以作为updateuser的参数(id不能为空),这时候怎么办呢?分组校验登场。
@data@builder@apimodel(value = "user", subtypes = {addressparam.class})public class userparam implements serializable {private static final long serialversionuid = 1l;@notempty(message = "could not be empty") // 这里定为空,对于adduser时是不合适的private string userid;}
这时候可以使用validation分组
先定义分组(无需实现接口)
public interface addvalidationgroup {}public interface editvalidationgroup {}
在userparam的userid字段添加分组
@data@builder@apimodel(value = "user", subtypes = {addressparam.class})public class userparam implements serializable {private static final long serialversionuid = 1l;@notempty(message = "{user.msg.userid.notempty}", groups = {editvalidationgroup.class}) // 这里private string userid;}
controller中的接口使用校验时使用分组
ps: 需要使用@validated注解
@slf4j@api(value = "user interfaces", tags = "user interfaces")@restcontroller@requestmapping("/user")public class usercontroller {/*** http://localhost:8080/user/add .** @param userparam user param* @return user*/@apioperation("add user")@apiimplicitparam(name = "userparam", type = "body", datatypeclass = userparam.class, required = true)@postmapping("add")public responseentity<userparam> add(@validated(addvalidationgroup.class){return responseentity.ok(userparam);}/*** http://localhost:8080/user/add .** @param userparam user param* @return user*/@apioperation("edit user")@apiimplicitparam(name = "userparam", type = "body", datatypeclass = userparam.class, required = true)@postmapping("edit")public responseentity<userparam> edit(@validated(editvalidationgroup.class){return responseentity.ok(userparam);}}
测试
@validate和@valid什么区别?你会注意到,在前面的例子中使用的是@validate而不是@valid,你可能会想知道它们之间的不同之处
在检验controller的入参是否符合规范时,使用@validated或者@valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
分组
@validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。jsr-303标准并未包含分组功能。
注解地方
@validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
嵌套类型
比如本文例子中的address是user的一个嵌套属性, 只能用@valid
@data@builder@apimodel(value = "user", subtypes = {addressparam.class})public class userparam implements serializable {private static final long serialversionuid = 1l;@valid // 这里只能用@validprivate addressparam address;}
有哪些常用的校验?从以下三类理解。
jsr303/jsr-349: jsr303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@null,@notnull,@pattern,位于javax.validation.constraints包下。jsr-349是其的升级版本,添加了一些新特性。
@assertfalse 被注释的元素只能为false@asserttrue 被注释的元素只能为true@decimalmax 被注释的元素必须小于或等于{value}@decimalmin 被注释的元素必须大于或等于{value}@digits 被注释的元素数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)@email 被注释的元素不是一个合法的电子邮件地址@future 被注释的元素需要是一个将来的时间@futureorpresent 被注释的元素需要是一个将来或现在的时间@max 被注释的元素最大不能超过{value}@min 被注释的元素最小不能小于{value}@negative 被注释的元素必须是负数@negativeorzero 被注释的元素必须是负数或零@notblank 被注释的元素不能为空@notempty 被注释的元素不能为空@notnull 被注释的元素不能为null@null 被注释的元素必须为null@past 被注释的元素需要是一个过去的时间@pastorpresent 被注释的元素需要是一个过去或现在的时间@pattern 被注释的元素需要匹配正则表达式"{regexp}"@positive 被注释的元素必须是正数@positiveorzero 被注释的元素必须是正数或零@size 被注释的元素个数必须在{min}和{max}之间
hibernate validation:hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如@email,@length,@range等等
@creditcardnumber 被注释的元素不合法的信用卡号码@currency 被注释的元素不合法的货币 (必须是{value}其中之一)@ean 被注释的元素不合法的{type}条形码@email 被注释的元素不是一个合法的电子邮件地址 (已过期)@length 被注释的元素长度需要在{min}和{max}之间@codepointlength 被注释的元素长度需要在{min}和{max}之间@luhncheck 被注释的元素${validatedvalue}的校验码不合法, luhn模10校验和不匹配@mod10check 被注释的元素${validatedvalue}的校验码不合法, 模10校验和不匹配@mod11check 被注释的元素${validatedvalue}的校验码不合法, 模11校验和不匹配@modcheck 被注释的元素${validatedvalue}的校验码不合法, ${modtype}校验和不匹配 (已过期)@notblank 被注释的元素不能为空 (已过期)@notempty 被注释的元素不能为空 (已过期)@parametersscriptassert 被注释的元素执行脚本表达式"{script}"没有返回期望结果@range 被注释的元素需要在{min}和{max}之间@safehtml 被注释的元素可能有不安全的html内容@scriptassert 被注释的元素执行脚本表达式"{script}"没有返回期望结果@url 被注释的元素需要是一个合法的url@durationmax 被注释的元素必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}@durationmin 被注释的元素必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
spring validation:spring validation对hibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中
自定义validation?如果上面的注解不能满足我们检验参数的要求,我们能不能自定义校验规则呢? 可以。
定义注解
package tech.pdai.springboot.validation.group.validation.custom;import javax.validation.constraint;import javax.validation.payload;import java.lang.annotation.documented;import java.lang.annotation.retention;import java.lang.annotation.target;import static java.lang.annotation.elementtype.*;import static java.lang.annotation.retentionpolicy.runtime;@target({ method, field, annotation_type, constructor, parameter, type_use })@retention(runtime)@documented@constraint(validatedby = {telephonenumbervalidator.class}) // 指定校验器public @interface telephonenumber {string message() default "invalid telephone number";class<?>[] groups() default { };class<? extends payload>[] payload() default { };}
定义校验器
public class telephonenumbervalidator implements constraintvalidator<telephonenumber, string> {private static final string regex_tel = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";@overridepublic boolean isvalid(string s, constraintvalidatorcontext constraintvalidatorcontext){try {return pattern.matches(regex_tel, s);} catch (exception e) {return false;}}}
使用
@data@builder@apimodel(value = "user", subtypes = {addressparam.class})public class userparam implements serializable {private static final long serialversionuid = 1l;@notempty(message = "{user.msg.userid.notempty}", groups = {editvalidationgroup.class})private string userid;@telephonenumber(message = "invalid telephone number") // 这里private string telephone;}
以上就是springboot接口怎么对参数进行校验的详细内容。