github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/validate/README.md (about) 1 ## validate 2 3 validate包核查模块,用于对入参的校验 4 5 ## 快速使用 6 这里举个例子,快速使用 7 8 ### 基于isc-gobase的web的项目的示例: 9 ```go 10 // main.go 文件 11 package main 12 13 import ( 14 "bytes" 15 "encoding/json" 16 "github.com/gin-gonic/gin" 17 "github.com/isyscore/isc-gobase/http" 18 "github.com/isyscore/isc-gobase/isc" 19 "github.com/isyscore/isc-gobase/logger" 20 "github.com/isyscore/isc-gobase/server" 21 "github.com/isyscore/isc-gobase/server/rsp" 22 "github.com/isyscore/isc-gobase/validate" 23 "io/ioutil" 24 "strings" 25 ) 26 27 func main() { 28 server.Post("test/insert", InsertData) 29 server.Run() 30 } 31 32 // InsertData 数据插入 33 func InsertData(c *gin.Context) { 34 insertReq := InsertReq{} 35 36 // 读取body数据,可以采用isc提供的工具 37 err := isc.DataToObject(c.Request.Body, &insertReq) 38 if err != nil { 39 // ... 省略异常日志 40 return 41 } 42 43 // api示例:核查入参 44 if result, msg := validate.Check(insertReq); !result { 45 // 参数异常 46 rsp.FailedOfStandard(c, 53, msg) 47 logger.Error(msg) 48 return 49 } 50 51 rsp.SuccessOfStandard(c, "ok") 52 } 53 54 type InsertReq struct { 55 Name string `match:"value={zhou, chen}"` 56 Profile string `match:"range=[0, 10)"` 57 } 58 ``` 59 ```yaml 60 # application.yml 文件 61 api-module: app/sample 62 63 base: 64 api: 65 # api前缀 66 prefix: /api 67 application: 68 # 应用名称 69 name: sample 70 server: 71 # 是否启用,默认:true 72 enable: true 73 # 端口号 74 port: 8080 75 # web框架gin的配置 76 gin: 77 # 有三种模式:debug/release/test 78 mode: release 79 80 ``` 81 82 请求 83 ```shell 84 curl -X POST \ 85 http://localhost:8080/api/app/sample/test/insert \ 86 -H 'Content-Type: application/json' \ 87 -d '{ 88 "name": "zhou", 89 "profile": "abcde-abcde" 90 }' 91 ``` 92 返回异常 93 ```json 94 { 95 "code": 53, 96 "data": null, 97 "message": "长度不合法" 98 } 99 ``` 100 101 ### 非web的普通类核查示例: 102 103 ```go 104 package main 105 106 import ( 107 "github.com/isyscore/isc-gobase/validate" 108 "github.com/isyscore/isc-gobase/logger" 109 ) 110 111 type DemoInsert struct { 112 // 对属性修饰 113 Name string `match:"value=zhou"` 114 Age int 115 } 116 117 func main() { 118 var value DemoInsert 119 var result bool 120 var errMsg string 121 122 value = DemoInsert{Name: "chen"} 123 // 核查 124 result, errMsg = validate.Check(value) 125 if !result { 126 // 属性 Name 的值 chen 不在只可用列表 [zhou] 中 127 logger.Error(errMsg) 128 } 129 } 130 ``` 131 说明:<br/> 132 133 1. 这里提供方法Check,用于核查是否符合条件 134 2. 提供标签match,标签内容中提供匹配器:value,该匹配器表示匹配的具体的一些值 135 136 ## api说明 137 只提供两个Api,`Check` 和 `CheckWithParameter` 138 ```go 139 // 入参: 140 // @any 待核查对象 141 // @fieldNames 待核查对象的核查属性;不指定则核查所有属性名 142 // 返回值: 143 // bool 是否匹配合法:true-合法,false-不合法 144 // string 不合法对应的说明 145 func Check(object any, fieldNames ...string) (bool, string) {} 146 147 // 入参: 148 // @parameterMap 额外的参数,用于在自定义函数中进行使用,可见下面的customize的用法 149 // @any 待核查对象 150 // @fieldNames 待核查对象的核查属性;不指定则核查所有属性名 151 // 返回值: 152 // bool 是否匹配合法:true-合法,false-不合法 153 // string 不合法对应的说明 154 func CheckWithParameter(parameterMap map[string]interface{}, object interface{}, fieldNames ...string) (bool, string) {} 155 ``` 156 157 ## 更多功能 158 159 这里将核查部分分为匹配和处理两部分,匹配可以有多种的匹配器,核查的逻辑是只要有任何一个匹配器匹配上则认为匹配上,处理模块用于对匹配上的结果进行处理,比如返回指定的异常,或者匹配后接受还是拒绝对应的值,或者匹配后将某个值更改掉(待支持) 160 161 #### 匹配模块 162 163 - value:匹配指定的值 164 - isBlank:值是否为空字符 165 - isUnBlank:值是否为非空字符 166 - range:匹配数值的范围(最大值和最小值,用法是数学表达式):数值(整数和浮点数)的大小、字符串的长度、数组的长度、时间的范围、时间的移动 167 - model:匹配指定的类型: 168 - id_card:身份证 169 - phone: 手机号 170 - fixed_phone:固定电话 171 - mail: 邮件地址 172 - ip: ip地址 173 - condition:修饰的属性的表达式的匹配,提供#current和#root占位符,用于获取相邻属性的值 174 - regex:匹配正则表达式 175 - customize:匹配自定义的回调函数 176 177 #### 处理模块 178 179 - errMsg: 自定义的异常 180 - accept: 匹配后接受还是拒绝 181 - disable: 是否启用匹配,默认启用 182 183 184 ## 匹配模块 185 186 匹配器可以有多个一起修饰,只要匹配上一个,则认为匹配上 187 188 ### 1. 值匹配器:value 189 匹配指定的一些值,可以修饰一个,也可以修饰多个值,可以修饰字符,也可修饰整数(int、int8、int16、int32、int64)、无符号整数(uint、uint8、uint16、uint32、uint64)、浮点数(float32、float64)、bool类型和string类型。<br/> 190 191 提示: 192 - 中间逗号也可以为中文,为了防止某些手误写错为中文字符 193 194 195 ```go 196 // 修饰一个值 197 type ValueBaseEntityOne struct { 198 Name string `match:"value=zhou"` 199 Age int `match:"value=12"` 200 } 201 202 // 修饰一个值 203 type ValueBaseEntity struct { 204 Name string `match:"value={zhou, 宋江}"` 205 Age int `match:"value={12, 13}"` 206 } 207 ``` 208 209 如果有自定义类型嵌套,则可以使用标签`check`,用于解析复杂结构 210 ```go 211 type ValueInnerEntity struct { 212 InnerName string `match:"value={inner_zhou, inner_宋江}"` 213 InnerAge int `match:"value={2212, 2213}"` 214 } 215 216 type ValueStructEntity struct { 217 Name string `match:"value={zhou, 宋江}"` 218 Age int `match:"value={12, 13}"` 219 220 Inner ValueInnerEntity `match:"check"` 221 } 222 ``` 223 修饰的结构可以有如下 224 - 自定义结构 225 - 数组/分片:对应类型只有为复杂结构才会核查 226 - map:其中的key和value类型只有是复杂结构才会核查 227 228 ### 2. 空值匹配器:isBlank 229 匹配string类型的值是否为空字符,false:字符不为空则匹配上,true:字符为空则匹配上 230 ```go 231 // 默认为true 232 type IsBlankEntity2 struct { 233 Name string `match:"isBlank"` 234 Age int 235 } 236 237 // 同上 238 type IsBlankEntity3 struct { 239 Name string `match:"isBlank=true"` 240 Age int 241 } 242 243 type IsBlankEntity1 struct { 244 Name string `match:"isBlank=false"` 245 Age int 246 } 247 248 ``` 249 250 ### 3. 非空匹配器:isUnBlank 251 匹配string类型的值是否为非空字符,true:字符非空则匹配上,false:字符为空则匹配上 252 ```go 253 // 默认为true 254 type IsBlankEntity2 struct { 255 Name string `match:"isBlank"` 256 Age int 257 } 258 259 // 同上 260 type IsBlankEntity3 struct { 261 Name string `match:"isBlank=true"` 262 Age int 263 } 264 265 type IsBlankEntity1 struct { 266 Name string `match:"isBlank=false"` 267 Age int 268 } 269 270 ``` 271 272 ### 4. 范围匹配器:range 273 匹配类型的指定范围,方式使用数学表达式"["、"]"、"("、")",使用数学表达式的开闭符号 274 - [:表示大于等于 275 - ]:表示小于等于 276 - (:表示大于 277 - ):表示小于 278 279 比如:<br/> 280 - [1, 10):表示大于等于1而且小于10 281 - (1, 10):表示大于1而且小于10 282 - (1,):表示大于1 283 - (,10]:表示小于等于10,也可以[,10] 284 285 修饰的类型有 286 - 整数:比较大小,int、int8、int16、int32、int64 287 - 无符号整数:比较大小,uint、uint8、uint16、uint32、uint64 288 - 浮点数:比较大小,float32、float64 289 - 分片:匹配分片的长度 290 - 字符串:匹配字符串的长度 291 - 时间类型(time.Time):时间的范围,时间格式支持如下 292 - yyyy 293 - yyyy-MM 294 - yyyy-MM-dd 295 - yyyy-MM-dd HH 296 - yyyy-MM-dd HH:mm 297 - yyyy-MM-dd HH:mm:ss 298 - yyyy-MM-dd HH:mm:ss.SSS 299 - now:表示当前时间 300 - 变量时间:除了使用数学表达式外,还支持past、future这两个关键字,用于表示过去和未来的时间 301 - 时间计算:用于计算当前的时间向前或者向后推几个小时或者几分钟这种;(-1M, ):表示最近一个月的时间;(-1M3d, ):表示最近一个月零3天的时间,表示大于时间向前推一个月零三天的时间<br/> 302 - -/+:表示往前推还是往后推 303 - y:年 304 - M:月 305 - d:日 306 - h:小时 307 - m:分钟 308 - s:秒 309 310 ```go 311 // 整数类型1 312 type RangeIntEntity1 struct { 313 Age int `match:"range=[1, 2]"` 314 } 315 316 // 整数类型2 317 type RangeIntEntity2 struct { 318 Age int `match:"range=[3,]"` 319 } 320 321 // 整数类型3 322 type RangeIntEntity3 struct { 323 Age int `match:"range=[3,)"` 324 } 325 326 // 浮点数类型 327 type RangeFloatEntity struct { 328 Money float32 `match:"range=[10.37, 20.31]"` 329 } 330 331 // 字符类型 332 type RangeStringEntity struct { 333 Name string `match:"range=[2, 12]"` 334 } 335 336 // 分片类型 337 type RangeSliceEntity struct { 338 Age []int `match:"range=[2, 6]"` 339 } 340 341 // 时间类型1 342 type RangeTimeEntity1 struct { 343 CreateTime time.Time `match:"range=[2019-07-13 12:00:23.321, 2019-08-23 12:00:23.321]"` 344 } 345 346 // 时间类型2 347 type RangeTimeEntity2 struct { 348 CreateTime time.Time `match:"range=[2019-07-13 12:00:23.321, ]"` 349 } 350 351 // 时间类型3 352 type RangeTimeEntity3 struct { 353 CreateTime time.Time `match:"range=(, 2019-07-23 12:00:23.321]"` 354 } 355 356 // 时间类型4 357 type RangeTimeEntity4 struct { 358 CreateTime time.Time `match:"range=[2019-07-23 12:00:23.321, now)"` 359 } 360 361 // 时间类型4 362 type RangeTimeEntity5 struct { 363 CreateTime time.Time `match:"range=past"` 364 } 365 366 // 时间类型4 367 type RangeTimeEntity6 struct { 368 CreateTime time.Time `match:"range=future"` 369 } 370 371 // 时间计算:年 372 type RangeTimeCalEntity1 struct { 373 CreateTime time.Time `match:"range=(-1y, )"` 374 } 375 376 // 时间计算:月 377 type RangeTimeCalEntity2 struct { 378 CreateTime time.Time `match:"range=(-1M, )"` 379 } 380 381 // 时间计算:月日 382 // 顺序不能乱:yMdhms,中间可以为空,比如:-1y3d 383 type RangeTimeCalEntity2And1 struct { 384 CreateTime time.Time `match:"range=(-1M3d, )"` 385 } 386 387 // 时间计算:日 388 type RangeTimeCalEntity3 struct { 389 CreateTime time.Time `match:"range=(-3d, )"` 390 } 391 392 // 时间计算:时 393 type RangeTimeCalEntity4 struct { 394 CreateTime time.Time `match:"range=(-4h, )"` 395 } 396 397 // 时间计算:分 398 type RangeTimeCalEntity5 struct { 399 CreateTime time.Time `match:"range=(-12m, )"` 400 } 401 402 // 时间计算:秒 403 type RangeTimeCalEntity6 struct { 404 CreateTime time.Time `match:"range=(-120s, )"` 405 } 406 407 // 时间计算:正负号 408 type RangeTimeCalEntity7 struct { 409 CreateTime time.Time `match:"range=(2h, )"` 410 } 411 ``` 412 413 ### 5. 类型匹配器:model 414 类型匹配器:指定的几种内置类型进行匹配 415 - id_card:身份证 416 - phone: 手机号 417 - fixed_phone:固定电话 418 - mail: 邮件地址 419 - ip: ip地址 420 421 ```go 422 type ValueModelIdCardEntity struct { 423 Data string `match:"model=id_card"` 424 } 425 426 type ValueModelPhone struct { 427 Data string `match:"model=phone"` 428 } 429 430 type ValueModelFixedPhoneEntity struct { 431 Data string `match:"model=fixed_phone"` 432 } 433 434 type ValueModelEmailEntity struct { 435 Data string `match:"model=mail"` 436 } 437 438 type ValueModelIpAddressEntity struct { 439 Data string `match:"model=ip"` 440 } 441 ``` 442 443 ### 6. 表达式匹配器:condition 444 表达式匹配器:用于数学计算表达式进行计算,表达式是返回bool类型的表达式。提供两个占位符 445 - \#current:当前修饰的值 446 - \#root:当前属性所在的对象,比如:#root.Age,表示当前对象中的其他属性Age的值 447 448 ```go 449 // 测试基本表达式 450 type ValueConditionEntity1 struct { 451 Data1 int `match:"condition=#current + #root.Data2 > 100"` 452 Data2 int `match:"condition=#current < 20"` 453 Data3 int `match:"condition=(++#current) >31"` 454 } 455 456 // 测试表达式 457 type ValueConditionEntity2 struct { 458 Age int `match:"condition=#root.Judge"` 459 Judge bool 460 } 461 ``` 462 463 ### 7. 正则表达式匹配器:regex 464 正则表达式匹配器:用于匹配自定义的正则表达式 465 466 ```go 467 type ValueRegexEntity struct { 468 Name string `match:"regex=^zhou.*zhen$"` 469 Age int `match:"regex=^\\d+$"` 470 } 471 ``` 472 473 ### 8. 自定义回调匹配器:customize 474 该匹配器可以用于自定义扩展,比如实际业务场景,某个字段在数据库中存在,这种情况就需要用户自定义扩展 475 476 比如: 477 ```go 478 package fun 479 480 type CustomizeEntity1 struct { 481 // fun.Judge1是对应的函数 482 Name string `match:"customize=judge1Name"` 483 } 484 485 func JudgeString1(name string) bool { 486 if name == "zhou" || name == "宋江" { 487 return true 488 } 489 490 return false 491 } 492 493 // 由于go反射功能没那么强,因此需要用户自己先将函数和name进行注册 494 func init() { 495 validate.RegisterCustomize("judge1Name", JudgeString1) 496 } 497 ``` 498 ##### 说明: 499 500 其中自定义的函数有相关的要求,参数可以为一个,也可以为两个,也可以为三个<br/> 501 其中的参数类型有严格限制 502 - 属性类型 503 - 属性所在对象类型 504 - 外部参数类型`map[string]interface{}` 505 506 507 参数:<br/> 508 - 一个参数: 509 - 1:属性类型 510 - 2:属性所在对象类型 511 - 两个参数 512 - 1:属性所在对象类型,2:属性类型 513 - 1:属性所在对象类型,2:外部参数类型`map[string]interface{}` 514 - 1:属性类型,2:属性所在对象类型 515 - 1:属性类型,2:外部参数类型`map[string]interface{}` 516 - 三个参数:前两个参数为:属性类型和所在对象类型的组合 517 - 1:属性类型,2:属性所在对象类型,3:外部参数类型`map[string]interface{}` 518 - 1:属性所在对象类型,2:属性类型,3:外部参数类型`map[string]interface{}` 519 520 返回值:<br/> 521 - 一个值:则为bool类型(表示是否匹配上) 522 - 两个值:第一个为bool类型(表示是否匹配上),第二个为string类型(匹配或者没有匹配上的自定义错误) 523 524 更多详情请见测试类`customize_test.go` <br/><br/> 525 示例: 526 ```go 527 package fun 528 529 import ( 530 "fmt" 531 "github.com/isyscore/isc-gobase/validate" 532 ) 533 534 type CustomizeEntity2 struct { 535 Name string `match:"customize=judge2Name"` 536 } 537 538 type CustomizeEntity3 struct { 539 Name string `match:"customize=judge3Name"` 540 Age int 541 } 542 543 func JudgeString2(name string) (bool, string) { 544 if name == "zhou" || name == "宋江" { 545 return true, "" 546 } 547 548 return false, "没有命中可用的值'zhou'和'宋江'" 549 } 550 551 func JudgeString3(customize CustomizeEntity3, name string) (bool, string) { 552 if name == "zhou" || name == "宋江" { 553 if customize.Age > 12 { 554 return true, "" 555 } else { 556 return false, "用户[" + name + "]" + "没有满足年龄age > 12," + "当前年龄为:" + fmt.Sprintf("%v", customize.Age) 557 } 558 559 } else { 560 return false, "没有命中可用的值'zhou'和'宋江'" 561 } 562 } 563 564 // 由于go反射功能没那么强,因此需要用户自己先将函数和name进行注册 565 func init() { 566 validate.RegisterCustomize("judge2Name", JudgeString2) 567 validate.RegisterCustomize("judge3Name", JudgeString3) 568 } 569 ``` 570 571 ## 处理模块 572 当匹配后如何处理,这里分为了如下几种处理 573 574 ### 1. 匹配上接受/拒绝:accept 575 匹配后是接收还是拒绝,目前业内的处理方式都是匹配后接收,在概念上其实叫白名单,对于黑名单的处理,业内是没有,而我们这里用accept实现白名单和黑名单的概念 576 577 ```go 578 type AcceptEntity1 struct { 579 // 表示只要匹配上name为zhou的,则拒绝 580 Name string `match:"value=zhou" accept:"false"` 581 Age int 582 } 583 584 type AcceptEntity2 struct { 585 // 表示name为空,则拒绝,表示需要非空才行,以下同`match:"isUnBlank"` 586 Name string `match:"isBlank" accept:"false"` 587 Age int 588 } 589 590 // 只要任何一个匹配上,则接受 591 type AcceptEntity3 struct { 592 Name string `match:"isBlank=true value=zhou" accept:"true"` 593 Age int 594 } 595 ``` 596 597 ### 2. 自定义异常:errMsg 598 匹配后返回自定义的异常,提供了两个占位符#current表示修饰的当前属性的值,#root当前属性所在的结构的值,#root.Age表示当前结构中的属性Age对应的值 599 600 ```go 601 type ErrMsgEntity1 struct { 602 Name string `match:"value=zhou" errMsg:"对应的值不合法"` 603 Age int 604 } 605 606 type ErrMsgEntity2 struct { 607 Name string `match:"value=zhou"` 608 Age int `match:"condition=#current > 10" errMsg:"当前的值不合法,应该大于10,当前值为#current,对应的名字为#root.Name"` 609 } 610 ``` 611 612 ### 3. 启用:disable 613 表示是否启用整个核查 614 ```go 615 type DisableEntity1 struct { 616 Name string `match:"value=zhou" disable:"true"` 617 Age int 618 } 619 620 ```