Compare commits

..

28 Commits

Author SHA1 Message Date
afb1adee3d version updates 2019-03-28 17:58:33 +08:00
22fa7a37f3 add UseNumber support for gjson; add more unit test cases for gjson 2019-03-28 17:51:05 +08:00
6a58bfc574 add stdout printing support for ghttp.Server logging 2019-03-28 09:34:16 +08:00
64124c60fc add fatal error when no router set or statis feature enabled for ghttp.Server 2019-03-27 16:23:35 +08:00
9a0066de62 add file/folder search support for gcfg/gview in order of envpath/pwdpath/binpath/mainpath; add gfile.Search function 2019-03-27 11:48:53 +08:00
22c7c7403b fix issue in basic http auth check for server side 2019-03-27 09:16:23 +08:00
83db8e4b15 add wss example for websocket 2019-03-22 17:26:39 +08:00
25f2e121e7 version updates 2019-03-22 15:09:22 +08:00
1325a145d8 fix issue in config auto reload of gcfg 2019-03-22 15:08:43 +08:00
9bc49c0b29 add logging for error in ghttp.Request 2019-03-22 14:31:02 +08:00
8e84e5b0f3 update comment of gredis 2019-03-22 11:22:03 +08:00
a42e6b0c45 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:43 +08:00
51bb7a9854 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:15 +08:00
62580b5719 add examples for gredis 2019-03-21 23:51:57 +08:00
15cfd5ce5c add g.Export 2019-03-21 18:21:53 +08:00
11c89c4090 fix issue in GetOrSetFuncLock of gmap/gcache; add gutil.IsEmpty/Export functions 2019-03-21 18:20:20 +08:00
4a12cb9f27 version updates 2019-03-21 10:52:12 +08:00
32f575eddd update unit test of gins 2019-03-21 10:36:24 +08:00
fbb4cb3b1c add more unit test cases for gredis 2019-03-21 10:04:53 +08:00
c9d2d5e8ab add configuration support for maxIdle/maxActive/idleTimeout/maxConnLifetime of gredis; update gjson.Append/Len functions; add more unit test cases for gredis/gins/gstr/gjson 2019-03-21 00:14:23 +08:00
93763192f2 version updates 2019-03-19 17:55:02 +08:00
80e0eae6b0 add TLSConfig support for ghttp.Server 2019-03-19 17:48:37 +08:00
4e3d735b90 add logging for gcron 2019-03-19 13:58:18 +08:00
60e5a7da28 add more unit test cases for grand/gstr 2019-03-18 23:52:25 +08:00
997b5ba889 README updates 2019-03-18 14:10:30 +08:00
b3e7ca1963 travis updates 2019-03-18 14:05:46 +08:00
5a82d695c1 mv greuseport to a new repo 2019-03-18 13:56:16 +08:00
64a0427150 travis updates 2019-03-18 13:34:51 +08:00
89 changed files with 2671 additions and 1042 deletions

View File

@ -1,6 +1,7 @@
language: go
go:
- "1.10.x"
- "1.11.x"
- "1.12.x"

View File

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

View File

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

View File

@ -109,7 +109,9 @@ func (gm *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interf
if f, ok := value.(func() interface {}); ok {
value = f()
}
gm.m[key] = value
if value != nil {
gm.m[key] = value
}
return value
}

View File

@ -109,7 +109,9 @@ func (gm *StringInterfaceMap) doSetWithLockCheck(key string, value interface{})
if f, ok := value.(func() interface {}); ok {
value = f()
}
gm.m[key] = value
if value != nil {
gm.m[key] = value
}
return value
}

View File

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

View File

@ -1,3 +1,9 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test
import (

View File

@ -1,4 +1,8 @@
// 方法操作
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test

View File

@ -1,4 +1,8 @@
// 链式操作
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test

View File

@ -1,4 +1,8 @@
// 事务操作
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test

View File

@ -11,30 +11,36 @@
package gredis
import (
"time"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"github.com/gogf/gf/g/container/gmap"
"fmt"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"time"
)
const (
gDEFAULT_POOL_MAX_IDLE = 1
gDEFAULT_POOL_MAX_ACTIVE = 10
gDEFAULT_POOL_IDLE_TIMEOUT = 180 * time.Second
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
)
// Redis客户端
// Redis客户端(管理连接池)
type Redis struct {
pool *redis.Pool
pool *redis.Pool
config Config
}
// Redis连接对象(连接池中的单个连接)
type Conn redis.Conn
// Redis服务端但节点连接配置信息
type Config struct {
Host string // IP/域名
Port int // 端口
Db int // db
Pass string // 密码
Host string // 地址
Port int // 端口
Db int // 数据库
Pass string // 授权密码
MaxIdle int // 最大允许空闲存在的连接数(默认为0表示不存在闲置连接)
MaxActive int // 最大连接数量限制(默认为0表示不限制)
IdleTimeout time.Duration // 连接最大空闲时间(默认为60秒,不允许设置为0)
MaxConnLifetime time.Duration // 连接最长存活时间(默认为60秒,不允许设置为0)
}
// Redis链接池统计信息
@ -45,88 +51,123 @@ type PoolStats struct {
// 连接池map
var pools = gmap.NewStringInterfaceMap()
// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
//
// 创建redis操作对象.
func New(config Config) *Redis {
r := &Redis{}
poolKey := fmt.Sprintf("%s:%d,%d", config.Host, config.Port, config.Db)
if v := pools.Get(poolKey); v == nil {
pool := &redis.Pool {
MaxIdle : gDEFAULT_POOL_MAX_IDLE,
MaxActive : gDEFAULT_POOL_MAX_ACTIVE,
IdleTimeout : gDEFAULT_POOL_IDLE_TIMEOUT,
MaxConnLifetime : gDEFAULT_POOL_MAX_LIFE_TIME,
Dial : func() (redis.Conn, error) {
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
return nil, err
}
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
if config.IdleTimeout == 0 {
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
}
if config.MaxConnLifetime == 0 {
config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME
}
return &Redis{
config : config,
pool : pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
return &redis.Pool {
IdleTimeout : config.IdleTimeout,
MaxConnLifetime : config.MaxConnLifetime,
Dial : func() (redis.Conn, error) {
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
return nil, err
}
}
if _, err := c.Do("SELECT", config.Db); err != nil {
return nil, err
}
return c, nil
},
// 用来测试连接是否可用
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
pools.Set(poolKey, pool)
r.pool = pool
} else {
r.pool = v.(*redis.Pool)
// 密码设置
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
return nil, err
}
}
// 数据库设置
if _, err := c.Do("SELECT", config.Db); err != nil {
return nil, err
}
return c, nil
},
// 在被应用从连接池中获取出来之后,用以测试连接是否可用,
// 如果返回error那么关闭该连接对象重新创建新的连接。
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}).(*redis.Pool),
}
return r
}
// 关闭redis管理对象将会关闭底层的
// Close closes the redis connection pool,
// it will release all connections reserved by this pool.
//
// 关闭redis管理对象将会关闭底层的连接池。
func (r *Redis) Close() error {
pools.Remove(fmt.Sprintf("%v", r.config))
return r.pool.Close()
}
// 获得一个原生的redis连接对象用于自定义连接操作
// 但是需要注意的是如果不再使用该连接对象时需要手动Close连接否则会造成连接数超限。
func (r *Redis) GetConn() redis.Conn {
return r.pool.Get()
// See GetConn.
func (r *Redis) Conn() Conn {
return r.GetConn()
}
// GetConn returns a raw connection object,
// which expose more methods communication with server.
// **You should call Close function manually if you do not use this connection any further.**
//
// 获得一个原生的redis连接对象用于自定义连接操作
// 但是需要注意的是如果不再使用该连接对象时需要手动Close连接否则会造成连接数超限。
func (r *Redis) GetConn() Conn {
return r.pool.Get().(Conn)
}
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
//
// 设置属性 - MaxIdle
func (r *Redis) SetMaxIdle(value int) {
r.pool.MaxIdle = value
}
// SetMaxIdle sets the MaxActive attribute of the connection pool.
//
// 设置属性 - MaxActive
func (r *Redis) SetMaxActive(value int) {
r.pool.MaxActive = value
}
// SetMaxIdle sets the IdleTimeout attribute of the connection pool.
//
// 设置属性 - IdleTimeout
func (r *Redis) SetIdleTimeout(value time.Duration) {
r.pool.IdleTimeout = value
}
// SetMaxIdle sets the MaxConnLifetime attribute of the connection pool.
//
// 设置属性 - MaxConnLifetime
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
r.pool.MaxConnLifetime = value
}
// 获取当前连接池统计信息
// Stats returns pool's statistics.
//
// 获取当前连接池统计信息。
func (r *Redis) Stats() *PoolStats {
return &PoolStats{r.pool.Stats()}
}
// 执行同步命令 - Do
// Do sends a command to the server and returns the received reply.
// Do automatically get a connection from pool, and close it when reply received.
//
// 执行同步命令自动从连接池中获取连接使用完毕后关闭连接丢回连接池开发者不用自行Close.
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
conn := r.pool.Get()
defer conn.Close()
return conn.Do(command, args...)
}
// Deprecated.
// Send writes the command to the client's output buffer.
//
// 执行异步命令 - Send
func (r *Redis) Send(command string, args ...interface{}) error {
conn := r.pool.Get()

View File

@ -0,0 +1,112 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gredis_test
import (
"github.com/gogf/gf/g/database/gredis"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
var (
config = gredis.Config{
Host : "127.0.0.1",
Port : 6379,
Db : 1,
}
)
func Test_NewClose(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
gtest.AssertNE(redis, nil)
err := redis.Close()
gtest.Assert(err, nil)
})
}
func Test_Do(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
_, err := redis.Do("SET", "k", "v")
gtest.Assert(err, nil)
r, err := redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
_, err = redis.Do("DEL", "k")
gtest.Assert(err, nil)
r, err = redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, nil)
})
}
func Test_Send(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
err := redis.Send("SET", "k", "v")
gtest.Assert(err, nil)
r, err := redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
})
}
func Test_Stats(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
redis.SetMaxIdle(2)
redis.SetMaxActive(100)
redis.SetIdleTimeout(500*time.Millisecond)
redis.SetMaxConnLifetime(500*time.Millisecond)
array := make([]gredis.Conn, 0)
for i := 0; i < 10; i++ {
array = append(array, redis.Conn())
}
stats := redis.Stats()
gtest.Assert(stats.ActiveCount, 10)
gtest.Assert(stats.IdleCount, 0)
for i := 0; i < 10; i++ {
array[i].Close()
}
stats = redis.Stats()
gtest.Assert(stats.ActiveCount, 2)
gtest.Assert(stats.IdleCount, 2)
//time.Sleep(3000*time.Millisecond)
//stats = redis.Stats()
//fmt.Println(stats)
//gtest.Assert(stats.ActiveCount, 0)
//gtest.Assert(stats.IdleCount, 0)
})
}
func Test_Conn(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
conn := redis.Conn()
defer conn.Close()
r, err := conn.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
_, err = conn.Do("DEL", "k")
gtest.Assert(err, nil)
r, err = conn.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, nil)
})
}

View File

@ -51,5 +51,5 @@ func ToUTF8(charset string, src string) (dst string, err error) {
// UTF8转指定字符集
func UTF8To(charset string, src string) (dst string, err error) {
return Convert(charset, "UTF-8", src)
return Convert(charset, "UTF-8", src)
}

View File

@ -1,10 +1,16 @@
package gcharset
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gcharset_test
import (
"github.com/gogf/gf/g/encoding/gcharset"
"testing"
)
var testData = []struct {
utf8, other, otherEncoding string
}{
@ -52,7 +58,7 @@ var testData = []struct {
func TestDecode(t *testing.T) {
for _, data := range testData {
str := ""
str, err := Convert("UTF-8", data.otherEncoding, data.other)
str, err := gcharset.Convert("UTF-8", data.otherEncoding, data.other)
if err != nil {
t.Errorf("Could not create decoder for %v", err)
continue
@ -68,7 +74,7 @@ func TestDecode(t *testing.T) {
func TestEncode(t *testing.T) {
for _, data := range testData {
str := ""
str, err := Convert(data.otherEncoding, "UTF-8", data.utf8)
str, err := gcharset.Convert(data.otherEncoding, "UTF-8", data.utf8)
if err != nil {
t.Errorf("Could not create decoder for %v", err)
continue
@ -86,7 +92,7 @@ func TestConvert(t *testing.T) {
dstCharset := "gbk"
dst := "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed"
str, err := Convert(dstCharset, srcCharset, src)
str, err := gcharset.Convert(dstCharset, srcCharset, src)
if err != nil {
t.Errorf("convert error. %v", err)
return

View File

@ -4,10 +4,13 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gjson provides quite flexible and useful API for JSON/XML/YAML/TOML content handling.
// Package gjson provides flexible and useful API for JSON/XML/YAML/TOML content handling.
//
// JSON/XML/YAML/TOML数据格式处理。
package gjson
import (
"bytes"
"encoding/json"
"errors"
"fmt"
@ -20,6 +23,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"
@ -38,20 +42,20 @@ type Json struct {
}
// 将变量转换为Json对象进行处理该变量至少应当是一个map或者slice否者转换没有意义
func New(value interface{}, unsafe...bool) *Json {
func New(data interface{}, unsafe...bool) *Json {
j := (*Json)(nil)
switch value.(type) {
switch data.(type) {
case map[string]interface{}, []interface{}, nil:
j = &Json {
p : &value,
p : &data,
c : byte(gDEFAULT_SPLIT_CHAR),
vc : false ,
}
case string, []byte:
j, _ = LoadContent(gconv.Bytes(value))
j, _ = LoadContent(gconv.Bytes(data))
default:
v := (interface{})(nil)
if m := gconv.Map(value); m != nil {
if m := gconv.Map(data); m != nil {
v = m
j = &Json {
p : &v,
@ -59,7 +63,7 @@ func New(value interface{}, unsafe...bool) *Json {
vc : false,
}
} else {
v = gconv.Interfaces(value)
v = gconv.Interfaces(data)
j = &Json {
p : &v,
c : byte(gDEFAULT_SPLIT_CHAR),
@ -72,36 +76,43 @@ func New(value interface{}, unsafe...bool) *Json {
}
// 创建一个非并发安全的Json对象
func NewUnsafe(value...interface{}) *Json {
if len(value) > 0 {
return New(value[0], true)
func NewUnsafe(data...interface{}) *Json {
if len(data) > 0 {
return New(data[0], true)
}
return New(nil, true)
}
// 编码go变量为json字符串并返回json字符串指针
func Encode (v interface{}) ([]byte, error) {
return json.Marshal(v)
// 识别当前给定内容是否为JSON格式
func Valid(data interface{}) bool {
return json.Valid(gconv.Bytes(data))
}
// 解码字符串为interface{}变量
func Decode (b []byte) (interface{}, error) {
var v interface{}
if err := DecodeTo(b, &v); err != nil {
// 编码go变量为json字符串并返回json字符串指针
func Encode(value interface{}) ([]byte, error) {
return json.Marshal(value)
}
// 解码字符串/[]byte为interface{}变量
func Decode(data interface{}) (interface{}, error) {
var value interface{}
if err := DecodeTo(gconv.Bytes(data), &value); err != nil {
return nil, err
} else {
return v, nil
return value, nil
}
}
// 解析json字符串为go变量注意第二个参数为指针(任意结构的变量)
func DecodeTo (b []byte, v interface{}) error {
return json.Unmarshal(b, v)
// 解析json字符串/[]byte为go变量注意第二个参数为指针(任意结构的变量)
func DecodeTo(data interface{}, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(gconv.Bytes(data)))
decoder.UseNumber()
return decoder.Decode(v)
}
// 解析json字符串为gjson.Json对象并返回操作对象指针
func DecodeToJson (b []byte) (*Json, error) {
if v, err := Decode(b); err != nil {
func DecodeToJson(data interface{}) (*Json, error) {
if v, err := Decode(gconv.Bytes(data)); err != nil {
return nil, err
} else {
return New(v), nil
@ -109,48 +120,53 @@ func DecodeToJson (b []byte) (*Json, error) {
}
// 支持多种配置文件类型转换为json格式内容并解析为gjson.Json对象
func Load (path string) (*Json, error) {
func Load(path string) (*Json, error) {
return LoadContent(gfcache.GetBinContents(path), gfile.Ext(path))
}
// 支持的配置文件格式xml, json, yaml/yml, toml,
// 默认为自动识别当无法检测成功时使用json解析。
func LoadContent(data []byte, dataType...string) (*Json, error) {
func LoadContent(data interface{}, dataType...string) (*Json, error) {
var err error
var result interface{}
b := gconv.Bytes(data)
t := "json"
if len(dataType) > 0 {
t = dataType[0]
} else {
if gregex.IsMatch(`<.+>.*</.+>`, data) {
if json.Valid(b) {
t = "json"
} else if gregex.IsMatch(`<.+>.*</.+>`, b) {
t = "xml"
} else if gregex.IsMatch(`\w+\s*:\s*\w+`, data) {
} else if gregex.IsMatch(`\w+\s*:\s*.+`, b) {
t = "yml"
} else if gregex.IsMatch(`\w+\s*=\s*\w+`, data) {
} else if gregex.IsMatch(`\w+\s*=\s*.+`, b) {
t = "toml"
}
}
// 其他数据格式解析
switch t {
case "xml", ".xml":
data, err = gxml.ToJson(data)
if err != nil {
return nil, err
}
case "json", ".json":
// ok
case "xml", ".xml":
b, err = gxml.ToJson(b)
case "yml", "yaml", ".yml", ".yaml":
data, err = gyaml.ToJson(data)
if err != nil {
return nil, err
}
case "yml", "yaml", ".yml", ".yaml":
b, err = gyaml.ToJson(b)
case "toml", ".toml":
data, err = gtoml.ToJson(data)
if err != nil {
return nil, err
}
case "toml", ".toml":
b, err = gtoml.ToJson(b)
default:
err = errors.New("nonsupport type " + t)
}
if err != nil {
return nil, err
}
if result == nil {
if err := json.Unmarshal(data, &result); err != nil {
decoder := json.NewDecoder(bytes.NewReader(b))
decoder.UseNumber()
if err := decoder.Decode(&result); err != nil {
return nil, err
}
}
@ -200,7 +216,7 @@ func (j *Json) GetMap(pattern string) map[string]interface{} {
return nil
}
// 将检索值转换为Json对象指针返回
// 将检索值转换为Json对象指针返回
func (j *Json) GetJson(pattern string) *Json {
result := j.Get(pattern)
if result != nil {
@ -209,18 +225,24 @@ func (j *Json) GetJson(pattern string) *Json {
return nil
}
// 获得一个数组[]interface{},方便操作,不需要自己做类型转换
// 注意如果获取的值不存在或者类型与json类型不匹配那么将会返回nil
func (j *Json) GetArray(pattern string) []interface{} {
result := j.Get(pattern)
if result != nil {
if r, ok := result.([]interface{}); ok {
return r
// 将检索值转换为Json对象指针数组返回。
func (j *Json) GetJsons(pattern string) []*Json {
array := j.GetArray(pattern)
if len(array) > 0 {
jsons := make([]*Json, len(array))
for i := 0; i < len(array); i++ {
jsons[i] = New(array[i], !j.mu.IsSafe())
}
return jsons
}
return nil
}
// 获得一个数组[]interface{},方便操作,不需要自己做类型转换。
func (j *Json) GetArray(pattern string) []interface{} {
return gconv.Interfaces(j.Get(pattern))
}
// 返回指定json中的string
func (j *Json) GetString(pattern string) string {
return gconv.String(j.Get(pattern))
@ -481,16 +503,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 +532,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 +568,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 +581,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 +600,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 +632,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 +671,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 +694,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
}
@ -687,34 +741,64 @@ func (j *Json) ToXml(rootTag...string) ([]byte, error) {
return gxml.Encode(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlString(rootTag...string) (string, error) {
b, e := j.ToXml(rootTag...)
return string(b), e
}
func (j *Json) ToXmlIndent(rootTag...string) ([]byte, error) {
return gxml.EncodeWithIndent(j.ToMap(), rootTag...)
}
func (j *Json) ToXmlIndentString(rootTag...string) (string, error) {
b, e := j.ToXmlIndent(rootTag...)
return string(b), e
}
func (j *Json) ToJson() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
return Encode(*(j.p))
}
func (j *Json) ToJsonString() (string, error) {
b, e := j.ToJson()
return string(b), e
}
func (j *Json) ToJsonIndent() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
return json.MarshalIndent(*(j.p), "", "\t")
}
func (j *Json) ToJsonIndentString() (string, error) {
b, e := j.ToJsonIndent()
return string(b), e
}
func (j *Json) ToYaml() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
return gyaml.Encode(*(j.p))
}
func (j *Json) ToYamlString() (string, error) {
b, e := j.ToYaml()
return string(b), e
}
func (j *Json) ToToml() ([]byte, error) {
j.mu.RLock()
defer j.mu.RUnlock()
return gtoml.Encode(*(j.p))
}
func (j *Json) ToTomlString() (string, error) {
b, e := j.ToToml()
return string(b), e
}
// 转换为指定的struct对象
func (j *Json) ToStruct(o interface{}) error {
j.mu.RLock()

View File

@ -0,0 +1,30 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gjson_test
import (
"github.com/gogf/gf/g/encoding/gjson"
"testing"
)
func Benchmark_Set1(b *testing.B) {
for i := 0; i < b.N; i++ {
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("k1.k11", []int{1,2,3})
}
}
func Benchmark_Set2(b *testing.B) {
for i := 0; i < b.N; i++ {
p := gjson.New([]string{"a"})
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
}
}

View File

@ -0,0 +1,253 @@
// 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"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_New(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j := gjson.New(data)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
})
}
func Test_NewUnsafe(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j := gjson.NewUnsafe(data)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}
func Test_Valid(t *testing.T) {
data1 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
data2 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`)
gtest.Case(t, func() {
gtest.Assert(gjson.Valid(data1), true)
gtest.Assert(gjson.Valid(data2), false)
})
}
func Test_Encode(t *testing.T) {
value := g.Slice{1, 2, 3}
gtest.Case(t, func() {
b, err := gjson.Encode(value)
gtest.Assert(err, nil)
gtest.Assert(b, []byte(`[1,2,3]`))
})
}
func Test_Decode(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
v, err := gjson.Decode(data)
gtest.Assert(err, nil)
gtest.Assert(v, g.Map{
"n" : 123456789,
"a" : g.Slice{1, 2, 3},
"m" : g.Map{
"k" : "v",
},
})
})
gtest.Case(t, func() {
var v interface{}
err := gjson.DecodeTo(data, &v)
gtest.Assert(err, nil)
gtest.Assert(v, g.Map{
"n" : 123456789,
"a" : g.Slice{1, 2, 3},
"m" : g.Map{
"k" : "v",
},
})
})
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}
func Test_SplitChar(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
j.SetSplitChar(byte('#'))
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m#k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a#1"), 2)
})
}
func Test_ViolenceCheck(t *testing.T) {
data := []byte(`{"m":{"a":[1,2,3], "v1.v2":"4"}}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("m.a.2"), 3)
gtest.Assert(j.Get("m.v1.v2"), nil)
j.SetViolenceCheck(true)
gtest.Assert(j.Get("m.v1.v2"), 4)
})
}
func Test_GetToVar(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
var m map[string]string
var n int
var a []int
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
j.GetToVar("n", &n)
j.GetToVar("m", &m)
j.GetToVar("a", &a)
gtest.Assert(n, "123456789")
gtest.Assert(m, g.Map{"k" : "v"})
gtest.Assert(a, g.Slice{1, 2, 3})
})
}
func Test_GetMap(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.Assert(j.GetMap("n"), g.Map{})
gtest.Assert(j.GetMap("m"), g.Map{"k" : "v"})
gtest.Assert(j.GetMap("a"), g.Map{})
})
}
func Test_GetJson(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
j2 := j.GetJson("m")
gtest.AssertNE(j2, nil)
gtest.Assert(j2.Get("k"), "v")
gtest.Assert(j2.Get("a"), nil)
gtest.Assert(j2.Get("n"), nil)
})
}
func Test_GetArray(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.Assert(j.GetArray("n"), g.Array{123456789})
gtest.Assert(j.GetArray("m"), g.Array{g.Map{"k":"v"}})
gtest.Assert(j.GetArray("a"), g.Array{1,2,3})
})
}
func Test_GetString(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.AssertEQ(j.GetString("n"), "123456789")
gtest.AssertEQ(j.GetString("m"), `{"k":"v"}`)
gtest.AssertEQ(j.GetString("a"), `[1,2,3]`)
gtest.AssertEQ(j.GetString("i"), "")
})
}
func Test_GetStrings(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.AssertEQ(j.GetStrings("n"), g.SliceStr{"123456789"})
gtest.AssertEQ(j.GetStrings("m"), g.SliceStr{`{"k":"v"}`})
gtest.AssertEQ(j.GetStrings("a"), g.SliceStr{"1", "2", "3"})
gtest.AssertEQ(j.GetStrings("i"), g.SliceStr{})
})
}
func Test_GetInterfaces(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.DecodeToJson(data)
gtest.Assert(err, nil)
gtest.AssertEQ(j.GetInterfaces("n"), g.Array{123456789})
gtest.AssertEQ(j.GetInterfaces("m"), g.Array{g.Map{"k":"v"}})
gtest.AssertEQ(j.GetInterfaces("a"), g.Array{1,2,3})
})
}
func Test_Len(t *testing.T) {
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a", 1)
p.Append("a", 2)
gtest.Assert(p.Len("a"), 2)
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a.b", 1)
p.Append("a.c", 2)
gtest.Assert(p.Len("a"), 2)
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Set("a", 1)
gtest.Assert(p.Len("a"), -1)
})
}
func Test_Append(t *testing.T) {
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a", 1)
p.Append("a", 2)
gtest.Assert(p.Get("a"), g.Slice{1, 2})
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a.b", 1)
p.Append("a.c", 2)
gtest.Assert(p.Get("a"), g.Map{
"b" : g.Slice{1},
"c" : g.Slice{2},
})
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Set("a", 1)
err := p.Append("a", 2)
gtest.AssertNE(err, nil)
gtest.Assert(p.Get("a"), 1)
})
}

View File

@ -0,0 +1,138 @@
// 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"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_Load_JSON(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
// JSON
gtest.Case(t, func() {
j, err := gjson.LoadContent(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
// JSON
gtest.Case(t, func() {
path := "test.json"
gfile.PutBinContents(path, data)
defer gfile.Remove(path)
j, err := gjson.Load(path)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}
func Test_Load_XML(t *testing.T) {
data := []byte(`<doc><a>1</a><a>2</a><a>3</a><m><k>v</k></m><n>123456789</n></doc>`)
// XML
gtest.Case(t, func() {
j, err := gjson.LoadContent(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("doc.n"), "123456789")
gtest.Assert(j.Get("doc.m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("doc.m.k"), "v")
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("doc.a.1"), 2)
})
// XML
gtest.Case(t, func() {
path := "test.xml"
gfile.PutBinContents(path, data)
defer gfile.Remove(path)
j, err := gjson.Load(path)
gtest.Assert(err, nil)
gtest.Assert(j.Get("doc.n"), "123456789")
gtest.Assert(j.Get("doc.m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("doc.m.k"), "v")
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("doc.a.1"), 2)
})
}
func Test_Load_YAML(t *testing.T) {
data := []byte(`
a:
- 1
- 2
- 3
m:
k: v
"n": 123456789
`)
// YAML
gtest.Case(t, func() {
j, err := gjson.LoadContent(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
// YAML
gtest.Case(t, func() {
path := "test.yaml"
gfile.PutBinContents(path, data)
defer gfile.Remove(path)
j, err := gjson.Load(path)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}
func Test_Load_TOML(t *testing.T) {
data := []byte(`
a = ["1", "2", "3"]
n = "123456789"
[m]
k = "v"
`)
// TOML
gtest.Case(t, func() {
j, err := gjson.LoadContent(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
// TOML
gtest.Case(t, func() {
path := "test.toml"
gfile.PutBinContents(path, data)
defer gfile.Remove(path)
j, err := gjson.Load(path)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789")
gtest.Assert(j.Get("m"), g.Map{"k" : "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}

View File

@ -0,0 +1,230 @@
// 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/encoding/gjson"
"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)
}
}

View File

@ -10,9 +10,8 @@ package gparser_test
import (
"bytes"
"testing"
"github.com/gogf/gf/g/encoding/gparser"
"fmt"
"testing"
)
func Test_Set1(t *testing.T) {
@ -23,7 +22,7 @@ func Test_Set1(t *testing.T) {
})
p.Set("k1.k11", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)) != 0 {
t.Error("expect:", string(e))
}
@ -37,7 +36,7 @@ func Test_Set2(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("0.1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -53,7 +52,7 @@ func Test_Set3(t *testing.T) {
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -69,7 +68,7 @@ func Test_Set4(t *testing.T) {
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -83,7 +82,7 @@ func Test_Set5(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -97,7 +96,7 @@ func Test_Set6(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -114,7 +113,7 @@ func Test_Set7(t *testing.T) {
})
p.Set("0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -131,7 +130,7 @@ func Test_Set8(t *testing.T) {
})
p.Set("0.0.0.0.0.0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -148,7 +147,7 @@ func Test_Set9(t *testing.T) {
})
p.Set("k1.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -163,7 +162,7 @@ func Test_Set10(t *testing.T) {
p := gparser.New(nil)
p.Set("a.b.c", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -178,7 +177,7 @@ func Test_Set11(t *testing.T) {
p, _ := gparser.LoadContent([]byte(`{"a":{"b":{"c":1}}}`), "json")
p.Remove("a.b.c")
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -193,7 +192,7 @@ func Test_Set12(t *testing.T) {
p.Set("0", 0)
p.Set("1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -208,7 +207,7 @@ func Test_Set13(t *testing.T) {
p.Set("array.0", 0)
p.Set("array.1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}

View File

@ -5,6 +5,8 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gxml provides accessing and converting for XML content.
//
// XML数据格式解析。
package gxml
import (
@ -19,9 +21,9 @@ import (
)
// 将XML内容解析为map变量
func Decode(xmlbyte []byte) (map[string]interface{}, error) {
prepare(xmlbyte)
return mxj.NewMapXml(xmlbyte)
func Decode(content []byte) (map[string]interface{}, error) {
prepare(content)
return mxj.NewMapXml(content)
}
// 将map变量解析为XML格式内容
@ -34,9 +36,9 @@ func EncodeWithIndent(v map[string]interface{}, rootTag...string) ([]byte, error
}
// XML格式内容直接转换为JSON格式内容
func ToJson(xmlbyte []byte) ([]byte, error) {
prepare(xmlbyte)
mv, err := mxj.NewMapXml(xmlbyte)
func ToJson(content []byte) ([]byte, error) {
prepare(content)
mv, err := mxj.NewMapXml(content)
if err == nil {
return mv.Json()
} else {
@ -61,10 +63,10 @@ func prepare(xmlbyte []byte) error {
if err != nil {
return err
}
xmlEncode := "UTF-8"
if len(matchStr) == 2 {
xmlEncode = matchStr[1]
xmlEncode = matchStr[1]
}
charset := mahonia.GetCharset(xmlEncode)

View File

@ -21,8 +21,10 @@ import (
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gview"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"time"
)
const (
@ -73,11 +75,18 @@ func View(name...string) *gview.View {
}
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEW, group)
return instances.GetOrSetFuncLock(key, func() interface{} {
path := cmdenv.Get("gf.gview.path", gfile.Pwd()).String()
view := gview.New(path)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
view.AddPath(p)
view := gview.New(gfile.Pwd())
// 自定义的环境变量/启动参数路径,优先级最高,覆盖默认的工作目录
if envPath := cmdenv.Get("gf.gview.path").String(); envPath != "" && gfile.Exists(envPath) {
view.SetPath(envPath)
}
// 二进制文件执行目录
if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
view.AddPath(selfPath)
}
// 开发环境源码main包目录
if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
view.AddPath(mainPath)
}
// 框架内置函数
view.BindFunc("config", funcConfig)
@ -94,21 +103,18 @@ func Config(file...string) *gcfg.Config {
}
return instances.GetOrSetFuncLock(fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile),
func() interface{} {
pwdPath := gfile.Pwd()
envPath := cmdenv.Get("gf.gcfg.path").String()
selfPath := gfile.SelfDir()
mainPath := gfile.MainPkgPath()
config := gcfg.New(pwdPath, configFile)
// 默认当前工作目录
config := gcfg.New(gfile.Pwd(), configFile)
// 自定义的环境变量/启动参数路径,优先级最高,覆盖默认的工作目录
if envPath != "" && gfile.Exists(envPath) {
if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" && gfile.Exists(envPath) {
config.SetPath(envPath)
}
// 二进制文件执行目录
if selfPath != "" && gfile.Exists(selfPath) {
if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
config.AddPath(selfPath)
}
// 开发环境源码main包目录
if mainPath != "" && gfile.Exists(mainPath) {
if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
config.AddPath(mainPath)
}
return config
@ -163,18 +169,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 +230,34 @@ func Redis(name...string) *gredis.Redis {
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group)
result := instances.GetOrSetFuncLock(key, func() interface{} {
if m := config.GetMap("redis"); m != nil {
// host:port[,db[,pass]]
// host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x]
if v, ok := m[group]; ok {
line := gconv.String(v)
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
if len(array) > 4 {
line := gconv.String(v)
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)\?(.+)`, line)
if len(array) == 6 {
parse, _ := gstr.Parse(array[5])
config := gredis.Config{
Host : array[1],
Port : gconv.Int(array[2]),
Db : gconv.Int(array[3]),
Pass : array[4],
}
if v, ok := parse["maxIdle"]; ok {
config.MaxIdle = gconv.Int(v)
}
if v, ok := parse["maxActive"]; ok {
config.MaxActive = gconv.Int(v)
}
if v, ok := parse["idleTimeout"]; ok {
config.IdleTimeout = gconv.TimeDuration(v)*time.Second
}
if v, ok := parse["maxConnLifetime"]; ok {
config.MaxConnLifetime = gconv.TimeDuration(v)*time.Second
}
return gredis.New(config)
}
array, _ = gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
if len(array) == 5 {
return gredis.New(gredis.Config{
Host : array[1],
Port : gconv.Int(array[2]),

View File

@ -7,7 +7,6 @@
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
@ -46,8 +45,9 @@ test = "v=3"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
default = "127.0.0.1:6379,7"
cache = "127.0.0.1:6379,8"
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
`
path := "config.toml"
err := gfile.PutContents(path, config)
@ -59,12 +59,14 @@ test = "v=3"
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
fmt.Println("gins Test_Redis", gins.Config().Get("test"))
//fmt.Println("gins Test_Redis", gins.Config().Get("test"))
redisDefault := gins.Redis()
redisCache := gins.Redis("cache")
redisDisk := gins.Redis("disk")
gtest.AssertNE(redisDefault, nil)
gtest.AssertNE(redisCache, nil)
gtest.AssertNE(redisDisk, nil)
r, err := redisDefault.Do("PING")
gtest.Assert(err, nil)
@ -73,6 +75,12 @@ test = "v=3"
r, err = redisCache.Do("PING")
gtest.Assert(err, nil)
gtest.Assert(r, "PONG")
_, err = redisDisk.Do("SET", "k", "v")
gtest.Assert(err, nil)
r, err = redisDisk.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
})
}

View File

@ -7,20 +7,10 @@
package g
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/util/gutil"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/container/gvar"
)
const (
LOG_LEVEL_ALL = glog.LEVEL_ALL
LOG_LEVEL_DEBU = glog.LEVEL_DEBU
LOG_LEVEL_INFO = glog.LEVEL_INFO
LOG_LEVEL_NOTI = glog.LEVEL_NOTI
LOG_LEVEL_WARN = glog.LEVEL_WARN
LOG_LEVEL_ERRO = glog.LEVEL_ERRO
LOG_LEVEL_CRIT = glog.LEVEL_CRIT
)
// NewVar creates a *Var.
@ -39,11 +29,18 @@ func Wait() {
// Dump dumps a variable to stdout with more manually readable.
//
// 打印变量
// 格式化打印变量.
func Dump(i...interface{}) {
gutil.Dump(i...)
}
// Export exports a variable to string with more manually readable.
//
// 格式化导出变量.
func Export(i...interface{}) string {
return gutil.Export(i...)
}
// Throw throws a exception, which can be caught by Catch function.
// It always be used in TryCatch function.
//
@ -55,4 +52,15 @@ func Throw(exception interface{}) {
// TryCatch does the try...catch... logic.
func TryCatch(try func(), catch ... func(exception interface{})) {
gutil.TryCatch(try, catch...)
}
// IsEmpty checks given value empty or not.
// false: integer(0), bool(false), slice/map(len=0), nil;
// true : other.
//
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}

View File

@ -0,0 +1,96 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// HTTP客户端请求.
package ghttp
import (
"github.com/gogf/gf/g/text/gregex"
"strings"
"time"
)
// 是否模拟浏览器模式(自动保存提交COOKIE)
func (c *Client) SetBrowserMode(enabled bool) {
c.browserMode = enabled
}
// 设置HTTP Header
func (c *Client) SetHeader(key, value string) {
c.header[key] = value
}
// 通过字符串设置HTTP Header
func (c *Client) SetHeaderRaw(header string) {
for _, line := range strings.Split(strings.TrimSpace(header), "\n") {
array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line)
if len(array) >= 3 {
c.header[array[1]] = array[2]
}
}
}
// 设置COOKIE
func (c *Client) SetCookie(key, value string) {
c.cookies[key] = value
}
// 使用Map设置COOKIE
func (c *Client) SetCookieMap(cookieMap map[string]string) {
for k, v := range cookieMap {
c.cookies[k] = v
}
}
// 设置请求的URL前缀
func (c *Client) SetPrefix(prefix string) {
c.prefix = prefix
}
// 设置请求过期时间
func (c *Client) SetTimeOut(t time.Duration) {
c.Timeout = t
}
// 设置HTTP访问账号密码
func (c *Client) SetBasicAuth(user, pass string) {
c.authUser = user
c.authPass = pass
}
// 设置失败重试次数及间隔,失败仅针对网络请求失败情况。
// 重试间隔时间单位为秒。
func (c *Client) SetRetry(retryCount int, retryInterval int) {
c.retryCount = retryCount
c.retryInterval = retryInterval
}
// 链式操作, See SetBrowserMode
func (c *Client) BrowserMode(enabled bool) *Client {
c.browserMode = enabled
return c
}
// 链式操作, See SetTimeOut
func (c *Client) TimeOut(t time.Duration) *Client {
c.Timeout = t
return c
}
// 链式操作, See SetBasicAuth
func (c *Client) BasicAuth(user, pass string) *Client {
c.authUser = user
c.authPass = pass
return c
}
// 链式操作, See SetRetry
func (c *Client) Retry(retryCount int, retryInterval int) *Client {
c.retryCount = retryCount
c.retryInterval = retryInterval
return c
}

View File

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

View File

@ -57,7 +57,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
return request
}
// 获取Web Socket连接对象(如果是非WS请求会失败注意检查然会的error结果)
// 获取Web Socket连接对象(如果是非WS请求会失败注意检查返回的error结果)
func (r *Request) WebSocket() (*WebSocket, error) {
if conn, err := wsUpgrader.Upgrade(r.Response.ResponseWriter.ResponseWriter, r.Request, nil); err == nil {
return &WebSocket {
@ -81,26 +81,29 @@ func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
// 获取原始请求输入二进制。
func (r *Request) GetRaw() []byte {
err := error(nil)
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
r.rawContent, err = ioutil.ReadAll(r.Body)
if err != nil {
r.Error("error reading request body: ", err)
}
}
return r.rawContent
}
// 获取原始请求输入字符串。
func (r *Request) GetRawString() string {
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
}
return string(r.rawContent)
return string(r.GetRaw())
}
// 获取原始json请求输入字符串并解析为json对象
func (r *Request) GetJson() *gjson.Json {
data := r.GetRaw()
if data != nil {
if len(data) > 0 {
if j, err := gjson.DecodeToJson(data); err == nil {
return j
} else {
r.Error(err, ": ", string(data))
}
}
return nil
@ -221,14 +224,14 @@ func (r *Request) GetReferer() string {
// 获得结构体对象的参数名称标签构成map返回
func (r *Request) getStructParamsTagMap(object interface{}) map[string]string {
tagmap := make(map[string]string)
tagMap := make(map[string]string)
fields := structs.Fields(object)
for _, field := range fields {
if tag := field.Tag("params"); tag != "" {
for _, v := range strings.Split(tag, ",") {
tagmap[strings.TrimSpace(v)] = field.Name()
tagMap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagmap
return tagMap
}

View File

@ -26,21 +26,21 @@ func (r *Request) setBasicAuth(tips...string) {
}
// 设置HTTP基础账号密码认证如果用户没有提交账号密码那么提示用户输出信息。
// 验证成功之后返回true否则返回false
// 验证成功之后返回true否则返回false
func (r *Request) BasicAuth(user, pass string, tips...string) bool {
auth := r.Header.Get("Authorization")
if auth == "" {
r.setBasicAuth(tips...)
return false
}
auths := strings.SplitN(auth, " ", 2)
if len(auths) != 2 {
authArray := strings.SplitN(auth, " ", 2)
if len(authArray) != 2 {
r.Response.WriteStatus(http.StatusForbidden)
return false
}
switch auths[0] {
switch authArray[0] {
case "Basic":
authStr, err := gbase64.Decode(auths[1])
authStr, err := gbase64.Decode(authArray[1])
if err != nil {
r.Response.WriteStatus(http.StatusForbidden, err.Error())
return false
@ -54,11 +54,12 @@ func (r *Request) BasicAuth(user, pass string, tips...string) bool {
r.setBasicAuth(tips...)
return false
}
return true
default:
r.Response.WriteStatus(http.StatusForbidden)
return false
}
return true
return false
}

View File

@ -0,0 +1,14 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp
import "fmt"
// 打印error日志
func (r *Request) Error(value... interface{}) {
r.Server.handleErrorLog(fmt.Sprint(value...), r)
}

View File

@ -169,7 +169,7 @@ func (r *Request) GetPostMap(def...map[string]string) map[string]string {
}
// 将所有的request参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) {
func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) error {
tagmap := r.getStructParamsTagMap(object)
if len(mapping) > 0 {
for k, v := range mapping[0] {
@ -180,5 +180,5 @@ func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]strin
for k, v := range r.GetPostMap() {
params[k] = v
}
gconv.Struct(params, object, tagmap)
return gconv.Struct(params, object, tagmap)
}

View File

@ -177,7 +177,7 @@ func (r *Request) GetQueryMap(def ... map[string]string) map[string]string {
}
// 将所有的get参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) {
func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) error {
tagmap := r.getStructParamsTagMap(object)
if len(mapping) > 0 {
for k, v := range mapping[0] {
@ -188,5 +188,5 @@ func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]stri
for k, v := range r.GetQueryMap() {
params[k] = v
}
gconv.Struct(params, object, tagmap)
return gconv.Struct(params, object, tagmap)
}

View File

@ -162,7 +162,7 @@ func (r *Request) GetRequestMap(def...map[string]string) map[string]string {
}
// 将所有的request参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]string) {
func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]string) error {
tagmap := r.getStructParamsTagMap(object)
if len(mapping) > 0 {
for k, v := range mapping[0] {
@ -178,6 +178,6 @@ func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]st
params = j.ToMap()
}
}
gconv.Struct(params, object, tagmap)
return gconv.Struct(params, object, tagmap)
}

View File

@ -15,7 +15,6 @@ import (
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/genv"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc"
"github.com/gogf/gf/g/os/gtimer"
@ -108,8 +107,6 @@ const (
HOOK_AFTER_SERVE = "AfterServe"
HOOK_BEFORE_OUTPUT = "BeforeOutput"
HOOK_AFTER_OUTPUT = "AfterOutput"
HOOK_BEFORE_CLOSE = "BeforeClose"
HOOK_AFTER_CLOSE = "AfterClose"
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
gDEFAULT_SERVER = "default"
@ -187,9 +184,9 @@ func serverProcessInit() {
}
// 是否处于开发环境
if gfile.MainPkgPath() != "" {
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
}
//if gfile.MainPkgPath() != "" {
// glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
//}
}
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
@ -217,8 +214,6 @@ func GetServer(name...interface{}) (*Server) {
servedCount : gtype.NewInt(),
logger : glog.New(),
}
// 日志的标准输出默认关闭,但是错误信息会特殊处理
s.logger.SetStdPrint(false)
// 初始化时使用默认配置
s.SetConfig(defaultServerConfig)
// 记录到全局ServerMap中
@ -237,12 +232,17 @@ func (s *Server) Start() error {
return errors.New("server is already running")
}
// 没有注册任何路由,且没有开启文件服务,那么提示错误
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
glog.Fatal("[ghttp] no router set or static feature enabled, did you forget import the router?")
}
// 底层http server配置
if s.config.Handler == nil {
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) {
@ -277,7 +277,7 @@ func (s *Server) Start() error {
if gproc.IsChild() {
gtimer.SetTimeout(2*time.Second, func() {
if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil {
glog.Error("ghttp server error in process communication:", err)
glog.Error("[ghttp] server error in process communication:", err)
}
})
}
@ -350,7 +350,10 @@ func (s *Server) GetRouteMap() string {
}
addr := s.config.Addr
if s.config.HTTPSAddr != "" {
addr += ",tls" + s.config.HTTPSAddr
if len(addr) > 0 {
addr += ","
}
addr += "tls" + s.config.HTTPSAddr
}
for _, a := range m {
data := make([]string, 8)
@ -398,7 +401,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 +483,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()
}

View File

@ -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 // 默认访问的文件列表
@ -74,8 +76,9 @@ type ServerConfig struct {
// 日志配置
LogPath string // 存放日志的目录路径
LogHandler LogHandler // 自定义日志处理回调方法
ErrorLogEnabled bool // 是否开启error log
AccessLogEnabled bool // 是否开启access log
LogStdPrint bool // 是否打印日志到终端(默认开启)
ErrorLogEnabled bool // 是否开启error log(默认开启)
AccessLogEnabled bool // 是否开启access log(默认关闭)
// 其他设置
NameToUriType int // 服务注册时对象和方法名称转换为URI时的规则
@ -108,12 +111,11 @@ var defaultServerConfig = ServerConfig {
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
SessionIdName : gDEFAULT_SESSION_ID_NAME,
LogStdPrint : true,
ErrorLogEnabled : true,
AccessLogEnabled : false,
GzipContentTypes : defaultGzipContentTypes,
DumpRouteMap : true,
RouterCacheExpire : 60,
Rewrites : make(map[string]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

View File

@ -23,6 +23,15 @@ func (s *Server)SetLogPath(path string) {
s.logger.SetPath(path)
}
// 设置日志内容是否输出到终端
func (s *Server)SetLogStdPrint(enabled bool) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.config.LogStdPrint = enabled
}
// 设置是否开启access log日志功能
func (s *Server)SetAccessLogEnabled(enabled bool) {
if s.Status() == SERVER_STATUS_RUNNING {

View File

@ -57,15 +57,12 @@ func (s *Server)SetServerRoot(root string) {
return
}
// RealPath的作用除了校验地址正确性以外还转换分隔符号为当前系统正确的文件分隔符号
path := gfile.RealPath(root)
if path == "" {
path = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + root)
realPath, err := gfile.Search(root)
if err != nil {
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: %s`, err.Error()))
}
if path == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root))
}
glog.Debug("[ghttp] SetServerRoot path:", path)
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
glog.Debug("[ghttp] SetServerRoot path:", realPath)
s.config.SearchPaths = []string{strings.TrimRight(realPath, gfile.Separator)}
s.config.FileServerEnabled = true
}
@ -75,13 +72,9 @@ func (s *Server) AddSearchPath(path string) {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
realPath := gfile.RealPath(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
}
if realPath == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: path "%s" does not exist`, path))
realPath, err := gfile.Search(path)
if err != nil {
glog.Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: %s`, err.Error()))
}
s.config.SearchPaths = append(s.config.SearchPaths, realPath)
s.config.FileServerEnabled = true
@ -93,13 +86,9 @@ func (s *Server) AddStaticPath(prefix string, path string) {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
realPath := gfile.RealPath(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
}
if realPath == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] AddStaticPath failed: path "%s" does not exist`, path))
realPath, err := gfile.Search(path)
if err != nil {
glog.Fatal(fmt.Sprintf(`[ghttp] AddStaticPath failed: %s`, err.Error()))
}
addItem := staticPathItem {
prefix : prefix,

View File

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

View File

@ -52,6 +52,13 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
if !request.IsExited() {
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
}
// error log
if e := recover(); e != nil {
request.Response.WriteStatus(http.StatusInternalServerError)
s.handleErrorLog(e, request)
}
// access log
s.handleAccessLog(request)
// 输出Cookie
request.Cookie.Output()
// 输出缓冲区
@ -60,18 +67,8 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
// 事件 - BeforeClose
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
// access log
s.handleAccessLog(request)
// error log使用recover进行判断
if e := recover(); e != nil {
request.Response.WriteStatus(http.StatusInternalServerError)
s.handleErrorLog(e, request)
}
// 更新Session会话超时时间
request.Session.UpdateExpire()
s.callHookHandler(HOOK_AFTER_CLOSE, request)
}()
// ============================================================

View File

@ -9,7 +9,7 @@ package ghttp
import (
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
)
// 处理服务错误信息主要是panichttp请求的status由access log进行管理
@ -22,19 +22,19 @@ func (s *Server) handleAccessLog(r *Request) {
v(r)
return
}
content := fmt.Sprintf(`"%s %s %s %s" %d`,
r.Method, r.Host, r.URL.String(), r.Proto,
content := fmt.Sprintf(`%d "%s %s %s %s"`,
r.Response.Status,
r.Method, r.Host, r.URL.String(), r.Proto,
)
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
s.logger.Cat("access").Backtrace(false, 2).Println(content)
s.logger.Cat("access").Backtrace(false, 2).StdPrint(s.config.LogStdPrint).Println(content)
}
// 处理服务错误信息主要是panichttp请求的status由access log进行管理
func (s *Server) handleErrorLog(error interface{}, r *Request) {
// 错误输出默认是开启的
if !s.IsErrorLogEnabled() && gfile.MainPkgPath() == "" {
if !s.IsErrorLogEnabled() {
return
}
@ -46,17 +46,11 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) {
// 错误日志信息
content := fmt.Sprintf(`%v, "%s %s %s %s"`, error, r.Method, r.Host, r.URL.String(), r.Proto)
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
if s.logger.GetPath() == "" {
// 错误信息特殊处理,在未开启日志文件保存时强制强制输出到终端
s.logger.Cat("error").Backtrace(true, 2).StdPrint(true).Error(content)
if r.LeaveTime > r.EnterTime {
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
} else {
s.logger.Cat("error").Backtrace(true, 2).Error(content)
// 开发环境下(MainPkgPath)自动输出错误信息到标准输出
if gfile.MainPkgPath() != "" {
s.logger.Cat("error").Backtrace(true, 2).StdPrint(true).Error(content)
}
content += fmt.Sprintf(` %.3f`, float64(gtime.Microsecond() - r.EnterTime)/1000)
}
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
s.logger.Cat("error").Backtrace(true, 2).StdPrint(s.config.LogStdPrint).Error(content)
}

View File

@ -37,7 +37,7 @@ func (s *Server)parsePattern(pattern string) (domain, method, path string, err e
}
}
if path == "" {
err = errors.New("invalid pattern")
err = errors.New("invalid pattern: URI should not be empty")
}
// 去掉末尾的"/"符号,与路由匹配时处理一致
if path != "/" {
@ -72,11 +72,11 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
}
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
glog.Error("invalid pattern:", pattern)
glog.Error("invalid pattern:", pattern, err)
return
}
if len(uri) == 0 || uri[0] != '/' {
glog.Error("invalid pattern:", pattern)
glog.Error("invalid pattern:", pattern, "URI should lead with '/'")
return
}
// 注册地址记录及重复注册判断
@ -84,7 +84,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
caller := s.getHandlerRegisterCallerLine(handler)
if len(hook) == 0 {
if item, ok := s.routesMap[regkey]; ok {
glog.Errorfln(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
return
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -119,6 +119,9 @@ func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, expire
if f, ok := value.(func() interface {}); ok {
value = f()
}
if value == nil {
return nil
}
c.data[key] = memCacheItem{v : value, e : expireTimestamp}
c.dataMu.Unlock()
c.eventList.PushBack(&memCacheEvent{k : key, e : expireTimestamp})

View File

@ -86,10 +86,40 @@ func (c *Config) filePath(file...string) (path string) {
// 设置配置管理器的配置文件存放目录绝对路径
func (c *Config) SetPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
if realPath == "" {
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
glog.Error(fmt.Sprintf(`[gcfg] SetPath failed: %s`, err.Error()))
// 判断相对路径
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
}
// 目录不存在错误处理
if realPath == "" {
buffer := bytes.NewBuffer(nil)
if c.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" should be directory type`, path))
glog.Error(err)
return err
}
// 重复判断
@ -112,10 +142,40 @@ func (c *Config) SetViolenceCheck(check bool) {
// 添加配置管理器的配置文件搜索路径
func (c *Config) AddPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
if realPath == "" {
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
glog.Error(fmt.Sprintf(`[gcfg] AddPath failed: %s`, err.Error()))
// 判断相对路径
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
}
// 目录不存在错误处理
if realPath == "" {
buffer := bytes.NewBuffer(nil)
if c.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" should be directory type`, path))
glog.Error(err)
return err
}
// 重复判断
@ -155,23 +215,38 @@ func (c *Config) SetFileName(name string) {
c.name.Set(name)
}
// 获取配置管理对象的默认文件名称
func (c *Config) GetFileName() string {
return c.name.Val()
}
// 添加配置文件到配置管理器中,第二个参数为非必须,如果不输入表示添加进入默认的配置名称中
// 内部带缓存控制功能。
func (c *Config) getJson(file...string) *gjson.Json {
filePath := c.filePath(file...)
if filePath == "" {
name := c.name.Val()
if len(file) > 0 {
name = file[0]
}
r := c.jsons.GetOrSetFuncLock(name, func() interface{} {
filePath := c.filePath(file...)
if filePath == "" {
return nil
}
if j, err := gjson.Load(filePath); err == nil {
j.SetViolenceCheck(c.vc.Val())
// 添加配置文件监听,如果有任何变化,删除文件内容缓存,下一次查询会自动更新
gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
c.jsons.Remove(name)
})
return j
} else {
glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
}
return nil
}
if r := c.jsons.Get(filePath); r != nil {
})
if r != nil {
return r.(*gjson.Json)
}
if j, err := gjson.Load(filePath); err == nil {
j.SetViolenceCheck(c.vc.Val())
c.addMonitor(filePath)
c.jsons.Set(filePath, j)
return j
} else {
glog.Errorfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
}
return nil
}
@ -362,14 +437,3 @@ func (c *Config) Reload() {
c.jsons.Clear()
}
// 添加文件监控
func (c *Config) addMonitor(path string) {
// 防止多goroutine同时调用
if c.jsons.Get(path) != nil {
return
}
gfsnotify.Add(path, func(event *gfsnotify.Event) {
// 删除文件内容缓存,下一次查询会自动更新
c.jsons.Remove(event.Path)
})
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,63 @@
// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gfile
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/container/garray"
)
// 如果给定绝对路径将会去掉其中的相对路径符号后返回;
// 如果是给定的相对路径,那么将会按照以下路径优先级搜索文件(重复路径会去重)
// prioritySearchPaths、当前工作目录、二进制文件目录、源码main包目录(开发环境下)
func Search(name string, prioritySearchPaths...string) (realPath string, err error) {
// 是否绝对路径
realPath = RealPath(name)
if realPath != "" {
return
}
// 相对路径搜索
array := garray.NewStringArray(true)
// 自定义优先路径
array.Append(prioritySearchPaths...)
// 用户工作目录
array.Append(Pwd())
// 二进制所在目录
array.Append(SelfDir())
// 源码main包目录
if path := MainPkgPath(); path != "" {
array.Append(path)
}
// 路径去重
array.Unique()
// 执行相对路径搜索
array.RLockFunc(func(array []string) {
path := ""
for _, v := range array {
path = RealPath(v + Separator + name)
if path != "" {
realPath = path
break
}
}
})
// 目录不存在错误处理
if realPath == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("cannot find file/folder \"%s\" in following paths:", name))
array.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s", k + 1, v))
}
})
err = errors.New(buffer.String())
}
return
}

View File

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

View File

@ -12,7 +12,7 @@ import (
)
// To is a chaining function,
// which redirects current logging content output to the sepecified <writer>.
// which redirects current logging content output to the specified <writer>.
//
// 链式操作设置下一次写入日志内容的Writer
func (l *Logger) To(writer io.Writer) *Logger {
@ -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.
//

View File

@ -24,8 +24,8 @@ import (
"github.com/gogf/gf/g/os/gspath"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gview/internal/text/template"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"strings"
"sync"
)
@ -48,16 +48,10 @@ type FuncMap = map[string]interface{}
// 默认的视图对象
var viewObj *View
// 初始化默认的视图对象
// 初始化默认的视图对象, 默认加载包不会初始化,使用包方法才会初始化模板引擎对象。
func checkAndInitDefaultView() {
if viewObj == nil {
// gfile.MainPkgPath() 用以判断是否开发环境
mainPkgPath := gfile.MainPkgPath()
if gfile.MainPkgPath() == "" {
viewObj = New(gfile.Pwd())
} else {
viewObj = New(mainPkgPath)
}
viewObj = New(gfile.Pwd())
}
}
@ -106,13 +100,40 @@ func New(path...string) *View {
// 设置模板目录绝对路径
func (view *View) SetPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
// 判断相对路径
view.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
}
// 目录不存在错误处理
if realPath == "" {
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
glog.Error(fmt.Sprintf(`[gview] SetPath failed: %s`, err.Error()))
buffer := bytes.NewBuffer(nil)
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] SetPath failed: cannot find directory \"%s\" in following paths:", path))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gview] SetPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gview] SetPath failed: path "%s" should be directory type`, path))
glog.Error(err)
return err
}
// 重复判断
@ -127,13 +148,40 @@ func (view *View) SetPath(path string) error {
// 添加模板目录搜索路径
func (view *View) AddPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
// 判断相对路径
view.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
}
// 目录不存在错误处理
if realPath == "" {
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
glog.Error(fmt.Sprintf(`[gview] AddPath failed: %s`, err.Error()))
buffer := bytes.NewBuffer(nil)
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] AddPath failed: cannot find directory \"%s\" in following paths:", path))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gview] AddPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gview] AddPath failed: path "%s" should be directory type`, path))
glog.Error(err)
return err
}
// 重复判断

View File

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

View File

@ -12,92 +12,21 @@ import (
"strings"
)
// Parses the string into variables.
// f1=m&f2=n -> map[f1:m f2:n]
// f[a]=m&f[b]=n -> map[f:map[a:m b:n]]
// Parses the string into map[string]interface{}.
//
// f1=m&f2=n -> map[f1:m f2:n]
// f[a]=m&f[b]=n -> map[f:map[a:m b:n]]
// f[a][a]=m&f[a][b]=n -> map[f:map[a:map[a:m b:n]]]
// f[]=m&f[]=n -> map[f:[m n]]
// f[a][]=m&f[a][]=n -> map[f:map[a:[m n]]]
// f[][]=m&f[][]=n -> map[f:[map[]]] // Currently does not support nested slice.
// f=m&f[a]=n -> error // This is not the same as PHP.
// a .[[b=c -> map[a___[b:c]
// f[]=m&f[]=n -> map[f:[m n]]
// f[a][]=m&f[a][]=n -> map[f:map[a:[m n]]]
// f[][]=m&f[][]=n -> map[f:[map[]]] // Currently does not support nested slice.
// f=m&f[a]=n -> error
// a .[[b=c -> map[a___[b:c]
//
// 将字符串解析成Map。
func Parse(encodedString string, result map[string]interface{}) error {
// build nested map.
var build func(map[string]interface{}, []string, interface{}) error
build = func(result map[string]interface{}, keys []string, value interface{}) error {
length := len(keys)
// trim ',"
key := strings.Trim(keys[0], "'\"")
if length == 1 {
result[key] = value
return nil
}
// The end is slice. like f[], f[a][]
if keys[1] == "" && length == 2 {
// todo nested slice
if key == "" {
return nil
}
val, ok := result[key]
if !ok {
result[key] = []interface{}{value}
return nil
}
children, ok := val.([]interface{})
if !ok {
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
}
result[key] = append(children, value)
return nil
}
// The end is slice + map. like f[][a]
if keys[1] == "" && length > 2 && keys[2] != "" {
val, ok := result[key]
if !ok {
result[key] = []interface{}{}
val = result[key]
}
children, ok := val.([]interface{})
if !ok {
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
}
if l := len(children); l > 0 {
if child, ok := children[l-1].(map[string]interface{}); ok {
if _, ok := child[keys[2]]; !ok {
build(child, keys[2:], value)
return nil
}
}
}
child := map[string]interface{}{}
build(child, keys[2:], value)
result[key] = append(children, child)
return nil
}
// map. like f[a], f[a][b]
val, ok := result[key]
if !ok {
result[key] = map[string]interface{}{}
val = result[key]
}
children, ok := val.(map[string]interface{})
if !ok {
return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val)
}
if err := build(children, keys[1:], value); err != nil {
return err
}
return nil
}
// split encodedString.
parts := strings.Split(encodedString, "&")
func Parse(s string) (result map[string]interface{}, err error) {
result = make(map[string]interface{})
parts := strings.Split(s, "&")
for _, part := range parts {
pos := strings.Index(part, "=")
if pos <= 0 {
@ -105,7 +34,7 @@ func Parse(encodedString string, result map[string]interface{}) error {
}
key, err := url.QueryUnescape(part[:pos])
if err != nil {
return err
return nil, err
}
for key[0] == ' ' {
key = key[1:]
@ -115,9 +44,8 @@ func Parse(encodedString string, result map[string]interface{}) error {
}
value, err := url.QueryUnescape(part[pos+1:])
if err != nil {
return err
return nil, err
}
// split into multiple keys
var keys []string
left := 0
@ -127,11 +55,11 @@ func Parse(encodedString string, result map[string]interface{}) error {
} else if k == ']' {
if left > 0 {
if len(keys) == 0 {
keys = append(keys, key[:left])
keys = append(keys, key[ : left])
}
keys = append(keys, key[left+1:i])
keys = append(keys, key[left + 1 : i])
left = 0
if i+1 < len(key) && key[i+1] != '[' {
if i+1 < len(key) && key[i + 1] != '[' {
break
}
}
@ -149,7 +77,7 @@ func Parse(encodedString string, result map[string]interface{}) error {
first += string(chr)
}
if chr == '[' {
first += keys[0][i+1:]
first += keys[0][i + 1: ]
break
}
}
@ -157,9 +85,78 @@ func Parse(encodedString string, result map[string]interface{}) error {
// build nested map
if err := build(result, keys, value); err != nil {
return err
return nil, err
}
}
return result, nil
}
// build nested map.
func build(result map[string]interface{}, keys []string, value interface{}) error {
length := len(keys)
// trim ',"
key := strings.Trim(keys[0], "'\"")
if length == 1 {
result[key] = value
return nil
}
// The end is slice. like f[], f[a][]
if keys[1] == "" && length == 2 {
// todo nested slice
if key == "" {
return nil
}
val, ok := result[key]
if !ok {
result[key] = []interface{}{value}
return nil
}
children, ok := val.([]interface{})
if !ok {
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
}
result[key] = append(children, value)
return nil
}
// The end is slice + map. like f[][a]
if keys[1] == "" && length > 2 && keys[2] != "" {
val, ok := result[key]
if !ok {
result[key] = []interface{}{}
val = result[key]
}
children, ok := val.([]interface{})
if !ok {
return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val)
}
if l := len(children); l > 0 {
if child, ok := children[l-1].(map[string]interface{}); ok {
if _, ok := child[keys[2]]; !ok {
build(child, keys[2:], value)
return nil
}
}
}
child := map[string]interface{}{}
build(child, keys[2:], value)
result[key] = append(children, child)
return nil
}
// map. like f[a], f[a][b]
val, ok := result[key]
if !ok {
result[key] = map[string]interface{}{}
val = result[key]
}
children, ok := val.(map[string]interface{})
if !ok {
return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val)
}
if err := build(children, keys[1:], value); err != nil {
return err
}
return nil
}

View File

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

View File

@ -9,6 +9,7 @@
package gstr_test
import (
"github.com/gogf/gf/g/text/gstr"
"testing"
)
@ -31,4 +32,22 @@ func Benchmark_BytesToString(b *testing.B) {
}
}
}
func Benchmark_Parse1(b *testing.B) {
for i := 0; i < b.N; i++ {
gstr.Parse("a=1&b=2")
}
}
func Benchmark_Parse2(b *testing.B) {
for i := 0; i < b.N; i++ {
gstr.Parse("m[]=1&m[]=2")
}
}
func Benchmark_Parse3(b *testing.B) {
for i := 0; i < b.N; i++ {
gstr.Parse("m[a1][b1][c1][d1]=1&m[a2][b2]=2&m[a3][b3][c3]=3")
}
}

View File

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

View File

@ -0,0 +1,75 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package gstr_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/text/gstr"
"testing"
)
func Test_Parse(t *testing.T) {
gtest.Case(t, func() {
// slice
m, err := gstr.Parse("a[]=1&a[]=2")
gtest.Assert(err, nil)
gtest.Assert(m, g.Map{
"a" : g.Slice{"1", "2"},
})
// map
m, err = gstr.Parse("a=1&b=2&c=3")
gtest.Assert(err, nil)
gtest.Assert(m, g.Map{
"a" : "1",
"b" : "2",
"c" : "3",
})
// map
m, err = gstr.Parse("m[a]=1&m[b]=2&m[c]=3")
gtest.Assert(err, nil)
gtest.Assert(m, g.Map{
"m" : g.Map{
"a" : "1",
"b" : "2",
"c" : "3",
},
})
// map - slice
m, err = gstr.Parse("m[a][]=1&m[a][]=2")
gtest.Assert(err, nil)
gtest.Assert(m, g.Map{
"m" : g.Map{
"a" : g.Slice{"1", "2"},
},
})
// map - complicated
m, err = gstr.Parse("m[a1][b1][c1][d1]=1&m[a2][b2]=2&m[a3][b3][c3]=3")
gtest.Assert(err, nil)
gtest.Assert(m, g.Map{
"m" : g.Map{
"a1" : g.Map{
"b1" : g.Map{
"c1" : g.Map{
"d1" : "1",
},
},
},
"a2" : g.Map{
"b2" : "2",
},
"a3" : g.Map{
"b3" : g.Map{
"c3" : "3",
},
},
},
})
})
}

View File

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

View File

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

View File

@ -8,6 +8,7 @@ package gconv
import (
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
@ -102,6 +103,11 @@ func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
// 只转换公开属性
fieldName := rt.Field(i).Name
if !gstr.IsLetterUpper(fieldName[0]) {
continue
}
name = ""
// 检查tag, 支持gconv, json标签, 优先使用gconv
if len(noTagCheck) == 0 || !noTagCheck[0] {
@ -111,7 +117,7 @@ func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
}
}
if name == "" {
name = strings.TrimSpace(rt.Field(i).Name)
name = strings.TrimSpace(fieldName)
} else {
// 支持标准库json特性: -, omitempty
name = strings.TrimSpace(name)

View File

@ -7,6 +7,7 @@
package gconv
import (
"github.com/gogf/gf/g/text/gstr"
"reflect"
)
@ -311,7 +312,12 @@ func Interfaces(i interface{}) []interface{} {
array = append(array, rv.Index(i).Interface())
}
case reflect.Struct:
rt := rv.Type()
for i := 0; i < rv.NumField(); i++ {
// 只获取公开属性
if !gstr.IsLetterUpper(rt.Field(i).Name[0]) {
continue
}
array = append(array, rv.Field(i).Interface())
}
default:

View File

@ -78,6 +78,10 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
attrMap := make(map[string]struct{})
elemType := elem.Type()
for i := 0; i < elem.NumField(); i++ {
// 只转换公开属性
if !gstr.IsLetterUpper(elemType.Field(i).Name[0]) {
continue
}
attrMap[elemType.Field(i).Name] = struct{}{}
}
for mapK, mapV := range paramsMap {

View File

@ -112,3 +112,15 @@ func Test_Map_StructWithJsonTag(t *testing.T) {
gtest.Assert(map2["password2"], "456")
})
}
// 私有属性不会进行转换
func Test_Map_PrivateAttribute(t *testing.T) {
type User struct {
Id int
name string
}
gtest.Case(t, func() {
user := &User{1, "john"}
gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
})
}

View File

@ -7,6 +7,7 @@
package gconv_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/test/gtest"
"testing"
@ -23,3 +24,15 @@ func Test_Slice(t *testing.T) {
gtest.AssertEQ(gconv.Interfaces(value), []interface{}{123.456})
})
}
// 私有属性不会进行转换
func Test_Slice_PrivateAttribute(t *testing.T) {
type User struct {
Id int
name string
}
gtest.Case(t, func() {
user := &User{1, "john"}
gtest.Assert(gconv.Interfaces(user), g.Slice{1})
})
}

View File

@ -8,12 +8,11 @@ package gconv_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
)
func Test_Struct_Basic1(t *testing.T) {
gtest.Case(t, func() {
type User struct {
@ -302,3 +301,18 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
}
})
}
// 私有属性不会进行转换
func Test_Struct_PrivateAttribute(t *testing.T) {
type User struct {
Id int
name string
}
gtest.Case(t, func() {
user := new(User)
err := gconv.Struct(g.Map{"id" : 1, "name" : "john"}, user)
gtest.Assert(err, nil)
gtest.Assert(user.Id, 1)
gtest.Assert(user.name, "")
})
}

View File

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

View File

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

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

View File

@ -13,17 +13,27 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/util/gconv"
"os"
"reflect"
"runtime"
)
// 格式化打印变量(类似于PHP-vardump)
// 格式化打印变量
func Dump(i...interface{}) {
s := Export(i...)
if s != "" {
fmt.Println(s)
}
}
// 格式化导出变量
func Export(i...interface{}) string {
buffer := bytes.NewBuffer(nil)
for _, v := range i {
if b, ok := v.([]byte); ok {
fmt.Print(string(b))
buffer.Write(b)
} else {
// 主要针对 map[interface{}]* 进行处理json无法进行encode
// 这里强制对所有map进行反射处理转换
@ -32,24 +42,20 @@ func Dump(i...interface{}) {
m := make(map[string]interface{})
keys := refValue.MapKeys()
for _, k := range keys {
key := gconv.String(k.Interface())
m[key] = refValue.MapIndex(k).Interface()
m[gconv.String(k.Interface())] = refValue.MapIndex(k).Interface()
}
v = m
}
// json encode并打印到终端
buffer := &bytes.Buffer{}
// JSON格式化
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", "\t")
if err := encoder.Encode(v); err == nil {
fmt.Print(buffer.String())
} else {
if err := encoder.Encode(v); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
//fmt.Println()
}
return buffer.String()
}
// 打印完整的调用回溯信息
@ -84,4 +90,15 @@ func TryCatch(try func(), catch ... func(exception interface{})) {
try()
}
// IsEmpty checks given value empty or not.
// false: integer(0), bool(false), slice/map(len=0), nil;
// true : other.
//
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}

View File

@ -7,6 +7,7 @@
package gvalid
import (
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/third/github.com/fatih/structs"
"strings"
@ -59,14 +60,19 @@ func CheckStruct(object interface{}, rules interface{}, msgs...CustomMsg) *Error
case map[string]string:
checkRules = v
}
// 首先, 按照属性循环一遍将strcut的属性、数值、tag解析
// 首先, 按照属性循环一遍将struct的属性、数值、tag解析
for _, field := range fields {
params[field.Name()] = field.Value()
fieldName := field.Name()
// 只检测公开属性
if !gstr.IsLetterUpper(fieldName[0]) {
continue
}
params[fieldName] = field.Value()
if tag := field.Tag("gvalid"); tag != "" {
// sequence tag == struct tag, 这里的name为别名
name, rule, msg := parseSequenceTag(tag)
if len(name) == 0 {
name = field.Name()
name = fieldName
}
// params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用
if _, ok := params[name]; !ok {
@ -151,3 +157,4 @@ func CheckStruct(object interface{}, rules interface{}, msgs...CustomMsg) *Error
}
return nil
}

View File

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

View File

@ -0,0 +1,16 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
conn := g.Redis().Conn()
defer conn.Close()
conn.Do("SET", "k", "v")
v, _ := conn.Do("GET", "k")
fmt.Println(gconv.String(v))
}

View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
conn := g.Redis().Conn()
defer conn.Close()
conn.Send("SET", "foo", "bar")
conn.Send("GET", "foo")
conn.Flush()
// reply from SET
conn.Receive()
// reply from GET
v, _ := conn.Receive()
fmt.Println(gconv.String(v))
}

View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
conn := g.Redis().Conn()
defer conn.Close()
_, err := conn.Do("SUBSCRIBE", "channel")
if err != nil {
panic(err)
}
for {
reply, err := conn.Receive()
if err != nil {
panic(err)
}
fmt.Println(gconv.Strings(reply))
}
}

View File

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

View File

@ -1,25 +0,0 @@
package main
import (
"net/http"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/log/handler", func(r *ghttp.Request){
r.Response.WriteStatus(http.StatusNotFound, "文件找不到了")
})
s.SetAccessLogEnabled(true)
s.SetErrorLogEnabled(true)
//s.SetLogHandler(func(r *ghttp.Request, error ...interface{}) {
// if len(error) > 0 {
// // 如果是错误日志
// fmt.Println("错误产生了:", error[0])
// }
// // 这里是请求日志
// fmt.Println("请求处理完成,请求地址:", r.URL.String(), "请求结果:", r.Response.Status)
//})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<title>gf websocket echo server</title>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<div class="container">
<div class="list-group" id="divShow"></div>
<div>
<div><input class="form-control" id="txtContent" autofocus rows="6" placeholder="请输入发送内容"></div>
<div><button class="btn btn-default" id="btnSend" style="margin-top:15px"> </button></div>
</div>
</div>
</body>
</html>
<script type="application/javascript">
// 显示提示信息
function showInfo(content) {
$("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow")
}
// 显示警告信息
function showWaring(content) {
$("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow")
}
// 显示成功信息
function showSuccess(content) {
$("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow")
}
// 显示错误信息
function showError(content) {
$("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow")
}
$(function () {
var url = "wss://127.0.0.1:8199/wss";
var ws = new WebSocket(url);
try {
// ws连接成功
ws.onopen = function () {
showInfo("WebSocket Server [" + url +"] 连接成功!");
};
// ws连接关闭
ws.onclose = function () {
if (ws) {
ws.close();
ws = null;
}
showError("WebSocket Server [" + url +"] 连接关闭!");
};
// ws连接错误
ws.onerror = function () {
if (ws) {
ws.close();
ws = null;
}
showError("WebSocket Server [" + url +"] 连接关闭!");
};
// ws数据返回处理
ws.onmessage = function (result) {
showWaring(" > " + result.data);
};
} catch (e) {
alert(e.message);
}
// 按钮点击发送数据
$("#btnSend").on("click", function () {
if (ws == null) {
showError("WebSocket Server [" + url +"] 连接失败请F5刷新页面!");
return;
}
var content = $.trim($("#txtContent").val()).replace("/[\n]/g", "");
if (content.length <= 0) {
alert("请输入发送内容!");
return;
}
$("#txtContent").val("")
showSuccess(content);
ws.send(content);
});
// 回车按钮触发发送点击事件
$("#txtContent").on("keydown", function (event) {
if (event.keyCode == 13) {
$("#btnSend").trigger("click");
}
});
})
</script>

View File

@ -0,0 +1,33 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
)
func main() {
s := g.Server()
s.BindHandler("/wss", func(r *ghttp.Request) {
ws, err := r.WebSocket()
if err != nil {
glog.Error(err)
r.Exit()
}
for {
msgType, msg, err := ws.ReadMessage()
if err != nil {
return
}
if err = ws.WriteMessage(msgType, msg); err != nil {
return
}
}
})
s.SetServerRoot(gfile.MainPkgPath())
s.EnableHTTPS("../../https/server.crt", "../../https/server.key")
s.SetPort(8199)
s.Run()
}

View File

@ -3,6 +3,7 @@ package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
)
@ -24,6 +25,7 @@ func main() {
}
}
})
s.SetServerRoot(gfile.MainPkgPath())
s.SetPort(8199)
s.Run()
}

View File

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

View File

@ -1,12 +1,18 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
//t := gconv.GTime("2010-10-10 00:00:01")
fmt.Println(gconv.String(gtime.Millisecond()))
value := interface{}(nil)
data := []byte(`{"n": 123456789}`)
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
err := decoder.Decode(&value)
//err := json.Unmarshal(data, &value)
fmt.Println(err)
fmt.Println(value)
}

View File

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