github.com/zhongdalu/gf@v1.0.0/g/util/gvalid/gvalid_check.go (about) 1 // Copyright 2017-2018 gf Author(https://github.com/zhongdalu/gf). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/zhongdalu/gf. 6 7 package gvalid 8 9 import ( 10 "regexp" 11 "strconv" 12 "strings" 13 14 "github.com/zhongdalu/gf/g/container/gmap" 15 "github.com/zhongdalu/gf/g/encoding/gjson" 16 "github.com/zhongdalu/gf/g/net/gipv4" 17 "github.com/zhongdalu/gf/g/net/gipv6" 18 "github.com/zhongdalu/gf/g/os/gtime" 19 "github.com/zhongdalu/gf/g/text/gregex" 20 "github.com/zhongdalu/gf/g/util/gconv" 21 ) 22 23 const ( 24 gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)` // 单条规则匹配正则 25 ) 26 27 var ( 28 // 默认错误消息管理对象(并发安全) 29 errorMsgMap = gmap.NewStrStrMap() 30 31 // 单规则正则对象,这里使用包内部变量存储,不需要多次解析 32 ruleRegex, _ = regexp.Compile(gSINGLE_RULE_PATTERN) 33 34 // 即使参数为空(nil|"")也需要校验的规则,主要是必需规则及关联规则 35 mustCheckRulesEvenValueEmpty = map[string]struct{}{ 36 "required": struct{}{}, 37 "required-if": struct{}{}, 38 "required-unless": struct{}{}, 39 "required-with": struct{}{}, 40 "required-with-all": struct{}{}, 41 "required-without": struct{}{}, 42 "required-without-all": struct{}{}, 43 "same": struct{}{}, 44 "different": struct{}{}, 45 "in": struct{}{}, 46 "not-in": struct{}{}, 47 "regex": struct{}{}, 48 } 49 // 所有支持的校验规则 50 allSupportedRules = map[string]struct{}{ 51 "required": struct{}{}, 52 "required-if": struct{}{}, 53 "required-unless": struct{}{}, 54 "required-with": struct{}{}, 55 "required-with-all": struct{}{}, 56 "required-without": struct{}{}, 57 "required-without-all": struct{}{}, 58 "date": struct{}{}, 59 "date-format": struct{}{}, 60 "email": struct{}{}, 61 "phone": struct{}{}, 62 "telephone": struct{}{}, 63 "passport": struct{}{}, 64 "password": struct{}{}, 65 "password2": struct{}{}, 66 "password3": struct{}{}, 67 "postcode": struct{}{}, 68 "id-number": struct{}{}, 69 "qq": struct{}{}, 70 "ip": struct{}{}, 71 "ipv4": struct{}{}, 72 "ipv6": struct{}{}, 73 "mac": struct{}{}, 74 "url": struct{}{}, 75 "domain": struct{}{}, 76 "length": struct{}{}, 77 "min-length": struct{}{}, 78 "max-length": struct{}{}, 79 "between": struct{}{}, 80 "min": struct{}{}, 81 "max": struct{}{}, 82 "json": struct{}{}, 83 "integer": struct{}{}, 84 "float": struct{}{}, 85 "boolean": struct{}{}, 86 "same": struct{}{}, 87 "different": struct{}{}, 88 "in": struct{}{}, 89 "not-in": struct{}{}, 90 "regex": struct{}{}, 91 } 92 // 布尔Map 93 boolMap = map[string]struct{}{ 94 // true 95 "1": struct{}{}, 96 "true": struct{}{}, 97 "on": struct{}{}, 98 "yes": struct{}{}, 99 // false 100 "": struct{}{}, 101 "0": struct{}{}, 102 "false": struct{}{}, 103 "off": struct{}{}, 104 "no": struct{}{}, 105 } 106 ) 107 108 // 检测单条数据的规则: 109 // 110 // 1. value为需要校验的数据,可以为任意基本数据类型; 111 // 112 // 2. msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持 string/map/struct/*struct, 113 // 允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误; 114 // 115 // 3. params参数为联合校验参数,支持任意的map/struct/*struct类型,对于需要联合校验的规则有效,如:required-*、same、different; 116 func Check(value interface{}, rules string, msgs interface{}, params ...interface{}) *Error { 117 // 内部会将参数全部转换为字符串类型进行校验 118 val := strings.TrimSpace(gconv.String(value)) 119 data := make(map[string]string) 120 errorMsgs := make(map[string]string) 121 if len(params) > 0 { 122 for k, v := range gconv.Map(params[0]) { 123 data[k] = gconv.String(v) 124 } 125 } 126 // 自定义错误消息处理 127 msgArray := make([]string, 0) 128 customMsgMap := make(map[string]string) 129 switch v := msgs.(type) { 130 case string: 131 msgArray = strings.Split(v, "|") 132 default: 133 for k, v := range gconv.Map(msgs) { 134 customMsgMap[k] = gconv.String(v) 135 } 136 } 137 ruleItems := strings.Split(strings.TrimSpace(rules), "|") 138 // 规则项预处理, 主要解决规则中存在的"|"关键字符号 139 for i := 0; ; { 140 array := strings.Split(ruleItems[i], ":") 141 if _, ok := allSupportedRules[array[0]]; !ok { 142 if i > 0 && ruleItems[i-1][:5] == "regex" { 143 ruleItems[i-1] += "|" + ruleItems[i] 144 ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) 145 } else { 146 return newErrorStr("parse_error", "invalid rules:"+rules) 147 } 148 } else { 149 i++ 150 } 151 if i == len(ruleItems) { 152 break 153 } 154 } 155 for index := 0; index < len(ruleItems); { 156 item := ruleItems[index] 157 results := ruleRegex.FindStringSubmatch(item) 158 ruleKey := strings.TrimSpace(results[1]) 159 ruleVal := strings.TrimSpace(results[2]) 160 match := false 161 if len(msgArray) > index { 162 customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index]) 163 } 164 switch ruleKey { 165 // 必须字段 166 case "required": 167 fallthrough 168 case "required-if": 169 fallthrough 170 case "required-unless": 171 fallthrough 172 case "required-with": 173 fallthrough 174 case "required-with-all": 175 fallthrough 176 case "required-without": 177 fallthrough 178 case "required-without-all": 179 match = checkRequired(val, ruleKey, ruleVal, data) 180 181 // 长度范围 182 case "length": 183 fallthrough 184 case "min-length": 185 fallthrough 186 case "max-length": 187 if msg := checkLength(val, ruleKey, ruleVal, customMsgMap); msg != "" { 188 errorMsgs[ruleKey] = msg 189 } else { 190 match = true 191 } 192 193 // 大小范围 194 case "min": 195 fallthrough 196 case "max": 197 fallthrough 198 case "between": 199 if msg := checkSize(val, ruleKey, ruleVal, customMsgMap); msg != "" { 200 errorMsgs[ruleKey] = msg 201 } else { 202 match = true 203 } 204 205 // 自定义正则判断 206 case "regex": 207 // 需要判断是否被|符号截断,如果是,那么需要进行整合 208 for i := index + 1; i < len(ruleItems); i++ { 209 // 判断下一个规则是否合法,不合法那么和当前正则规则进行整合 210 if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) { 211 ruleVal += "|" + ruleItems[i] 212 index++ 213 } 214 } 215 match = gregex.IsMatchString(ruleVal, val) 216 217 // 日期格式, 218 case "date": 219 // 使用标准日期格式检查,但是日期之间必须带连接符号 220 if _, err := gtime.StrToTime(val); err == nil { 221 match = true 222 break 223 } 224 // 检查是否不带日期连接符号的格式 225 if _, err := gtime.StrToTime(val, "Ymd"); err == nil { 226 match = true 227 break 228 } 229 230 // 日期格式,需要给定日期格式 231 case "date-format": 232 if _, err := gtime.StrToTimeFormat(val, ruleVal); err == nil { 233 match = true 234 } 235 236 // 两字段值应相同(非敏感字符判断,非类型判断) 237 case "same": 238 if v, ok := data[ruleVal]; ok { 239 if strings.Compare(val, v) == 0 { 240 match = true 241 } 242 } 243 244 // 两字段值不应相同(非敏感字符判断,非类型判断) 245 case "different": 246 match = true 247 if v, ok := data[ruleVal]; ok { 248 if strings.Compare(val, v) == 0 { 249 match = false 250 } 251 } 252 253 // 字段值应当在指定范围中 254 case "in": 255 array := strings.Split(ruleVal, ",") 256 for _, v := range array { 257 if strings.Compare(val, strings.TrimSpace(v)) == 0 { 258 match = true 259 break 260 } 261 } 262 263 // 字段值不应当在指定范围中 264 case "not-in": 265 match = true 266 array := strings.Split(ruleVal, ",") 267 for _, v := range array { 268 if strings.Compare(val, strings.TrimSpace(v)) == 0 { 269 match = false 270 break 271 } 272 } 273 274 /* 275 * 验证所给手机号码是否符合手机号的格式. 276 * 移动: 134、135、136、137、138、139、150、151、152、157、158、159、182、183、184、187、188、178(4G)、147(上网卡); 277 * 联通: 130、131、132、155、156、185、186、176(4G)、145(上网卡)、175; 278 * 电信: 133、153、180、181、189 、177(4G); 279 * 卫星通信: 1349 280 * 虚拟运营商: 170、173 281 * 2018新增: 16x, 19x 282 */ 283 case "phone": 284 match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, val) 285 286 // 国内座机电话号码:"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX" 287 case "telephone": 288 match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, val) 289 290 // 腾讯QQ号,从10000开始 291 case "qq": 292 match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, val) 293 294 // 中国邮政编码 295 case "postcode": 296 match = gregex.IsMatchString(`^\d{6}$`, val) 297 298 /* 299 公民身份证号 300 xxxxxx yyyy MM dd 375 0 十八位 301 xxxxxx yy MM dd 75 0 十五位 302 303 地区:[1-9]\d{5} 304 年的前两位:(18|19|([23]\d)) 1800-2399 305 年的后两位:\d{2} 306 月份:((0[1-9])|(10|11|12)) 307 天数:(([0-2][1-9])|10|20|30|31) 闰年不能禁止29+ 308 309 三位顺序码:\d{3} 310 两位顺序码:\d{2} 311 校验码: [0-9Xx] 312 313 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$ 314 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$ 315 316 总: 317 (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$) 318 */ 319 case "id-number": 320 match = gregex.IsMatchString(`(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)`, val) 321 322 // 通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间) 323 case "passport": 324 match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, val) 325 326 // 通用密码(任意可见字符,长度在6~18之间) 327 case "password": 328 match = gregex.IsMatchString(`^[\w\S]{6,18}$`, val) 329 330 // 中等强度密码(在弱密码的基础上,必须包含大小写字母和数字) 331 case "password2": 332 if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) && gregex.IsMatchString(`[a-z]+`, val) && gregex.IsMatchString(`[A-Z]+`, val) && gregex.IsMatchString(`\d+`, val) { 333 match = true 334 } 335 336 // 强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符) 337 case "password3": 338 if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) && gregex.IsMatchString(`[a-z]+`, val) && gregex.IsMatchString(`[A-Z]+`, val) && gregex.IsMatchString(`\d+`, val) && gregex.IsMatchString(`[^a-zA-Z0-9]+`, val) { 339 match = true 340 } 341 342 // json 343 case "json": 344 if _, err := gjson.Decode([]byte(val)); err == nil { 345 match = true 346 } 347 348 // 整数 349 case "integer": 350 if _, err := strconv.Atoi(val); err == nil { 351 match = true 352 } 353 354 // 小数 355 case "float": 356 if _, err := strconv.ParseFloat(val, 10); err == nil { 357 match = true 358 } 359 360 // 布尔值(1,true,on,yes:true | 0,false,off,no,"":false) 361 case "boolean": 362 match = false 363 if _, ok := boolMap[strings.ToLower(val)]; ok { 364 match = true 365 } 366 367 // 邮件 368 case "email": 369 match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, val) 370 371 // URL 372 case "url": 373 match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, val) 374 375 // domain 376 case "domain": 377 match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z-]{0,62}\.)+([0-9a-zA-Z][0-9a-zA-Z-]{0,62})\.?$`, val) 378 379 // IP(IPv4/IPv6) 380 case "ip": 381 match = gipv4.Validate(val) || gipv6.Validate(val) 382 383 // IPv4 384 case "ipv4": 385 match = gipv4.Validate(val) 386 387 // IPv6 388 case "ipv6": 389 match = gipv6.Validate(val) 390 391 // MAC地址 392 case "mac": 393 match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, val) 394 395 default: 396 errorMsgs[ruleKey] = "Invalid rule name:" + ruleKey 397 } 398 399 // 错误消息整合 400 if !match { 401 // 不存在则使用默认的错误信息, 402 // 如果在校验过程中已经设置了错误信息,那么这里便不作处理 403 if _, ok := errorMsgs[ruleKey]; !ok { 404 if msg, ok := customMsgMap[ruleKey]; ok { 405 errorMsgs[ruleKey] = msg 406 } else { 407 errorMsgs[ruleKey] = errorMsgMap.Get(ruleKey) 408 } 409 } 410 } 411 index++ 412 } 413 if len(errorMsgs) > 0 { 414 return newError([]string{rules}, ErrorMap{ 415 // 单条数值校验没有键名 416 "": errorMsgs, 417 }) 418 } 419 return nil 420 } 421 422 // 判断必须字段 423 func checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool { 424 required := false 425 switch ruleKey { 426 // 必须字段 427 case "required": 428 required = true 429 430 // 必须字段(当任意所给定字段值与所给值相等时) 431 case "required-if": 432 required = false 433 array := strings.Split(ruleVal, ",") 434 // 必须为偶数,才能是键值对匹配 435 if len(array)%2 == 0 { 436 for i := 0; i < len(array); { 437 tk := array[i] 438 tv := array[i+1] 439 if v, ok := params[tk]; ok { 440 if strings.Compare(tv, v) == 0 { 441 required = true 442 break 443 } 444 } 445 i += 2 446 } 447 } 448 449 // 必须字段(当所给定字段值与所给值都不相等时) 450 case "required-unless": 451 required = true 452 array := strings.Split(ruleVal, ",") 453 // 必须为偶数,才能是键值对匹配 454 if len(array)%2 == 0 { 455 for i := 0; i < len(array); { 456 tk := array[i] 457 tv := array[i+1] 458 if v, ok := params[tk]; ok { 459 if strings.Compare(tv, v) == 0 { 460 required = false 461 break 462 } 463 } 464 i += 2 465 } 466 } 467 468 // 必须字段(当所给定任意字段值不为空时) 469 case "required-with": 470 required = false 471 array := strings.Split(ruleVal, ",") 472 for i := 0; i < len(array); i++ { 473 if params[array[i]] != "" { 474 required = true 475 break 476 } 477 } 478 479 // 必须字段(当所给定所有字段值都不为空时) 480 case "required-with-all": 481 required = true 482 array := strings.Split(ruleVal, ",") 483 for i := 0; i < len(array); i++ { 484 if params[array[i]] == "" { 485 required = false 486 break 487 } 488 } 489 490 // 必须字段(当所给定任意字段值为空时) 491 case "required-without": 492 required = false 493 array := strings.Split(ruleVal, ",") 494 for i := 0; i < len(array); i++ { 495 if params[array[i]] == "" { 496 required = true 497 break 498 } 499 } 500 501 // 必须字段(当所给定所有字段值都为空时) 502 case "required-without-all": 503 required = true 504 array := strings.Split(ruleVal, ",") 505 for i := 0; i < len(array); i++ { 506 if params[array[i]] != "" { 507 required = false 508 break 509 } 510 } 511 } 512 if required { 513 return !(value == "") 514 } else { 515 return true 516 } 517 } 518 519 // 对字段值长度进行检测 520 func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { 521 msg := "" 522 switch ruleKey { 523 // 长度范围 524 case "length": 525 array := strings.Split(ruleVal, ",") 526 min := 0 527 max := 0 528 if len(array) > 0 { 529 if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil { 530 min = v 531 } 532 } 533 if len(array) > 1 { 534 if v, err := strconv.Atoi(strings.TrimSpace(array[1])); err == nil { 535 max = v 536 } 537 } 538 if len(value) < min || len(value) > max { 539 if v, ok := customMsgMap[ruleKey]; !ok { 540 msg = errorMsgMap.Get(ruleKey) 541 } else { 542 msg = v 543 } 544 msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) 545 msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) 546 return msg 547 } 548 549 // 最小长度 550 case "min-length": 551 if min, err := strconv.Atoi(ruleVal); err == nil { 552 if len(value) < min { 553 if v, ok := customMsgMap[ruleKey]; !ok { 554 msg = errorMsgMap.Get(ruleKey) 555 } else { 556 msg = v 557 } 558 msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) 559 } 560 } else { 561 msg = "校验参数[" + ruleVal + "]应当为整数类型" 562 } 563 564 // 最大长度 565 case "max-length": 566 if max, err := strconv.Atoi(ruleVal); err == nil { 567 if len(value) > max { 568 if v, ok := customMsgMap[ruleKey]; !ok { 569 msg = errorMsgMap.Get(ruleKey) 570 } else { 571 msg = v 572 } 573 msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) 574 } 575 } else { 576 msg = "校验参数[" + ruleVal + "]应当为整数类型" 577 } 578 } 579 return msg 580 } 581 582 // 对字段值大小进行检测 583 func checkSize(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { 584 msg := "" 585 switch ruleKey { 586 // 大小范围 587 case "between": 588 array := strings.Split(ruleVal, ",") 589 min := float64(0) 590 max := float64(0) 591 if len(array) > 0 { 592 if v, err := strconv.ParseFloat(strings.TrimSpace(array[0]), 10); err == nil { 593 min = v 594 } 595 } 596 if len(array) > 1 { 597 if v, err := strconv.ParseFloat(strings.TrimSpace(array[1]), 10); err == nil { 598 max = v 599 } 600 } 601 if v, err := strconv.ParseFloat(value, 10); err == nil { 602 if v < min || v > max { 603 if v, ok := customMsgMap[ruleKey]; !ok { 604 msg = errorMsgMap.Get(ruleKey) 605 } else { 606 msg = v 607 } 608 msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) 609 msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) 610 } 611 } else { 612 msg = "输入参数[" + value + "]应当为数字类型" 613 } 614 615 // 最小值 616 case "min": 617 if min, err := strconv.ParseFloat(ruleVal, 10); err == nil { 618 if v, err := strconv.ParseFloat(value, 10); err == nil { 619 if v < min { 620 if v, ok := customMsgMap[ruleKey]; !ok { 621 msg = errorMsgMap.Get(ruleKey) 622 } else { 623 msg = v 624 } 625 msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) 626 } 627 } else { 628 msg = "输入参数[" + value + "]应当为数字类型" 629 } 630 } else { 631 msg = "校验参数[" + ruleVal + "]应当为数字类型" 632 } 633 634 // 最大值 635 case "max": 636 if max, err := strconv.ParseFloat(ruleVal, 10); err == nil { 637 if v, err := strconv.ParseFloat(value, 10); err == nil { 638 if v > max { 639 if v, ok := customMsgMap[ruleKey]; !ok { 640 msg = errorMsgMap.Get(ruleKey) 641 } else { 642 msg = v 643 } 644 msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) 645 } 646 } else { 647 msg = "输入参数[" + value + "]应当为数字类型" 648 } 649 } else { 650 msg = "校验参数[" + ruleVal + "]应当为数字类型" 651 } 652 } 653 return msg 654 }