mirror of
https://gitee.com/johng/gf
synced 2026-06-10 19:31:44 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62580b5719 | |||
| 15cfd5ce5c | |||
| 11c89c4090 | |||
| 4a12cb9f27 | |||
| 32f575eddd | |||
| fbb4cb3b1c | |||
| c9d2d5e8ab | |||
| 93763192f2 | |||
| 80e0eae6b0 | |||
| 4e3d735b90 | |||
| 60e5a7da28 | |||
| 997b5ba889 | |||
| b3e7ca1963 | |||
| 5a82d695c1 | |||
| 64a0427150 |
@ -1,6 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
|
||||
52
README.MD
52
README.MD
@ -65,42 +65,30 @@ func main() {
|
||||
|
||||
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
|
||||
|
||||
# Contributors(TOP 10)
|
||||
# Contributors
|
||||
|
||||
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png?1530630243" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
|
||||
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
|
||||
|
||||
<br /><br /><br />
|
||||
- [johng](https://gitee.com/johng)
|
||||
- [zhaopengme](https://github.com/zhaopengme)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [zseeker](https://gitee.com/zseeker)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
- [chenyang351](https://github.com/chenyang351)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [zhangjinfu](https://gitee.com/zhangjinfu)
|
||||
- [garfieldkwong](https://gitee.com/garfieldkwong)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
|
||||
# Donators
|
||||
|
||||
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
|
||||
- [tiangenglan](https://gitee.com/tiangenglan)
|
||||
- [zhuhuan12](https://gitee.com/zhuhuan12)
|
||||
- [zfan_codes](https://gitee.com/zfan_codes)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [mg91](https://gitee.com/mg91)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [flyke-xu](https://gitee.com/flyke-xu)
|
||||
|
||||
|
||||
|
||||
|
||||
43
README_ZH.MD
43
README_ZH.MD
@ -85,30 +85,27 @@ func main() {
|
||||
<img src="https://goframe.org/images/donate.png" width="300"/>
|
||||
</a>
|
||||
|
||||
# 贡献者(TOP 10)
|
||||
# 贡献者
|
||||
|
||||
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
|
||||
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
|
||||
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
|
||||
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
|
||||
|
||||
<br /><br /><br />
|
||||
- [johng](https://gitee.com/johng)
|
||||
- [zhaopengme](https://github.com/zhaopengme)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [zseeker](https://gitee.com/zseeker)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
- [chenyang351](https://github.com/chenyang351)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [zhangjinfu](https://gitee.com/zhangjinfu)
|
||||
- [garfieldkwong](https://gitee.com/garfieldkwong)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
|
||||
# 捐赠者
|
||||
|
||||
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
|
||||
- [tiangenglan](https://gitee.com/tiangenglan)
|
||||
- [zhuhuan12](https://gitee.com/zhuhuan12)
|
||||
- [zfan_codes](https://gitee.com/zfan_codes)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [mg91](https://gitee.com/mg91)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [flyke-xu](https://gitee.com/flyke-xu)
|
||||
|
||||
@ -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,122 @@ 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
|
||||
},
|
||||
// 用来测试连接是否可用
|
||||
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"
|
||||
@ -79,6 +79,11 @@ func NewUnsafe(value...interface{}) *Json {
|
||||
return New(nil, true)
|
||||
}
|
||||
|
||||
// 识别当前给定内容是否为JSON格式
|
||||
func Valid (v interface{}) bool {
|
||||
return json.Valid(gconv.Bytes(v))
|
||||
}
|
||||
|
||||
// 编码go变量为json字符串,并返回json字符串指针
|
||||
func Encode (v interface{}) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
@ -481,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,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
|
||||
}
|
||||
@ -534,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
|
||||
@ -547,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
|
||||
@ -565,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的键名;
|
||||
@ -584,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
|
||||
@ -620,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
|
||||
@ -640,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,89 +9,58 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"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请求
|
||||
@ -117,6 +86,7 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
}
|
||||
req := (*http.Request)(nil)
|
||||
if strings.Contains(param, "@file:") {
|
||||
// 文件上传
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buffer)
|
||||
for _, item := range strings.Split(param, "&") {
|
||||
@ -150,11 +120,17 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
}
|
||||
} else {
|
||||
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(param))); err != nil {
|
||||
// 识别提交数据格式
|
||||
paramBytes := []byte(param)
|
||||
if r, err := http.NewRequest("POST", url, bytes.NewReader(paramBytes)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
req = r
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if json.Valid(paramBytes) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
}
|
||||
}
|
||||
// 自定义header
|
||||
@ -181,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),
|
||||
@ -303,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 {
|
||||
@ -101,6 +101,8 @@ func (r *Request) GetJson() *gjson.Json {
|
||||
if data != nil {
|
||||
if j, err := gjson.DecodeToJson(data); err == nil {
|
||||
return j
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -221,14 +223,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
|
||||
}
|
||||
@ -242,7 +242,7 @@ func (s *Server) Start() error {
|
||||
s.config.Handler = http.HandlerFunc(s.defaultHttpHandle)
|
||||
}
|
||||
// 不允许访问的路由注册(使用HOOK实现)
|
||||
// @TODO 去掉HOOK的实现方式
|
||||
// TODO 去掉HOOK的实现方式
|
||||
if s.config.DenyRoutes != nil {
|
||||
for _, v := range s.config.DenyRoutes {
|
||||
s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) {
|
||||
@ -398,7 +398,8 @@ func Wait() {
|
||||
// 开启底层Web Server执行
|
||||
func (s *Server) startServer(fdMap listenerFdMap) {
|
||||
var httpsEnabled bool
|
||||
if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 {
|
||||
// 判断是否启用HTTPS
|
||||
if len(s.config.TLSConfig.Certificates) > 0 || (len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0) {
|
||||
// ================
|
||||
// HTTPS
|
||||
// ================
|
||||
@ -479,7 +480,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
|
||||
s.serverCount.Add(1)
|
||||
err := (error)(nil)
|
||||
if server.isHttps {
|
||||
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath)
|
||||
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath, &s.config.TLSConfig)
|
||||
} else {
|
||||
err = server.ListenAndServe()
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
@ -44,6 +45,7 @@ type ServerConfig struct {
|
||||
WriteTimeout time.Duration // 写入超时
|
||||
IdleTimeout time.Duration // 等待超时
|
||||
MaxHeaderBytes int // 最大的header长度
|
||||
TLSConfig tls.Config
|
||||
|
||||
// 静态文件配置
|
||||
IndexFiles []string // 默认访问的文件列表
|
||||
@ -191,28 +193,46 @@ func (s *Server)SetHTTPSPort(port...int) {
|
||||
}
|
||||
}
|
||||
|
||||
// 开启HTTPS支持,但是必须提供Cert和Key文件
|
||||
func (s *Server)EnableHTTPS(certFile, keyFile string) {
|
||||
// 开启HTTPS支持,但是必须提供Cert和Key文件,tlsConfig为可选项
|
||||
func (s *Server)EnableHTTPS(certFile, keyFile string, tlsConfig...tls.Config) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
certFileRealPath := gfile.RealPath(certFile)
|
||||
if certFileRealPath == "" {
|
||||
certFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + certFileRealPath)
|
||||
certFileRealPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + certFile)
|
||||
if certFileRealPath == "" {
|
||||
certFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + certFile)
|
||||
}
|
||||
}
|
||||
if certFileRealPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile))
|
||||
}
|
||||
keyFileRealPath := gfile.RealPath(keyFile)
|
||||
if keyFileRealPath == "" {
|
||||
keyFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + keyFileRealPath)
|
||||
keyFileRealPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + keyFile)
|
||||
if keyFileRealPath == "" {
|
||||
keyFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + keyFile)
|
||||
}
|
||||
}
|
||||
if keyFileRealPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: keyFile "%s" does not exist`, keyFile))
|
||||
}
|
||||
s.config.HTTPSCertPath = certFileRealPath
|
||||
s.config.HTTPSKeyPath = keyFileRealPath
|
||||
if len(tlsConfig) > 0 {
|
||||
s.config.TLSConfig = tlsConfig[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 设置TLS配置对象
|
||||
func (s *Server)SetTLSConfig(tlsConfig tls.Config) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.TLSConfig = tlsConfig
|
||||
}
|
||||
|
||||
// 设置http server参数 - ReadTimeout
|
||||
|
||||
@ -84,18 +84,22 @@ func (s *gracefulServer) setFd(fd int) {
|
||||
}
|
||||
|
||||
// 执行HTTPS监听
|
||||
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
|
||||
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig...*tls.Config) error {
|
||||
addr := s.httpServer.Addr
|
||||
config := &tls.Config{}
|
||||
if s.httpServer.TLSConfig != nil {
|
||||
config := (*tls.Config)(nil)
|
||||
if len(tlsConfig) > 0 {
|
||||
config = tlsConfig[0]
|
||||
} else if s.httpServer.TLSConfig != nil {
|
||||
*config = *s.httpServer.TLSConfig
|
||||
}
|
||||
if config.NextProtos == nil {
|
||||
config.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
err := error(nil)
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if len(config.Certificates) == 0 {
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error()))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
// Package greuseport provides Listen and Dial functions that set socket
|
||||
// options in order to be able to reuse ports. You should only use this
|
||||
// package if you know what SO_REUSEADDR and SO_REUSEPORT are.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// // listen on the same port.
|
||||
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||
//
|
||||
// // dial from the same port.
|
||||
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235")
|
||||
// c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235")
|
||||
//
|
||||
// Note: cant dial self because tcp/ip stacks use 4-tuples to identify connections,
|
||||
// and doing so would clash.
|
||||
package greuseport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
Enabled = false
|
||||
listenConfig = net.ListenConfig {
|
||||
Control: Control,
|
||||
}
|
||||
)
|
||||
|
||||
// Listen listens at the given network and address. see net.Listen
|
||||
// Returns a net.Listener created from a file discriptor for a socket
|
||||
// with SO_REUSEPORT and SO_REUSEADDR option set.
|
||||
func Listen(network, address string) (net.Listener, error) {
|
||||
return listenConfig.Listen(context.Background(), network, address)
|
||||
}
|
||||
|
||||
// ListenPacket listens at the given network and address. see net.ListenPacket
|
||||
// Returns a net.Listener created from a file discriptor for a socket
|
||||
// with SO_REUSEPORT and SO_REUSEADDR option set.
|
||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||
return listenConfig.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
// Dial dials the given network and address. see net.Dialer.Dial
|
||||
// Returns a net.Conn created from a file discriptor for a socket
|
||||
// with SO_REUSEPORT and SO_REUSEADDR option set.
|
||||
func Dial(network, laddr, raddr string) (net.Conn, error) {
|
||||
nla, err := ResolveAddr(network, laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := net.Dialer {
|
||||
Control: Control,
|
||||
LocalAddr: nla,
|
||||
}
|
||||
return d.Dial(network, raddr)
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package greuseport
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func ResolveAddr(network, address string) (net.Addr, error) {
|
||||
switch network {
|
||||
case "ip", "ip4", "ip6":
|
||||
return net.ResolveIPAddr(network, address)
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
return net.ResolveTCPAddr(network, address)
|
||||
case "udp", "udp4", "udp6":
|
||||
return net.ResolveUDPAddr(network, address)
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
return net.ResolveUnixAddr(network, address)
|
||||
default:
|
||||
return nil, net.UnknownNetworkError(network)
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
// +build !windows,!linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd
|
||||
|
||||
package greuseport
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// See net.RawConn.Control
|
||||
func Control(network, address string, c syscall.RawConn) (err error) {
|
||||
return nil
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
// +build linux darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package greuseport
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/third/golang.org/x/sys/unix"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Enabled = true
|
||||
}
|
||||
|
||||
// See net.RawConn.Control
|
||||
func Control(network, address string, c syscall.RawConn) (err error) {
|
||||
c.Control(func(fd uintptr) {
|
||||
if err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
if err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package greuseport
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/third/golang.org/x/sys/windows"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// See net.RawConn.Control
|
||||
func Control(network, address string, c syscall.RawConn) (err error) {
|
||||
return c.Control(func(fd uintptr) {
|
||||
if err = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1); err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,213 +0,0 @@
|
||||
// +build linux darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package greuseport_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/net/greuseport"
|
||||
"html"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
httpServerOneResponse = "1"
|
||||
httpServerTwoResponse = "2"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerOne = NewHTTPServer(httpServerOneResponse)
|
||||
httpServerTwo = NewHTTPServer(httpServerTwoResponse)
|
||||
)
|
||||
|
||||
func NewHTTPServer(resp string) *httptest.Server {
|
||||
return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, resp)
|
||||
}))
|
||||
}
|
||||
func TestNewReusablePortListener(t *testing.T) {
|
||||
listenerOne, err := greuseport.Listen("tcp4", "localhost:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerOne.Close()
|
||||
|
||||
listenerTwo, err := greuseport.Listen("tcp", "127.0.0.1:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerTwo.Close()
|
||||
|
||||
listenerThree, err := greuseport.Listen("tcp6", "[::]:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerThree.Close()
|
||||
|
||||
listenerFour, err := greuseport.Listen("tcp6", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerFour.Close()
|
||||
|
||||
listenerFive, err := greuseport.Listen("tcp4", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerFive.Close()
|
||||
|
||||
listenerSix, err := greuseport.Listen("tcp", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerSix.Close()
|
||||
}
|
||||
|
||||
func TestListen(t *testing.T) {
|
||||
listenerOne, err := greuseport.Listen("tcp4", "localhost:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerOne.Close()
|
||||
|
||||
listenerTwo, err := greuseport.Listen("tcp", "127.0.0.1:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerTwo.Close()
|
||||
|
||||
listenerThree, err := greuseport.Listen("tcp6", "[::]:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerThree.Close()
|
||||
|
||||
listenerFour, err := greuseport.Listen("tcp6", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerFour.Close()
|
||||
|
||||
listenerFive, err := greuseport.Listen("tcp4", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerFive.Close()
|
||||
|
||||
listenerSix, err := greuseport.Listen("tcp", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerSix.Close()
|
||||
}
|
||||
|
||||
func TestNewReusablePortServers(t *testing.T) {
|
||||
listenerOne, err := greuseport.Listen("tcp4", "localhost:10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerOne.Close()
|
||||
|
||||
listenerTwo, err := greuseport.Listen("tcp6", ":10081")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer listenerTwo.Close()
|
||||
|
||||
httpServerOne.Listener = listenerOne
|
||||
httpServerTwo.Listener = listenerTwo
|
||||
|
||||
httpServerOne.Start()
|
||||
httpServerTwo.Start()
|
||||
|
||||
// Server One — First Response
|
||||
resp1, err := http.Get(httpServerOne.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
body1, err := ioutil.ReadAll(resp1.Body)
|
||||
resp1.Body.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(body1) != httpServerOneResponse && string(body1) != httpServerTwoResponse {
|
||||
t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body1))
|
||||
}
|
||||
|
||||
// Server Two — First Response
|
||||
resp2, err := http.Get(httpServerTwo.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
body2, err := ioutil.ReadAll(resp2.Body)
|
||||
resp1.Body.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(body2) != httpServerOneResponse && string(body2) != httpServerTwoResponse {
|
||||
t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body2))
|
||||
}
|
||||
|
||||
httpServerTwo.Close()
|
||||
|
||||
// Server One — Second Response
|
||||
resp3, err := http.Get(httpServerOne.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
body3, err := ioutil.ReadAll(resp3.Body)
|
||||
resp1.Body.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(body3) != httpServerOneResponse {
|
||||
t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body3))
|
||||
}
|
||||
|
||||
// Server One — Third Response
|
||||
resp5, err := http.Get(httpServerOne.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
body5, err := ioutil.ReadAll(resp5.Body)
|
||||
resp1.Body.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(body5) != httpServerOneResponse {
|
||||
t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body5))
|
||||
}
|
||||
|
||||
httpServerOne.Close()
|
||||
}
|
||||
|
||||
func BenchmarkNewReusablePortListener(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
listener, err := greuseport.Listen("tcp", ":10081")
|
||||
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
} else {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleNewReusablePortListener() {
|
||||
listener, err := greuseport.Listen("tcp", ":8881")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
server := &http.Server{}
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println(os.Getgid())
|
||||
fmt.Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path))
|
||||
})
|
||||
|
||||
panic(server.Serve(listener))
|
||||
}
|
||||
@ -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(event.Path)
|
||||
})
|
||||
return j
|
||||
} else {
|
||||
glog.Errorfln(`[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)
|
||||
})
|
||||
}
|
||||
|
||||
77
g/os/gcfg/gcfg_z_unit_test.go
Normal file
77
g/os/gcfg/gcfg_z_unit_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gcfg_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/os/gcfg"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
config := `
|
||||
v1 = 1
|
||||
v2 = "true"
|
||||
v3 = "off"
|
||||
v4 = "1.23"
|
||||
array = [1,2,3]
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
gtest.Case(t, func() {
|
||||
path := "config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
|
||||
c := gcfg.New(".")
|
||||
gtest.Assert(c.Get("v1"), 1)
|
||||
gtest.AssertEQ(c.GetInt("v1"), 1)
|
||||
gtest.AssertEQ(c.GetInt8("v1"), int8(1))
|
||||
gtest.AssertEQ(c.GetInt16("v1"), int16(1))
|
||||
gtest.AssertEQ(c.GetInt32("v1"), int32(1))
|
||||
gtest.AssertEQ(c.GetInt64("v1"), int64(1))
|
||||
gtest.AssertEQ(c.GetUint("v1"), uint(1))
|
||||
gtest.AssertEQ(c.GetUint8("v1"), uint8(1))
|
||||
gtest.AssertEQ(c.GetUint16("v1"), uint16(1))
|
||||
gtest.AssertEQ(c.GetUint32("v1"), uint32(1))
|
||||
gtest.AssertEQ(c.GetUint64("v1"), uint64(1))
|
||||
|
||||
gtest.AssertEQ(c.GetVar("v1").String(), "1")
|
||||
gtest.AssertEQ(c.GetVar("v1").Bool(), true)
|
||||
gtest.AssertEQ(c.GetVar("v2").String(), "true")
|
||||
gtest.AssertEQ(c.GetVar("v2").Bool(), true)
|
||||
|
||||
gtest.AssertEQ(c.GetString("v1"), "1")
|
||||
gtest.AssertEQ(c.GetFloat32("v4"), float32(1.23))
|
||||
gtest.AssertEQ(c.GetFloat64("v4"), float64(1.23))
|
||||
gtest.AssertEQ(c.GetString("v2"), "true")
|
||||
gtest.AssertEQ(c.GetBool("v2"), true)
|
||||
gtest.AssertEQ(c.GetBool("v3"), false)
|
||||
|
||||
gtest.AssertEQ(c.Contains("v1"), true)
|
||||
gtest.AssertEQ(c.Contains("v2"), true)
|
||||
gtest.AssertEQ(c.Contains("v3"), true)
|
||||
gtest.AssertEQ(c.Contains("v4"), true)
|
||||
gtest.AssertEQ(c.Contains("v5"), false)
|
||||
|
||||
gtest.AssertEQ(c.GetInts("array"), []int{1,2,3})
|
||||
gtest.AssertEQ(c.GetStrings("array"), []string{"1","2","3"})
|
||||
gtest.AssertEQ(c.GetArray("array"), []interface{}{"1","2","3"})
|
||||
gtest.AssertEQ(c.GetInterfaces("array"), []interface{}{"1","2","3"})
|
||||
gtest.AssertEQ(c.GetMap("redis"), map[string]interface{}{
|
||||
"disk" : "127.0.0.1:6379,0",
|
||||
"cache" : "127.0.0.1:6379,1",
|
||||
})
|
||||
gtest.AssertEQ(c.GetFilePath(), gfile.Pwd() + gfile.Separator + "config.toml")
|
||||
|
||||
})
|
||||
}
|
||||
@ -29,7 +29,27 @@ var (
|
||||
defaultCron = New()
|
||||
)
|
||||
|
||||
// 添加执行方法,可以给定名字,以便于后续执行删除
|
||||
// 设置日志输出路径
|
||||
func SetLogPath(path string) {
|
||||
defaultCron.SetLogPath(path)
|
||||
}
|
||||
|
||||
// 获取设置的日志输出路径
|
||||
func GetLogPath() string {
|
||||
return defaultCron.GetLogPath()
|
||||
}
|
||||
|
||||
// 设置日志输出等级。
|
||||
func SetLogLevel(level int) {
|
||||
defaultCron.SetLogLevel(level)
|
||||
}
|
||||
|
||||
// 获取日志输出等级。
|
||||
func GetLogLevel() int {
|
||||
return defaultCron.GetLogLevel()
|
||||
}
|
||||
|
||||
// 添加定时任务,可以给定名字,以便于后续执行删除
|
||||
func Add(pattern string, job func(), name ... string) (*Entry, error) {
|
||||
return defaultCron.Add(pattern, job, name...)
|
||||
}
|
||||
|
||||
@ -12,26 +12,51 @@ import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gtimer"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 定时任务管理对象
|
||||
type Cron struct {
|
||||
idgen *gtype.Int // 用于唯一名称生成
|
||||
status *gtype.Int // 定时任务状态(0: 未执行; 1: 运行中; 2: 已停止; -1:删除关闭)
|
||||
entries *gmap.StringInterfaceMap // 所有的定时任务项
|
||||
idGen *gtype.Int64 // 用于唯一名称生成
|
||||
status *gtype.Int // 定时任务状态(0: 未执行; 1: 运行中; 2: 已停止; -1:删除关闭)
|
||||
entries *gmap.StringInterfaceMap // 所有的定时任务项
|
||||
logPath *gtype.String // 日志文件输出目录
|
||||
logLevel *gtype.Int // 日志输出等级
|
||||
}
|
||||
|
||||
// 创建自定义的定时任务管理对象
|
||||
func New() *Cron {
|
||||
return &Cron {
|
||||
idgen : gtype.NewInt(1000000),
|
||||
status : gtype.NewInt(STATUS_RUNNING),
|
||||
entries : gmap.NewStringInterfaceMap(),
|
||||
idGen : gtype.NewInt64(),
|
||||
status : gtype.NewInt(STATUS_RUNNING),
|
||||
entries : gmap.NewStringInterfaceMap(),
|
||||
logPath : gtype.NewString(),
|
||||
logLevel : gtype.NewInt(glog.LEVEL_PROD),
|
||||
}
|
||||
}
|
||||
|
||||
// 设置日志输出路径
|
||||
func (c *Cron) SetLogPath(path string) {
|
||||
c.logPath.Set(path)
|
||||
}
|
||||
|
||||
// 获取设置的日志输出路径
|
||||
func (c *Cron) GetLogPath() string {
|
||||
return c.logPath.Val()
|
||||
}
|
||||
|
||||
// 设置日志输出等级。
|
||||
func (c *Cron) SetLogLevel(level int) {
|
||||
c.logLevel.Set(level)
|
||||
}
|
||||
|
||||
// 获取日志输出等级。
|
||||
func (c *Cron) GetLogLevel() int {
|
||||
return c.logLevel.Val()
|
||||
}
|
||||
|
||||
// 添加定时任务
|
||||
func (c *Cron) Add(pattern string, job func(), name ... string) (*Entry, error) {
|
||||
if len(name) > 0 {
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
package gcron
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gtimer"
|
||||
"strconv"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -17,6 +20,7 @@ type Entry struct {
|
||||
cron *Cron // 所属定时任务
|
||||
entry *gtimer.Entry // 定时器任务对象
|
||||
schedule *cronSchedule // 定时任务配置对象
|
||||
jobName string // 任务注册方法名称
|
||||
Name string // 定时任务名称
|
||||
Job func() // 注册定时任务方法
|
||||
Time time.Time // 注册时间
|
||||
@ -31,13 +35,14 @@ func (c *Cron) addEntry(pattern string, job func(), singleton bool, times int, n
|
||||
entry := &Entry {
|
||||
cron : c,
|
||||
schedule : schedule,
|
||||
jobName : runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(),
|
||||
Job : job,
|
||||
Time : time.Now(),
|
||||
}
|
||||
if len(name) > 0 {
|
||||
entry.Name = name[0]
|
||||
} else {
|
||||
entry.Name = strconv.Itoa(c.idgen.Add(1))
|
||||
entry.Name = "gcron-" + gconv.String(c.idGen.Add(1))
|
||||
}
|
||||
entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, times, gtimer.STATUS_STOPPED)
|
||||
entry.entry.Start()
|
||||
@ -89,20 +94,29 @@ func (entry *Entry) Close() {
|
||||
// 定时任务检查执行
|
||||
func (entry *Entry) check() {
|
||||
if entry.schedule.meet(time.Now()) {
|
||||
path := entry.cron.GetLogPath()
|
||||
level := entry.cron.GetLogLevel()
|
||||
switch entry.cron.status.Val() {
|
||||
case STATUS_STOPPED:
|
||||
return
|
||||
|
||||
case STATUS_CLOSED:
|
||||
entry.cron.Remove(entry.Name)
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s remove", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
gtimer.Exit()
|
||||
|
||||
case STATUS_READY: fallthrough
|
||||
case STATUS_RUNNING:
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
defer func() {
|
||||
if entry.entry.Status() == STATUS_CLOSED {
|
||||
entry.cron.Remove(entry.Name)
|
||||
}
|
||||
if err := recover(); err != nil {
|
||||
glog.Path(path).Level(level).Errorfln("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err)
|
||||
} else {
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
}
|
||||
}()
|
||||
entry.Job()
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ import (
|
||||
|
||||
const (
|
||||
LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
|
||||
LEVEL_DEV = LEVEL_ALL
|
||||
LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
|
||||
LEVEL_DEBU = 1 << iota
|
||||
LEVEL_INFO
|
||||
LEVEL_NOTI
|
||||
@ -141,6 +143,14 @@ func To(writer io.Writer) *Logger {
|
||||
return logger.To(writer)
|
||||
}
|
||||
|
||||
// Path is a chaining function,
|
||||
// which sets the directory path to <path> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志路径。
|
||||
func Path(path string) *Logger {
|
||||
return logger.Path(path)
|
||||
}
|
||||
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
|
||||
@ -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 {
|
||||
@ -26,6 +26,23 @@ func (l *Logger) To(writer io.Writer) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Path is a chaining function,
|
||||
// which sets the directory path to <path> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志路径。
|
||||
func (l *Logger) Path(path string) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
logger = l.Clone()
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
if path != "" {
|
||||
logger.SetPath(path)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
|
||||
@ -588,7 +588,7 @@ func HideStr(str string, percent int, hide string) string {
|
||||
buffer.WriteString(string(hideStr))
|
||||
buffer.WriteString(string(rs[start + hideLen : ]))
|
||||
if len(array) > 1 {
|
||||
buffer.WriteString(array[1])
|
||||
buffer.WriteString("@" + array[1])
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -46,11 +46,10 @@ func PosI(haystack, needle string, startOffset...int) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
haystack = haystack[offset : ]
|
||||
if offset < 0 {
|
||||
offset += length
|
||||
}
|
||||
pos := strings.Index(strings.ToLower(haystack), strings.ToLower(needle))
|
||||
pos := strings.Index(strings.ToLower(haystack[offset : ]), strings.ToLower(needle))
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -85,7 +85,9 @@ func Test_UcFirst(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := "abcdEFG乱入的中文abcdefg"
|
||||
e1 := "AbcdEFG乱入的中文abcdefg"
|
||||
gtest.Assert(gstr.UcFirst(""), "")
|
||||
gtest.Assert(gstr.UcFirst(s1), e1)
|
||||
gtest.Assert(gstr.UcFirst(e1), e1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -93,7 +95,9 @@ func Test_LcFirst(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := "AbcdEFG乱入的中文abcdefg"
|
||||
e1 := "abcdEFG乱入的中文abcdefg"
|
||||
gtest.Assert(gstr.LcFirst(""), "")
|
||||
gtest.Assert(gstr.LcFirst(s1), e1)
|
||||
gtest.Assert(gstr.LcFirst(e1), e1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -131,9 +135,11 @@ func Test_IsNumeric(t *testing.T) {
|
||||
|
||||
func Test_SubStr(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 0), "我爱GoFrame")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 2), "GoFrame")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 2, 2), "Go")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 0), "我爱GoFrame")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 2), "GoFrame")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 2, 2), "Go")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", -1, 30), "我爱GoFrame")
|
||||
gtest.Assert(gstr.SubStr("我爱GoFrame", 30, 30), "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -143,6 +149,7 @@ func Test_StrLimit(t *testing.T) {
|
||||
gtest.Assert(gstr.StrLimit("我爱GoFrame", 2, ""), "我爱")
|
||||
gtest.Assert(gstr.StrLimit("我爱GoFrame", 2, "**"), "我爱**")
|
||||
gtest.Assert(gstr.StrLimit("我爱GoFrame", 4, ""), "我爱Go")
|
||||
gtest.Assert(gstr.StrLimit("*", 4, ""), "*")
|
||||
})
|
||||
}
|
||||
|
||||
@ -154,8 +161,9 @@ func Test_Reverse(t *testing.T) {
|
||||
|
||||
func Test_NumberFormat(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.NumberFormat(1234567.8910, 2, ".", ","), "1,234,567.89")
|
||||
gtest.Assert(gstr.NumberFormat(1234567.8910, 2, "#", "/"), "1/234/567#89")
|
||||
gtest.Assert(gstr.NumberFormat(1234567.8910, 2, ".", ","), "1,234,567.89")
|
||||
gtest.Assert(gstr.NumberFormat(1234567.8910, 2, "#", "/"), "1/234/567#89")
|
||||
gtest.Assert(gstr.NumberFormat(-1234567.8910, 2, "#", "/"), "-1/234/567#89")
|
||||
})
|
||||
}
|
||||
|
||||
@ -163,6 +171,7 @@ func Test_ChunkSplit(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.ChunkSplit("1234", 1, "#"), "1#2#3#4#")
|
||||
gtest.Assert(gstr.ChunkSplit("我爱123", 1, "#"), "我#爱#1#2#3#")
|
||||
gtest.Assert(gstr.ChunkSplit("1234", 1, ""), "1\r\n2\r\n3\r\n4\r\n")
|
||||
})
|
||||
}
|
||||
|
||||
@ -204,6 +213,7 @@ func Test_CountChars(t *testing.T) {
|
||||
func Test_WordWrap(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.WordWrap("12 34", 2, "<br>"), "12<br>34")
|
||||
gtest.Assert(gstr.WordWrap("12 34", 2, "\n"), "12\n34")
|
||||
gtest.Assert(gstr.WordWrap("A very long woooooooooooooooooord. and something", 7, "<br>"),
|
||||
"A very<br>long<br>woooooooooooooooooord.<br>and<br>something")
|
||||
})
|
||||
@ -226,6 +236,8 @@ func Test_Repeat(t *testing.T) {
|
||||
func Test_Str(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.Str("name@example.com", "@"), "@example.com")
|
||||
gtest.Assert(gstr.Str("name@example.com", ""), "")
|
||||
gtest.Assert(gstr.Str("name@example.com", "z"), "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -274,7 +286,8 @@ func Test_Ord(t *testing.T) {
|
||||
|
||||
func Test_HideStr(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.HideStr("15928008611", 40, "*"), "159****8611")
|
||||
gtest.Assert(gstr.HideStr("15928008611", 40, "*"), "159****8611")
|
||||
gtest.Assert(gstr.HideStr("john@kohg.cn", 40, "*"), "jo*n@kohg.cn")
|
||||
})
|
||||
}
|
||||
|
||||
@ -322,4 +335,51 @@ func Test_CountI(t *testing.T) {
|
||||
gtest.Assert(gstr.CountI(s, "b"), 1)
|
||||
gtest.Assert(gstr.CountI(s, "d"), 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Compare(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.Compare("a", "b"), -1)
|
||||
gtest.Assert(gstr.Compare("a", "a"), 0)
|
||||
gtest.Assert(gstr.Compare("b", "a"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Equal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.Equal("a", "A"), true)
|
||||
gtest.Assert(gstr.Equal("a", "a"), true)
|
||||
gtest.Assert(gstr.Equal("b", "a"), false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func Test_Contains(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.Contains("abc", "a"), true)
|
||||
gtest.Assert(gstr.Contains("abc", "A"), false)
|
||||
gtest.Assert(gstr.Contains("abc", "ab"), true)
|
||||
gtest.Assert(gstr.Contains("abc", "abc"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ContainsI(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.ContainsI("abc", "a"), true)
|
||||
gtest.Assert(gstr.ContainsI("abc", "A"), true)
|
||||
gtest.Assert(gstr.ContainsI("abc", "Ab"), true)
|
||||
gtest.Assert(gstr.ContainsI("abc", "ABC"), true)
|
||||
gtest.Assert(gstr.ContainsI("abc", "ABCD"), false)
|
||||
gtest.Assert(gstr.ContainsI("abc", "D"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ContainsAny(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.ContainsAny("abc", "a"), true)
|
||||
gtest.Assert(gstr.ContainsAny("abc", "cd"), true)
|
||||
gtest.Assert(gstr.ContainsAny("abc", "de"), false)
|
||||
gtest.Assert(gstr.ContainsAny("abc", "A"), false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -17,17 +17,21 @@ import (
|
||||
func Test_Pos(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := "abcdEFGabcdefg"
|
||||
gtest.Assert(gstr.Pos(s1, "ab"), 0)
|
||||
gtest.Assert(gstr.Pos(s1, "ab", 2), 7)
|
||||
gtest.Assert(gstr.Pos(s1, "ab"), 0)
|
||||
gtest.Assert(gstr.Pos(s1, "ab", 2), 7)
|
||||
gtest.Assert(gstr.Pos(s1, "abd", 0), -1)
|
||||
gtest.Assert(gstr.Pos(s1, "e", -4), 11)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_PosI(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := "abcdEFGabcdefg"
|
||||
gtest.Assert(gstr.PosI(s1, "zz"), -1)
|
||||
gtest.Assert(gstr.PosI(s1, "ab"), 0)
|
||||
gtest.Assert(gstr.PosI(s1, "ef", 2), 4)
|
||||
gtest.Assert(gstr.PosI(s1, "zz"), -1)
|
||||
gtest.Assert(gstr.PosI(s1, "ab"), 0)
|
||||
gtest.Assert(gstr.PosI(s1, "ef", 2), 4)
|
||||
gtest.Assert(gstr.PosI(s1, "abd", 0), -1)
|
||||
gtest.Assert(gstr.PosI(s1, "E", -4), 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -38,7 +42,9 @@ func Test_PosR(t *testing.T) {
|
||||
gtest.Assert(gstr.PosR(s1, "zz"), -1)
|
||||
gtest.Assert(gstr.PosR(s1, "ab"), 7)
|
||||
gtest.Assert(gstr.PosR(s2, "ab", -2), 0)
|
||||
gtest.Assert(gstr.PosR(s1, "ef"), 11)
|
||||
gtest.Assert(gstr.PosR(s1, "ef"), 11)
|
||||
gtest.Assert(gstr.PosR(s1, "abd", 0), -1)
|
||||
gtest.Assert(gstr.PosR(s1, "e", -4), -1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,9 +52,11 @@ func Test_PosRI(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := "abcdEFGabcdefg"
|
||||
s2 := "abcdEFGz1cdeab"
|
||||
gtest.Assert(gstr.PosRI(s1, "zz"), -1)
|
||||
gtest.Assert(gstr.PosRI(s1, "AB"), 7)
|
||||
gtest.Assert(gstr.PosRI(s2, "AB", -2), 0)
|
||||
gtest.Assert(gstr.PosRI(s1, "EF"), 11)
|
||||
gtest.Assert(gstr.PosRI(s1, "zz"), -1)
|
||||
gtest.Assert(gstr.PosRI(s1, "AB"), 7)
|
||||
gtest.Assert(gstr.PosRI(s2, "AB", -2), 0)
|
||||
gtest.Assert(gstr.PosRI(s1, "EF"), 11)
|
||||
gtest.Assert(gstr.PosRI(s1, "abd", 0), -1)
|
||||
gtest.Assert(gstr.PosRI(s1, "e", -5), 4)
|
||||
})
|
||||
}
|
||||
@ -79,10 +79,11 @@ func Bytes(i interface{}) []byte {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if r, ok := i.([]byte); ok {
|
||||
return r
|
||||
} else {
|
||||
return gbinary.Encode(i)
|
||||
switch value := i.(type) {
|
||||
case string: return []byte(value)
|
||||
case []byte: return value
|
||||
default:
|
||||
return gbinary.Encode(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,12 +16,12 @@ var (
|
||||
|
||||
// 随机计算是否满足给定的概率(分子/分母)
|
||||
func Meet(num, total int) bool {
|
||||
return Rand(0, total) <= num
|
||||
return Intn(total) < num
|
||||
}
|
||||
|
||||
// 随机计算是否满足给定的概率(float32)
|
||||
func MeetProb(prob float32) bool {
|
||||
return Rand(0, 1e7) <= int(prob*1e7)
|
||||
return Intn(1e7) < int(prob*1e7)
|
||||
}
|
||||
|
||||
// Rand 别名, 返回: [min, max]
|
||||
@ -92,6 +92,8 @@ func RandLetters(n int) string {
|
||||
}
|
||||
|
||||
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
|
||||
//
|
||||
// 返回[0, n)的随机数组成的slice。
|
||||
func Perm(n int) []int {
|
||||
m := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
|
||||
@ -47,7 +47,7 @@ func init() {
|
||||
// 自定义的 rand.Intn ,绝对随机, 返回: [0, max)
|
||||
func Intn (max int) int {
|
||||
n := int(<- bufferChan)%max
|
||||
if n < 0 {
|
||||
if (max > 0 && n < 0) || (max < 0 && n > 0) {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
|
||||
146
g/util/grand/grand_z_unit_test.go
Normal file
146
g/util/grand/grand_z_unit_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package grand_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func Test_Intn(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 1000000; i++ {
|
||||
n := grand.Intn(100)
|
||||
gtest.AssertLT(n, 100)
|
||||
gtest.AssertGTE(n, 0)
|
||||
}
|
||||
for i := 0; i < 1000000; i++ {
|
||||
n := grand.Intn(-100)
|
||||
gtest.AssertLTE(n, 0)
|
||||
gtest.AssertGT(n, -100)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Meet(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.Meet(100, 100), true)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.Meet(0, 100), false)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.Meet(50, 100), []bool{true, false})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MeetProb(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.MeetProb(1), true)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.MeetProb(0), false)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.MeetProb(0.5), []bool{true, false})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_N(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.N(1, 1), 1)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.N(0, 0), 0)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.N(1, 2), []int{1, 2})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.Rand(1, 1), 1)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(grand.Rand(0, 0), 0)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.Rand(1, 2), []int{1, 2})
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.Rand(-1, 2), []int{-1, 0, 1, 2})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Str(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.Str(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RandStr(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.RandStr(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Digits(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.Digits(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RandDigits(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.RandDigits(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Letters(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.Letters(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RandLetters(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.Assert(len(grand.RandLetters(5)), 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Perm(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
gtest.AssertIN(grand.Perm(5), []int{0,1,2,3,4})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -487,6 +487,22 @@ func getQueriedSqls() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
//data := g.Map{
|
||||
// "nickname" : "john",
|
||||
//}
|
||||
//db.SetDebug(true)
|
||||
//r, err := db.Table("user").Where("id=1").Data(data).Update()
|
||||
//fmt.Println(err)
|
||||
//fmt.Println(r.RowsAffected())
|
||||
|
||||
//data2 := g.Map{
|
||||
// "adsys1" : "ss",
|
||||
//}
|
||||
//db.SetDebug(true)
|
||||
//r, err := db.Table("cd_adsys").Where("adsys0=1").Data(data2).Update()
|
||||
//fmt.Println(err)
|
||||
//fmt.Println(r.RowsAffected())
|
||||
//return
|
||||
//db.SetDebug(true)
|
||||
//r, err := db.Table("test").Where("id=1").One()
|
||||
//fmt.Println(r["datetime"])
|
||||
|
||||
@ -16,5 +16,4 @@ func main() {
|
||||
redis.Do("SET", "k", "v")
|
||||
v, _ := redis.Do("GET", "k")
|
||||
fmt.Println(gconv.String(v))
|
||||
}
|
||||
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ func main() {
|
||||
s.BindHandler("/", func(r *ghttp.Request){
|
||||
r.Response.Writeln("来自于HTTPS的:哈喽世界!")
|
||||
})
|
||||
s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key")
|
||||
s.EnableHTTPS("./server.crt", "./server.key")
|
||||
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 = "ws://127.0.0.1:8199/ws";
|
||||
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>
|
||||
30
geg/net/ghttp/server/websocket/echo-wss/main.go
Normal file
30
geg/net/ghttp/server/websocket/echo-wss/main.go
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/ws", 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.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/net/greuseport"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 创建**两个**进程,并通过HTTP访问,查看返回结果。
|
||||
func main() {
|
||||
listener, err := greuseport.Listen("tcp", ":8881")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
server := &http.Server{}
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "gid: %d, pid: %d\n", os.Getgid(), os.Getpid())
|
||||
})
|
||||
|
||||
panic(server.Serve(listener))
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//t := gconv.GTime("2010-10-10 00:00:01")
|
||||
fmt.Println(gconv.String(gtime.Millisecond()))
|
||||
s := "127.0.0.1:6379,1,nhytaf176tg?maxIdle=1&maxActive=0&idleTimeout=60&maxConnLifetime=60"
|
||||
array, err := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)\?(.+)`, s)
|
||||
g.Dump(err)
|
||||
g.Dump(array)
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.5.16"
|
||||
const VERSION = "v1.5.18"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user