作者:刘仁鹏
GitHub地址:https://github.com/LingPaicoder/agile-base
转载请注明出处
1. 简介
今天为大家介绍一款自研的,使用 Kotlin 语言实现的,用于做 参数上下文校验 的工具类。说起校验器,大家比较熟悉的是Java的BeanValidation规范。但BeanValidation规范是用于做 参数字面值校验 的,即只能对 参数本身的值 做某种规则性的判断,如果需要对参数进行某种运算后的结果(例如算数运算、DB操作等)进行规则判断,就无法实现了。
这种对 参数进行某种运算后的结果 进行校验的方式,我把它称作 参数上下文校验。而本文要介绍的校验器就是为了解决这种应用场景而生的。
先来看一下校验器实际使用时的效果吧,这是一个对参数是否符合身份证号规则进行判断的方法:
fun validate(idCard: String): Boolean =
try {
idCard must lengthEq(ID_CARD_LENGTH)
val front17Part = idCard.substring(0, ID_CARD_LENGTH - 1)
front17Part must beDigit
val lastChar = getLastCharByFront17Part(front17Part)
lastChar must eq(idCard.last())
true
} catch (e: CheckException) {
false
}
代码中的第3、5、7行就是三次校验动作,这里通过must这个 中缀操作符,使校验代码更加贴近自然语言,代码的自解释性和可读性更强。
那么这是怎么实现的呢?接下来就一起来看一下对校验器更全面的介绍。
2.特性
下面这个图是校验器的特性总览。
接下来介绍一下校验器的使用方法:
1.通过doCheck方法以传统Java语法方式做校验
@Test
fun doCheckTest() {
val trueIdCard = "130802198108204219"
doCheck(trueIdCard, "身份证号", beIdCard)
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11007, desc=身份证号必须符合身份证格式")
val falseIdCard = "130802198108204210"
doCheck(falseIdCard, "身份证号", beIdCard)
}
2.通过alias、must中缀方法以类自然语言方式编程做校验(自解释性强,推荐)
@Test
fun mustTest() {
val trueIdCard = "130802198108204219"
trueIdCard alias "身份证号" must beIdCard
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11007, desc=身份证号必须符合身份证格式")
val falseIdCard = "130802198108204210"
falseIdCard alias "身份证号" must beIdCard
}
3.若校验失败则会抛出CheckException异常
@Test
fun exceptionTest() {
try {
val falseIdCard = "130802198108204210"
falseIdCard alias "身份证号" must beIdCard
} catch (e: CheckException) {
Assert.assertEquals(-11007L, e.code)
Assert.assertEquals("身份证号必须符合身份证格式", e.desc)
Assert.assertEquals("code=-11007, desc=身份证号必须符合身份证格式", e.message)
}
}
4.通过ruler.or方法实现规则的“或”逻辑
@Test
fun orTest() {
val beName = beEmpty.or(lengthGte(2) and lengthLte(10))
var specialName = ""
specialName alias "姓名" must beName
specialName = "张三"
specialName alias "姓名" must beName
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10")
specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫"
specialName alias "姓名" must beName
}
5.通过and、Ruler.ofAll()、可变长参数实现规则的“且”逻辑
@Test
fun andTest() {
var specialName = "张三"
// 下面三种写法效果相同
specialName alias "姓名" must (lengthGte(2) and lengthLte(10))
specialName alias "姓名" must be(Ruler.ofAll(lengthGte(2), lengthLte(10)))
specialName alias "姓名" must be(lengthGte(2), lengthLte(10))
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10")
specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫"
specialName alias "姓名" must (lengthGte(2) and lengthLte(10))
}
6.除了beNullVal和nullVal()之外的其他内置Ruler,都会先进行beNotNull校验. 若业务场景允许为null,可用或逻辑处理
@Test
fun nullTest() {
val idCard: String? = null
idCard alias "身份证号" must beNullVal.or(beIdCard)
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11013, desc=身份证号不能为Null")
idCard alias "身份证号" must beIdCard
}
7.可为校验对象设置别名,如果不需要别名,也可不传,别名默认值为空串""
@Test
fun aliasTest() {
val idCard: String? = null
// 不使用别名
idCard must beNullVal.or(beIdCard)
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-11013, desc=身份证号不能为Null")
// 使用别名
idCard alias "身份证号" must beIdCard
}
8.自定义错误编号和错误描述(支持在desc中对norm进行格式化)
@Test
fun userDefinedFailCodeAndDescTest() {
val specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫"
val nameDesc = "姓名"
val nameTooLongCode = -2L
val lteNorm = 10
// 注意此处%d的用法
val nameTooLongDesc = "长度超过限制,允许的最大长度:%d"
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-2, desc=姓名长度超过限制,允许的最大长度:10")
specialName alias nameDesc must be(lengthGte(2),
lengthLte(lteNorm, nameTooLongCode, nameTooLongDesc))
}
9.为实体类不同操作封装个性化规则/自定义规则
data class Custom(var customId: String?, var name: String?, var age: Int?)
@Test
fun testEntity() {
val customAddRuler = Ruler { custom: Custom? ->
doCheck(custom, "商家", AnyRuler.beNotNull)
custom?.customId alias "商家Id" must beNotEmpty
custom?.name alias "商家姓名" must beNotEmpty
custom?.age alias "商家年龄" must lte(60)
}
val custom = Custom("123", "张三", 80)
thrown.expect(CheckException::class.java)
thrown.expectMessage("code=-18005, desc=商家年龄必须小于或等于60")
custom must be(customAddRuler)
}
3. 其他
校验器的GitHub地址是https://github.com/LingPaicoder/agile-base。
所有的校验器相关代码都放在 com.lpcoder.agile.base.check 包下。
每个内置规则都有专门的单测代码,放在test的 com.lpcoder.agile.base.check.ruler 包下。
校验器的特性单测放在test的 com.lpcoder.agile.base.check.CheckTest 类中。
欢迎留言反馈对校验器的改进意见。
end
网友评论