diff --git a/g/encoding/gxml/gxml.go b/g/encoding/gxml/gxml.go deleted file mode 100644 index 19501031c..000000000 --- a/g/encoding/gxml/gxml.go +++ /dev/null @@ -1,232 +0,0 @@ -package gxml - -import ( - "fmt" - "strings" - "strconv" - "io/ioutil" - "encoding/xml" -) - -// xml解析结果存放数组 -type Xml struct { - // 注意这是一个指针 - value *interface{} -} - -// 一个xml变量 -type XmlVar interface{} - -// 编码go变量为xml字符串,并返回xml字符串指针 -func Encode (v interface{}) ([]byte, error) { - return xml.Marshal(v) -} - -// 解码字符串为interface{}变量 -func Decode (b []byte) (interface{}, error) { - var v interface{} - if err := DecodeTo(b, &v); err == nil { - return nil, err - } else { - return v, nil - } -} - -// 解析xml字符串为go变量,注意第二个参数为指针 -func DecodeTo (b []byte, v interface{}) error { - return xml.Unmarshal(b, v) -} - -// 解析xml字符串为gxml.Xml对象,并返回操作对象指针 -func DecodeToXml (b []byte) (*Xml, error) { - if v, err := Decode(b); err != nil { - return &Xml{&v}, nil - } else { - return nil, err - } -} - -// 加载xml文件内容,并转换为xml对象 -func Load (path string) (*Xml, error) { - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - var result interface{} - if err := xml.Unmarshal(data, &result); err != nil { - return nil, err - } - return &Xml{ &result }, nil -} - -// 将变量转换为Xml对象进行处理,该变量至少应当是一个map或者array,否者转换没有意义 -func NewXml(v *interface{}) *Xml { - return &Xml{ v } -} - -// 将指定的xml内容转换为指定结构返回,查找失败或者转换失败,目标对象转换为nil -// 注意第二个参数需要给的是变量地址 -func (p *Xml) GetToVar(pattern string, v interface{}) error { - r := p.Get(pattern) - if r != nil { - if t, err := Encode(r); err == nil { - return DecodeTo(t, v) - } else { - return err - } - } else { - v = nil - } - return nil -} - -// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换 -// 注意,如果获取的值不存在,或者类型与xml类型不匹配,那么将会返回nil -func (p *Xml) GetMap(pattern string) map[string]interface{} { - result := p.Get(pattern) - if result != nil { - if r, ok := result.(map[string]interface{}); ok { - return r - } - } - return nil -} - -// 将检索值转换为Xml对象指针返回 -func (p *Xml) GetXml(pattern string) *Xml { - result := p.Get(pattern) - if result != nil { - return &Xml{&result} - } - return nil -} - -// 获得一个数组[]interface{},方便操作,不需要自己做类型转换 -// 注意,如果获取的值不存在,或者类型与xml类型不匹配,那么将会返回nil -func (p *Xml) GetArray(pattern string) []interface{} { - result := p.Get(pattern) - if result != nil { - if r, ok := result.([]interface{}); ok { - return r - } - } - return nil -} - -// 返回指定xml中的string -func (p *Xml) GetString(pattern string) string { - result := p.Get(pattern) - if result != nil { - if r, ok := result.(string); ok { - return r - } - } - return "" -} - -// 返回指定xml中的bool -func (p *Xml) GetBool(pattern string) bool { - result := p.Get(pattern) - if result != nil { - str := fmt.Sprintf("%v", result) - if str != "" && str != "0" && str != "false" { - return true - } - } - return false -} - -// 返回指定xml中的float64 -func (p *Xml) GetFloat64(pattern string) float64 { - result := p.Get(pattern) - if result != nil { - if r, ok := result.(float64); ok { - return r - } - } - return 0 -} - -// 返回指定xml中的float64->int -func (p *Xml) GetInt(pattern string) int { - return int(p.GetFloat64(pattern)) -} - -// 返回指定xml中的float64->int64 -func (p *Xml) GetInt64(pattern string) int64 { - return int64(p.GetFloat64(pattern)) -} - -// 根据约定字符串方式访问xml解析数据,参数形如: "items.name.first", "list.0" -// 返回的结果类型的interface{},因此需要自己做类型转换 -// 如果找不到对应节点的数据,返回nil -func (p *Xml) Get(pattern string) interface{} { - var result interface{} - pointer := p.value - array := strings.Split(pattern, ".") - length := len(array) - for i:= 0; i < length; i++ { - switch (*pointer).(type) { - case map[string]interface{}: - if v, ok := (*pointer).(map[string]interface{})[array[i]]; ok { - if i == length - 1 { - result = v - } else { - pointer = &v - } - } else { - return nil - } - case []interface{}: - if isNumeric(array[i]) { - n, err := strconv.Atoi(array[i]) - if err == nil && len((*pointer).([]interface{})) > n { - if i == length - 1 { - result = (*pointer).([]interface{})[n] - break; - } else { - pointer = &(*pointer).([]interface{})[n] - } - } - } else { - return nil - } - default: - return nil - } - } - return result -} - -// 转换为map[string]interface{}类型,如果转换失败,返回nil -func (p *Xml) ToMap() map[string]interface{} { - pointer := p.value - switch (*pointer).(type) { - case map[string]interface{}: - return (*pointer).(map[string]interface{}) - default: - return nil - } -} - -// 转换为[]interface{}类型,如果转换失败,返回nil -func (p *Xml) ToArray() []interface{} { - pointer := p.value - switch (*pointer).(type) { - case []interface{}: - return (*pointer).([]interface{}) - default: - return nil - } -} - - -// 判断所给字符串是否为数字 -func isNumeric(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] < byte('0') || s[i] > byte('9') { - return false - } - } - return true -} \ No newline at end of file diff --git a/g/encoding/gxml/gxml_test.go b/g/encoding/gxml/gxml_test.go deleted file mode 100644 index 971d2d341..000000000 --- a/g/encoding/gxml/gxml_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package gxml_test - -import ( - "testing" - "fmt" - - "encoding/xml" -) - -var content = ` - - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - -` -func Test_Xml(t *testing.T) { - //xml, err := gxml.Decode(bytes.TrimSpace([]byte(content))) - //if err != nil { - // glog.Error(err) - //} - - //v := make(map[string]interface{}) - v := make([]interface{}, 0) - e := xml.Unmarshal([]byte(content), &v) - fmt.Println(e) - fmt.Println(v) -} diff --git a/g/net/gipv4/gipv4.go b/g/net/gipv4/gipv4.go index 621ee9420..e15f39e76 100644 --- a/g/net/gipv4/gipv4.go +++ b/g/net/gipv4/gipv4.go @@ -6,8 +6,14 @@ import ( "strings" "regexp" "fmt" + "gitee.com/johng/gf/g/util/gregx" ) +// 判断所给地址是否是一个IPv4地址 +func Validate(ip string) bool { + return gregx.IsMatchString(`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`, ip) +} + // ip字符串转为整形 func Ip2long(ipstr string) (ip uint32) { reg, _ := regexp.Compile(`^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$`) diff --git a/g/net/gipv6/gipv6.go b/g/net/gipv6/gipv6.go new file mode 100644 index 000000000..a9c099cea --- /dev/null +++ b/g/net/gipv6/gipv6.go @@ -0,0 +1,9 @@ +package gipv6 + +import "gitee.com/johng/gf/g/util/gregx" + +// 判断所给地址是否是一个IPv6地址 +func Validate(ip string) bool { + return gregx.IsMatchString(`^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$`, ip) +} + diff --git a/g/os/gtime/time.go b/g/os/gtime/time.go index e3d1d4a3b..11c7d50ad 100644 --- a/g/os/gtime/time.go +++ b/g/os/gtime/time.go @@ -2,7 +2,6 @@ package gtime import ( "time" - "fmt" ) // 类似与js中的SetTimeout,一段时间后执行回调函数 diff --git a/g/util/gregx/regx.go b/g/util/gregx/regx.go index f5eff1266..8b78663d3 100644 --- a/g/util/gregx/regx.go +++ b/g/util/gregx/regx.go @@ -17,6 +17,25 @@ func IsMatchString(pattern string, src string) bool { return IsMatch(pattern, []byte(src)) } +// 正则匹配,并返回匹配的列表 +func MatchString(pattern string, src string) ([]string, error) { + reg, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + s := reg.FindStringSubmatch(src) + return s, nil +} + +func MatchAllString(pattern string, src string) ([][]string, error) { + reg, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + s := reg.FindAllStringSubmatch(src, -1) + return s, nil +} + // 正则替换(全部替换) func Replace(pattern string, src, replace []byte) ([]byte, error) { reg, err := regexp.Compile(pattern) diff --git a/g/util/gvalid/gvalid.go b/g/util/gvalid/gvalid.go index 89d400663..7e7da3766 100644 --- a/g/util/gvalid/gvalid.go +++ b/g/util/gvalid/gvalid.go @@ -3,41 +3,46 @@ /* 参考:https://laravel.com/docs/5.5/validation#available-validation-rules 规则如下: -required 格式:required 说明:必需参数 -required_if 格式:required_if:field,value,... 说明:必需参数(当给定字段值与所给任意值相等时) -required_with 格式:required_with:foo,bar,... 说明:必需参数(当所给定任意字段值不为空时) -required_with_all 格式:required_with_all:foo,bar,... 说明:必须参数(当所给定所有字段值都不为空时) -date 格式:date 说明:参数为常用日期类型,格式:2006-01-02, 20060102, 2006.01.02 -date_format 格式:date_format:format 说明:判断日期是否为指定的日期格式,format为Go日期格式(可以包含时间) -email 格式:email 说明:EMAIL邮箱地址 -phone 格式:phone 说明:手机号 -telephone 格式:telephone 说明:国内座机电话号码,"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX" -passport 格式:passport 说明:通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间) -password 格式:password 说明:通用密码(任意可见字符,长度在6~18之间) -password2 格式:password2 说明:中等强度密码(在弱密码的基础上,必须包含大小写字母和数字) -password3 格式:password3 说明:强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符) -postcode 格式:id_number 说明:中国邮政编码 -id_number 格式:id_number 说明:公民身份证号码 -qq 格式:qq 说明:腾讯QQ号码 -ip 格式:ip 说明:IP地址(IPv4) -mac 格式:mac 说明:MAC地址 -url 格式:url 说明:URL -length 格式:length:min,max 说明:参数长度为min到max -min_length 格式:min_length:min 说明:参数长度最小为min -max_length 格式:max_length:max 说明:参数长度最大为max -between 格式:between:min,max 说明:参数大小为min到max -min 格式:min:min 说明:参数最小为min -max 格式:max:max 说明:参数最大为max -json 格式:json 说明:判断数据格式为JSON -xml 格式:xml 说明:判断数据格式为XML -integer 格式:integer 说明:整数 -float 格式:float 说明:浮点数 -boolean 格式:boolean 说明:布尔值(1,true,on,yes:true | 0,false,off,no,"":false) -same 格式:same:field 说明:参数值必需与field参数的值相同 -different 格式:different:field 说明:参数值不能与field参数的值相同 -in 格式:in:foo,bar,... 说明:参数值应该在foo,bar,...中 -not_in 格式:not_in:foo,bar,... 说明:参数值不应该在foo,bar,...中 -regex 格式:regex:pattern 说明:参数值应当满足正则匹配规则pattern(使用preg_match判断) +required 格式:required 说明:必需参数 +required-if 格式:required-if:field,value,... 说明:必需参数(当任意所给定字段值与所给值相等时,即:当field字段的值为value时,当前验证字段为必须参数) +required-unless 格式:required-unless:field,value,... 说明:必需参数(当所给定字段值与所给值都不相等时,即:当field字段的值不为value时,当前验证字段为必须参数) +required-with 格式:required-with:field1,field2,... 说明:必需参数(当所给定任意字段值不为空时) +required-with-all 格式:required-with-all:field1,field2,... 说明:必须参数(当所给定所有字段值都不为空时) +required-without 格式:required-without:field1,field2,... 说明:必需参数(当所给定任意字段值为空时) +required-without-all 格式:required-without-all:field1,field2,...说明:必须参数(当所给定所有字段值都为空时) +date 格式:date 说明:参数为常用日期类型,格式:2006-01-02, 20060102, 2006.01.02 +date-format 格式:date-format:format 说明:判断日期是否为指定的日期格式,format为Go日期格式(可以包含时间) +email 格式:email 说明:EMAIL邮箱地址 +phone 格式:phone 说明:手机号 +telephone 格式:telephone 说明:国内座机电话号码,"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX" +passport 格式:passport 说明:通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间) +password 格式:password 说明:通用密码(任意可见字符,长度在6~18之间) +password2 格式:password2 说明:中等强度密码(在弱密码的基础上,必须包含大小写字母和数字) +password3 格式:password3 说明:强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符) +postcode 格式:postcode 说明:中国邮政编码 +id-number 格式:id-number 说明:公民身份证号码 +qq 格式:qq 说明:腾讯QQ号码 +ip 格式:ip 说明:IPv4/IPv6地址 +ipv4 格式:ipv4 说明:IPv4地址 +ipv6 格式:ipv6 说明:IPv6地址 +mac 格式:mac 说明:MAC地址 +url 格式:url 说明:URL +domain 格式:domain 说明:域名 +length 格式:length:min,max 说明:参数长度为min到max(长度参数为整形) +min-length 格式:min-length:min 说明:参数长度最小为min(长度参数为整形) +max-length 格式:max-length:max 说明:参数长度最大为max(长度参数为整形) +between 格式:between:min,max 说明:参数大小为min到max(支持整形和浮点类型参数) +min 格式:min:min 说明:参数最小为min(支持整形和浮点类型参数) +max 格式:max:max 说明:参数最大为max(支持整形和浮点类型参数) +json 格式:json 说明:判断数据格式为JSON +integer 格式:integer 说明:整数 +float 格式:float 说明:浮点数 +boolean 格式:boolean 说明:布尔值(1,true,on,yes:true | 0,false,off,no,"":false) +same 格式:same:field 说明:参数值必需与field参数的值相同 +different 格式:different:field 说明:参数值不能与field参数的值相同 +in 格式:in:value1,value2,... 说明:参数值应该在value1,value2,...中(字符串匹配) +not-in 格式:not-in:value1,value2,... 说明:参数值不应该在value1,value2,...中(字符串匹配) +regex 格式:regex:pattern 说明:参数值应当满足正则匹配规则pattern */ package gvalid @@ -48,129 +53,237 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gregx" "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/net/gipv6" + "gitee.com/johng/gf/g/net/gipv4" ) // 默认规则校验错误消息(可以通过接口自定义错误消息) var defaultMessages = map[string]string { - "required" : "字段不能为空", - "required_if" : "字段不能为空", - "required_with" : "字段不能为空", - "required_with_all" : "字段不能为空", - "date" : "日期格式不正确", - "email" : "邮箱地址格式不正确", - "phone" : "手机号码格式不正确", - "telephone" : "电话号码格式不正确", - "passport" : "账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间", - "password" : "密码格式不合法,密码格式为任意6-18位的可见字符", - "password2" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字", - "password3" : "密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符", - "postcode" : "邮政编码不正确", - "id_number" : "身份证号码不正确", - "qq" : "QQ号码格式不正确", - "ip" : "IP地址格式不正确", - "mac" : "MAC地址格式不正确", - "url" : "URL地址格式不正确", - "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" : "字段值不合法", + "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" : "字段值不合法", } -// 检测一条数据的规则,其中values参数为非必须参数,可以传递所有的校验参数进来,进行多参数对比(部分校验规则需要) -func CheckRule(value, rule string, values...map[string]string) map[string]string { - msgs := make(map[string]string) - params := make(map[string]string) - if len(values) > 0 { - params = values[0] - } - items := strings.Split(strings.TrimSpace(rule), "|") - for _, item := range items { - reg, _ := regexp.Compile(`^(\w+):{0,1}(.*)`) - results := reg.FindStringSubmatch(item) - rulekey := results[1] - ruleval := results[2] - match := false - switch rulekey { - // 必须字段 - case "required": - match = !(value == "") - break +const ( + gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)` // 单条规则匹配正则 +) - // 必须字段(当给定字段值与所给任意值相等时) - case "required_if": - required := false - array := strings.Split(strings.TrimSpace(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 - } - } - if required { - match = !(value == "") - } else { - match = true - } - break +// 默认错误消息管理对象(并发安全) +var errorMsgMap = gmap.NewStringStringMap() - // 必须字段(当所给定任意字段值不为空时) - case "required_with": - required := false - array := strings.Split(strings.TrimSpace(ruleval), ",") - for i := 0; i < len(array); i++ { - if v, ok := params[array[i]]; ok { - if v != "" { +// 单规则正则对象,这里使用包内部变量存储,不需要多次解析 +var ruleRegex, _ = regexp.Compile(gSINGLE_RULE_PATTERN) + +// 初始化错误消息管理对象 +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 } - if required { - match = !(value == "") - } else { - match = true - } - break + } - // 必须字段(当所给定所有字段值都不为空时) - case "required_with_all": - required := true - array := strings.Split(strings.TrimSpace(ruleval), ",") - for i := 0; i < len(array); i++ { - if v, ok := params[array[i]]; ok { - if v == "" { + // 必须字段(当所给定字段值与所给值都不相等时) + 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 } - if required { - match = !(value == "") - } else { - match = true + } + + // 必须字段(当所给定任意字段值不为空时) + 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 + } } - 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 + } +} + +// 检测单条数据的规则,其中values参数为非必须参数,可以传递所有的校验参数进来,进行多参数对比(部分校验规则需要) +// msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持string/map[string]string,允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误 +// values参数为表单联合校验参数,对于需要联合校验的规则有效,如:required-*、same、different +func Check(value, rules string, msgs interface{}, values...map[string]string) map[string]string { + value = strings.TrimSpace(value) + params := make(map[string]string) + errmsgs := make(map[string]string) + if len(values) > 0 { + params = values[0] + } + // 自定义错误消息处理 + list := make([]string, 0) + cmsgs := make(map[string]string) + switch msgs.(type) { + case map[string]string: + cmsgs = msgs.(map[string]string) + case string: + list = strings.Split(msgs.(string), "|") + } + 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 { + cmsgs[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, params) + + // 自定义正则判断 + case "regex": + // 需要判断是否被|符号截断,如果是,那么需要进行整合 + for i := index + 1; i < len(items); i++ { + // 判断下一个规则是否合法,不合法那么和当前正则规则进行整合 + if !gregx.IsMatchString(gSINGLE_RULE_PATTERN, items[i]) { + ruleval += "|" + items[i] + index++ + } + } + match = gregx.IsMatchString(ruleval, value) // 日期格式, case "date": @@ -180,14 +293,12 @@ func CheckRule(value, rule string, values...map[string]string) map[string]string break } } - break // 日期格式,需要给定日期格式 - case "date_format": + case "date-format": if _, err := gtime.StrToTime(value, ruleval); err == nil { match = true } - break // 两字段值应相同(非敏感字符判断,非类型判断) case "same": @@ -196,7 +307,6 @@ func CheckRule(value, rule string, values...map[string]string) map[string]string match = true } } - break // 两字段值不应相同(非敏感字符判断,非类型判断) case "different": @@ -206,35 +316,27 @@ func CheckRule(value, rule string, values...map[string]string) map[string]string match = false } } - break // 字段值应当在指定范围中 case "in": - array := strings.Split(strings.TrimSpace(ruleval), ",") + array := strings.Split(ruleval, ",") for _, v := range array { if strings.Compare(value, strings.TrimSpace(v)) == 0 { match = true break } } - break // 字段值不应当在指定范围中 - case "not_in": + case "not-in": match = true - array := strings.Split(strings.TrimSpace(ruleval), ",") + array := strings.Split(ruleval, ",") for _, v := range array { if strings.Compare(value, strings.TrimSpace(v)) == 0 { match = false break } } - break - - // 自定义正则判断 - //case "regex": - // $ruleMatch = @preg_match($ruleAttr, $value) ? true : false; - // break /* * 验证所给手机号码是否符合手机号的格式. @@ -246,22 +348,18 @@ func CheckRule(value, rule string, values...map[string]string) map[string]string */ case "phone": match = gregx.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) - break // 国内座机电话号码:"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX" case "telephone": match = gregx.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, value) - break // 腾讯QQ号,从10000开始 case "qq": match = gregx.IsMatchString(`^[1-9][0-9]{4,}$`, value) - break // 中国邮政编码 case "postcode": match = gregx.IsMatchString(`^[1-9]\d{5}$`, value) - break /* 公民身份证号 @@ -284,182 +382,218 @@ func CheckRule(value, rule string, values...map[string]string) map[string]string 总: (^[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": + case "id-number": match = gregx.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) - break // 通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间) case "passport": match = gregx.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, value) - break // 通用密码(任意可见字符,长度在6~18之间) case "password": match = gregx.IsMatchString(`^[\w\S]{6,18}$`, value) - break // 中等强度密码(在弱密码的基础上,必须包含大小写字母和数字) case "password2": if gregx.IsMatchString(`^[\w\S]{6,18}$`, value) && gregx.IsMatchString(`[a-z]+`, value) && gregx.IsMatchString(`[A-Z]+`, value) && gregx.IsMatchString(`\d+`, value) { match = true } - break // 强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符) case "password3": if gregx.IsMatchString(`^[\w\S]{6,18}$`, value) && gregx.IsMatchString(`[a-z]+`, value) && gregx.IsMatchString(`[A-Z]+`, value) && gregx.IsMatchString(`\d+`, value) && gregx.IsMatchString(`\S+`, value) { match = true } - break // 长度范围 case "length": - array := strings.Split(strings.TrimSpace(ruleval), ",") + array := strings.Split(ruleval, ",") min := 0 max := 0 if len(array) > 0 { - if v, err := strconv.Atoi(array[0]); err == nil { + if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil { min = v } } if len(array) > 1 { - if v, err := strconv.Atoi(array[1]); err == nil { + if v, err := strconv.Atoi(strings.TrimSpace(array[1])); err == nil { max = v } } if len(value) >= min && len(value) <= max { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) + msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) + errmsgs[rulekey] = msg } - break // 最小长度 - case "min_length": - if min, err := strconv.Atoi(strings.TrimSpace(ruleval)); err == nil { + case "min-length": + if min, err := strconv.Atoi(ruleval); err == nil { if len(value) >= min { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) + errmsgs[rulekey] = msg } + } else { + errmsgs[rulekey] = "校验参数[" + ruleval + "]应当为整数类型" } - break // 最大长度 - case "max_length": - if max, err := strconv.Atoi(strings.TrimSpace(ruleval)); err == nil { + case "max-length": + if max, err := strconv.Atoi(ruleval); err == nil { if len(value) <= max { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) + errmsgs[rulekey] = msg } + } else { + errmsgs[rulekey] = "校验参数[" + ruleval + "]应当为整数类型" } - break // 大小范围 case "between": - array := strings.Split(strings.TrimSpace(ruleval), ",") - min := 0 - max := 0 + array := strings.Split(ruleval, ",") + min := float64(0) + max := float64(0) if len(array) > 0 { - if v, err := strconv.Atoi(array[0]); err == nil { + if v, err := strconv.ParseFloat(strings.TrimSpace(array[0]), 10); err == nil { min = v } } if len(array) > 1 { - if v, err := strconv.Atoi(array[1]); err == nil { + if v, err := strconv.ParseFloat(strings.TrimSpace(array[1]), 10); err == nil { max = v } } - if v, err := strconv.Atoi(value); err == nil { + if v, err := strconv.ParseFloat(value, 10); err == nil { if v >= min && v <= max { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) + msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) + errmsgs[rulekey] = msg } + } else { + errmsgs[rulekey] = "输入参数[" + value + "]应当为数字类型" } - break // 最小值 case "min": - if min, err := strconv.Atoi(strings.TrimSpace(ruleval)); err == nil { - if v, err := strconv.Atoi(value); err == nil { + if min, err := strconv.ParseFloat(ruleval, 10); err == nil { + if v, err := strconv.ParseFloat(value, 10); err == nil { if v >= min { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) + errmsgs[rulekey] = msg } + } else { + errmsgs[rulekey] = "输入参数[" + value + "]应当为数字类型" } + } else { + errmsgs[rulekey] = "校验参数[" + ruleval + "]应当为数字类型" } - break // 最大值 case "max": - if max, err := strconv.Atoi(strings.TrimSpace(ruleval)); err == nil { - if v, err := strconv.Atoi(value); err == nil { + if max, err := strconv.ParseFloat(ruleval, 10); err == nil { + if v, err := strconv.ParseFloat(value, 10); err == nil { if v <= max { match = true + } else { + msg := errorMsgMap.Get(rulekey) + msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) + errmsgs[rulekey] = msg } + } else { + errmsgs[rulekey] = "输入参数[" + value + "]应当为数字类型" } + } else { + errmsgs[rulekey] = "校验参数[" + ruleval + "]应当为数字类型" } - break // json case "json": if _, err := gjson.Decode([]byte(value)); err == nil { match = true } - break - - //// xml - //case "xml": - // $checkResult = @Lib_XmlParser::isXml($value); - // $ruleMatch = ($checkResult !== null && $checkResult !== false); - // break // 整数 case "integer": if _, err := strconv.Atoi(value); err == nil { match = true } - break // 小数 case "float": if _, err := strconv.ParseFloat(value, 10); err == nil { match = true } - break // 布尔值(1,true,on,yes:true | 0,false,off,no,"":false) case "boolean": if value != "" && value != "0" && value != "false" && value != "off" && value != "no" { match = true } - break // 邮件 case "email": match = gregx.IsMatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`, value) - break // URL case "url": match = gregx.IsMatchString(`^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$`, value) - break - // IP + // domain + case "domain": + match = gregx.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 = gregx.IsMatchString(`^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$`, value) - break + 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 = gregx.IsMatchString(`^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$`, value) - break default: - msgs[rulekey] = "Invalid rule name:" + rulekey - break + errmsgs[rulekey] = "Invalid rule name:" + rulekey } // 错误消息整合 if !match { - msgs[rulekey] = defaultMessages[rulekey] + // 判断是否存在自定义的错误信息 + if msg, ok := cmsgs[rulekey]; ok { + errmsgs[rulekey] = msg + } else { + // 不存在则使用默认的错误信息, + // 如果在校验过程中已经设置了错误信息,那么这里便不作处理 + if _, ok := errmsgs[rulekey]; !ok { + errmsgs[rulekey] = errorMsgMap.Get(rulekey) + } + } } + index++ } - if len(msgs) > 0 { - return msgs + if len(errmsgs) > 0 { + return errmsgs } return nil } \ No newline at end of file diff --git a/g/util/gvalid/gvalid_test.go b/g/util/gvalid/gvalid_test.go new file mode 100644 index 000000000..a930ca045 --- /dev/null +++ b/g/util/gvalid/gvalid_test.go @@ -0,0 +1,157 @@ +package gvalid_test + +import ( + "testing" + "gitee.com/johng/gf/g/util/gvalid" + "strings" +) + +func Test_Regex(t *testing.T) { + rule := `regex:\d{6}|\D{6}|length:6,16` + if m := gvalid.Check("123456", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.Check("abcde6", rule, nil); m == nil { + t.Error("校验失败") + } +} + +func Test_Required(t *testing.T) { + if m := gvalid.Check("1", "required", nil); m != nil { + t.Error(m) + } + if m := gvalid.Check("", "required", nil); m == nil { + t.Error(m) + } + if m := gvalid.Check("", "required-if:id,1,age,18", nil, map[string]string{"id" : "1", "age" : "19"}); m == nil { + t.Error("required校验失败") + } + if m := gvalid.Check("", "required-if:id,1,age,18", nil, map[string]string{"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 { + t.Error(m) + } + if m := gvalid.Check("0.0.0.0", "ipv4", nil); m != nil { + t.Error(m) + } + if m := gvalid.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 { + t.Error(m) + } + if m := gvalid.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 { + t.Error(m) + } + if m := gvalid.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 { + t.Error(m) + } + if m := gvalid.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 { + t.Error(m) + } + if m := gvalid.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 { + t.Error(m) + } + if m := gvalid.Check("10.02", rule, nil); m == nil { + t.Error("大小范围校验失败") + } +} + +func Test_SetDefaultErrorMsgs(t *testing.T) { + rule := "integer|length:6,16" + msgs := map[string]string { + "integer" : "请输入一个整数", + "length" : "参数长度不对啊老铁", + } + gvalid.SetDefaultErrorMsgs(msgs) + m := gvalid.Check("6.66", rule, nil) + if len(m) != 2 { + t.Error("规则校验失败") + } else { + if v, ok := m["integer"]; ok { + if strings.Compare(v, msgs["integer"]) != 0 { + t.Error("错误信息不匹配") + } + } + if v, ok := m["length"]; ok { + if strings.Compare(v, msgs["length"]) != 0 { + t.Error("错误信息不匹配") + } + } + } +} + +func Test_CustomError1(t *testing.T) { + rule := "integer|length:6,16" + msgs := map[string]string { + "integer" : "请输入一个整数", + "length" : "参数长度不对啊老铁", + } + m := gvalid.Check("6.66", rule, msgs) + if len(m) != 2 { + t.Error("规则校验失败") + } else { + if v, ok := m["integer"]; ok { + if strings.Compare(v, msgs["integer"]) != 0 { + t.Error("错误信息不匹配") + } + } + if v, ok := m["length"]; ok { + if strings.Compare(v, msgs["length"]) != 0 { + t.Error("错误信息不匹配") + } + } + } +} + +func Test_CustomError2(t *testing.T) { + rule := "integer|length:6,16" + msgs := "请输入一个整数|参数长度不对啊老铁" + m := gvalid.Check("6.66", rule, msgs) + if len(m) != 2 { + t.Error("规则校验失败") + } else { + if v, ok := m["integer"]; ok { + if strings.Compare(v, "请输入一个整数") != 0 { + t.Error("错误信息不匹配") + } + } + if v, ok := m["length"]; ok { + if strings.Compare(v, "参数长度不对啊老铁") != 0 { + t.Error("错误信息不匹配") + } + } + } +} \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index 0608a9655..018390b5f 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,10 +2,10 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/util/gregx" + "gitee.com/johng/gf/g/util/gvalid" ) func main() { - fmt.Println(gregx.IsMatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`, "joh-n_cn@johng.cn")) + fmt.Println(gvalid.Check("10.0.0.0", "ip", nil)) } \ No newline at end of file