mirror of
https://gitee.com/johng/gf
synced 2026-06-29 02:26:29 +08:00
add configuration support for maxIdle/maxActive/idleTimeout/maxConnLifetime of gredis; update gjson.Append/Len functions; add more unit test cases for gredis/gins/gstr/gjson
This commit is contained in:
@ -18,7 +18,7 @@ import (
|
||||
|
||||
type Var struct {
|
||||
value interface{} // 变量值
|
||||
safe bool // 当为true时,value为 *gtype.Interface 类型
|
||||
safe bool // 当为true时, value为 *gtype.Interface 类型
|
||||
}
|
||||
|
||||
// 创建一个动态变量,value参数可以为nil
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
// Copyright 2019 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 gdb_test
|
||||
|
||||
import (
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
// 方法操作
|
||||
// Copyright 2019 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 gdb_test
|
||||
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
// 链式操作
|
||||
// Copyright 2019 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 gdb_test
|
||||
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
// 事务操作
|
||||
// Copyright 2019 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 gdb_test
|
||||
|
||||
|
||||
@ -11,30 +11,36 @@
|
||||
package gredis
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
gDEFAULT_POOL_MAX_IDLE = 1
|
||||
gDEFAULT_POOL_MAX_ACTIVE = 10
|
||||
gDEFAULT_POOL_IDLE_TIMEOUT = 180 * time.Second
|
||||
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
|
||||
gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second
|
||||
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
|
||||
)
|
||||
|
||||
// Redis客户端
|
||||
// Redis客户端(管理连接池)
|
||||
type Redis struct {
|
||||
pool *redis.Pool
|
||||
}
|
||||
|
||||
// Redis连接对象(连接池中的单个连接)
|
||||
type Conn redis.Conn
|
||||
|
||||
|
||||
// Redis服务端但节点连接配置信息
|
||||
type Config struct {
|
||||
Host string // IP/域名
|
||||
Port int // 端口
|
||||
Db int // db
|
||||
Pass string // 密码
|
||||
Host string // 地址
|
||||
Port int // 端口
|
||||
Db int // 数据库
|
||||
Pass string // 授权密码
|
||||
MaxIdle int // 最大允许空闲存在的连接数(默认为0表示不存在闲置连接)
|
||||
MaxActive int // 最大连接数量限制(默认为0表示不限制)
|
||||
IdleTimeout time.Duration // 连接最大空闲时间(默认为60秒,不允许设置为0)
|
||||
MaxConnLifetime time.Duration // 连接最长存活时间(默认为60秒,不允许设置为0)
|
||||
}
|
||||
|
||||
// Redis链接池统计信息
|
||||
@ -45,81 +51,108 @@ type PoolStats struct {
|
||||
// 连接池map
|
||||
var pools = gmap.NewStringInterfaceMap()
|
||||
|
||||
// New creates a redis client object with given configuration.
|
||||
// Redis client maintains a connection pool automatically.
|
||||
//
|
||||
// 创建redis操作对象.
|
||||
func New(config Config) *Redis {
|
||||
r := &Redis{}
|
||||
poolKey := fmt.Sprintf("%s:%d,%d", config.Host, config.Port, config.Db)
|
||||
if v := pools.Get(poolKey); v == nil {
|
||||
pool := &redis.Pool {
|
||||
MaxIdle : gDEFAULT_POOL_MAX_IDLE,
|
||||
MaxActive : gDEFAULT_POOL_MAX_ACTIVE,
|
||||
IdleTimeout : gDEFAULT_POOL_IDLE_TIMEOUT,
|
||||
MaxConnLifetime : gDEFAULT_POOL_MAX_LIFE_TIME,
|
||||
Dial : func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(config.Pass) > 0 {
|
||||
if _, err := c.Do("AUTH", config.Pass); err != nil {
|
||||
if config.IdleTimeout == 0 {
|
||||
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
|
||||
}
|
||||
if config.MaxConnLifetime == 0 {
|
||||
config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME
|
||||
}
|
||||
return &Redis{
|
||||
pool : pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
|
||||
return &redis.Pool {
|
||||
IdleTimeout : config.IdleTimeout,
|
||||
MaxConnLifetime : config.MaxConnLifetime,
|
||||
Dial : func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err := c.Do("SELECT", config.Db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
},
|
||||
// 用来测试连接是否可用
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
pools.Set(poolKey, pool)
|
||||
r.pool = pool
|
||||
} else {
|
||||
r.pool = v.(*redis.Pool)
|
||||
// 密码设置
|
||||
if len(config.Pass) > 0 {
|
||||
if _, err := c.Do("AUTH", config.Pass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// 数据库设置
|
||||
if _, err := c.Do("SELECT", config.Db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
},
|
||||
// 用来测试连接是否可用
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
}).(*redis.Pool),
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Close closes the redis connection pool,
|
||||
// it will release all connections reserved by this pool.
|
||||
//
|
||||
// 关闭redis管理对象,将会关闭底层的
|
||||
func (r *Redis) Close() error {
|
||||
return r.pool.Close()
|
||||
}
|
||||
|
||||
// 获得一个原生的redis连接对象,用于自定义连接操作,
|
||||
// 但是需要注意的是如果不再使用该连接对象时,需要手动Close连接,否则会造成连接数超限。
|
||||
func (r *Redis) GetConn() redis.Conn {
|
||||
return r.pool.Get()
|
||||
// See GetConn.
|
||||
func (r *Redis) Conn() Conn {
|
||||
return r.GetConn()
|
||||
}
|
||||
|
||||
// GetConn returns a raw connection object, which expose more methods communication with server.
|
||||
// You should call Close function manually if you do not use this connection any more.
|
||||
//
|
||||
// 获得一个原生的redis连接对象,用于自定义连接操作,
|
||||
// 但是需要注意的是如果不再使用该连接对象时,需要手动Close连接,否则会造成连接数超限。
|
||||
func (r *Redis) GetConn() Conn {
|
||||
return r.pool.Get().(Conn)
|
||||
}
|
||||
|
||||
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
|
||||
//
|
||||
// 设置属性 - MaxIdle
|
||||
func (r *Redis) SetMaxIdle(value int) {
|
||||
r.pool.MaxIdle = value
|
||||
}
|
||||
|
||||
// SetMaxIdle sets the MaxActive attribute of the connection pool.
|
||||
//
|
||||
// 设置属性 - MaxActive
|
||||
func (r *Redis) SetMaxActive(value int) {
|
||||
r.pool.MaxActive = value
|
||||
}
|
||||
|
||||
// SetMaxIdle sets the IdleTimeout attribute of the connection pool.
|
||||
//
|
||||
// 设置属性 - IdleTimeout
|
||||
func (r *Redis) SetIdleTimeout(value time.Duration) {
|
||||
r.pool.IdleTimeout = value
|
||||
}
|
||||
|
||||
// SetMaxIdle sets the MaxConnLifetime attribute of the connection pool.
|
||||
//
|
||||
// 设置属性 - MaxConnLifetime
|
||||
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
|
||||
r.pool.MaxConnLifetime = value
|
||||
}
|
||||
|
||||
// Stats returns pool's statistics.
|
||||
//
|
||||
// 获取当前连接池统计信息
|
||||
func (r *Redis) Stats() *PoolStats {
|
||||
return &PoolStats{r.pool.Stats()}
|
||||
}
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
//
|
||||
// 执行同步命令 - Do
|
||||
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
|
||||
conn := r.pool.Get()
|
||||
@ -127,6 +160,8 @@ func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
|
||||
return conn.Do(command, args...)
|
||||
}
|
||||
|
||||
// Send writes the command to the client's output buffer.
|
||||
//
|
||||
// 执行异步命令 - Send
|
||||
func (r *Redis) Send(command string, args ...interface{}) error {
|
||||
conn := r.pool.Get()
|
||||
@ -134,3 +169,4 @@ func (r *Redis) Send(command string, args ...interface{}) error {
|
||||
return conn.Send(command, args...)
|
||||
}
|
||||
|
||||
|
||||
|
||||
93
g/database/gredis/gredis_unit_test.go
Normal file
93
g/database/gredis/gredis_unit_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2019 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 gredis_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/database/gredis"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
config = gredis.Config{
|
||||
Host : "127.0.0.1",
|
||||
Port : 6379,
|
||||
Db : 1,
|
||||
}
|
||||
)
|
||||
|
||||
func Test_NewClose(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
gtest.AssertNE(redis, nil)
|
||||
err := redis.Close()
|
||||
gtest.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Do(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
defer redis.Close()
|
||||
_, err := redis.Do("SET", "k", "v")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
r, err := redis.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, []byte("v"))
|
||||
|
||||
_, err = redis.Do("DEL", "k")
|
||||
gtest.Assert(err, nil)
|
||||
r, err = redis.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Send(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
defer redis.Close()
|
||||
err := redis.Send("SET", "k", "v")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
r, err := redis.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, []byte("v"))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Stats(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
defer redis.Close()
|
||||
redis.SetMaxIdle(2)
|
||||
redis.SetMaxActive(100)
|
||||
redis.SetIdleTimeout(500*time.Millisecond)
|
||||
redis.SetMaxConnLifetime(500*time.Millisecond)
|
||||
|
||||
array := make([]gredis.Conn, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
array = append(array, redis.Conn())
|
||||
}
|
||||
stats := redis.Stats()
|
||||
gtest.Assert(stats.ActiveCount, 10)
|
||||
gtest.Assert(stats.IdleCount, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
array[i].Close()
|
||||
}
|
||||
stats = redis.Stats()
|
||||
gtest.Assert(stats.ActiveCount, 2)
|
||||
gtest.Assert(stats.IdleCount, 2)
|
||||
//time.Sleep(3000*time.Millisecond)
|
||||
//stats = redis.Stats()
|
||||
//fmt.Println(stats)
|
||||
//gtest.Assert(stats.ActiveCount, 0)
|
||||
//gtest.Assert(stats.IdleCount, 0)
|
||||
})
|
||||
}
|
||||
@ -9,7 +9,6 @@ package gjson
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/encoding/gtoml"
|
||||
"github.com/gogf/gf/g/encoding/gxml"
|
||||
@ -20,6 +19,7 @@ import (
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -486,16 +486,28 @@ done:
|
||||
// 数据结构转换,map参数必须转换为map[string]interface{},数组参数必须转换为[]interface{}
|
||||
func (j *Json) convertValue(value interface{}) interface{} {
|
||||
switch value.(type) {
|
||||
case map[string]interface{}:
|
||||
return value
|
||||
case []interface{}:
|
||||
return value
|
||||
default:
|
||||
// 这里效率会比较低,当然比直接用反射也不会差到哪儿去
|
||||
// 为了操作的灵活性,牺牲了一定的效率
|
||||
b, _ := Encode(value)
|
||||
v, _ := Decode(b)
|
||||
return v
|
||||
case map[string]interface{}:
|
||||
return value
|
||||
case []interface{}:
|
||||
return value
|
||||
default:
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Array: return gconv.Interfaces(value)
|
||||
case reflect.Slice: return gconv.Interfaces(value)
|
||||
case reflect.Map: return gconv.Map(value)
|
||||
case reflect.Struct: return gconv.Map(value)
|
||||
default:
|
||||
// 最后使用JSON编解码
|
||||
b, _ := Encode(value)
|
||||
v, _ := Decode(b)
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,23 +515,23 @@ func (j *Json) convertValue(value interface{}) interface{} {
|
||||
// 返回修改后的父级指针
|
||||
func (j *Json) setPointerWithValue(pointer *interface{}, key string, value interface{}) *interface{} {
|
||||
switch (*pointer).(type) {
|
||||
case map[string]interface{}:
|
||||
(*pointer).(map[string]interface{})[key] = value
|
||||
return &value
|
||||
case []interface{}:
|
||||
n, _ := strconv.Atoi(key)
|
||||
if len((*pointer).([]interface{})) > n {
|
||||
(*pointer).([]interface{})[n] = value
|
||||
return &(*pointer).([]interface{})[n]
|
||||
} else {
|
||||
s := make([]interface{}, n + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[n] = value
|
||||
*pointer = s
|
||||
return &s[n]
|
||||
}
|
||||
default:
|
||||
*pointer = value
|
||||
case map[string]interface{}:
|
||||
(*pointer).(map[string]interface{})[key] = value
|
||||
return &value
|
||||
case []interface{}:
|
||||
n, _ := strconv.Atoi(key)
|
||||
if len((*pointer).([]interface{})) > n {
|
||||
(*pointer).([]interface{})[n] = value
|
||||
return &(*pointer).([]interface{})[n]
|
||||
} else {
|
||||
s := make([]interface{}, n + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[n] = value
|
||||
*pointer = s
|
||||
return &s[n]
|
||||
}
|
||||
default:
|
||||
*pointer = value
|
||||
}
|
||||
return pointer
|
||||
}
|
||||
@ -539,7 +551,7 @@ func (j *Json) Get(pattern...string) interface{} {
|
||||
if j.vc {
|
||||
result = j.getPointerByPattern(queryPattern)
|
||||
} else {
|
||||
result = j.getPointerByPatternWithoutSplitCharViolenceCheck(queryPattern)
|
||||
result = j.getPointerByPatternWithoutViolenceCheck(queryPattern)
|
||||
}
|
||||
if result != nil {
|
||||
return *result
|
||||
@ -552,17 +564,18 @@ func (j *Json) Contains(pattern...string) bool {
|
||||
return j.Get(pattern...) != nil
|
||||
}
|
||||
|
||||
// 计算指定pattern的元素长度(pattern对应数据类型为map[string]interface{}/[]interface{}时有效)
|
||||
// 计算指定pattern的元素长度(pattern对应数据类型为map/slice时有效)。
|
||||
// 当pattern对应的数据类型非map/slice时,返回-1。
|
||||
func (j *Json) Len(pattern string) int {
|
||||
p := j.getPointerByPattern(pattern)
|
||||
if p != nil {
|
||||
switch (*p).(type) {
|
||||
case map[string]interface{}:
|
||||
return len((*p).(map[string]interface{}))
|
||||
case []interface{}:
|
||||
return len((*p).([]interface{}))
|
||||
default:
|
||||
return -1
|
||||
case map[string]interface{}:
|
||||
return len((*p).(map[string]interface{}))
|
||||
case []interface{}:
|
||||
return len((*p).([]interface{}))
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
@ -570,14 +583,27 @@ func (j *Json) Len(pattern string) int {
|
||||
|
||||
// 指定pattern追加元素
|
||||
func (j *Json) Append(pattern string, value interface{}) error {
|
||||
length := j.Len(pattern)
|
||||
if length != -1 {
|
||||
return j.Set(fmt.Sprintf("%s.%d", pattern, length), value)
|
||||
p := j.getPointerByPattern(pattern)
|
||||
if p == nil {
|
||||
return j.Set(fmt.Sprintf("%s.0", pattern), value)
|
||||
}
|
||||
return errors.New(fmt.Sprintf("cannot find item for pattern: %s", pattern))
|
||||
switch (*p).(type) {
|
||||
case []interface{}:
|
||||
return j.Set(fmt.Sprintf("%s.%d", pattern, len((*p).([]interface{}))), value)
|
||||
}
|
||||
return fmt.Errorf("invalid variable type of %s", pattern)
|
||||
}
|
||||
|
||||
// 根据pattern层级查找**变量指针**
|
||||
// 根据pattern获取对应元素项的指针
|
||||
func (j *Json) getPointerByPattern(pattern string) *interface{} {
|
||||
if j.vc {
|
||||
return j.getPointerByPatternWithViolenceCheck(pattern)
|
||||
} else {
|
||||
return j.getPointerByPatternWithoutViolenceCheck(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据pattern层级查找**变量指针**, 执行冲突检测。
|
||||
// 检索方式:例如检索 a.a.a ,值为1
|
||||
// 1. 检索 a.a.a.a 是否存在对应map的键名;
|
||||
// 2. 检索 a.a.a 是否存在对应map的键名;
|
||||
@ -589,7 +615,10 @@ func (j *Json) Append(pattern string, value interface{}) error {
|
||||
// 8. 在m2中检索 a.a 否存在对应map的键名;
|
||||
// 9. 在m2中检索 a 否存在对应map的键名,检索到有值,值为1;
|
||||
// 这样检索的复杂度很高,主要是为了避免键名中存在分隔符号(默认为".")的情况,避免歧义。
|
||||
func (j *Json) getPointerByPattern(pattern string) *interface{} {
|
||||
func (j *Json) getPointerByPatternWithViolenceCheck(pattern string) *interface{} {
|
||||
if !j.vc {
|
||||
return j.getPointerByPatternWithoutViolenceCheck(pattern)
|
||||
}
|
||||
index := len(pattern)
|
||||
start := 0
|
||||
length := 0
|
||||
@ -625,7 +654,10 @@ func (j *Json) getPointerByPattern(pattern string) *interface{} {
|
||||
}
|
||||
|
||||
// 层级检索,内部不执行分隔符冲突检查,检索效率会有所提高,但是冲突需要开发者自己根据自定义的分隔符来进行解决
|
||||
func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string) *interface{} {
|
||||
func (j *Json) getPointerByPatternWithoutViolenceCheck(pattern string) *interface{} {
|
||||
if j.vc {
|
||||
return j.getPointerByPatternWithViolenceCheck(pattern)
|
||||
}
|
||||
pointer := j.p
|
||||
if len(pattern) == 0 {
|
||||
return pointer
|
||||
@ -645,21 +677,21 @@ func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 判断给定的key在当前的pointer下是否有值,并返回对应的pointer
|
||||
// 判断给定的key在当前的pointer下是否有值,并返回对应的pointer,
|
||||
// 注意这里返回的指针都是临时变量的内存地址
|
||||
func (j *Json) checkPatternByPointer(key string, pointer *interface{}) *interface{} {
|
||||
switch (*pointer).(type) {
|
||||
case map[string]interface{}:
|
||||
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
|
||||
return &v
|
||||
}
|
||||
case []interface{}:
|
||||
if gstr.IsNumeric(key) {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err == nil && len((*pointer).([]interface{})) > n {
|
||||
return &(*pointer).([]interface{})[n]
|
||||
case map[string]interface{}:
|
||||
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
|
||||
return &v
|
||||
}
|
||||
case []interface{}:
|
||||
if gstr.IsNumeric(key) {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err == nil && len((*pointer).([]interface{})) > n {
|
||||
return &(*pointer).([]interface{})[n]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
30
g/encoding/gjson/gjson_bench_test.go
Normal file
30
g/encoding/gjson/gjson_bench_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 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 gjson_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gjson"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Set1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p := gjson.New(map[string]string{
|
||||
"k1" : "v1",
|
||||
"k2" : "v2",
|
||||
})
|
||||
p.Set("k1.k11", []int{1,2,3})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Set2(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
|
||||
}
|
||||
}
|
||||
|
||||
280
g/encoding/gjson/gjson_unit_test.go
Normal file
280
g/encoding/gjson/gjson_unit_test.go
Normal file
@ -0,0 +1,280 @@
|
||||
// Copyright 2017 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 gjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/encoding/gjson"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Set1(t *testing.T) {
|
||||
e := []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)
|
||||
p := gjson.New(map[string]string{
|
||||
"k1" : "v1",
|
||||
"k2" : "v2",
|
||||
})
|
||||
p.Set("k1.k11", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set2(t *testing.T) {
|
||||
e := []byte(`[[null,1]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set3(t *testing.T) {
|
||||
e := []byte(`{"kv":{"k1":"v1"}}`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("kv", map[string]string {
|
||||
"k1" : "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set4(t *testing.T) {
|
||||
e := []byte(`["a",[{"k1":"v1"}]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("1.0", map[string]string{
|
||||
"k1" : "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set5(t *testing.T) {
|
||||
e := []byte(`[[[[[[[[[[[[[[[[[[[[[1,2,3]]]]]]]]]]]]]]]]]]]]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set6(t *testing.T) {
|
||||
e := []byte(`["a",[1,2,3]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set7(t *testing.T) {
|
||||
e := []byte(`{"0":[null,[1,2,3]],"k1":"v1","k2":"v2"}`)
|
||||
p := gjson.New(map[string]string{
|
||||
"k1" : "v1",
|
||||
"k2" : "v2",
|
||||
})
|
||||
p.Set("0.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set8(t *testing.T) {
|
||||
e := []byte(`{"0":[[[[[[null,[1,2,3]]]]]]],"k1":"v1","k2":"v2"}`)
|
||||
p := gjson.New(map[string]string{
|
||||
"k1" : "v1",
|
||||
"k2" : "v2",
|
||||
})
|
||||
p.Set("0.0.0.0.0.0.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set9(t *testing.T) {
|
||||
e := []byte(`{"k1":[null,[1,2,3]],"k2":"v2"}`)
|
||||
p := gjson.New(map[string]string{
|
||||
"k1" : "v1",
|
||||
"k2" : "v2",
|
||||
})
|
||||
p.Set("k1.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func Test_Set10(t *testing.T) {
|
||||
e := []byte(`{"a":{"b":{"c":1}}}`)
|
||||
p := gjson.New(nil)
|
||||
p.Set("a.b.c", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func Test_Set11(t *testing.T) {
|
||||
e := []byte(`{"a":{"b":{}}}`)
|
||||
p, _ := gjson.LoadContent([]byte(`{"a":{"b":{"c":1}}}`), "json")
|
||||
p.Remove("a.b.c")
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set12(t *testing.T) {
|
||||
e := []byte(`[0,1]`)
|
||||
p := gjson.New(nil)
|
||||
p.Set("0", 0)
|
||||
p.Set("1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set13(t *testing.T) {
|
||||
e := []byte(`{"array":[0,1]}`)
|
||||
p := gjson.New(nil)
|
||||
p.Set("array.0", 0)
|
||||
p.Set("array.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set14(t *testing.T) {
|
||||
e := []byte(`{"f":{"a":1}}`)
|
||||
p := gjson.New(nil)
|
||||
p.Set("f", "m")
|
||||
p.Set("f.a", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Len(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Append("a", 1)
|
||||
p.Append("a", 2)
|
||||
gtest.Assert(p.Len("a"), 2)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Append("a.b", 1)
|
||||
p.Append("a.c", 2)
|
||||
gtest.Assert(p.Len("a"), 2)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Set("a", 1)
|
||||
gtest.Assert(p.Len("a"), -1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Append(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Append("a", 1)
|
||||
p.Append("a", 2)
|
||||
gtest.Assert(p.Get("a"), g.Slice{1, 2})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Append("a.b", 1)
|
||||
p.Append("a.c", 2)
|
||||
gtest.Assert(p.Get("a"), g.Map{
|
||||
"b" : g.Slice{1},
|
||||
"c" : g.Slice{2},
|
||||
})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
p := gjson.New(nil)
|
||||
p.Set("a", 1)
|
||||
err := p.Append("a", 2)
|
||||
gtest.AssertNE(err, nil)
|
||||
gtest.Assert(p.Get("a"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -10,9 +10,8 @@ package gparser_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Set1(t *testing.T) {
|
||||
@ -23,7 +22,7 @@ func Test_Set1(t *testing.T) {
|
||||
})
|
||||
p.Set("k1.k11", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -37,7 +36,7 @@ func Test_Set2(t *testing.T) {
|
||||
p := gparser.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -53,7 +52,7 @@ func Test_Set3(t *testing.T) {
|
||||
"k1" : "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -69,7 +68,7 @@ func Test_Set4(t *testing.T) {
|
||||
"k1" : "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -83,7 +82,7 @@ func Test_Set5(t *testing.T) {
|
||||
p := gparser.New([]string{"a"})
|
||||
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -97,7 +96,7 @@ func Test_Set6(t *testing.T) {
|
||||
p := gparser.New([]string{"a"})
|
||||
p.Set("1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -114,7 +113,7 @@ func Test_Set7(t *testing.T) {
|
||||
})
|
||||
p.Set("0.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -131,7 +130,7 @@ func Test_Set8(t *testing.T) {
|
||||
})
|
||||
p.Set("0.0.0.0.0.0.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -148,7 +147,7 @@ func Test_Set9(t *testing.T) {
|
||||
})
|
||||
p.Set("k1.1", []int{1,2,3})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -163,7 +162,7 @@ func Test_Set10(t *testing.T) {
|
||||
p := gparser.New(nil)
|
||||
p.Set("a.b.c", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -178,7 +177,7 @@ func Test_Set11(t *testing.T) {
|
||||
p, _ := gparser.LoadContent([]byte(`{"a":{"b":{"c":1}}}`), "json")
|
||||
p.Remove("a.b.c")
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -193,7 +192,7 @@ func Test_Set12(t *testing.T) {
|
||||
p.Set("0", 0)
|
||||
p.Set("1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -208,7 +207,7 @@ func Test_Set13(t *testing.T) {
|
||||
p.Set("array.0", 0)
|
||||
p.Set("array.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
fmt.Println(string(c))
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
|
||||
@ -21,8 +21,10 @@ import (
|
||||
"github.com/gogf/gf/g/os/gfsnotify"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gview"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -163,18 +165,34 @@ func Database(name...string) gdb.DB {
|
||||
if value, ok := nodem["priority"]; ok {
|
||||
node.Priority = gconv.Int(value)
|
||||
}
|
||||
// Deprecated
|
||||
if value, ok := nodem["linkinfo"]; ok {
|
||||
node.Linkinfo = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["linkInfo"]; ok {
|
||||
node.Linkinfo = gconv.String(value)
|
||||
}
|
||||
// Deprecated
|
||||
if value, ok := nodem["max-idle"]; ok {
|
||||
node.MaxIdleConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["maxIdle"]; ok {
|
||||
node.MaxIdleConnCount = gconv.Int(value)
|
||||
}
|
||||
// Deprecated
|
||||
if value, ok := nodem["max-open"]; ok {
|
||||
node.MaxOpenConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["maxOpen"]; ok {
|
||||
node.MaxOpenConnCount = gconv.Int(value)
|
||||
}
|
||||
// Deprecated
|
||||
if value, ok := nodem["max-lifetime"]; ok {
|
||||
node.MaxConnLifetime = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["maxLifetime"]; ok {
|
||||
node.MaxConnLifetime = gconv.Int(value)
|
||||
}
|
||||
cg = append(cg, node)
|
||||
}
|
||||
}
|
||||
@ -208,11 +226,34 @@ func Redis(name...string) *gredis.Redis {
|
||||
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group)
|
||||
result := instances.GetOrSetFuncLock(key, func() interface{} {
|
||||
if m := config.GetMap("redis"); m != nil {
|
||||
// host:port[,db[,pass]]
|
||||
// host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x]
|
||||
if v, ok := m[group]; ok {
|
||||
line := gconv.String(v)
|
||||
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
|
||||
if len(array) > 4 {
|
||||
line := gconv.String(v)
|
||||
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)\?(.+)`, line)
|
||||
if len(array) == 6 {
|
||||
parse, _ := gstr.Parse(array[5])
|
||||
config := gredis.Config{
|
||||
Host : array[1],
|
||||
Port : gconv.Int(array[2]),
|
||||
Db : gconv.Int(array[3]),
|
||||
Pass : array[4],
|
||||
}
|
||||
if v, ok := parse["maxIdle"]; ok {
|
||||
config.MaxIdle = gconv.Int(v)
|
||||
}
|
||||
if v, ok := parse["maxActive"]; ok {
|
||||
config.MaxActive = gconv.Int(v)
|
||||
}
|
||||
if v, ok := parse["idleTimeout"]; ok {
|
||||
config.IdleTimeout = gconv.TimeDuration(v)*time.Second
|
||||
}
|
||||
if v, ok := parse["maxConnLifetime"]; ok {
|
||||
config.MaxConnLifetime = gconv.TimeDuration(v)*time.Second
|
||||
}
|
||||
return gredis.New(config)
|
||||
}
|
||||
array, _ = gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
|
||||
if len(array) == 5 {
|
||||
return gredis.New(gredis.Config{
|
||||
Host : array[1],
|
||||
Port : gconv.Int(array[2]),
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/frame/gins"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
@ -48,6 +47,7 @@ test = "v=3"
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
disk = "127.0.0.1:6379,1,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
|
||||
`
|
||||
path := "config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
@ -59,12 +59,14 @@ test = "v=3"
|
||||
time.Sleep(500*time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
fmt.Println("gins Test_Redis", gins.Config().Get("test"))
|
||||
//fmt.Println("gins Test_Redis", gins.Config().Get("test"))
|
||||
|
||||
redisDefault := gins.Redis()
|
||||
redisCache := gins.Redis("cache")
|
||||
redisDisk := gins.Redis("disk")
|
||||
gtest.AssertNE(redisDefault, nil)
|
||||
gtest.AssertNE(redisCache, nil)
|
||||
gtest.AssertNE(redisDisk, nil)
|
||||
|
||||
r, err := redisDefault.Do("PING")
|
||||
gtest.Assert(err, nil)
|
||||
@ -73,6 +75,12 @@ test = "v=3"
|
||||
r, err = redisCache.Do("PING")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, "PONG")
|
||||
|
||||
_, err = redisDisk.Do("SET", "k", "v")
|
||||
gtest.Assert(err, nil)
|
||||
r, err = redisDisk.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, []byte("v"))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
96
g/net/ghttp/ghttp_client_config.go
Normal file
96
g/net/ghttp/ghttp_client_config.go
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// HTTP客户端请求.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
func (c *Client) SetBrowserMode(enabled bool) {
|
||||
c.browserMode = enabled
|
||||
}
|
||||
|
||||
// 设置HTTP Header
|
||||
func (c *Client) SetHeader(key, value string) {
|
||||
c.header[key] = value
|
||||
}
|
||||
|
||||
// 通过字符串设置HTTP Header
|
||||
func (c *Client) SetHeaderRaw(header string) {
|
||||
for _, line := range strings.Split(strings.TrimSpace(header), "\n") {
|
||||
array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line)
|
||||
if len(array) >= 3 {
|
||||
c.header[array[1]] = array[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置COOKIE
|
||||
func (c *Client) SetCookie(key, value string) {
|
||||
c.cookies[key] = value
|
||||
}
|
||||
|
||||
// 使用Map设置COOKIE
|
||||
func (c *Client) SetCookieMap(cookieMap map[string]string) {
|
||||
for k, v := range cookieMap {
|
||||
c.cookies[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求的URL前缀
|
||||
func (c *Client) SetPrefix(prefix string) {
|
||||
c.prefix = prefix
|
||||
}
|
||||
|
||||
// 设置请求过期时间
|
||||
func (c *Client) SetTimeOut(t time.Duration) {
|
||||
c.Timeout = t
|
||||
}
|
||||
|
||||
// 设置HTTP访问账号密码
|
||||
func (c *Client) SetBasicAuth(user, pass string) {
|
||||
c.authUser = user
|
||||
c.authPass = pass
|
||||
}
|
||||
|
||||
// 设置失败重试次数及间隔,失败仅针对网络请求失败情况。
|
||||
// 重试间隔时间单位为秒。
|
||||
func (c *Client) SetRetry(retryCount int, retryInterval int) {
|
||||
c.retryCount = retryCount
|
||||
c.retryInterval = retryInterval
|
||||
}
|
||||
|
||||
// 链式操作, See SetBrowserMode
|
||||
func (c *Client) BrowserMode(enabled bool) *Client {
|
||||
c.browserMode = enabled
|
||||
return c
|
||||
}
|
||||
|
||||
// 链式操作, See SetTimeOut
|
||||
func (c *Client) TimeOut(t time.Duration) *Client {
|
||||
c.Timeout = t
|
||||
return c
|
||||
}
|
||||
|
||||
// 链式操作, See SetBasicAuth
|
||||
func (c *Client) BasicAuth(user, pass string) *Client {
|
||||
c.authUser = user
|
||||
c.authPass = pass
|
||||
return c
|
||||
}
|
||||
|
||||
// 链式操作, See SetRetry
|
||||
func (c *Client) Retry(retryCount int, retryInterval int) *Client {
|
||||
c.retryCount = retryCount
|
||||
c.retryInterval = retryInterval
|
||||
return c
|
||||
}
|
||||
@ -9,90 +9,58 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"time"
|
||||
"bytes"
|
||||
"strings"
|
||||
"net/http"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"io"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http客户端
|
||||
type Client struct {
|
||||
http.Client // 底层http client对象
|
||||
header map[string]string // HEADER信息Map
|
||||
cookies map[string]string // 自定义COOKIE
|
||||
prefix string // 设置请求的URL前缀
|
||||
authUser string // HTTP基本权限设置:名称
|
||||
authPass string // HTTP基本权限设置:密码
|
||||
browserMode bool // 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
http.Client // 底层http client对象
|
||||
header map[string]string // HEADER信息Map
|
||||
cookies map[string]string // 自定义COOKIE
|
||||
prefix string // 设置请求的URL前缀
|
||||
authUser string // HTTP基本权限设置:名称
|
||||
authPass string // HTTP基本权限设置:密码
|
||||
browserMode bool // 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
retryCount int // 失败重试次数(网络失败情况下)
|
||||
retryInterval int // 失败重试间隔
|
||||
}
|
||||
|
||||
// http客户端对象指针
|
||||
func NewClient() (*Client) {
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
Client : http.Client {
|
||||
Transport: &http.Transport {
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
},
|
||||
header : make(map[string]string),
|
||||
cookies : make(map[string]string),
|
||||
header : make(map[string]string),
|
||||
cookies : make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
func (c *Client) SetBrowserMode(enabled bool) {
|
||||
c.browserMode = enabled
|
||||
}
|
||||
|
||||
// 设置HTTP Header
|
||||
func (c *Client) SetHeader(key, value string) {
|
||||
c.header[key] = value
|
||||
}
|
||||
|
||||
// 通过字符串设置HTTP Header
|
||||
func (c *Client) SetHeaderRaw(header string) {
|
||||
for _, line := range strings.Split(strings.TrimSpace(header), "\n") {
|
||||
array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line)
|
||||
if len(array) >= 3 {
|
||||
c.header[array[1]] = array[2]
|
||||
}
|
||||
// 克隆当前客户端对象,复制属性。
|
||||
func (c *Client) Clone() *Client {
|
||||
newClient := NewClient()
|
||||
*newClient = *c
|
||||
newClient.header = make(map[string]string)
|
||||
newClient.cookies = make(map[string]string)
|
||||
for k, v := range c.header {
|
||||
newClient.header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 设置COOKIE
|
||||
func (c *Client) SetCookie(key, value string) {
|
||||
c.cookies[key] = value
|
||||
}
|
||||
|
||||
// 使用Map设置COOKIE
|
||||
func (c *Client) SetCookieMap(cookieMap map[string]string) {
|
||||
for k, v := range cookieMap {
|
||||
c.cookies[k] = v
|
||||
for k, v := range c.cookies {
|
||||
newClient.cookies[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求的URL前缀
|
||||
func (c *Client) SetPrefix(prefix string) {
|
||||
c.prefix = prefix
|
||||
}
|
||||
|
||||
// 设置请求过期时间
|
||||
func (c *Client) SetTimeOut(t time.Duration) {
|
||||
c.Timeout = t
|
||||
}
|
||||
|
||||
// 设置HTTP访问账号密码
|
||||
func (c *Client) SetBasicAuth(user, pass string) {
|
||||
c.authUser = user
|
||||
c.authPass = pass
|
||||
return newClient
|
||||
}
|
||||
|
||||
// GET请求
|
||||
@ -189,9 +157,18 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
req.SetBasicAuth(c.authUser, c.authPass)
|
||||
}
|
||||
// 执行请求
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
resp := (*http.Response)(nil)
|
||||
for {
|
||||
if r, err := c.Do(req); err != nil {
|
||||
if c.retryCount > 0 {
|
||||
c.retryCount--
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
resp = r
|
||||
break
|
||||
}
|
||||
}
|
||||
r := &ClientResponse{
|
||||
cookies : make(map[string]string),
|
||||
@ -311,9 +288,18 @@ func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse,
|
||||
}
|
||||
}
|
||||
// 执行请求
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
resp := (*http.Response)(nil)
|
||||
for {
|
||||
if r, err := c.Do(req); err != nil {
|
||||
if c.retryCount > 0 {
|
||||
c.retryCount--
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
resp = r
|
||||
break
|
||||
}
|
||||
}
|
||||
r := &ClientResponse{
|
||||
cookies : make(map[string]string),
|
||||
@ -37,7 +37,7 @@ func (s *Server)parsePattern(pattern string) (domain, method, path string, err e
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
err = errors.New("invalid pattern")
|
||||
err = errors.New("invalid pattern: URI should not be empty")
|
||||
}
|
||||
// 去掉末尾的"/"符号,与路由匹配时处理一致
|
||||
if path != "/" {
|
||||
@ -72,11 +72,11 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
}
|
||||
domain, method, uri, err := s.parsePattern(pattern)
|
||||
if err != nil {
|
||||
glog.Error("invalid pattern:", pattern)
|
||||
glog.Error("invalid pattern:", pattern, err)
|
||||
return
|
||||
}
|
||||
if len(uri) == 0 || uri[0] != '/' {
|
||||
glog.Error("invalid pattern:", pattern)
|
||||
glog.Error("invalid pattern:", pattern, "URI should lead with '/'")
|
||||
return
|
||||
}
|
||||
// 注册地址记录及重复注册判断
|
||||
@ -84,7 +84,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
caller := s.getHandlerRegisterCallerLine(handler)
|
||||
if len(hook) == 0 {
|
||||
if item, ok := s.routesMap[regkey]; ok {
|
||||
glog.Errorfln(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
|
||||
glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the sepecified <writer>.
|
||||
// which redirects current logging content output to the specified <writer>.
|
||||
//
|
||||
// 链式操作,设置下一次写入日志内容的Writer
|
||||
func (l *Logger) To(writer io.Writer) *Logger {
|
||||
|
||||
@ -12,92 +12,21 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parses the string into variables.
|
||||
// f1=m&f2=n -> map[f1:m f2:n]
|
||||
// f[a]=m&f[b]=n -> map[f:map[a:m b:n]]
|
||||
// Parses the string into map[string]interface{}.
|
||||
//
|
||||
// f1=m&f2=n -> map[f1:m f2:n]
|
||||
// f[a]=m&f[b]=n -> map[f:map[a:m b:n]]
|
||||
// f[a][a]=m&f[a][b]=n -> map[f:map[a:map[a:m b:n]]]
|
||||
// f[]=m&f[]=n -> map[f:[m n]]
|
||||
// f[a][]=m&f[a][]=n -> map[f:map[a:[m n]]]
|
||||
// f[][]=m&f[][]=n -> map[f:[map[]]] // Currently does not support nested slice.
|
||||
// f=m&f[a]=n -> error // This is not the same as PHP.
|
||||
// a .[[b=c -> map[a___[b:c]
|
||||
// f[]=m&f[]=n -> map[f:[m n]]
|
||||
// f[a][]=m&f[a][]=n -> map[f:map[a:[m n]]]
|
||||
// f[][]=m&f[][]=n -> map[f:[map[]]] // Currently does not support nested slice.
|
||||
// f=m&f[a]=n -> error
|
||||
// a .[[b=c -> map[a___[b:c]
|
||||
//
|
||||
// 将字符串解析成Map。
|
||||
func Parse(encodedString string, result map[string]interface{}) error {
|
||||
// build nested map.
|
||||
var build func(map[string]interface{}, []string, interface{}) error
|
||||
build = func(result map[string]interface{}, keys []string, value interface{}) error {
|
||||
length := len(keys)
|
||||
// trim ',"
|
||||
key := strings.Trim(keys[0], "'\"")
|
||||
if length == 1 {
|
||||
result[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// The end is slice. like f[], f[a][]
|
||||
if keys[1] == "" && length == 2 {
|
||||
// todo nested slice
|
||||
if key == "" {
|
||||
return nil
|
||||
}
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = []interface{}{value}
|
||||
return nil
|
||||
}
|
||||
children, ok := val.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
result[key] = append(children, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// The end is slice + map. like f[][a]
|
||||
if keys[1] == "" && length > 2 && keys[2] != "" {
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = []interface{}{}
|
||||
val = result[key]
|
||||
}
|
||||
children, ok := val.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
if l := len(children); l > 0 {
|
||||
if child, ok := children[l-1].(map[string]interface{}); ok {
|
||||
if _, ok := child[keys[2]]; !ok {
|
||||
build(child, keys[2:], value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
child := map[string]interface{}{}
|
||||
build(child, keys[2:], value)
|
||||
result[key] = append(children, child)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// map. like f[a], f[a][b]
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = map[string]interface{}{}
|
||||
val = result[key]
|
||||
}
|
||||
children, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
if err := build(children, keys[1:], value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// split encodedString.
|
||||
parts := strings.Split(encodedString, "&")
|
||||
func Parse(s string) (result map[string]interface{}, err error) {
|
||||
result = make(map[string]interface{})
|
||||
parts := strings.Split(s, "&")
|
||||
for _, part := range parts {
|
||||
pos := strings.Index(part, "=")
|
||||
if pos <= 0 {
|
||||
@ -105,7 +34,7 @@ func Parse(encodedString string, result map[string]interface{}) error {
|
||||
}
|
||||
key, err := url.QueryUnescape(part[:pos])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
for key[0] == ' ' {
|
||||
key = key[1:]
|
||||
@ -115,9 +44,8 @@ func Parse(encodedString string, result map[string]interface{}) error {
|
||||
}
|
||||
value, err := url.QueryUnescape(part[pos+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// split into multiple keys
|
||||
var keys []string
|
||||
left := 0
|
||||
@ -127,11 +55,11 @@ func Parse(encodedString string, result map[string]interface{}) error {
|
||||
} else if k == ']' {
|
||||
if left > 0 {
|
||||
if len(keys) == 0 {
|
||||
keys = append(keys, key[:left])
|
||||
keys = append(keys, key[ : left])
|
||||
}
|
||||
keys = append(keys, key[left+1:i])
|
||||
keys = append(keys, key[left + 1 : i])
|
||||
left = 0
|
||||
if i+1 < len(key) && key[i+1] != '[' {
|
||||
if i+1 < len(key) && key[i + 1] != '[' {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -149,7 +77,7 @@ func Parse(encodedString string, result map[string]interface{}) error {
|
||||
first += string(chr)
|
||||
}
|
||||
if chr == '[' {
|
||||
first += keys[0][i+1:]
|
||||
first += keys[0][i + 1: ]
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -157,9 +85,78 @@ func Parse(encodedString string, result map[string]interface{}) error {
|
||||
|
||||
// build nested map
|
||||
if err := build(result, keys, value); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// build nested map.
|
||||
func build(result map[string]interface{}, keys []string, value interface{}) error {
|
||||
length := len(keys)
|
||||
// trim ',"
|
||||
key := strings.Trim(keys[0], "'\"")
|
||||
if length == 1 {
|
||||
result[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// The end is slice. like f[], f[a][]
|
||||
if keys[1] == "" && length == 2 {
|
||||
// todo nested slice
|
||||
if key == "" {
|
||||
return nil
|
||||
}
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = []interface{}{value}
|
||||
return nil
|
||||
}
|
||||
children, ok := val.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
result[key] = append(children, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// The end is slice + map. like f[][a]
|
||||
if keys[1] == "" && length > 2 && keys[2] != "" {
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = []interface{}{}
|
||||
val = result[key]
|
||||
}
|
||||
children, ok := val.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
if l := len(children); l > 0 {
|
||||
if child, ok := children[l-1].(map[string]interface{}); ok {
|
||||
if _, ok := child[keys[2]]; !ok {
|
||||
build(child, keys[2:], value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
child := map[string]interface{}{}
|
||||
build(child, keys[2:], value)
|
||||
result[key] = append(children, child)
|
||||
return nil
|
||||
}
|
||||
|
||||
// map. like f[a], f[a][b]
|
||||
val, ok := result[key]
|
||||
if !ok {
|
||||
result[key] = map[string]interface{}{}
|
||||
val = result[key]
|
||||
}
|
||||
children, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val)
|
||||
}
|
||||
if err := build(children, keys[1:], value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
package gstr_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -31,4 +32,22 @@ func Benchmark_BytesToString(b *testing.B) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Parse1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gstr.Parse("a=1&b=2")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Parse2(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gstr.Parse("m[]=1&m[]=2")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Parse3(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gstr.Parse("m[a1][b1][c1][d1]=1&m[a2][b2]=2&m[a3][b3][c3]=3")
|
||||
}
|
||||
}
|
||||
75
g/text/gstr/gstr_z_unit_parse_test.go
Normal file
75
g/text/gstr/gstr_z_unit_parse_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gstr_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Parse(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
// slice
|
||||
m, err := gstr.Parse("a[]=1&a[]=2")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m, g.Map{
|
||||
"a" : g.Slice{"1", "2"},
|
||||
})
|
||||
// map
|
||||
m, err = gstr.Parse("a=1&b=2&c=3")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m, g.Map{
|
||||
"a" : "1",
|
||||
"b" : "2",
|
||||
"c" : "3",
|
||||
})
|
||||
// map
|
||||
m, err = gstr.Parse("m[a]=1&m[b]=2&m[c]=3")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m, g.Map{
|
||||
"m" : g.Map{
|
||||
"a" : "1",
|
||||
"b" : "2",
|
||||
"c" : "3",
|
||||
},
|
||||
})
|
||||
// map - slice
|
||||
m, err = gstr.Parse("m[a][]=1&m[a][]=2")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m, g.Map{
|
||||
"m" : g.Map{
|
||||
"a" : g.Slice{"1", "2"},
|
||||
},
|
||||
})
|
||||
// map - complicated
|
||||
m, err = gstr.Parse("m[a1][b1][c1][d1]=1&m[a2][b2]=2&m[a3][b3][c3]=3")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(m, g.Map{
|
||||
"m" : g.Map{
|
||||
"a1" : g.Map{
|
||||
"b1" : g.Map{
|
||||
"c1" : g.Map{
|
||||
"d1" : "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"a2" : g.Map{
|
||||
"b2" : "2",
|
||||
},
|
||||
"a3" : g.Map{
|
||||
"b3" : g.Map{
|
||||
"c3" : "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//t := gconv.GTime("2010-10-10 00:00:01")
|
||||
fmt.Println(gconv.String(gtime.Millisecond()))
|
||||
s := "127.0.0.1:6379,1,nhytaf176tg?maxIdle=1&maxActive=0&idleTimeout=60&maxConnLifetime=60"
|
||||
array, err := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)\?(.+)`, s)
|
||||
g.Dump(err)
|
||||
g.Dump(array)
|
||||
}
|
||||
Reference in New Issue
Block a user