improve parameter parsing feature for ghttp.Request

This commit is contained in:
John
2020-01-01 14:18:00 +08:00
parent 0a89daa513
commit fe5d2e5685
17 changed files with 327 additions and 71 deletions

View File

@ -5,20 +5,20 @@ import (
"github.com/gogf/gf/net/ghttp"
)
type User struct {
Uid int `json:"uid"`
Name string `json:"name" params:"username"`
Pass1 string `json:"pass1" params:"password1,userpass1"`
Pass2 string `json:"pass2" params:"password3,userpass2"`
}
func main() {
type User struct {
Uid int `json:"uid"`
Name string `json:"name" p:"username"`
Pass1 string `json:"pass1" p:"password1"`
Pass2 string `json:"pass2" p:"password2"`
}
s := g.Server()
s.BindHandler("/user", func(r *ghttp.Request) {
user := new(User)
r.GetToStruct(user)
//r.GetPostToStruct(user)
//r.GetQueryToStruct(user)
var user *User
if err := r.Parse(&user); err != nil {
panic(err)
}
r.Response.WriteJson(user)
})
s.SetPort(8199)

View File

@ -1,13 +1,17 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
db := g.DB()
// 开启调试模式以便于记录所有执行的SQL
db.SetDebug(true)
db.Table("user").Delete("score < ", 60)
s := g.Server()
s.BindHandler("/test", func(r *ghttp.Request) {
fmt.Println(r.GetBody())
r.Response.Write(r.GetBody())
})
s.SetPort(8199)
s.Run()
}

11
.example/other/test2.go Normal file
View File

@ -0,0 +1,11 @@
package main
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
)
func main() {
r := ghttp.PostContent("http://127.0.0.1:8199/test", `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`)
fmt.Println(r)
}

View File

@ -8,7 +8,6 @@
package gxml
import (
"fmt"
"strings"
"github.com/clbanning/mxj"
@ -16,7 +15,7 @@ import (
"github.com/gogf/gf/text/gregex"
)
// 将XML内容解析为map变量
// Decode parses <content> into and returns as map.
func Decode(content []byte) (map[string]interface{}, error) {
res, err := convert(content)
if err != nil {
@ -25,23 +24,42 @@ func Decode(content []byte) (map[string]interface{}, error) {
return mxj.NewMapXml(res)
}
// 将map变量解析为XML格式内容
func Encode(v map[string]interface{}, rootTag ...string) ([]byte, error) {
return mxj.Map(v).Xml(rootTag...)
// DecodeWithoutRoot parses <content> into a map, and returns the map without root level.
func DecodeWithoutRoot(content []byte) (map[string]interface{}, error) {
res, err := convert(content)
if err != nil {
return nil, err
}
m, err := mxj.NewMapXml(res)
if err != nil {
return nil, err
}
for _, v := range m {
if r, ok := v.(map[string]interface{}); ok {
return r, nil
}
}
return m, nil
}
func EncodeWithIndent(v map[string]interface{}, rootTag ...string) ([]byte, error) {
return mxj.Map(v).XmlIndent("", "\t", rootTag...)
// Encode encodes map <m> to a XML format content as bytes.
// The optional parameter <rootTag> is used to specify the XML root tag.
func Encode(m map[string]interface{}, rootTag ...string) ([]byte, error) {
return mxj.Map(m).Xml(rootTag...)
}
// XML格式内容直接转换为JSON格式内容
// Encode encodes map <m> to a XML format content as bytes with indent.
// The optional parameter <rootTag> is used to specify the XML root tag.
func EncodeWithIndent(m map[string]interface{}, rootTag ...string) ([]byte, error) {
return mxj.Map(m).XmlIndent("", "\t", rootTag...)
}
// ToJson converts <content> as XML format into JSON format bytes.
func ToJson(content []byte) ([]byte, error) {
res, err := convert(content)
if err != nil {
fmt.Println("convert error. ", err)
return nil, err
}
mv, err := mxj.NewMapXml(res)
if err == nil {
return mv.Json()
@ -50,7 +68,7 @@ func ToJson(content []byte) ([]byte, error) {
}
}
// XML字符集预处理
// convert converts the encoding of given XML content from XML root tag into UTF-8 encoding content.
func convert(xml []byte) (res []byte, err error) {
patten := `<\?xml.*encoding\s*=\s*['|"](.*?)['|"].*\?>`
matchStr, err := gregex.MatchString(patten, string(xml))

View File

@ -11,6 +11,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/text/gregex"
"io"
"mime/multipart"
"net/http"
@ -91,10 +92,11 @@ func (c *Client) Post(url string, data ...interface{}) (resp *ClientResponse, er
// Custom Content-Type.
req.Header.Set("Content-Type", v)
} else {
// Auto detecting and setting the post content format: JSON.
if json.Valid(paramBytes) {
// Auto detecting and setting the post content format: JSON.
req.Header.Set("Content-Type", "application/json")
} else {
} else if gregex.IsMatchString(`^\w+=.+`, param) {
// If the parameters passed like "name=value", it then uses form type.
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}

View File

@ -7,15 +7,39 @@
package ghttp
import (
"bytes"
"encoding/json"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/encoding/gurl"
"github.com/gogf/gf/encoding/gxml"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gvalid"
"io/ioutil"
"mime/multipart"
)
var (
// xmlHeaderBytes is the most common XML format header.
xmlHeaderBytes = []byte("<?xml")
)
// Parse calls r.GetStruct to convert the parameters, which are sent from client,
// to given struct, and then calls gvalid.CheckStruct validating the struct according
// to the validation tag of the struct.
//
// See GetStruct, gvalid.CheckStruct.
func (r *Request) Parse(pointer interface{}) error {
if err := r.GetStruct(pointer); err != nil {
return err
}
if err := gvalid.CheckStruct(pointer, nil); err != nil {
return err
}
return nil
}
// Get is alias of GetRequest, which is one of the most commonly used functions for
// retrieving parameter.
// See GetRequest.
@ -131,18 +155,18 @@ func (r *Request) GetMapStrStr(def ...map[string]interface{}) map[string]string
// GetStruct is alias of GetRequestToStruct.
// See GetRequestToStruct.
func (r *Request) GetStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetRequestToStruct(pointer, mapping...)
return r.GetRequestStruct(pointer, mapping...)
}
// GetToStruct is alias of GetRequestToStruct.
// See GetRequestToStruct.
// Deprecated.
func (r *Request) GetToStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetRequestToStruct(pointer, mapping...)
return r.GetRequestStruct(pointer, mapping...)
}
// ParseQuery parses query string into r.queryMap.
func (r *Request) ParseQuery() {
// parseQuery parses query string into r.queryMap.
func (r *Request) parseQuery() {
if r.parsedQuery {
return
}
@ -157,19 +181,36 @@ func (r *Request) ParseQuery() {
}
// ParseRaw parses the request raw data into r.rawMap.
func (r *Request) ParseBody() {
// Note that it also supports JSON data from client request.
func (r *Request) parseBody() {
if r.parsedBody {
return
}
r.parsedBody = true
if body := r.GetBodyString(); len(body) > 0 {
r.bodyMap, _ = gstr.Parse(body)
if body := r.GetBody(); len(body) > 0 {
// Trim space/new line characters.
body = bytes.TrimSpace(body)
// JSON format checks.
if body[0] == '{' && body[len(body)-1] == '}' {
_ = json.Unmarshal(body, &r.bodyMap)
}
// XML format checks.
if len(body) > 5 && bytes.EqualFold(body[:5], xmlHeaderBytes) {
r.bodyMap, _ = gxml.DecodeWithoutRoot(body)
}
if body[0] == '<' && body[len(body)-1] == '>' {
r.bodyMap, _ = gxml.DecodeWithoutRoot(body)
}
// Default parameters decoding.
if r.bodyMap == nil {
r.bodyMap, _ = gstr.Parse(r.GetBodyString())
}
}
}
// ParseForm parses the request form for HTTP method PUT, POST, PATCH.
// parseForm parses the request form for HTTP method PUT, POST, PATCH.
// The form data is pared into r.formMap.
func (r *Request) ParseForm() {
func (r *Request) parseForm() {
if r.parsedForm {
return
}
@ -217,7 +258,7 @@ func (r *Request) ParseForm() {
panic(err)
}
} else {
r.ParseBody()
r.parseBody()
if len(r.bodyMap) > 0 {
r.formMap = r.bodyMap
}
@ -227,7 +268,7 @@ func (r *Request) ParseForm() {
// GetMultipartForm parses and returns the form as multipart form.
func (r *Request) GetMultipartForm() *multipart.Form {
r.ParseForm()
r.parseForm()
return r.MultipartForm
}

View File

@ -14,7 +14,7 @@ import (
// SetForm sets custom form value with key-value pair.
func (r *Request) SetForm(key string, value interface{}) {
r.ParseForm()
r.parseForm()
if r.formMap == nil {
r.formMap = make(map[string]interface{})
}
@ -25,7 +25,7 @@ func (r *Request) SetForm(key string, value interface{}) {
// It returns <def> if <key> does not exist in the form.
// It returns nil if <def> is not passed.
func (r *Request) GetForm(key string, def ...interface{}) interface{} {
r.ParseForm()
r.parseForm()
if len(r.formMap) > 0 {
if v, ok := r.formMap[key]; ok {
return v
@ -105,7 +105,7 @@ func (r *Request) GetFormInterfaces(key string, def ...interface{}) []interface{
// The parameter <kvMap> specifies the keys retrieving from client parameters,
// the associated values are the default values if the client does not pass.
func (r *Request) GetFormMap(kvMap ...map[string]interface{}) map[string]interface{} {
r.ParseForm()
r.parseForm()
if len(kvMap) > 0 && kvMap[0] != nil {
if len(r.formMap) == 0 {
return kvMap[0]
@ -158,7 +158,7 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
// given struct object. Note that the parameter <pointer> is a pointer to the struct object.
// The optional parameter <mapping> is used to specify the key to attribute mapping.
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
r.ParseForm()
r.parseForm()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {

View File

@ -18,14 +18,16 @@ import (
//
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
// in order of priority: form > body.
//
// Deprecated.
func (r *Request) GetPost(key string, def ...interface{}) interface{} {
r.ParseForm()
r.parseForm()
if len(r.formMap) > 0 {
if v, ok := r.formMap[key]; ok {
return v
}
}
r.ParseBody()
r.parseBody()
if len(r.bodyMap) > 0 {
if v, ok := r.bodyMap[key]; ok {
return v
@ -37,66 +39,82 @@ func (r *Request) GetPost(key string, def ...interface{}) interface{} {
return nil
}
// Deprecated.
func (r *Request) GetPostVar(key string, def ...interface{}) *gvar.Var {
return gvar.New(r.GetPost(key, def...))
}
// Deprecated.
func (r *Request) GetPostString(key string, def ...interface{}) string {
return r.GetPostVar(key, def...).String()
}
// Deprecated.
func (r *Request) GetPostBool(key string, def ...interface{}) bool {
return r.GetPostVar(key, def...).Bool()
}
// Deprecated.
func (r *Request) GetPostInt(key string, def ...interface{}) int {
return r.GetPostVar(key, def...).Int()
}
// Deprecated.
func (r *Request) GetPostInt32(key string, def ...interface{}) int32 {
return r.GetPostVar(key, def...).Int32()
}
// Deprecated.
func (r *Request) GetPostInt64(key string, def ...interface{}) int64 {
return r.GetPostVar(key, def...).Int64()
}
// Deprecated.
func (r *Request) GetPostInts(key string, def ...interface{}) []int {
return r.GetPostVar(key, def...).Ints()
}
// Deprecated.
func (r *Request) GetPostUint(key string, def ...interface{}) uint {
return r.GetPostVar(key, def...).Uint()
}
// Deprecated.
func (r *Request) GetPostUint32(key string, def ...interface{}) uint32 {
return r.GetPostVar(key, def...).Uint32()
}
// Deprecated.
func (r *Request) GetPostUint64(key string, def ...interface{}) uint64 {
return r.GetPostVar(key, def...).Uint64()
}
// Deprecated.
func (r *Request) GetPostFloat32(key string, def ...interface{}) float32 {
return r.GetPostVar(key, def...).Float32()
}
// Deprecated.
func (r *Request) GetPostFloat64(key string, def ...interface{}) float64 {
return r.GetPostVar(key, def...).Float64()
}
// Deprecated.
func (r *Request) GetPostFloats(key string, def ...interface{}) []float64 {
return r.GetPostVar(key, def...).Floats()
}
// Deprecated.
func (r *Request) GetPostArray(key string, def ...interface{}) []string {
return r.GetPostVar(key, def...).Strings()
}
// Deprecated.
func (r *Request) GetPostStrings(key string, def ...interface{}) []string {
return r.GetPostVar(key, def...).Strings()
}
// Deprecated.
func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{} {
return r.GetPostVar(key, def...).Interfaces()
}
@ -107,9 +125,11 @@ func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{
//
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
// in order of priority: form > body.
//
// Deprecated.
func (r *Request) GetPostMap(kvMap ...map[string]interface{}) map[string]interface{} {
r.ParseForm()
r.ParseBody()
r.parseForm()
r.parseBody()
var ok, filter bool
if len(kvMap) > 0 && kvMap[0] != nil {
filter = true
@ -146,6 +166,8 @@ func (r *Request) GetPostMap(kvMap ...map[string]interface{}) map[string]interfa
// as map[string]string. The parameter <kvMap> specifies the keys
// retrieving from client parameters, the associated values are the default values if the client
// does not pass.
//
// Deprecated.
func (r *Request) GetPostMapStrStr(kvMap ...map[string]interface{}) map[string]string {
postMap := r.GetPostMap(kvMap...)
if len(postMap) > 0 {
@ -162,6 +184,8 @@ func (r *Request) GetPostMapStrStr(kvMap ...map[string]interface{}) map[string]s
// as map[string]*gvar.Var. The parameter <kvMap> specifies the keys
// retrieving from client parameters, the associated values are the default values if the client
// does not pass.
//
// Deprecated.
func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*gvar.Var {
postMap := r.GetPostMap(kvMap...)
if len(postMap) > 0 {
@ -178,6 +202,8 @@ func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*
// and converts them to given struct object. Note that the parameter <pointer> is a pointer
// to the struct object. The optional parameter <mapping> is used to specify the key to
// attribute mapping.
//
// Deprecated.
func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
@ -189,6 +215,7 @@ func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]strin
}
// GetPostToStruct is alias of GetQueryStruct. See GetPostStruct.
//
// Deprecated.
func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]string) error {
return r.GetPostStruct(pointer, mapping...)

View File

@ -15,7 +15,7 @@ import (
// SetQuery sets custom query value with key-value pair.
func (r *Request) SetQuery(key string, value interface{}) {
r.ParseQuery()
r.parseQuery()
if r.queryMap == nil {
r.queryMap = make(map[string]interface{})
}
@ -29,13 +29,13 @@ func (r *Request) SetQuery(key string, value interface{}) {
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
// in order of priority: query > body.
func (r *Request) GetQuery(key string, def ...interface{}) interface{} {
r.ParseQuery()
r.parseQuery()
if len(r.queryMap) > 0 {
if v, ok := r.queryMap[key]; ok {
return v
}
}
r.ParseBody()
r.parseBody()
if len(r.bodyMap) > 0 {
if v, ok := r.bodyMap[key]; ok {
return v
@ -118,8 +118,8 @@ func (r *Request) GetQueryInterfaces(key string, def ...interface{}) []interface
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
// in order of priority: query > body.
func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interface{} {
r.ParseQuery()
r.ParseBody()
r.parseQuery()
r.parseBody()
var m map[string]interface{}
if len(kvMap) > 0 && kvMap[0] != nil {
if len(r.queryMap) == 0 && len(r.bodyMap) == 0 {
@ -193,7 +193,7 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
// to the struct object. The optional parameter <mapping> is used to specify the key to
// attribute mapping.
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
r.ParseQuery()
r.parseQuery()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {

View File

@ -26,7 +26,7 @@ func (r *Request) GetRequest(key string, def ...interface{}) interface{} {
value = r.GetForm(key)
}
if value == nil {
r.ParseBody()
r.parseBody()
if len(r.bodyMap) > 0 {
value = r.bodyMap[key]
}
@ -168,9 +168,9 @@ func (r *Request) GetRequestInterfaces(key string, def ...interface{}) []interfa
// Note that if there're multiple parameters with the same name, the parameters are retrieved and overwrote
// in order of priority: router < query < body < form < custom.
func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]interface{} {
r.ParseQuery()
r.ParseForm()
r.ParseBody()
r.parseQuery()
r.parseForm()
r.parseBody()
var ok, filter bool
var length int
if len(kvMap) > 0 && kvMap[0] != nil {

View File

@ -17,7 +17,51 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Params_Json(t *testing.T) {
func Test_Params_Json_Request(t *testing.T) {
type User struct {
Id int
Name string
Time *time.Time
Pass1 string `p:"password1" v:"password1"`
Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"`
}
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/get", func(r *ghttp.Request) {
r.Response.WriteExit(r.Get("id"), r.Get("name"))
})
s.BindHandler("/map", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
r.Response.WriteExit(m["id"], m["name"], m["password1"], m["password2"])
}
})
s.BindHandler("/parse", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/get", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john`)
gtest.Assert(client.GetContent("/map", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123"}`), `密码强度不足; 两次密码不一致`)
})
}
func Test_Params_Json_Response(t *testing.T) {
type User struct {
Uid int
Name string

View File

@ -23,33 +23,49 @@ func Test_Params_Struct(t *testing.T) {
Id int
Name string
Time *time.Time
Pass1 string `params:"password1"`
Pass2 string `params:"password2" gvalid:"passwd1 @required|length:2,20|password3#||密码强度不足"`
Pass1 string `p:"password1"`
Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"`
}
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/struct1", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
user := new(User)
r.GetToStruct(user)
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
if err := r.GetStruct(user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.BindHandler("/struct2", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
user := (*User)(nil)
r.GetToStruct(&user)
if err := r.GetStruct(&user); err != nil {
r.Response.WriteExit(err)
}
if user != nil {
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
}
}
})
s.BindHandler("/struct-valid", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
user := new(User)
r.GetToStruct(user)
err := gvalid.CheckStruct(user, nil)
r.Response.Write(err.Maps())
if err := r.GetStruct(user); err != nil {
r.Response.WriteExit(err)
}
if err := gvalid.CheckStruct(user, nil); err != nil {
r.Response.WriteExit(err)
}
}
})
s.BindHandler("/parse", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.SetPort(p)
@ -65,6 +81,10 @@ func Test_Params_Struct(t *testing.T) {
gtest.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
gtest.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`)
gtest.Assert(client.PostContent("/struct2", ``), ``)
gtest.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `{"passwd1":{"length":"字段长度为2到20个字符","password3":"密码强度不足"}}`)
gtest.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
gtest.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
gtest.Assert(client.GetContent("/parse", `id=1&name=john&password1=123&password2=456`), `密码强度不足`)
gtest.Assert(client.GetContent("/parse", `id=1&name=john&password1=123Abc!@#&password2=123Abc!@#`), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
})
}

View File

@ -0,0 +1,65 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
)
func Test_Params_Xml_Request(t *testing.T) {
type User struct {
Id int
Name string
Time *time.Time
Pass1 string `p:"password1" v:"password1"`
Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"`
}
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/get", func(r *ghttp.Request) {
r.Response.WriteExit(r.Get("id"), r.Get("name"))
})
s.BindHandler("/map", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
r.Response.WriteExit(m["id"], m["name"], m["password1"], m["password2"])
}
})
s.BindHandler("/parse", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
content1 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`
content2 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123</password2></doc>`
gtest.Assert(client.GetContent("/get", content1), `1john`)
gtest.Assert(client.PostContent("/get", content1), `1john`)
gtest.Assert(client.GetContent("/map", content1), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/map", content1), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/parse", content1), `1john123Abc!@#123Abc!@#`)
gtest.Assert(client.PostContent("/parse", content2), `密码强度不足; 两次密码不一致`)
})
}

View File

@ -87,7 +87,15 @@ var (
)
// SetTimeZone sets the time zone for current whole process.
// The parameter <zone> is an area string specifying corresponding time zone, eg: Asia/Shanghai.
// The parameter <zone> is an area string specifying corresponding time zone,
// eg: Asia/Shanghai.
//
// Note that the time zone database needed by LoadLocation may not be
// present on all systems, especially non-Unix systems.
// LoadLocation looks in the directory or uncompressed zip file
// named by the ZONEINFO environment variable, if any, then looks in
// known installation locations on Unix systems,
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
func SetTimeZone(zone string) error {
location, err := time.LoadLocation(zone)
if err == nil {

View File

@ -65,6 +65,13 @@ type CustomMsg = map[string]interface{}
// 解析单条sequence tag格式: [数值键名/别名@]校验规则[#错误提示]
// 其中校验规则如果有多个那么以"|"符号分隔,错误提示同理。
func parseSequenceTag(tag string) (name, rule, msg string) {
// Just a alias name.
// Eg: password1
if gregex.IsMatchString(`^\w+$`, tag) {
return tag, "", ""
}
// Complete sequence tag.
// Eg: required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致
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])
}

View File

@ -21,7 +21,8 @@ import (
)
const (
gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)` // 单条规则匹配正则
// 单条规则匹配正则
gSINGLE_RULE_PATTERN = `^([\w-]+):{0,1}(.*)`
)
var (
@ -113,6 +114,9 @@ var (
//
// 3. params参数为联合校验参数支持任意的map/struct/*struct类型对于需要联合校验的规则有效required-*、same、different
func Check(value interface{}, rules string, msgs interface{}, params ...interface{}) *Error {
if rules == "" {
return nil
}
// 内部会将参数全部转换为字符串类型进行校验
val := strings.TrimSpace(gconv.String(value))
data := make(map[string]string)

View File

@ -112,6 +112,11 @@ func (e *Error) String() string {
return strings.Join(e.Strings(), "; ")
}
// Error implements interface of error.Error.
func (e *Error) Error() string {
return e.String()
}
// 只返回错误信息,构造成字符串数组返回
func (e *Error) Strings() (errs []string) {
errs = make([]string, 0)