Compare commits

..

15 Commits

51 changed files with 1427 additions and 406 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -18,7 +18,7 @@ import (
type Var struct {
value interface{} // 变量值
safe bool // 当为true时,value为 *gtype.Interface 类型
safe bool // 当为true时, value为 *gtype.Interface 类型
}
// 创建一个动态变量value参数可以为nil

View File

@ -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 (

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View 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)
})
}

View File

@ -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
}

View 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})
}
}

View 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)
})
}

View File

@ -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))
}

View File

@ -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]),

View File

@ -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"))
})
}

View File

@ -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)
}

View 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
}

View File

@ -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),

View File

@ -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
}

View File

@ -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
}

View 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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -10,6 +10,7 @@ package ghttp
import (
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
)
// 处理服务错误信息主要是panichttp请求的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() == "" {

View File

@ -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
}
}

View File

@ -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})

View File

@ -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)
})
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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")
}
}

View 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",
},
},
},
})
})
}

View File

@ -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)

View File

@ -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:

View File

@ -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 {

View File

@ -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})
})
}

View File

@ -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})
})
}

View File

@ -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, "")
})
}

View File

@ -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)
}

View File

@ -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
}

View 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))
}

View 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))
}

View 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))
}
}

View File

@ -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()
}

View 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>

View 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()
}

View File

@ -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()
}

View File

@ -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")))
}

View File

@ -1,5 +1,5 @@
package gf
const VERSION = "v1.5.17"
const VERSION = "v1.5.20"
const AUTHORS = "john<john@goframe.org>"