mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
初步完成gvalid模块校验结果的顺序特性
This commit is contained in:
2
TODO.MD
2
TODO.MD
@ -42,7 +42,7 @@
|
||||
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
|
||||
1. 模板引擎增加对对象的支持(参考https://segmentfault.com/q/1010000016829214);
|
||||
1. 改进gfpool在文件指针变化时的更新;
|
||||
|
||||
1. 去掉ghhtp路由注册和检索时的并发安全锁控制,由于注册并不会产生并发安全,并且不能动态注册(去掉动态注册特性),因此并发安全锁便没有意义;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,25 +1,15 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 数据校验.
|
||||
// 本来打算取名gvalidator的,名字太长了,缩写一下
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"gitee.com/johng/gf/third/github.com/fatih/structs"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/net/gipv4"
|
||||
"gitee.com/johng/gf/g/net/gipv6"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/encoding/gjson"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -67,701 +57,43 @@ not-in 格式:not-in:value1,value2,... 说明:参
|
||||
regex 格式:regex:pattern 说明:参数值应当满足正则匹配规则pattern
|
||||
*/
|
||||
|
||||
// 默认规则校验错误消息(可以通过接口自定义错误消息)
|
||||
var defaultMessages = map[string]string {
|
||||
"required" : "字段不能为空",
|
||||
"required-if" : "字段不能为空",
|
||||
"required-unless" : "字段不能为空",
|
||||
"required-with" : "字段不能为空",
|
||||
"required-with-all" : "字段不能为空",
|
||||
"required-without" : "字段不能为空",
|
||||
"required-without-all" : "字段不能为空",
|
||||
"date" : "日期格式不正确",
|
||||
"date-format" : "日期格式不正确",
|
||||
"email" : "邮箱地址格式不正确",
|
||||
"phone" : "手机号码格式不正确",
|
||||
"telephone" : "电话号码格式不正确",
|
||||
"passport" : "账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间",
|
||||
"password" : "密码格式不合法,密码格式为任意6-18位的可见字符",
|
||||
"password2" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字",
|
||||
"password3" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符",
|
||||
"postcode" : "邮政编码不正确",
|
||||
"id-number" : "身份证号码不正确",
|
||||
"qq" : "QQ号码格式不正确",
|
||||
"ip" : "IP地址格式不正确",
|
||||
"ipv4" : "IPv4地址格式不正确",
|
||||
"ipv6" : "IPv6地址格式不正确",
|
||||
"mac" : "MAC地址格式不正确",
|
||||
"url" : "URL地址格式不正确",
|
||||
"domain" : "域名格式不正确",
|
||||
"length" : "字段长度为:min到:max个字符",
|
||||
"min-length" : "字段最小长度为:min",
|
||||
"max-length" : "字段最大长度为:max",
|
||||
"between" : "字段大小为:min到:max",
|
||||
"min" : "字段最小值为:min",
|
||||
"max" : "字段最大值为:max",
|
||||
"json" : "字段应当为JSON格式",
|
||||
"xml" : "字段应当为XML格式",
|
||||
"array" : "字段应当为数组",
|
||||
"integer" : "字段应当为整数",
|
||||
"float" : "字段应当为浮点数",
|
||||
"boolean" : "字段应当为布尔值",
|
||||
"same" : "字段值不合法",
|
||||
"different" : "字段值不合法",
|
||||
"in" : "字段值不合法",
|
||||
"not-in" : "字段值不合法",
|
||||
"regex" : "字段值不合法",
|
||||
// 自定义错误信息: map[键名] => 字符串|map[规则]错误信息
|
||||
type CustomMsg = map[string]interface{}
|
||||
|
||||
// 解析单条sequence tag,格式: [数值键名/别名@]校验规则[#错误提示],
|
||||
// 其中校验规则如果有多个那么以"|"符号分隔,错误提示同理。
|
||||
func parseSequenceTag(tag string) (name, rule, msg string) {
|
||||
match, _ := gregex.MatchString(`\s*((\w+)\s*@){0,1}\s*([^#]+)\s*(#\s*(.*)){0,1}\s*`, tag)
|
||||
return strings.TrimSpace(match[2]), strings.TrimSpace(match[3]), strings.TrimSpace(match[5])
|
||||
}
|
||||
|
||||
const (
|
||||
gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)` // 单条规则匹配正则
|
||||
)
|
||||
|
||||
var (
|
||||
// 默认错误消息管理对象(并发安全)
|
||||
errorMsgMap = gmap.NewStringStringMap()
|
||||
|
||||
// 单规则正则对象,这里使用包内部变量存储,不需要多次解析
|
||||
ruleRegex, _ = regexp.Compile(gSINGLE_RULE_PATTERN)
|
||||
|
||||
// 即时参数为空(nil|"")也需要校验的规则,主要是必需规则及关联规则
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
||||
"required" : struct{}{},
|
||||
"required-if" : struct{}{},
|
||||
"required-unless" : struct{}{},
|
||||
"required-with" : struct{}{},
|
||||
"required-with-all" : struct{}{},
|
||||
"required-without" : struct{}{},
|
||||
"required-without-all" : struct{}{},
|
||||
"same" : struct{}{},
|
||||
"different" : struct{}{},
|
||||
"in" : struct{}{},
|
||||
"not-in" : struct{}{},
|
||||
"regex" : struct{}{},
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化错误消息管理对象
|
||||
func init() {
|
||||
errorMsgMap.BatchSet(defaultMessages)
|
||||
}
|
||||
|
||||
// 替换默认的错误提示为指定的自定义提示
|
||||
// 主要作用:
|
||||
// 1、便于多语言错误提示设置;
|
||||
// 2、默认错误提示信息不满意;
|
||||
func SetDefaultErrorMsgs(msgs map[string]string) {
|
||||
errorMsgMap.BatchSet(msgs)
|
||||
}
|
||||
|
||||
// 判断必须字段
|
||||
func checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool {
|
||||
required := false
|
||||
switch ruleKey {
|
||||
// 必须字段
|
||||
case "required":
|
||||
required = true
|
||||
|
||||
// 必须字段(当任意所给定字段值与所给值相等时)
|
||||
case "required-if":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
// 必须为偶数,才能是键值对匹配
|
||||
if len(array)%2 == 0 {
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
if v, ok := params[tk]; ok {
|
||||
if strings.Compare(tv, v) == 0 {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定字段值与所给值都不相等时)
|
||||
case "required-unless":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
// 必须为偶数,才能是键值对匹配
|
||||
if len(array)%2 == 0 {
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
if v, ok := params[tk]; ok {
|
||||
if strings.Compare(tv, v) == 0 {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定任意字段值不为空时)
|
||||
case "required-with":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v != "" {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定所有字段值都不为空时)
|
||||
case "required-with-all":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v == "" {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定任意字段值为空时)
|
||||
case "required-without":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v == "" {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定所有字段值都为空时)
|
||||
case "required-without-all":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v != "" {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if required {
|
||||
return !(value == "")
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 对字段值长度进行检测
|
||||
func checkLength(value, ruleKey, ruleVal string, custonMsgs map[string]string) string {
|
||||
msg := ""
|
||||
switch ruleKey {
|
||||
// 长度范围
|
||||
case "length":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
min := 0
|
||||
max := 0
|
||||
if len(array) > 0 {
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
if len(array) > 1 {
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(array[1])); err == nil {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
if len(value) < min || len(value) > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1)
|
||||
msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1)
|
||||
return msg
|
||||
}
|
||||
|
||||
// 最小长度
|
||||
case "min-length":
|
||||
if min, err := strconv.Atoi(ruleVal); err == nil {
|
||||
if len(value) < min {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为整数类型"
|
||||
}
|
||||
|
||||
// 最大长度
|
||||
case "max-length":
|
||||
if max, err := strconv.Atoi(ruleVal); err == nil {
|
||||
if len(value) > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为整数类型"
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// 对字段值大小进行检测
|
||||
func checkSize(value, ruleKey, ruleVal string, custonMsgs map[string]string) string {
|
||||
msg := ""
|
||||
switch ruleKey {
|
||||
// 大小范围
|
||||
case "between":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
min := float64(0)
|
||||
max := float64(0)
|
||||
if len(array) > 0 {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(array[0]), 10); err == nil {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
if len(array) > 1 {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(array[1]), 10); err == nil {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v < min || v > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1)
|
||||
msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
|
||||
// 最小值
|
||||
case "min":
|
||||
if min, err := strconv.ParseFloat(ruleVal, 10); err == nil {
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v < min {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为数字类型"
|
||||
}
|
||||
|
||||
// 最大值
|
||||
case "max":
|
||||
if max, err := strconv.ParseFloat(ruleVal, 10); err == nil {
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为数字类型"
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// 检测键值对参数Map,注意返回参数是一个2维的关联数组,第一维键名为参数键名,第二维为带有错误的校验规则名称,值为错误信息
|
||||
func CheckMap(params map[string]interface{}, rules map[string]string, msgs...map[string]interface{}) Error {
|
||||
var value interface{}
|
||||
// 自定义消息,非必须参数,因此这里需要做判断
|
||||
customMsgs := make(map[string]interface{})
|
||||
if len(msgs) > 0 && len(msgs[0]) > 0 {
|
||||
customMsgs = msgs[0]
|
||||
}
|
||||
errorMsgs := make(map[string]map[string]string)
|
||||
// 以校验规则作为基础
|
||||
for key, rule := range rules {
|
||||
value = nil
|
||||
if v, ok := params[key]; ok {
|
||||
value = v
|
||||
// 解析sequence tag为标准校验规则及自定义错误
|
||||
func parseSequenceTags(tags []string) (rules map[string]string, msgs map[string]interface{}) {
|
||||
rules = make(map[string]string)
|
||||
msgs = make(map[string]interface{})
|
||||
for _, tag := range tags {
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
// 校验规则
|
||||
if len(name) == 0 {
|
||||
continue
|
||||
}
|
||||
msg, _ := customMsgs[key]
|
||||
if e := Check(value, rule, msg, params); e != nil {
|
||||
_, m := e.FirstItem()
|
||||
// 如果值为nil|"",并且不需要require*验证时,其他验证失效
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
for k, _ := range m {
|
||||
if _, ok := mustCheckRulesEvenValueEmpty[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
rules[name] = rule
|
||||
// 错误提示
|
||||
if len(msg) > 0 {
|
||||
ruleArray := strings.Split(rule, "|")
|
||||
msgArray := strings.Split(msg, "|")
|
||||
for k, v := range ruleArray {
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, ok := errorMsgs[key]; !ok {
|
||||
errorMsgs[key] = make(map[string]string)
|
||||
}
|
||||
for k, v := range m {
|
||||
errorMsgs[key][k] = v
|
||||
// 关联校验会有":"符号
|
||||
array := strings.Split(v, ":")
|
||||
if _, ok := msgs[name]; !ok {
|
||||
msgs[name] = make(map[string]string)
|
||||
}
|
||||
msgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
return errorMsgs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 校验struct对象属性,object参数也可以是一个指向对象的指针,返回值同CheckMap方法
|
||||
func CheckStruct(st interface{}, rules map[string]string, msgs...map[string]interface{}) Error {
|
||||
fields := structs.Fields(st)
|
||||
if rules == nil {
|
||||
rules = make(map[string]string)
|
||||
}
|
||||
params := make(map[string]interface{})
|
||||
errMsgs := (map[string]interface{})(nil)
|
||||
if len(msgs) == 0 {
|
||||
errMsgs = make(map[string]interface{})
|
||||
} else {
|
||||
errMsgs = msgs[0]
|
||||
}
|
||||
for _, field := range fields {
|
||||
params[field.Name()] = field.Value()
|
||||
if tag := field.Tag("gvalid"); tag != "" {
|
||||
match, _ := gregex.MatchString(`\s*((\w+)\s*@){0,1}\s*([^#]+)\s*(#\s*(.*)){0,1}\s*`, tag)
|
||||
name := match[2]
|
||||
rule := match[3]
|
||||
msg := match[5]
|
||||
if len(name) == 0 {
|
||||
name = field.Name()
|
||||
}
|
||||
// params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用
|
||||
if _, ok := params[name]; !ok {
|
||||
params[name] = field.Value()
|
||||
}
|
||||
// 校验规则
|
||||
if _, ok := rules[name]; !ok {
|
||||
rules[name] = rule
|
||||
}
|
||||
// 错误提示
|
||||
if len(msg) > 0 {
|
||||
ruleArray := strings.Split(rule, "|")
|
||||
msgArray := strings.Split(msg, "|")
|
||||
for k, v := range ruleArray {
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
if _, ok := errMsgs[name]; !ok {
|
||||
errMsgs[name] = make(map[string]string)
|
||||
}
|
||||
errMsgs[name].(map[string]string)[array[0]] = msgArray[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return CheckMap(params, rules, errMsgs)
|
||||
}
|
||||
|
||||
// 检测单条数据的规则.
|
||||
// val为校验数据,可以为任意基本数据格式;
|
||||
// msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持string/map[string]string,允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误;
|
||||
// params参数为表单联合校验参数,对于需要联合校验的规则有效,如:required-*、same、different;
|
||||
func Check(val interface{}, rules string, msgs interface{}, params...map[string]interface{}) Error {
|
||||
// 内部会将参数全部转换为字符串类型进行校验
|
||||
value := strings.TrimSpace(gconv.String(val))
|
||||
data := make(map[string]string)
|
||||
errorMsgs := make(map[string]string)
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
data[k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
// 自定义错误消息处理
|
||||
list := make([]string, 0)
|
||||
custonMsgs := make(map[string]string)
|
||||
switch value := msgs.(type) {
|
||||
case map[string]string:
|
||||
custonMsgs = value
|
||||
case string:
|
||||
list = strings.Split(value, "|")
|
||||
}
|
||||
items := strings.Split(strings.TrimSpace(rules), "|")
|
||||
for index := 0; index < len(items); {
|
||||
item := items[index]
|
||||
results := ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey := strings.TrimSpace(results[1])
|
||||
ruleVal := strings.TrimSpace(results[2])
|
||||
match := false
|
||||
if len(list) > index {
|
||||
custonMsgs[ruleKey] = strings.TrimSpace(list[index])
|
||||
}
|
||||
switch ruleKey {
|
||||
// 必须字段
|
||||
case "required": fallthrough
|
||||
case "required-if": fallthrough
|
||||
case "required-unless": fallthrough
|
||||
case "required-with": fallthrough
|
||||
case "required-with-all": fallthrough
|
||||
case "required-without": fallthrough
|
||||
case "required-without-all":
|
||||
match = checkRequired(value, ruleKey, ruleVal, data)
|
||||
|
||||
// 长度范围
|
||||
case "length": fallthrough
|
||||
case "min-length": fallthrough
|
||||
case "max-length":
|
||||
if msg := checkLength(value, ruleKey, ruleVal, custonMsgs); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 大小范围
|
||||
case "min": fallthrough
|
||||
case "max": fallthrough
|
||||
case "between":
|
||||
if msg := checkSize(value, ruleKey, ruleVal, custonMsgs); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 自定义正则判断
|
||||
case "regex":
|
||||
// 需要判断是否被|符号截断,如果是,那么需要进行整合
|
||||
for i := index + 1; i < len(items); i++ {
|
||||
// 判断下一个规则是否合法,不合法那么和当前正则规则进行整合
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, items[i]) {
|
||||
ruleVal += "|" + items[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(ruleVal, value)
|
||||
|
||||
// 日期格式,
|
||||
case "date":
|
||||
if _, err := gtime.StrToTime(value); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// 日期格式,需要给定日期格式
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(value, ruleVal); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 两字段值应相同(非敏感字符判断,非类型判断)
|
||||
case "same":
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(value, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
// 两字段值不应相同(非敏感字符判断,非类型判断)
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(value, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
|
||||
// 字段值应当在指定范围中
|
||||
case "in":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(value, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 字段值不应当在指定范围中
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(value, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 验证所给手机号码是否符合手机号的格式.
|
||||
* 移动:134、135、136、137、138、139、150、151、152、157、158、159、182、183、184、187、188、178(4G)、147(上网卡);
|
||||
* 联通:130、131、132、155、156、185、186、176(4G)、145(上网卡)、175;
|
||||
* 电信:133、153、180、181、189 、177(4G);
|
||||
* 卫星通信: 1349
|
||||
* 虚拟运营商:170、173
|
||||
*/
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$`, value)
|
||||
|
||||
// 国内座机电话号码:"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, value)
|
||||
|
||||
// 腾讯QQ号,从10000开始
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, value)
|
||||
|
||||
// 中国邮政编码
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^[1-9]\d{5}$`, value)
|
||||
|
||||
/*
|
||||
公民身份证号
|
||||
xxxxxx yyyy MM dd 375 0 十八位
|
||||
xxxxxx yy MM dd 75 0 十五位
|
||||
|
||||
地区:[1-9]\d{5}
|
||||
年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
年的后两位:\d{2}
|
||||
月份:((0[1-9])|(10|11|12))
|
||||
天数:(([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
|
||||
三位顺序码:\d{3}
|
||||
两位顺序码:\d{2}
|
||||
校验码: [0-9Xx]
|
||||
|
||||
十八位:^[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{2}$
|
||||
|
||||
总:
|
||||
(^[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{2}$)
|
||||
*/
|
||||
case "id-number":
|
||||
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{2}$)`, value)
|
||||
|
||||
// 通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间)
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, value)
|
||||
|
||||
// 通用密码(任意可见字符,长度在6~18之间)
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, value)
|
||||
|
||||
// 中等强度密码(在弱密码的基础上,必须包含大小写字母和数字)
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, value) && gregex.IsMatchString(`[a-z]+`, value) && gregex.IsMatchString(`[A-Z]+`, value) && gregex.IsMatchString(`\d+`, value) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符)
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, value) && gregex.IsMatchString(`[a-z]+`, value) && gregex.IsMatchString(`[A-Z]+`, value) && gregex.IsMatchString(`\d+`, value) && gregex.IsMatchString(`\S+`, value) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// json
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(value)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 整数
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(value); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 小数
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 布尔值(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
case "boolean":
|
||||
if value != "" && value != "0" && value != "false" && value != "off" && value != "no" {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 邮件
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`, value)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$`, value)
|
||||
|
||||
// domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z-]{0,62}\.)+([0-9a-zA-Z][0-9a-zA-Z-]{0,62})\.?$`, value)
|
||||
|
||||
// IP(IPv4/IPv6)
|
||||
case "ip":
|
||||
match = gipv4.Validate(value) || gipv6.Validate(value)
|
||||
|
||||
// IPv4
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(value)
|
||||
|
||||
// IPv6
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(value)
|
||||
|
||||
// MAC地址
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$`, value)
|
||||
|
||||
default:
|
||||
errorMsgs[ruleKey] = "Invalid rule name:" + ruleKey
|
||||
}
|
||||
|
||||
// 错误消息整合
|
||||
if !match {
|
||||
// 不存在则使用默认的错误信息,
|
||||
// 如果在校验过程中已经设置了错误信息,那么这里便不作处理
|
||||
if _, ok := errorMsgs[ruleKey]; !ok {
|
||||
if msg, ok := custonMsgs[ruleKey]; ok {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
errorMsgs[ruleKey] = errorMsgMap.Get(ruleKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
e := make(Error)
|
||||
e[value] = errorMsgs
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
566
g/util/gvalid/gvalid_check.go
Normal file
566
g/util/gvalid/gvalid_check.go
Normal file
@ -0,0 +1,566 @@
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/encoding/gjson"
|
||||
"gitee.com/johng/gf/g/net/gipv4"
|
||||
"gitee.com/johng/gf/g/net/gipv6"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)` // 单条规则匹配正则
|
||||
)
|
||||
|
||||
var (
|
||||
// 默认错误消息管理对象(并发安全)
|
||||
errorMsgMap = gmap.NewStringStringMap()
|
||||
|
||||
// 单规则正则对象,这里使用包内部变量存储,不需要多次解析
|
||||
ruleRegex, _ = regexp.Compile(gSINGLE_RULE_PATTERN)
|
||||
|
||||
// 即使参数为空(nil|"")也需要校验的规则,主要是必需规则及关联规则
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{} {
|
||||
"required" : struct{}{},
|
||||
"required-if" : struct{}{},
|
||||
"required-unless" : struct{}{},
|
||||
"required-with" : struct{}{},
|
||||
"required-with-all" : struct{}{},
|
||||
"required-without" : struct{}{},
|
||||
"required-without-all" : struct{}{},
|
||||
"same" : struct{}{},
|
||||
"different" : struct{}{},
|
||||
"in" : struct{}{},
|
||||
"not-in" : struct{}{},
|
||||
"regex" : struct{}{},
|
||||
}
|
||||
)
|
||||
|
||||
// 检测单条数据的规则:
|
||||
// value为需要校验的数据,可以为任意基本数据类型;
|
||||
// msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持 string/map[string]string ,允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误;
|
||||
// params参数为联合校验参数,对于需要联合校验的规则有效,如:required-*、same、different;
|
||||
func Check(value interface{}, rules string, msgs interface{}, params...map[string]interface{}) *Error {
|
||||
// 内部会将参数全部转换为字符串类型进行校验
|
||||
val := strings.TrimSpace(gconv.String(value))
|
||||
data := make(map[string]string)
|
||||
errorMsgs := make(map[string]string)
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
data[k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
// 自定义错误消息处理
|
||||
list := make([]string, 0)
|
||||
customMsgs := make(map[string]string)
|
||||
switch v := msgs.(type) {
|
||||
case map[string]string:
|
||||
customMsgs = v
|
||||
|
||||
case string:
|
||||
list = strings.Split(v, "|")
|
||||
}
|
||||
items := strings.Split(strings.TrimSpace(rules), "|")
|
||||
for index := 0; index < len(items); {
|
||||
item := items[index]
|
||||
results := ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey := strings.TrimSpace(results[1])
|
||||
ruleVal := strings.TrimSpace(results[2])
|
||||
match := false
|
||||
if len(list) > index {
|
||||
customMsgs[ruleKey] = strings.TrimSpace(list[index])
|
||||
}
|
||||
switch ruleKey {
|
||||
// 必须字段
|
||||
case "required": fallthrough
|
||||
case "required-if": fallthrough
|
||||
case "required-unless": fallthrough
|
||||
case "required-with": fallthrough
|
||||
case "required-with-all": fallthrough
|
||||
case "required-without": fallthrough
|
||||
case "required-without-all":
|
||||
match = checkRequired(val, ruleKey, ruleVal, data)
|
||||
|
||||
// 长度范围
|
||||
case "length": fallthrough
|
||||
case "min-length": fallthrough
|
||||
case "max-length":
|
||||
if msg := checkLength(val, ruleKey, ruleVal, customMsgs); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 大小范围
|
||||
case "min": fallthrough
|
||||
case "max": fallthrough
|
||||
case "between":
|
||||
if msg := checkSize(val, ruleKey, ruleVal, customMsgs); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 自定义正则判断
|
||||
case "regex":
|
||||
// 需要判断是否被|符号截断,如果是,那么需要进行整合
|
||||
for i := index + 1; i < len(items); i++ {
|
||||
// 判断下一个规则是否合法,不合法那么和当前正则规则进行整合
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, items[i]) {
|
||||
ruleVal += "|" + items[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(ruleVal, val)
|
||||
|
||||
// 日期格式,
|
||||
case "date":
|
||||
if _, err := gtime.StrToTime(val); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// 日期格式,需要给定日期格式
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(val, ruleVal); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 两字段值应相同(非敏感字符判断,非类型判断)
|
||||
case "same":
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
// 两字段值不应相同(非敏感字符判断,非类型判断)
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
|
||||
// 字段值应当在指定范围中
|
||||
case "in":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 字段值不应当在指定范围中
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 验证所给手机号码是否符合手机号的格式.
|
||||
* 移动:134、135、136、137、138、139、150、151、152、157、158、159、182、183、184、187、188、178(4G)、147(上网卡);
|
||||
* 联通:130、131、132、155、156、185、186、176(4G)、145(上网卡)、175;
|
||||
* 电信:133、153、180、181、189 、177(4G);
|
||||
* 卫星通信: 1349
|
||||
* 虚拟运营商:170、173
|
||||
*/
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$`, val)
|
||||
|
||||
// 国内座机电话号码:"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, val)
|
||||
|
||||
// 腾讯QQ号,从10000开始
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, val)
|
||||
|
||||
// 中国邮政编码
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^[1-9]\d{5}$`, val)
|
||||
|
||||
/*
|
||||
公民身份证号
|
||||
xxxxxx yyyy MM dd 375 0 十八位
|
||||
xxxxxx yy MM dd 75 0 十五位
|
||||
|
||||
地区:[1-9]\d{5}
|
||||
年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
年的后两位:\d{2}
|
||||
月份:((0[1-9])|(10|11|12))
|
||||
天数:(([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
|
||||
三位顺序码:\d{3}
|
||||
两位顺序码:\d{2}
|
||||
校验码: [0-9Xx]
|
||||
|
||||
十八位:^[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{2}$
|
||||
|
||||
总:
|
||||
(^[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{2}$)
|
||||
*/
|
||||
case "id-number":
|
||||
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{2}$)`, val)
|
||||
|
||||
// 通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间)
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, val)
|
||||
|
||||
// 通用密码(任意可见字符,长度在6~18之间)
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, val)
|
||||
|
||||
// 中等强度密码(在弱密码的基础上,必须包含大小写字母和数字)
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) && gregex.IsMatchString(`[a-z]+`, val) && gregex.IsMatchString(`[A-Z]+`, val) && gregex.IsMatchString(`\d+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符)
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) && gregex.IsMatchString(`[a-z]+`, val) && gregex.IsMatchString(`[A-Z]+`, val) && gregex.IsMatchString(`\d+`, val) && gregex.IsMatchString(`\S+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// json
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(val)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 整数
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(val); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 小数
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(val, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 布尔值(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
case "boolean":
|
||||
if val != "" && val != "0" && val != "false" && val != "off" && val != "no" {
|
||||
match = true
|
||||
}
|
||||
|
||||
// 邮件
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`, val)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$`, val)
|
||||
|
||||
// domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z-]{0,62}\.)+([0-9a-zA-Z][0-9a-zA-Z-]{0,62})\.?$`, val)
|
||||
|
||||
// IP(IPv4/IPv6)
|
||||
case "ip":
|
||||
match = gipv4.Validate(val) || gipv6.Validate(val)
|
||||
|
||||
// IPv4
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(val)
|
||||
|
||||
// IPv6
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(val)
|
||||
|
||||
// MAC地址
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$`, val)
|
||||
|
||||
default:
|
||||
errorMsgs[ruleKey] = "Invalid rule name:" + ruleKey
|
||||
}
|
||||
|
||||
// 错误消息整合
|
||||
if !match {
|
||||
// 不存在则使用默认的错误信息,
|
||||
// 如果在校验过程中已经设置了错误信息,那么这里便不作处理
|
||||
if _, ok := errorMsgs[ruleKey]; !ok {
|
||||
if msg, ok := customMsgs[ruleKey]; ok {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
errorMsgs[ruleKey] = errorMsgMap.Get(ruleKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
return newError([]string{rules}, ErrorMap {
|
||||
// 单条数值校验没有键名
|
||||
"" : errorMsgs,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// 判断必须字段
|
||||
func checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool {
|
||||
required := false
|
||||
switch ruleKey {
|
||||
// 必须字段
|
||||
case "required":
|
||||
required = true
|
||||
|
||||
// 必须字段(当任意所给定字段值与所给值相等时)
|
||||
case "required-if":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
// 必须为偶数,才能是键值对匹配
|
||||
if len(array)%2 == 0 {
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
if v, ok := params[tk]; ok {
|
||||
if strings.Compare(tv, v) == 0 {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定字段值与所给值都不相等时)
|
||||
case "required-unless":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
// 必须为偶数,才能是键值对匹配
|
||||
if len(array)%2 == 0 {
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
if v, ok := params[tk]; ok {
|
||||
if strings.Compare(tv, v) == 0 {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定任意字段值不为空时)
|
||||
case "required-with":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v != "" {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定所有字段值都不为空时)
|
||||
case "required-with-all":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v == "" {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定任意字段值为空时)
|
||||
case "required-without":
|
||||
required = false
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v == "" {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 必须字段(当所给定所有字段值都为空时)
|
||||
case "required-without-all":
|
||||
required = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for i := 0; i < len(array); i++ {
|
||||
if v, ok := params[array[i]]; ok {
|
||||
if v != "" {
|
||||
required = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if required {
|
||||
return !(value == "")
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 对字段值长度进行检测
|
||||
func checkLength(value, ruleKey, ruleVal string, custonMsgs map[string]string) string {
|
||||
msg := ""
|
||||
switch ruleKey {
|
||||
// 长度范围
|
||||
case "length":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
min := 0
|
||||
max := 0
|
||||
if len(array) > 0 {
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
if len(array) > 1 {
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(array[1])); err == nil {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
if len(value) < min || len(value) > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1)
|
||||
msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1)
|
||||
return msg
|
||||
}
|
||||
|
||||
// 最小长度
|
||||
case "min-length":
|
||||
if min, err := strconv.Atoi(ruleVal); err == nil {
|
||||
if len(value) < min {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为整数类型"
|
||||
}
|
||||
|
||||
// 最大长度
|
||||
case "max-length":
|
||||
if max, err := strconv.Atoi(ruleVal); err == nil {
|
||||
if len(value) > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为整数类型"
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// 对字段值大小进行检测
|
||||
func checkSize(value, ruleKey, ruleVal string, custonMsgs map[string]string) string {
|
||||
msg := ""
|
||||
switch ruleKey {
|
||||
// 大小范围
|
||||
case "between":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
min := float64(0)
|
||||
max := float64(0)
|
||||
if len(array) > 0 {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(array[0]), 10); err == nil {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
if len(array) > 1 {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(array[1]), 10); err == nil {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v < min || v > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1)
|
||||
msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
|
||||
// 最小值
|
||||
case "min":
|
||||
if min, err := strconv.ParseFloat(ruleVal, 10); err == nil {
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v < min {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为数字类型"
|
||||
}
|
||||
|
||||
// 最大值
|
||||
case "max":
|
||||
if max, err := strconv.ParseFloat(ruleVal, 10); err == nil {
|
||||
if v, err := strconv.ParseFloat(value, 10); err == nil {
|
||||
if v > max {
|
||||
if v, ok := custonMsgs[ruleKey]; !ok {
|
||||
msg = errorMsgMap.Get(ruleKey)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1)
|
||||
}
|
||||
} else {
|
||||
msg = "输入参数[" + value + "]应当为数字类型"
|
||||
}
|
||||
} else {
|
||||
msg = "校验参数[" + ruleVal + "]应当为数字类型"
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
108
g/util/gvalid/gvalid_check_map.go
Normal file
108
g/util/gvalid/gvalid_check_map.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 检测键值对参数Map,
|
||||
// rules参数支持 []string / map[string]string 类型,前面一种类型支持返回校验结果顺序(具体格式参考struct tag),后一种不支持;
|
||||
// rules参数中得 map[string]string 是一个2维的关联数组,第一维键名为参数键名,第二维为带有错误的校验规则名称,值为错误信息。
|
||||
func CheckMap(params map[string]interface{}, rules interface{}, msgs...CustomMsg) *Error {
|
||||
// 真实校验规则数据结构
|
||||
checkRules := make(map[string]string)
|
||||
// 真实自定义错误信息数据结构
|
||||
customMsgs := make(CustomMsg)
|
||||
// 返回的顺序规则
|
||||
errorRules := make([]string, 0)
|
||||
// 返回的校验错误
|
||||
errorMaps := make(ErrorMap)
|
||||
// 解析rules参数
|
||||
switch v := rules.(type) {
|
||||
// 支持校验错误顺序: []sequence tag
|
||||
case []string:
|
||||
for _, tag := range v {
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
if len(name) == 0 {
|
||||
continue
|
||||
}
|
||||
// 错误提示
|
||||
if len(msg) > 0 {
|
||||
ruleArray := strings.Split(rule, "|")
|
||||
msgArray := strings.Split(msg, "|")
|
||||
for k, v := range ruleArray {
|
||||
// 如果msg条数比rule少,那么多余的rule使用默认的错误信息
|
||||
if len(msgArray) <= k {
|
||||
continue
|
||||
}
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
if _, ok := customMsgs[name]; !ok {
|
||||
customMsgs[name] = make(map[string]string)
|
||||
}
|
||||
customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
|
||||
}
|
||||
}
|
||||
checkRules[name] = rule
|
||||
errorRules = append(errorRules, name + "@" + rule)
|
||||
}
|
||||
|
||||
// 不支持校验错误顺序: map[键名]校验规则
|
||||
case map[string]string:
|
||||
checkRules = v
|
||||
}
|
||||
// 自定义错误消息,非必须参数,优先级比rules参数中定义的错误消息更高
|
||||
if len(msgs) > 0 && len(msgs[0]) > 0 {
|
||||
if len(customMsgs) > 0 {
|
||||
for k, v := range msgs[0] {
|
||||
customMsgs[k] = v
|
||||
}
|
||||
} else {
|
||||
customMsgs = msgs[0]
|
||||
}
|
||||
}
|
||||
// 开始执行校验: 以校验规则作为基础进行遍历校验
|
||||
value := (interface{})(nil)
|
||||
// 这里的rule变量为多条校验规则,不包含名字或者错误信息定义
|
||||
for key, rule := range checkRules {
|
||||
value = nil
|
||||
if v, ok := params[key]; ok {
|
||||
value = v
|
||||
}
|
||||
if e := Check(value, rule, customMsgs[key], params); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// 如果值为nil|"",并且不需要require*验证时,其他验证失效
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
for k, _ := range item {
|
||||
if _, ok := mustCheckRulesEvenValueEmpty[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, ok := errorMaps[key]; !ok {
|
||||
errorMaps[key] = make(map[string]string)
|
||||
}
|
||||
for k, v := range item {
|
||||
errorMaps[key][k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorMaps) > 0 {
|
||||
return newError(errorRules, errorMaps)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
153
g/util/gvalid/gvalid_check_struct.go
Normal file
153
g/util/gvalid/gvalid_check_struct.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/third/github.com/fatih/structs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 校验struct对象属性,object参数也可以是一个指向对象的指针,返回值同CheckMap方法。
|
||||
// struct的数据校验结果信息是顺序的。
|
||||
func CheckStruct(object interface{}, rules interface{}, msgs...CustomMsg) *Error {
|
||||
fields := structs.Fields(object)
|
||||
params := make(map[string]interface{})
|
||||
checkRules := make(map[string]string)
|
||||
customMsgs := make(CustomMsg)
|
||||
// 返回的顺序规则
|
||||
errorRules := make([]string, 0)
|
||||
// 返回的校验错误
|
||||
errorMaps := make(ErrorMap)
|
||||
// 解析rules参数
|
||||
switch v := rules.(type) {
|
||||
// 支持校验错误顺序: []sequence tag
|
||||
case []string:
|
||||
for _, tag := range v {
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
if len(name) == 0 {
|
||||
continue
|
||||
}
|
||||
// 错误提示
|
||||
if len(msg) > 0 {
|
||||
ruleArray := strings.Split(rule, "|")
|
||||
msgArray := strings.Split(msg, "|")
|
||||
for k, v := range ruleArray {
|
||||
// 如果msg条数比rule少,那么多余的rule使用默认的错误信息
|
||||
if len(msgArray) <= k {
|
||||
continue
|
||||
}
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
if _, ok := customMsgs[name]; !ok {
|
||||
customMsgs[name] = make(map[string]string)
|
||||
}
|
||||
customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
|
||||
}
|
||||
}
|
||||
checkRules[name] = rule
|
||||
errorRules = append(errorRules, name + "@" + rule)
|
||||
}
|
||||
|
||||
// 不支持校验错误顺序: map[键名]校验规则
|
||||
case map[string]string:
|
||||
checkRules = v
|
||||
}
|
||||
// 首先, 按照属性循环一遍将strcut的属性、数值、tag解析
|
||||
for _, field := range fields {
|
||||
params[field.Name()] = field.Value()
|
||||
if tag := field.Tag("gvalid"); tag != "" {
|
||||
// sequence tag == struct tag, 这里的name为别名
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
if len(name) == 0 {
|
||||
name = field.Name()
|
||||
}
|
||||
// params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用
|
||||
if _, ok := params[name]; !ok {
|
||||
params[name] = field.Value()
|
||||
}
|
||||
// 校验规则
|
||||
if _, ok := checkRules[name]; !ok {
|
||||
checkRules[name] = rule
|
||||
errorRules = append(errorRules, name + "@" + rule)
|
||||
} else {
|
||||
// 传递的rules规则会覆盖struct tag的规则
|
||||
continue
|
||||
}
|
||||
// 错误提示
|
||||
if len(msg) > 0 {
|
||||
ruleArray := strings.Split(rule, "|")
|
||||
msgArray := strings.Split(msg, "|")
|
||||
for k, v := range ruleArray {
|
||||
// 如果msg条数比rule少,那么多余的rule使用默认的错误信息
|
||||
if len(msgArray) <= k {
|
||||
continue
|
||||
}
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
if _, ok := customMsgs[name]; !ok {
|
||||
customMsgs[name] = make(map[string]string)
|
||||
}
|
||||
customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 自定义错误消息,非必须参数,优先级比rules参数中以及struct tag中定义的错误消息更高
|
||||
if len(msgs) > 0 && len(msgs[0]) > 0 {
|
||||
if len(customMsgs) > 0 {
|
||||
for k, v := range msgs[0] {
|
||||
customMsgs[k] = v
|
||||
}
|
||||
} else {
|
||||
customMsgs = msgs[0]
|
||||
}
|
||||
}
|
||||
|
||||
/* 以下逻辑和CheckMap相同 */
|
||||
|
||||
// 开始执行校验: 以校验规则作为基础进行遍历校验
|
||||
value := (interface{})(nil)
|
||||
// 这里的rule变量为多条校验规则,不包含名字或者错误信息定义
|
||||
for key, rule := range checkRules {
|
||||
value = nil
|
||||
if v, ok := params[key]; ok {
|
||||
value = v
|
||||
}
|
||||
if e := Check(value, rule, customMsgs[key], params); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// 如果值为nil|"",并且不需要require*验证时,其他验证失效
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
for k, _ := range item {
|
||||
if _, ok := mustCheckRulesEvenValueEmpty[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, ok := errorMaps[key]; !ok {
|
||||
errorMaps[key] = make(map[string]string)
|
||||
}
|
||||
for k, v := range item {
|
||||
errorMaps[key][k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorMaps) > 0 {
|
||||
return newError(errorRules, errorMaps)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
132
g/util/gvalid/gvalid_error.go
Normal file
132
g/util/gvalid/gvalid_error.go
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 返回错误对象。
|
||||
|
||||
package gvalid
|
||||
|
||||
import "strings"
|
||||
|
||||
// 校验错误对象
|
||||
type Error struct {
|
||||
rules []string // 校验结果顺序(可能为nil)
|
||||
errors ErrorMap // 校验结果(map无序)
|
||||
firstKey string // 第一条错误项键名(常用操作冗余数据)
|
||||
firstItem map[string]string // 第一条错误项(常用操作冗余数据)
|
||||
}
|
||||
|
||||
// 校验错误信息: map[键名]map[规则名]错误信息
|
||||
type ErrorMap map[string]map[string]string
|
||||
|
||||
|
||||
// 创建一个校验错误对象指针
|
||||
func newError(rules []string, errors map[string]map[string]string) *Error {
|
||||
return &Error {
|
||||
rules : rules,
|
||||
errors : errors,
|
||||
}
|
||||
}
|
||||
|
||||
// 获得规则与错误信息的map; 当校验结果为多条数据校验时,返回第一条错误map(此时类似FirstItem)
|
||||
func (e *Error) Map() map[string]string {
|
||||
_, m := e.FirstItem()
|
||||
return m
|
||||
}
|
||||
|
||||
// 获得原始校验结果ErrorMap
|
||||
func (e *Error) Maps() ErrorMap {
|
||||
return e.errors
|
||||
}
|
||||
|
||||
// 只获取第一个键名的校验错误项
|
||||
func (e *Error) FirstItem() (key string, msgs map[string]string) {
|
||||
if e.firstItem != nil {
|
||||
return e.firstKey, e.firstItem
|
||||
}
|
||||
// 有序
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
name, _, _ := parseSequenceTag(v)
|
||||
if m, ok := e.errors[name]; ok {
|
||||
e.firstKey = name
|
||||
e.firstItem = m
|
||||
return name, m
|
||||
}
|
||||
}
|
||||
}
|
||||
// 无序
|
||||
for k, m := range e.errors {
|
||||
e.firstKey = k
|
||||
e.firstItem = m
|
||||
return k, m
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 只获取第一个校验错误项的规则及错误信息
|
||||
func (e *Error) FirstRule() (rule string, err string) {
|
||||
// 有序
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
name, rule, _ := parseSequenceTag(v)
|
||||
if m, ok := e.errors[name]; ok {
|
||||
for _, rule := range strings.Split(rule, "|") {
|
||||
array := strings.Split(rule, ":")
|
||||
rule = strings.TrimSpace(array[0])
|
||||
if err, ok := m[rule]; ok {
|
||||
return rule, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 无序
|
||||
for _, m := range e.errors {
|
||||
for k, v := range m {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// 只获取第一个校验错误项的错误信息
|
||||
func (e *Error) FirstString() (err string) {
|
||||
_, err = e.FirstRule()
|
||||
return
|
||||
}
|
||||
|
||||
// 将所有错误信息构建称字符串,多个错误信息字符串使用"; "符号分隔
|
||||
func (e *Error) String() string {
|
||||
return strings.Join(e.Strings(), "; ")
|
||||
}
|
||||
|
||||
// 只返回错误信息,构造成字符串数组返回
|
||||
func (e *Error) Strings() (errs []string) {
|
||||
errs = make([]string, 0)
|
||||
// 有序
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
name, rule, _ := parseSequenceTag(v)
|
||||
if m, ok := e.errors[name]; ok {
|
||||
for _, rule := range strings.Split(rule, "|") {
|
||||
array := strings.Split(rule, ":")
|
||||
rule = strings.TrimSpace(array[0])
|
||||
if err, ok := m[rule]; ok {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
// 无序
|
||||
for _, m := range e.errors {
|
||||
for _, err := range m {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
68
g/util/gvalid/gvalid_message.go
Normal file
68
g/util/gvalid/gvalid_message.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 默认的错误消息定义。
|
||||
|
||||
package gvalid
|
||||
|
||||
// 默认规则校验错误消息(可以通过接口自定义错误消息)
|
||||
var defaultMessages = map[string]string {
|
||||
"required" : "字段不能为空",
|
||||
"required-if" : "字段不能为空",
|
||||
"required-unless" : "字段不能为空",
|
||||
"required-with" : "字段不能为空",
|
||||
"required-with-all" : "字段不能为空",
|
||||
"required-without" : "字段不能为空",
|
||||
"required-without-all" : "字段不能为空",
|
||||
"date" : "日期格式不正确",
|
||||
"date-format" : "日期格式不正确",
|
||||
"email" : "邮箱地址格式不正确",
|
||||
"phone" : "手机号码格式不正确",
|
||||
"telephone" : "电话号码格式不正确",
|
||||
"passport" : "账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间",
|
||||
"password" : "密码格式不合法,密码格式为任意6-18位的可见字符",
|
||||
"password2" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字",
|
||||
"password3" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符",
|
||||
"postcode" : "邮政编码不正确",
|
||||
"id-number" : "身份证号码不正确",
|
||||
"qq" : "QQ号码格式不正确",
|
||||
"ip" : "IP地址格式不正确",
|
||||
"ipv4" : "IPv4地址格式不正确",
|
||||
"ipv6" : "IPv6地址格式不正确",
|
||||
"mac" : "MAC地址格式不正确",
|
||||
"url" : "URL地址格式不正确",
|
||||
"domain" : "域名格式不正确",
|
||||
"length" : "字段长度为:min到:max个字符",
|
||||
"min-length" : "字段最小长度为:min",
|
||||
"max-length" : "字段最大长度为:max",
|
||||
"between" : "字段大小为:min到:max",
|
||||
"min" : "字段最小值为:min",
|
||||
"max" : "字段最大值为:max",
|
||||
"json" : "字段应当为JSON格式",
|
||||
"xml" : "字段应当为XML格式",
|
||||
"array" : "字段应当为数组",
|
||||
"integer" : "字段应当为整数",
|
||||
"float" : "字段应当为浮点数",
|
||||
"boolean" : "字段应当为布尔值",
|
||||
"same" : "字段值不合法",
|
||||
"different" : "字段值不合法",
|
||||
"in" : "字段值不合法",
|
||||
"not-in" : "字段值不合法",
|
||||
"regex" : "字段值不合法",
|
||||
}
|
||||
|
||||
// 初始化错误消息管理对象
|
||||
func init() {
|
||||
errorMsgMap.BatchSet(defaultMessages)
|
||||
}
|
||||
|
||||
// 替换默认的错误提示为指定的自定义提示
|
||||
// 主要作用:
|
||||
// 1、便于多语言错误提示设置;
|
||||
// 2、默认错误提示信息不满意;
|
||||
func SetDefaultErrorMsgs(msgs map[string]string) {
|
||||
errorMsgMap.BatchSet(msgs)
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package gvalid
|
||||
|
||||
import "strings"
|
||||
|
||||
// 校验错误对象
|
||||
type Error map[string]map[string]string
|
||||
|
||||
// 只获取第一个校验错误项
|
||||
func (e Error) FirstItem() (string, map[string]string) {
|
||||
for k, m := range e {
|
||||
return k, m
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 只获取第一个校验错误项的规则及错误信息
|
||||
func (e Error) FirstRule() (string, string) {
|
||||
for _, m := range e {
|
||||
for k, v := range m {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// 只获取第一个校验错误项的错误信息
|
||||
func (e Error) FirstString() (string) {
|
||||
for _, m := range e {
|
||||
for _, v := range m {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 将所有错误信息构建称字符串,多个错误信息字符串使用"; "符号分隔
|
||||
func (e Error) String() string {
|
||||
return strings.Join(e.Strings(), "; ")
|
||||
}
|
||||
|
||||
// 只返回错误信息,构造成字符串数组返回
|
||||
func (e Error) Strings() []string {
|
||||
array := make([]string, 0)
|
||||
for _, m := range e {
|
||||
for _, v := range m {
|
||||
array = append(array, v)
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -7,20 +7,19 @@
|
||||
// 单元测试
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gvalid_test
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Regex(t *testing.T) {
|
||||
rule := `regex:\d{6}|\D{6}|length:6,16`
|
||||
if m := gvalid.Check("123456", rule, nil); m != nil {
|
||||
if m := Check("123456", rule, nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("abcde6", rule, nil); m == nil {
|
||||
if m := Check("abcde6", rule, nil); m == nil {
|
||||
t.Error("校验失败")
|
||||
}
|
||||
}
|
||||
@ -34,14 +33,14 @@ func Test_CheckMap(t *testing.T) {
|
||||
"id" : "required|between:1,100",
|
||||
"name" : "required|length:6,16",
|
||||
}
|
||||
msgs := map[string]interface{} {
|
||||
msgs := CustomMsg {
|
||||
"id" : "ID不能为空|ID范围应当为:min到:max",
|
||||
"name" : map[string]string {
|
||||
"required" : "名称不能为空",
|
||||
"length" : "名称长度为:min到:max个字符",
|
||||
},
|
||||
}
|
||||
if m := gvalid.CheckMap(kvmap, rules, msgs); m == nil {
|
||||
if m := CheckMap(kvmap, rules, msgs); m == nil {
|
||||
t.Error("CheckMap校验失败")
|
||||
}
|
||||
|
||||
@ -60,7 +59,7 @@ func Test_CheckMap(t *testing.T) {
|
||||
"length" : "名称长度为:min到:max个字符",
|
||||
},
|
||||
}
|
||||
if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil {
|
||||
if m := CheckMap(kvmap, rules, msgs); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
}
|
||||
@ -82,80 +81,80 @@ func Test_CheckObject(t *testing.T) {
|
||||
"Age" : "年龄为18到30周岁",
|
||||
}
|
||||
obj := &Object{"john", 16}
|
||||
if m := gvalid.CheckStruct(obj, rules, msgs); m == nil {
|
||||
if m := CheckStruct(obj, rules, msgs); m == nil {
|
||||
t.Error("CheckObject校验失败")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Required(t *testing.T) {
|
||||
if m := gvalid.Check("1", "required", nil); m != nil {
|
||||
if m := Check("1", "required", nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("", "required", nil); m == nil {
|
||||
if m := Check("", "required", nil); m == nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("", "required-if:id,1,age,18", nil, map[string]interface{}{"id" : 1, "age" : 19}); m == nil {
|
||||
if m := Check("", "required-if:id,1,age,18", nil, map[string]interface{}{"id" : 1, "age" : 19}); m == nil {
|
||||
t.Error("Required校验失败")
|
||||
}
|
||||
if m := gvalid.Check("", "required-if:id,1,age,18", nil, map[string]interface{}{"id" : 2, "age" : 19}); m != nil {
|
||||
if m := Check("", "required-if:id,1,age,18", nil, map[string]interface{}{"id" : 2, "age" : 19}); m != nil {
|
||||
t.Error("Required校验失败")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Ip(t *testing.T) {
|
||||
if m := gvalid.Check("10.0.0.1", "ipv4", nil); m != nil {
|
||||
if m := Check("10.0.0.1", "ipv4", nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("0.0.0.0", "ipv4", nil); m != nil {
|
||||
if m := Check("0.0.0.0", "ipv4", nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("1920.0.0.0", "ipv4", nil); m == nil {
|
||||
if m := Check("1920.0.0.0", "ipv4", nil); m == nil {
|
||||
t.Error("ipv4校验失败")
|
||||
}
|
||||
if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil {
|
||||
if m := Check("fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil {
|
||||
if m := Check("fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil {
|
||||
t.Error(m)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Length(t *testing.T) {
|
||||
rule := "length:6,16"
|
||||
if m := gvalid.Check("123456", rule, nil); m != nil {
|
||||
if m := Check("123456", rule, nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("12345", rule, nil); m == nil {
|
||||
if m := Check("12345", rule, nil); m == nil {
|
||||
t.Error("长度校验失败")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MinLength(t *testing.T) {
|
||||
rule := "min-length:6"
|
||||
if m := gvalid.Check("123456", rule, nil); m != nil {
|
||||
if m := Check("123456", rule, nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("12345", rule, nil); m == nil {
|
||||
if m := Check("12345", rule, nil); m == nil {
|
||||
t.Error("长度校验失败")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MaxLength(t *testing.T) {
|
||||
rule := "max-length:6"
|
||||
if m := gvalid.Check("12345", rule, nil); m != nil {
|
||||
if m := Check("12345", rule, nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check("1234567", rule, nil); m == nil {
|
||||
if m := Check("1234567", rule, nil); m == nil {
|
||||
t.Error("长度校验失败")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Between(t *testing.T) {
|
||||
rule := "between:6.01, 10.01"
|
||||
if m := gvalid.Check(10, rule, nil); m != nil {
|
||||
if m := Check(10, rule, nil); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
if m := gvalid.Check(10.02, rule, nil); m == nil {
|
||||
if m := Check(10.02, rule, nil); m == nil {
|
||||
t.Error("大小范围校验失败")
|
||||
}
|
||||
}
|
||||
@ -166,17 +165,17 @@ func Test_SetDefaultErrorMsgs(t *testing.T) {
|
||||
"integer" : "请输入一个整数",
|
||||
"length" : "参数长度不对啊老铁",
|
||||
}
|
||||
gvalid.SetDefaultErrorMsgs(msgs)
|
||||
m := gvalid.Check("6.66", rule, nil)
|
||||
if len(m) != 2 {
|
||||
SetDefaultErrorMsgs(msgs)
|
||||
e := Check("6.66", rule, nil)
|
||||
if e == nil || len(e.Map()) != 2 {
|
||||
t.Error("规则校验失败")
|
||||
} else {
|
||||
if v, ok := m["integer"]; ok {
|
||||
if v, ok := e.Map()["integer"]; ok {
|
||||
if strings.Compare(v, msgs["integer"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
if v, ok := m["length"]; ok {
|
||||
if v, ok := e.Map()["length"]; ok {
|
||||
if strings.Compare(v, msgs["length"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
@ -190,16 +189,16 @@ func Test_CustomError1(t *testing.T) {
|
||||
"integer" : "请输入一个整数",
|
||||
"length" : "参数长度不对啊老铁",
|
||||
}
|
||||
m := gvalid.Check("6.66", rule, msgs)
|
||||
if len(m) != 2 {
|
||||
e := Check("6.66", rule, msgs)
|
||||
if e == nil || len(e.Map()) != 2 {
|
||||
t.Error("规则校验失败")
|
||||
} else {
|
||||
if v, ok := m["integer"]; ok {
|
||||
if v, ok := e.Map()["integer"]; ok {
|
||||
if strings.Compare(v, msgs["integer"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
if v, ok := m["length"]; ok {
|
||||
if v, ok := e.Map()["length"]; ok {
|
||||
if strings.Compare(v, msgs["length"]) != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
@ -210,16 +209,16 @@ func Test_CustomError1(t *testing.T) {
|
||||
func Test_CustomError2(t *testing.T) {
|
||||
rule := "integer|length:6,16"
|
||||
msgs := "请输入一个整数|参数长度不对啊老铁"
|
||||
m := gvalid.Check("6.66", rule, msgs)
|
||||
if len(m) != 2 {
|
||||
e := Check("6.66", rule, msgs)
|
||||
if e == nil || len(e.Map()) != 2 {
|
||||
t.Error("规则校验失败")
|
||||
} else {
|
||||
if v, ok := m["integer"]; ok {
|
||||
if v, ok := e.Map()["integer"]; ok {
|
||||
if strings.Compare(v, "请输入一个整数") != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
}
|
||||
if v, ok := m["length"]; ok {
|
||||
if v, ok := e.Map()["length"]; ok {
|
||||
if strings.Compare(v, "参数长度不对啊老铁") != 0 {
|
||||
t.Error("错误信息不匹配")
|
||||
}
|
||||
@ -236,7 +235,7 @@ func Test_CheckMapWithNilAndNotRequiredField(t *testing.T) {
|
||||
"id" : "required",
|
||||
"name" : "length:4,16",
|
||||
}
|
||||
if m := gvalid.CheckMap(data, rules); m != nil {
|
||||
if m := CheckMap(data, rules); m != nil {
|
||||
t.Error(m)
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -60,6 +60,8 @@ func main() {
|
||||
"same" : "两次密码输入不相等",
|
||||
},
|
||||
}
|
||||
gutil.Dump(gvalid.CheckMap(params, rules, msgs))
|
||||
if e := gvalid.CheckMap(params, rules, msgs); e != nil {
|
||||
g.Dump(e.Maps())
|
||||
}
|
||||
// map[passport:map[length:账号长度应当在6到16之间] password:map[same:两次密码输入不相等]]
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
)
|
||||
|
||||
|
||||
// same校验
|
||||
// 返回结果方法示例
|
||||
func main() {
|
||||
type User struct {
|
||||
Password string `gvalid:"password@password"`
|
||||
@ -18,11 +18,12 @@ func main() {
|
||||
ConfiemPassword : "",
|
||||
}
|
||||
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
gutil.Dump(err)
|
||||
gutil.Dump(err.String())
|
||||
gutil.Dump(err.Strings())
|
||||
gutil.Dump(err.FirstItem())
|
||||
gutil.Dump(err.FirstRule())
|
||||
gutil.Dump(err.FirstString())
|
||||
e := gvalid.CheckStruct(user, nil)
|
||||
g.Dump(e.Map())
|
||||
g.Dump(e.Maps())
|
||||
g.Dump(e.String())
|
||||
g.Dump(e.Strings())
|
||||
g.Dump(e.FirstItem())
|
||||
g.Dump(e.FirstRule())
|
||||
g.Dump(e.FirstString())
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
Name string `gvalid:"name @required|length:6,30#请输入用户名称|用户名称长度非法"`
|
||||
Name string `gvalid:"name @required|length:6,30#请输入用户名称|用户名称长度不够哦"`
|
||||
Pass1 string `gvalid:"password1@required|password3"`
|
||||
Pass2 string `gvalid:"password2@required|password3|same:password1#||两次密码不一致,请重新输入"`
|
||||
}
|
||||
@ -18,8 +18,7 @@ func main() {
|
||||
Pass2: "123",
|
||||
}
|
||||
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
gutil.Dump(err)
|
||||
gutil.Dump(err.String())
|
||||
gutil.Dump(err.FirstString())
|
||||
e := gvalid.CheckStruct(user, nil)
|
||||
g.Dump(e.String())
|
||||
g.Dump(e.FirstString())
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Uid int `gvalid:"uid @integer|min:1"`
|
||||
Uid int `gvalid:"uid @integer|min:1#用户UID不能为空"`
|
||||
Name string `gvalid:"name @required|length:6,30#请输入用户名称|用户名称长度非法"`
|
||||
Pass1 string `gvalid:"password1@required|password3"`
|
||||
Pass2 string `gvalid:"password2@required|password3|same:password1#||两次密码不一致,请重新输入"`
|
||||
@ -20,7 +20,7 @@ func main() {
|
||||
}
|
||||
|
||||
// 使用结构体定义的校验规则和错误提示进行校验
|
||||
gutil.Dump(gvalid.CheckStruct(user, nil))
|
||||
g.Dump(gvalid.CheckStruct(user, nil).Map())
|
||||
|
||||
// 自定义校验规则和错误提示,对定义的特定校验规则和错误提示进行覆盖
|
||||
rules := map[string]string {
|
||||
@ -31,5 +31,5 @@ func main() {
|
||||
"password3" : "名称不能为空",
|
||||
},
|
||||
}
|
||||
gutil.Dump(gvalid.CheckStruct(user, rules, msgs))
|
||||
g.Dump(gvalid.CheckStruct(user, rules, msgs).Map())
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
)
|
||||
|
||||
@ -14,5 +14,5 @@ func main() {
|
||||
|
||||
user := &User{}
|
||||
|
||||
gutil.Dump(gvalid.CheckStruct(user, nil))
|
||||
g.Dump(gvalid.CheckStruct(user, nil))
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gutil"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/util/gvalid"
|
||||
)
|
||||
|
||||
@ -18,5 +18,5 @@ func main() {
|
||||
ConfirmPassword : "",
|
||||
}
|
||||
|
||||
gutil.Dump(gvalid.CheckStruct(user, nil))
|
||||
g.Dump(gvalid.CheckStruct(user, nil))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user