mirror of
https://gitee.com/johng/gf
synced 2026-06-10 19:31:44 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22c7c7403b | |||
| 83db8e4b15 | |||
| 25f2e121e7 | |||
| 1325a145d8 | |||
| 9bc49c0b29 | |||
| 8e84e5b0f3 | |||
| a42e6b0c45 | |||
| 51bb7a9854 | |||
| 62580b5719 | |||
| 15cfd5ce5c | |||
| 11c89c4090 | |||
| 4a12cb9f27 | |||
| 32f575eddd | |||
| fbb4cb3b1c | |||
| c9d2d5e8ab |
@ -109,7 +109,9 @@ func (gm *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interf
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
gm.m[key] = value
|
||||
if value != nil {
|
||||
gm.m[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +109,9 @@ func (gm *StringInterfaceMap) doSetWithLockCheck(key string, value interface{})
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
gm.m[key] = value
|
||||
if value != nil {
|
||||
gm.m[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
pool *redis.Pool
|
||||
config Config
|
||||
}
|
||||
|
||||
// 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,88 +51,123 @@ 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{
|
||||
config : config,
|
||||
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
|
||||
},
|
||||
// 在被应用从连接池中获取出来之后,用以测试连接是否可用,
|
||||
// 如果返回error那么关闭该连接对象重新创建新的连接。
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
}).(*redis.Pool),
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// 关闭redis管理对象,将会关闭底层的
|
||||
// Close closes the redis connection pool,
|
||||
// it will release all connections reserved by this pool.
|
||||
//
|
||||
// 关闭redis管理对象,将会关闭底层的连接池。
|
||||
func (r *Redis) Close() error {
|
||||
pools.Remove(fmt.Sprintf("%v", r.config))
|
||||
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 further.**
|
||||
//
|
||||
// 获得一个原生的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
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// Do automatically get a connection from pool, and close it when reply received.
|
||||
//
|
||||
// 执行同步命令,自动从连接池中获取连接,使用完毕后关闭连接(丢回连接池),开发者不用自行Close.
|
||||
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return conn.Do(command, args...)
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
// Send writes the command to the client's output buffer.
|
||||
//
|
||||
// 执行异步命令 - Send
|
||||
func (r *Redis) Send(command string, args ...interface{}) error {
|
||||
conn := r.pool.Get()
|
||||
|
||||
112
g/database/gredis/gredis_unit_test.go
Normal file
112
g/database/gredis/gredis_unit_test.go
Normal file
@ -0,0 +1,112 @@
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Conn(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
defer redis.Close()
|
||||
conn := redis.Conn()
|
||||
defer conn.Close()
|
||||
|
||||
r, err := conn.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, []byte("v"))
|
||||
|
||||
_, err = conn.Do("DEL", "k")
|
||||
gtest.Assert(err, nil)
|
||||
r, err = conn.Do("GET", "k")
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r, nil)
|
||||
})
|
||||
}
|
||||
@ -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"
|
||||
@ -46,8 +45,9 @@ test = "v=3"
|
||||
priority = "1"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
default = "127.0.0.1:6379,7"
|
||||
cache = "127.0.0.1:6379,8"
|
||||
disk = "127.0.0.1:6379,9,?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"))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
34
g/g_func.go
34
g/g_func.go
@ -7,20 +7,10 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g/util/gutil"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
)
|
||||
|
||||
const (
|
||||
LOG_LEVEL_ALL = glog.LEVEL_ALL
|
||||
LOG_LEVEL_DEBU = glog.LEVEL_DEBU
|
||||
LOG_LEVEL_INFO = glog.LEVEL_INFO
|
||||
LOG_LEVEL_NOTI = glog.LEVEL_NOTI
|
||||
LOG_LEVEL_WARN = glog.LEVEL_WARN
|
||||
LOG_LEVEL_ERRO = glog.LEVEL_ERRO
|
||||
LOG_LEVEL_CRIT = glog.LEVEL_CRIT
|
||||
)
|
||||
|
||||
// NewVar creates a *Var.
|
||||
@ -39,11 +29,18 @@ func Wait() {
|
||||
|
||||
// Dump dumps a variable to stdout with more manually readable.
|
||||
//
|
||||
// 打印变量
|
||||
// 格式化打印变量.
|
||||
func Dump(i...interface{}) {
|
||||
gutil.Dump(i...)
|
||||
}
|
||||
|
||||
// Export exports a variable to string with more manually readable.
|
||||
//
|
||||
// 格式化导出变量.
|
||||
func Export(i...interface{}) string {
|
||||
return gutil.Export(i...)
|
||||
}
|
||||
|
||||
// Throw throws a exception, which can be caught by Catch function.
|
||||
// It always be used in TryCatch function.
|
||||
//
|
||||
@ -55,4 +52,15 @@ func Throw(exception interface{}) {
|
||||
// TryCatch does the try...catch... logic.
|
||||
func TryCatch(try func(), catch ... func(exception interface{})) {
|
||||
gutil.TryCatch(try, catch...)
|
||||
}
|
||||
|
||||
// IsEmpty checks given value empty or not.
|
||||
// false: integer(0), bool(false), slice/map(len=0), nil;
|
||||
// true : other.
|
||||
//
|
||||
// 判断给定的变量是否为空。
|
||||
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况,都为空。
|
||||
// 为空时返回true,否则返回false。
|
||||
func IsEmpty(value interface{}) bool {
|
||||
return empty.IsEmpty(value)
|
||||
}
|
||||
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),
|
||||
@ -57,7 +57,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
|
||||
return request
|
||||
}
|
||||
|
||||
// 获取Web Socket连接对象(如果是非WS请求会失败,注意检查然会的error结果)
|
||||
// 获取Web Socket连接对象(如果是非WS请求会失败,注意检查返回的error结果)
|
||||
func (r *Request) WebSocket() (*WebSocket, error) {
|
||||
if conn, err := wsUpgrader.Upgrade(r.Response.ResponseWriter.ResponseWriter, r.Request, nil); err == nil {
|
||||
return &WebSocket {
|
||||
@ -81,26 +81,29 @@ func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
|
||||
|
||||
// 获取原始请求输入二进制。
|
||||
func (r *Request) GetRaw() []byte {
|
||||
err := error(nil)
|
||||
if r.rawContent == nil {
|
||||
r.rawContent, _ = ioutil.ReadAll(r.Body)
|
||||
r.rawContent, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
r.Error("error reading request body: ", err)
|
||||
}
|
||||
}
|
||||
return r.rawContent
|
||||
}
|
||||
|
||||
// 获取原始请求输入字符串。
|
||||
func (r *Request) GetRawString() string {
|
||||
if r.rawContent == nil {
|
||||
r.rawContent, _ = ioutil.ReadAll(r.Body)
|
||||
}
|
||||
return string(r.rawContent)
|
||||
return string(r.GetRaw())
|
||||
}
|
||||
|
||||
// 获取原始json请求输入字符串,并解析为json对象
|
||||
func (r *Request) GetJson() *gjson.Json {
|
||||
data := r.GetRaw()
|
||||
if data != nil {
|
||||
if len(data) > 0 {
|
||||
if j, err := gjson.DecodeToJson(data); err == nil {
|
||||
return j
|
||||
} else {
|
||||
r.Error(err, ": ", string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -221,14 +224,14 @@ func (r *Request) GetReferer() string {
|
||||
|
||||
// 获得结构体对象的参数名称标签,构成map返回
|
||||
func (r *Request) getStructParamsTagMap(object interface{}) map[string]string {
|
||||
tagmap := make(map[string]string)
|
||||
tagMap := make(map[string]string)
|
||||
fields := structs.Fields(object)
|
||||
for _, field := range fields {
|
||||
if tag := field.Tag("params"); tag != "" {
|
||||
for _, v := range strings.Split(tag, ",") {
|
||||
tagmap[strings.TrimSpace(v)] = field.Name()
|
||||
tagMap[strings.TrimSpace(v)] = field.Name()
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagmap
|
||||
return tagMap
|
||||
}
|
||||
@ -26,21 +26,21 @@ func (r *Request) setBasicAuth(tips...string) {
|
||||
}
|
||||
|
||||
// 设置HTTP基础账号密码认证,如果用户没有提交账号密码,那么提示用户输出信息。
|
||||
// 验证成功之后返回true,否则返回false
|
||||
// 验证成功之后返回true,否则返回false。
|
||||
func (r *Request) BasicAuth(user, pass string, tips...string) bool {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
r.setBasicAuth(tips...)
|
||||
return false
|
||||
}
|
||||
auths := strings.SplitN(auth, " ", 2)
|
||||
if len(auths) != 2 {
|
||||
authArray := strings.SplitN(auth, " ", 2)
|
||||
if len(authArray) != 2 {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
switch auths[0] {
|
||||
switch authArray[0] {
|
||||
case "Basic":
|
||||
authStr, err := gbase64.Decode(auths[1])
|
||||
authStr, err := gbase64.Decode(authArray[1])
|
||||
if err != nil {
|
||||
r.Response.WriteStatus(http.StatusForbidden, err.Error())
|
||||
return false
|
||||
@ -54,11 +54,12 @@ func (r *Request) BasicAuth(user, pass string, tips...string) bool {
|
||||
r.setBasicAuth(tips...)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
default:
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
14
g/net/ghttp/ghttp_request_log.go
Normal file
14
g/net/ghttp/ghttp_request_log.go
Normal file
@ -0,0 +1,14 @@
|
||||
// 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 ghttp
|
||||
|
||||
import "fmt"
|
||||
|
||||
// 打印error日志
|
||||
func (r *Request) Error(value... interface{}) {
|
||||
r.Server.handleErrorLog(fmt.Sprint(value...), r)
|
||||
}
|
||||
@ -169,7 +169,7 @@ func (r *Request) GetPostMap(def...map[string]string) map[string]string {
|
||||
}
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) {
|
||||
func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(object)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
@ -180,5 +180,5 @@ func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]strin
|
||||
for k, v := range r.GetPostMap() {
|
||||
params[k] = v
|
||||
}
|
||||
gconv.Struct(params, object, tagmap)
|
||||
return gconv.Struct(params, object, tagmap)
|
||||
}
|
||||
@ -177,7 +177,7 @@ func (r *Request) GetQueryMap(def ... map[string]string) map[string]string {
|
||||
}
|
||||
|
||||
// 将所有的get参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) {
|
||||
func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(object)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
@ -188,5 +188,5 @@ func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]stri
|
||||
for k, v := range r.GetQueryMap() {
|
||||
params[k] = v
|
||||
}
|
||||
gconv.Struct(params, object, tagmap)
|
||||
return gconv.Struct(params, object, tagmap)
|
||||
}
|
||||
@ -162,7 +162,7 @@ func (r *Request) GetRequestMap(def...map[string]string) map[string]string {
|
||||
}
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]string) {
|
||||
func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(object)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
@ -178,6 +178,6 @@ func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]st
|
||||
params = j.ToMap()
|
||||
}
|
||||
}
|
||||
gconv.Struct(params, object, tagmap)
|
||||
return gconv.Struct(params, object, tagmap)
|
||||
}
|
||||
|
||||
|
||||
@ -350,7 +350,10 @@ func (s *Server) GetRouteMap() string {
|
||||
}
|
||||
addr := s.config.Addr
|
||||
if s.config.HTTPSAddr != "" {
|
||||
addr += ",tls" + s.config.HTTPSAddr
|
||||
if len(addr) > 0 {
|
||||
addr += ","
|
||||
}
|
||||
addr += "tls" + s.config.HTTPSAddr
|
||||
}
|
||||
for _, a := range m {
|
||||
data := make([]string, 8)
|
||||
|
||||
@ -10,6 +10,7 @@ package ghttp
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
)
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
@ -46,7 +47,11 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) {
|
||||
|
||||
// 错误日志信息
|
||||
content := fmt.Sprintf(`%v, "%s %s %s %s"`, error, r.Method, r.Host, r.URL.String(), r.Proto)
|
||||
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
|
||||
if r.LeaveTime > r.EnterTime {
|
||||
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
|
||||
} else {
|
||||
content += fmt.Sprintf(` %.3f`, float64(gtime.Microsecond() - r.EnterTime)/1000)
|
||||
}
|
||||
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
|
||||
|
||||
if s.logger.GetPath() == "" {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,6 +119,9 @@ func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, expire
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
c.data[key] = memCacheItem{v : value, e : expireTimestamp}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{k : key, e : expireTimestamp})
|
||||
|
||||
@ -155,23 +155,38 @@ func (c *Config) SetFileName(name string) {
|
||||
c.name.Set(name)
|
||||
}
|
||||
|
||||
// 获取配置管理对象的默认文件名称
|
||||
func (c *Config) GetFileName() string {
|
||||
return c.name.Val()
|
||||
}
|
||||
|
||||
// 添加配置文件到配置管理器中,第二个参数为非必须,如果不输入表示添加进入默认的配置名称中
|
||||
// 内部带缓存控制功能。
|
||||
func (c *Config) getJson(file...string) *gjson.Json {
|
||||
filePath := c.filePath(file...)
|
||||
if filePath == "" {
|
||||
name := c.name.Val()
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
r := c.jsons.GetOrSetFuncLock(name, func() interface{} {
|
||||
filePath := c.filePath(file...)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
}
|
||||
if j, err := gjson.Load(filePath); err == nil {
|
||||
j.SetViolenceCheck(c.vc.Val())
|
||||
// 添加配置文件监听,如果有任何变化,删除文件内容缓存,下一次查询会自动更新
|
||||
gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
|
||||
c.jsons.Remove(name)
|
||||
})
|
||||
return j
|
||||
} else {
|
||||
glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if r := c.jsons.Get(filePath); r != nil {
|
||||
})
|
||||
if r != nil {
|
||||
return r.(*gjson.Json)
|
||||
}
|
||||
if j, err := gjson.Load(filePath); err == nil {
|
||||
j.SetViolenceCheck(c.vc.Val())
|
||||
c.addMonitor(filePath)
|
||||
c.jsons.Set(filePath, j)
|
||||
return j
|
||||
} else {
|
||||
glog.Errorfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -362,14 +377,3 @@ func (c *Config) Reload() {
|
||||
c.jsons.Clear()
|
||||
}
|
||||
|
||||
// 添加文件监控
|
||||
func (c *Config) addMonitor(path string) {
|
||||
// 防止多goroutine同时调用
|
||||
if c.jsons.Get(path) != nil {
|
||||
return
|
||||
}
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
// 删除文件内容缓存,下一次查询会自动更新
|
||||
c.jsons.Remove(event.Path)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -8,6 +8,7 @@ package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
@ -102,6 +103,11 @@ func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
|
||||
rt := rv.Type()
|
||||
name := ""
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// 只转换公开属性
|
||||
fieldName := rt.Field(i).Name
|
||||
if !gstr.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
name = ""
|
||||
// 检查tag, 支持gconv, json标签, 优先使用gconv
|
||||
if len(noTagCheck) == 0 || !noTagCheck[0] {
|
||||
@ -111,7 +117,7 @@ func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(rt.Field(i).Name)
|
||||
name = strings.TrimSpace(fieldName)
|
||||
} else {
|
||||
// 支持标准库json特性: -, omitempty
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -311,7 +312,12 @@ func Interfaces(i interface{}) []interface{} {
|
||||
array = append(array, rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Struct:
|
||||
rt := rv.Type()
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// 只获取公开属性
|
||||
if !gstr.IsLetterUpper(rt.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
array = append(array, rv.Field(i).Interface())
|
||||
}
|
||||
default:
|
||||
|
||||
@ -78,6 +78,10 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
|
||||
attrMap := make(map[string]struct{})
|
||||
elemType := elem.Type()
|
||||
for i := 0; i < elem.NumField(); i++ {
|
||||
// 只转换公开属性
|
||||
if !gstr.IsLetterUpper(elemType.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
attrMap[elemType.Field(i).Name] = struct{}{}
|
||||
}
|
||||
for mapK, mapV := range paramsMap {
|
||||
|
||||
@ -112,3 +112,15 @@ func Test_Map_StructWithJsonTag(t *testing.T) {
|
||||
gtest.Assert(map2["password2"], "456")
|
||||
})
|
||||
}
|
||||
|
||||
// 私有属性不会进行转换
|
||||
func Test_Map_PrivateAttribute(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
name string
|
||||
}
|
||||
gtest.Case(t, func() {
|
||||
user := &User{1, "john"}
|
||||
gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
|
||||
})
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
@ -23,3 +24,15 @@ func Test_Slice(t *testing.T) {
|
||||
gtest.AssertEQ(gconv.Interfaces(value), []interface{}{123.456})
|
||||
})
|
||||
}
|
||||
|
||||
// 私有属性不会进行转换
|
||||
func Test_Slice_PrivateAttribute(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
name string
|
||||
}
|
||||
gtest.Case(t, func() {
|
||||
user := &User{1, "john"}
|
||||
gtest.Assert(gconv.Interfaces(user), g.Slice{1})
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,12 +8,11 @@ package gconv_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func Test_Struct_Basic1(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
@ -302,3 +301,18 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 私有属性不会进行转换
|
||||
func Test_Struct_PrivateAttribute(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
name string
|
||||
}
|
||||
gtest.Case(t, func() {
|
||||
user := new(User)
|
||||
err := gconv.Struct(g.Map{"id" : 1, "name" : "john"}, user)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(user.Id, 1)
|
||||
gtest.Assert(user.name, "")
|
||||
})
|
||||
}
|
||||
@ -13,17 +13,27 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// 格式化打印变量(类似于PHP-vardump)
|
||||
// 格式化打印变量
|
||||
func Dump(i...interface{}) {
|
||||
s := Export(i...)
|
||||
if s != "" {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化导出变量
|
||||
func Export(i...interface{}) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for _, v := range i {
|
||||
if b, ok := v.([]byte); ok {
|
||||
fmt.Print(string(b))
|
||||
buffer.Write(b)
|
||||
} else {
|
||||
// 主要针对 map[interface{}]* 进行处理,json无法进行encode,
|
||||
// 这里强制对所有map进行反射处理转换
|
||||
@ -32,24 +42,20 @@ func Dump(i...interface{}) {
|
||||
m := make(map[string]interface{})
|
||||
keys := refValue.MapKeys()
|
||||
for _, k := range keys {
|
||||
key := gconv.String(k.Interface())
|
||||
m[key] = refValue.MapIndex(k).Interface()
|
||||
m[gconv.String(k.Interface())] = refValue.MapIndex(k).Interface()
|
||||
}
|
||||
v = m
|
||||
}
|
||||
// json encode并打印到终端
|
||||
buffer := &bytes.Buffer{}
|
||||
// JSON格式化
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", "\t")
|
||||
if err := encoder.Encode(v); err == nil {
|
||||
fmt.Print(buffer.String())
|
||||
} else {
|
||||
if err := encoder.Encode(v); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
//fmt.Println()
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// 打印完整的调用回溯信息
|
||||
@ -84,4 +90,15 @@ func TryCatch(try func(), catch ... func(exception interface{})) {
|
||||
try()
|
||||
}
|
||||
|
||||
// IsEmpty checks given value empty or not.
|
||||
// false: integer(0), bool(false), slice/map(len=0), nil;
|
||||
// true : other.
|
||||
//
|
||||
// 判断给定的变量是否为空。
|
||||
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况,都为空。
|
||||
// 为空时返回true,否则返回false。
|
||||
func IsEmpty(value interface{}) bool {
|
||||
return empty.IsEmpty(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/third/github.com/fatih/structs"
|
||||
"strings"
|
||||
@ -59,14 +60,19 @@ func CheckStruct(object interface{}, rules interface{}, msgs...CustomMsg) *Error
|
||||
case map[string]string:
|
||||
checkRules = v
|
||||
}
|
||||
// 首先, 按照属性循环一遍将strcut的属性、数值、tag解析
|
||||
// 首先, 按照属性循环一遍将struct的属性、数值、tag解析
|
||||
for _, field := range fields {
|
||||
params[field.Name()] = field.Value()
|
||||
fieldName := field.Name()
|
||||
// 只检测公开属性
|
||||
if !gstr.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
params[fieldName] = field.Value()
|
||||
if tag := field.Tag("gvalid"); tag != "" {
|
||||
// sequence tag == struct tag, 这里的name为别名
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
if len(name) == 0 {
|
||||
name = field.Name()
|
||||
name = fieldName
|
||||
}
|
||||
// params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用
|
||||
if _, ok := params[name]; !ok {
|
||||
@ -151,3 +157,4 @@ func CheckStruct(object interface{}, rules interface{}, msgs...CustomMsg) *Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
16
geg/database/redis/gredis_conn1.go
Normal file
16
geg/database/redis/gredis_conn1.go
Normal file
@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn := g.Redis().Conn()
|
||||
defer conn.Close()
|
||||
conn.Do("SET", "k", "v")
|
||||
v, _ := conn.Do("GET", "k")
|
||||
fmt.Println(gconv.String(v))
|
||||
}
|
||||
|
||||
21
geg/database/redis/gredis_conn2.go
Normal file
21
geg/database/redis/gredis_conn2.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn := g.Redis().Conn()
|
||||
defer conn.Close()
|
||||
conn.Send("SET", "foo", "bar")
|
||||
conn.Send("GET", "foo")
|
||||
conn.Flush()
|
||||
// reply from SET
|
||||
conn.Receive()
|
||||
// reply from GET
|
||||
v, _ := conn.Receive()
|
||||
fmt.Println(gconv.String(v))
|
||||
}
|
||||
|
||||
24
geg/database/redis/gredis_conn3.go
Normal file
24
geg/database/redis/gredis_conn3.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn := g.Redis().Conn()
|
||||
defer conn.Close()
|
||||
_, err := conn.Do("SUBSCRIBE", "channel")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for {
|
||||
reply, err := conn.Receive()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(gconv.Strings(reply))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/log/handler", func(r *ghttp.Request){
|
||||
r.Response.WriteStatus(http.StatusNotFound, "文件找不到了")
|
||||
})
|
||||
s.SetAccessLogEnabled(true)
|
||||
s.SetErrorLogEnabled(true)
|
||||
//s.SetLogHandler(func(r *ghttp.Request, error ...interface{}) {
|
||||
// if len(error) > 0 {
|
||||
// // 如果是错误日志
|
||||
// fmt.Println("错误产生了:", error[0])
|
||||
// }
|
||||
// // 这里是请求日志
|
||||
// fmt.Println("请求处理完成,请求地址:", r.URL.String(), "请求结果:", r.Response.Status)
|
||||
//})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
93
geg/net/ghttp/server/websocket/echo-wss/index.html
Normal file
93
geg/net/ghttp/server/websocket/echo-wss/index.html
Normal file
@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>gf websocket echo server</title>
|
||||
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="list-group" id="divShow"></div>
|
||||
<div>
|
||||
<div><input class="form-control" id="txtContent" autofocus rows="6" placeholder="请输入发送内容"></div>
|
||||
<div><button class="btn btn-default" id="btnSend" style="margin-top:15px">发 送</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script type="application/javascript">
|
||||
// 显示提示信息
|
||||
function showInfo(content) {
|
||||
$("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow")
|
||||
}
|
||||
// 显示警告信息
|
||||
function showWaring(content) {
|
||||
$("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow")
|
||||
}
|
||||
// 显示成功信息
|
||||
function showSuccess(content) {
|
||||
$("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow")
|
||||
}
|
||||
// 显示错误信息
|
||||
function showError(content) {
|
||||
$("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow")
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var url = "wss://127.0.0.1:8199/wss";
|
||||
var ws = new WebSocket(url);
|
||||
try {
|
||||
// ws连接成功
|
||||
ws.onopen = function () {
|
||||
showInfo("WebSocket Server [" + url +"] 连接成功!");
|
||||
};
|
||||
// ws连接关闭
|
||||
ws.onclose = function () {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
showError("WebSocket Server [" + url +"] 连接关闭!");
|
||||
};
|
||||
// ws连接错误
|
||||
ws.onerror = function () {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
showError("WebSocket Server [" + url +"] 连接关闭!");
|
||||
};
|
||||
// ws数据返回处理
|
||||
ws.onmessage = function (result) {
|
||||
showWaring(" > " + result.data);
|
||||
};
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
|
||||
// 按钮点击发送数据
|
||||
$("#btnSend").on("click", function () {
|
||||
if (ws == null) {
|
||||
showError("WebSocket Server [" + url +"] 连接失败,请F5刷新页面!");
|
||||
return;
|
||||
}
|
||||
var content = $.trim($("#txtContent").val()).replace("/[\n]/g", "");
|
||||
if (content.length <= 0) {
|
||||
alert("请输入发送内容!");
|
||||
return;
|
||||
}
|
||||
$("#txtContent").val("")
|
||||
showSuccess(content);
|
||||
ws.send(content);
|
||||
});
|
||||
|
||||
// 回车按钮触发发送点击事件
|
||||
$("#txtContent").on("keydown", function (event) {
|
||||
if (event.keyCode == 13) {
|
||||
$("#btnSend").trigger("click");
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
33
geg/net/ghttp/server/websocket/echo-wss/main.go
Normal file
33
geg/net/ghttp/server/websocket/echo-wss/main.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/wss", func(r *ghttp.Request) {
|
||||
ws, err := r.WebSocket()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
r.Exit()
|
||||
}
|
||||
for {
|
||||
msgType, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = ws.WriteMessage(msgType, msg); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
s.SetServerRoot(gfile.MainPkgPath())
|
||||
s.EnableHTTPS("../../https/server.crt", "../../https/server.key")
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
)
|
||||
|
||||
@ -24,6 +25,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
})
|
||||
s.SetServerRoot(gfile.MainPkgPath())
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//t := gconv.GTime("2010-10-10 00:00:01")
|
||||
fmt.Println(gconv.String(gtime.Millisecond()))
|
||||
|
||||
fmt.Println(json.Valid([]byte("111")))
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.5.17"
|
||||
const VERSION = "v1.5.20"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user