美文网首页Kotlin
自研校验器工具类—Kotlin语言实现

自研校验器工具类—Kotlin语言实现

作者: agile4j | 来源:发表于2018-09-06 08:59 被阅读75次

作者:刘仁鹏
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.特性

下面这个图是校验器的特性总览。

校验器特性.svg-19.1kB校验器特性.svg-19.1kB

接下来介绍一下校验器的使用方法:

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

相关文章

网友评论

    本文标题:自研校验器工具类—Kotlin语言实现

    本文链接:https://www.haomeiwen.com/subject/dretgftx.html