Merge branch 'qiangg_comment' into develop

This commit is contained in:
John
2019-06-11 18:40:56 +08:00
25 changed files with 809 additions and 271 deletions

View File

@ -1,3 +1,63 @@
# `v1.7.0`
## 新功能/改进
1. 重构改进`glog`模块:
- 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效
- 增加日志内容的异步输出特性https://goframe.org/os/glog/async
- 增加日志输出内容的`Json`格式支持https://goframe.org/os/glog/json
- 增加`Flags`额外特性支持包括文件行号打印、自定义时间格式、异步输出等特性控制https://goframe.org/os/glog/flags
- 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成https://goframe.org/os/glog/writer
- 修改`SetStdPrint`方法名为`SetStdoutPrint`
- 修改链式方法`StdPrint`方法名为`Stdout`
- 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出
- 新增更多的链式方法支持https://goframe.org/os/glog/chain
1. 重构改进`gmap`模块:
- 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap`
- 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap`
- 改进`Map/Keys/Values`方法以提高性能
- 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes`
- 新增更多功能方法支持https://goframe.org/container/gmap/index
1. 改进`gtime`时间模块:
- 增加并完善更多的类`PHP`时间格式支持
- 新增更多功能方法,如`FormatTo`/`LayoutTo`等等
- 详见开发文档https://goframe.org/os/gtime/index
1. 改进`gdb`数据库模块:
- 增加对继承结构体的数据转换支持https://goframe.org/database/gdb/senior
- 新增`GetLastSql`方法用以在调试模式下获取最近一条执行的SQL语句
- 其他的细节处理改进
1. 改进`gtcp`通信模块:
- 完善处理细节,提高通信性能;
- 增加`TLS`服务端/客户端通信支持https://goframe.org/net/gtcp/tls
- 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题https://goframe.org/net/gtcp/conn/pkg
- TCP服务端增加`Close`方法
- 更多细节查看开发文档https://goframe.org/net/gtcp/index
1. 改进`gconv`类型转换模块
- 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration`
- 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换
- 详见开发文档https://goframe.org/util/gconv/struct
1. 改进`ghttp`模块:
- 日志输出增加`http/https`字段https://goframe.org/net/ghttp/logs
- 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性
- 增加`ghttp.Request.GetUrl`方法用以获取当前完整的URL请求地址
- `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`https://goframe.org/net/ghttp/client
1. 新增`gtree`树形数据结构容器支持https://goframe.org/container/gtree/index
1. 改进`gudp`通信模块具体请参考开发文档https://goframe.org/net/gudp/index
1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持https://goframe.org/os/gcfg/index
1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换https://goframe.org/database/gredis/index
1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes`
1. 改进`gjson`/`gparser`模块增加更多方法https://goframe.org/encoding/gjson/index
1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境;
1. 改进`grpool`协程池模块提高执行性能https://goframe.org/os/grpool/index
1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理
1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法
1. 去除`gvar.VarRead`接口类型支持
## Bug Fix
1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突;
1. 修复`gcron.AddOnce`方法的细节逻辑问题;
1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误;
1. 修复`gview`模板引擎的并发安全问题;
1. 修复`ghttp.Server`的SESSION初始化过期时间问题
# `v1.6.0` (2019-04-09)
## 新功能/改进

View File

@ -47,7 +47,7 @@
1. gtimer增加DelayAdd*方法返回Entry对象以便DelayAdd*的定时任务也能进行状态控制gcron同理需要改进
1. 改进gdb对pgsql/mssql/oracle的支持使用方法覆盖的方式改进操作而不是完全依靠正则替换的方式
1. gdb的Cache缓存功能增加可自定义缓存接口以便支持外部缓存功能缓存接口可以通过io.ReadWriter接口实现
1. grpool增加支持阻塞添加任务接口

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gchan provides graceful channel for safe operations.
// Package gchan provides graceful channel for no panic operations.
//
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
@ -14,12 +14,13 @@ import (
"github.com/gogf/gf/g/container/gtype"
)
// Graceful channel.
type Chan struct {
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given limit.
// New creates a graceful channel with given <limit>.
func New(limit int) *Chan {
return &Chan {
channel : make(chan interface{}, limit),
@ -31,7 +32,7 @@ func New(limit int) *Chan {
// It is safe to be called repeatedly.
func (c *Chan) Push(value interface{}) error {
if c.closed.Val() {
return errors.New("closed")
return errors.New("channel is closed")
}
c.channel <- value
return nil
@ -39,6 +40,7 @@ func (c *Chan) Push(value interface{}) error {
// Pop pops value from channel.
// If there's no value in channel, it would block to wait.
// If the channel is closed, it will return a nil value immediately.
func (c *Chan) Pop() interface{} {
return <- c.channel
}

View File

@ -37,8 +37,8 @@ const (
)
// New returns an empty queue object.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited by default.
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib chan.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited in default.
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib channel.
func New(limit...int) *Queue {
q := &Queue {
closed : make(chan struct{}, 0),
@ -103,7 +103,7 @@ func (q *Queue) Pop() interface{} {
// Close closes the queue.
// Notice: It would notify all goroutines return immediately,
// which are being blocked reading by Pop method.
// which are being blocked reading using Pop method.
func (q *Queue) Close() {
close(q.C)
close(q.events)

View File

@ -9,152 +9,172 @@
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"testing"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestSet_New(t *testing.T) {
gtest.Case(t, func() {
s := gset.New()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3, 4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3,4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3, 4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v interface{}) bool {
a1.Append(1)
return false
})
s.Iterator(func(v interface{}) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v interface{}) bool {
a1.Append(1)
return false
})
s.Iterator(func(v interface{}) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
gtest.Assert(m, map[interface{}]struct{}{
3 : struct{}{},
2 : struct{}{},
})
})
})
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
gtest.Assert(m, map[interface{}]struct{}{
3: struct{}{},
2: struct{}{},
})
})
})
}
func TestSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}

View File

@ -9,6 +9,7 @@
package gaes_test
import (
"github.com/gogf/gf/g/encoding/gbase64"
"testing"
"github.com/gogf/gf/g/crypto/gaes"
@ -16,52 +17,111 @@ import (
)
var (
content = []byte("pibigstar")
content = []byte("pibigstar")
content_16, _ = gbase64.Decode("v1jqsGHId/H8onlVHR8Vaw==")
content_24, _ = gbase64.Decode("0TXOaj5KMoLhNWmJ3lxY1A==")
content_32, _ = gbase64.Decode("qM/Waw1kkWhrwzek24rCSA==")
content_16_iv, _ = gbase64.Decode("DqQUXiHgW/XFb6Qs98+hrA==")
content_32_iv, _ = gbase64.Decode("ZuLgAOii+lrD5KJoQ7yQ8Q==")
// iv 长度必须等于blockSize只能为16
iv = []byte("Hello My GoFrame")
key_16 = []byte("1234567891234567")
key_24 = []byte("123456789123456789123456")
key_32 = []byte("12345678912345678912345678912345")
keys = []byte("12345678912345678912345678912346")
iv = []byte("Hello My GoFrame")
key_16 = []byte("1234567891234567")
key_17 = []byte("12345678912345670")
key_24 = []byte("123456789123456789123456")
key_32 = []byte("12345678912345678912345678912345")
keys = []byte("12345678912345678912345678912346")
key_err = []byte("1234")
key_32_err = []byte("1234567891234567891234567891234 ")
)
func TestEncrypt(t *testing.T) {
gtest.Case(t, func() {
_, err := gaes.Encrypt(content, key_16)
data, err := gaes.Encrypt(content, key_16)
gtest.Assert(err, nil)
_, err = gaes.Encrypt(content, key_24)
gtest.Assert(data, []byte(content_16))
data, err = gaes.Encrypt(content, key_24)
gtest.Assert(err, nil)
_, err = gaes.Encrypt(content, key_32)
gtest.Assert(data, []byte(content_24))
data, err = gaes.Encrypt(content, key_32)
gtest.Assert(err, nil)
_, err = gaes.Encrypt(content, key_16, iv)
gtest.Assert(data, []byte(content_32))
data, err = gaes.Encrypt(content, key_16, iv)
gtest.Assert(err, nil)
gtest.Assert(data, []byte(content_16_iv))
data, err = gaes.Encrypt(content, key_32, iv)
gtest.Assert(err, nil)
gtest.Assert(data, []byte(content_32_iv))
})
}
func TestDecrypt(t *testing.T) {
gtest.Case(t, func() {
encrypt, err := gaes.Encrypt(content, key_16)
decrypt, err := gaes.Decrypt(encrypt, key_16)
decrypt, err := gaes.Decrypt([]byte(content_16), key_16)
gtest.Assert(err, nil)
gtest.Assert(string(decrypt), string(content))
gtest.Assert(decrypt, content)
encrypt, err = gaes.Encrypt(content, key_24)
decrypt, err = gaes.Decrypt(encrypt, key_24)
decrypt, err = gaes.Decrypt([]byte(content_24), key_24)
gtest.Assert(err, nil)
gtest.Assert(string(decrypt), string(content))
gtest.Assert(decrypt, content)
encrypt, err = gaes.Encrypt(content, key_32)
decrypt, err = gaes.Decrypt(encrypt, key_32)
decrypt, err = gaes.Decrypt([]byte(content_32), key_32)
gtest.Assert(err, nil)
gtest.Assert(string(decrypt), string(content))
gtest.Assert(decrypt, content)
encrypt, err = gaes.Encrypt(content, key_32, iv)
decrypt, err = gaes.Decrypt(encrypt, key_32, iv)
decrypt, err = gaes.Decrypt([]byte(content_16_iv), key_16, iv)
gtest.Assert(err, nil)
gtest.Assert(string(decrypt), string(content))
gtest.Assert(decrypt, content)
encrypt, err = gaes.Encrypt(content, key_32, iv)
decrypt, err = gaes.Decrypt(encrypt, keys, iv)
decrypt, err = gaes.Decrypt([]byte(content_32_iv), key_32, iv)
gtest.Assert(err, nil)
gtest.Assert(decrypt, content)
decrypt, err = gaes.Decrypt([]byte(content_32_iv), keys, iv)
gtest.Assert(err, "invalid padding")
})
}
func TestEncryptErr(t *testing.T) {
gtest.Case(t, func() {
// encrypt key error
_, err := gaes.Encrypt(content, key_err)
gtest.AssertNE(err, nil)
})
}
func TestDecryptErr(t *testing.T) {
gtest.Case(t, func() {
// decrypt key error
encrypt, err := gaes.Encrypt(content, key_16)
_, err = gaes.Decrypt(encrypt, key_err)
gtest.AssertNE(err, nil)
// decrypt content too short error
_, err = gaes.Decrypt([]byte("test"), key_16)
gtest.AssertNE(err, nil)
// decrypt content size error
_, err = gaes.Decrypt(key_17, key_16)
gtest.AssertNE(err, nil)
})
}
func TestPKCS5UnPaddingErr(t *testing.T) {
gtest.Case(t, func() {
// PKCS5UnPadding blockSize zero
_, err := gaes.PKCS5UnPadding(content, 0)
gtest.AssertNE(err, nil)
// PKCS5UnPadding src len zero
_, err = gaes.PKCS5UnPadding([]byte(""), 16)
gtest.AssertNE(err, nil)
// PKCS5UnPadding src len > blockSize
_, err = gaes.PKCS5UnPadding(key_17, 16)
gtest.AssertNE(err, nil)
// PKCS5UnPadding src len > blockSize
_, err = gaes.PKCS5UnPadding(key_32_err, 32)
gtest.AssertNE(err, nil)
})
}

View File

@ -9,13 +9,14 @@
package gcrc32_test
import (
"github.com/gogf/gf/g/crypto/gmd5"
"testing"
"github.com/gogf/gf/g/crypto/gcrc32"
"github.com/gogf/gf/g/test/gtest"
)
func TestEncrypt(t *testing.T) {
func TestEncryptString(t *testing.T) {
gtest.Case(t, func() {
s := "pibigstar"
result := 693191136
@ -25,3 +26,19 @@ func TestEncrypt(t *testing.T) {
gtest.AssertEQ(int(encrypt2), result)
})
}
func TestEncrypt(t *testing.T) {
gtest.Case(t, func() {
s := "pibigstar"
result := 693191136
encrypt1 := gcrc32.Encrypt(s)
encrypt2 := gcrc32.Encrypt([]byte(s))
gtest.AssertEQ(int(encrypt1), result)
gtest.AssertEQ(int(encrypt2), result)
strmd5 := gmd5.Encrypt(s)
test1 := gcrc32.Encrypt(strmd5)
test2 := gcrc32.Encrypt([]byte(strmd5))
gtest.AssertEQ(test2, test1)
})
}

View File

@ -58,7 +58,7 @@ func (s *Session) init() {
// 否则执行初始化创建
s.id = s.request.Cookie.MakeSessionId()
s.data = gmap.NewStrAnyMap()
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge())
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
}
}

View File

@ -85,3 +85,36 @@ func Test_Router_Hook_Priority(t *testing.T) {
})
}
func Test_Router_Hook_Multi(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/multi-hook", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/multi-hook"), "12show")
})
}

View File

@ -7,81 +7,100 @@
// Package gcache provides high performance and concurrent-safe in-memory cache for process.
package gcache
// 全局缓存管理对象
// Default cache object.
var cache = New()
// (使用全局KV缓存对象)设置kv缓存键值对过期时间单位为**毫秒**
// Set sets cache with <key>-<value> pair, which is expired after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
func Set(key interface{}, value interface{}, expire int) {
cache.Set(key, value, expire)
}
// 当键名不存在时写入并返回true否则返回false。
// 常用来做对并发性要求不高的内存锁。
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
func SetIfNotExist(key interface{}, value interface{}, expire int) bool {
return cache.SetIfNotExist(key, value, expire)
}
// (使用全局KV缓存对象)批量设置kv缓存键值对过期时间单位为**毫秒**
// Sets batch sets cache with key-value pairs by <data>, which is expired after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
func Sets(data map[interface{}]interface{}, expire int) {
cache.Sets(data, expire)
}
// (使用全局KV缓存对象)获取指定键名的值
// Get returns the value of <key>.
// It returns nil if it does not exist or its value is nil.
func Get(key interface{}) interface{} {
return cache.Get(key)
}
// 当键名存在时返回其键值,否则写入指定的键值
// GetOrSet returns the value of <key>,
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
func GetOrSet(key interface{}, value interface{}, expire int) interface{} {
return cache.GetOrSet(key, value, expire)
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
// GetOrSetFunc returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} {
return cache.GetOrSetFunc(key, f, expire)
}
// GetOrSetFunc不同的是f是在写锁机制内执行
// GetOrSetFuncLock returns the value of <key>,
// or sets <key> with result of function <f> and returns its result
// if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=0 means it does not expire.
//
// Note that the function <f> is executed within writing mutex lock.
func GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} {
return cache.GetOrSetFuncLock(key, f, expire)
}
// 是否存在指定的键名true表示存在false表示不存在。
// Contains returns true if <key> exists in the cache, or else returns false.
func Contains(key interface{}) bool {
return cache.Contains(key)
}
// (使用全局KV缓存对象)删除指定键值对
// Remove deletes the <key> in the cache, and returns its value.
func Remove(key interface{}) interface{} {
return cache.Remove(key)
}
// (使用全局KV缓存对象)批量删除指定键值对
// Removes deletes <keys> in the cache.
func Removes(keys []interface{}) {
cache.Removes(keys)
}
// 返回缓存的所有数据键值对(不包含已过期数据)
// Data returns a copy of all key-value pairs in the cache as map type.
func Data() map[interface{}]interface{} {
return cache.Data()
}
// 获得所有的键名,组成数组返回
// Keys returns all keys in the cache as slice.
func Keys() []interface{} {
return cache.Keys()
}
// 获得所有的键名,组成字符串数组返回
// KeyStrings returns all keys in the cache as string slice.
func KeyStrings() []string {
return cache.KeyStrings()
}
// 获得所有的值,组成数组返回
// Values returns all values in the cache as slice.
func Values() []interface{} {
return cache.Values()
}
// 获得缓存对象的键值对数量
// Size returns the size of the cache.
func Size() int {
return cache.Size()
}

View File

@ -13,13 +13,12 @@ import (
"unsafe"
)
// 缓存对象。
// 底层只有一个缓存对象,如果需要提高并发性能,可新增缓存对象无锁哈希表,用键名做固定分区。
// Cache struct.
type Cache struct {
*memCache
}
// Cache对象按照缓存键名首字母做了分组
// New creates and returns a new cache object.
func New(lruCap...int) *Cache {
c := &Cache {
memCache : newMemCache(lruCap...),
@ -28,10 +27,8 @@ func New(lruCap...int) *Cache {
return c
}
// 清空缓存中的所有数据
// Clear clears all data of the cache.
func (c *Cache) Clear() {
// 使用原子操作替换缓存对象
old := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.memCache)), unsafe.Pointer(newMemCache()))
// 关闭旧的缓存对象
(*memCache)(old).Close()
}

View File

@ -18,21 +18,25 @@ import (
)
// 缓存对象
// Internal cache object.
type memCache struct {
dataMu sync.RWMutex
expireTimeMu sync.RWMutex
expireSetMu sync.RWMutex
cap int // 控制缓存池大小超过大小则按照LRU算法进行缓存过期处理(默认为0表示不进行限制)
data map[interface{}]memCacheItem // 缓存数据(所有的缓存数据存放哈希表)
expireTimes map[interface{}]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新)键值为1秒级时间戳
expireSets map[int64]*gset.Set // 分组过期时间对应的键名列表(用于自动过期快速删除)键值为1秒级时间戳
// <cap> limits the size of the cache pool.
// If the size of the cache exceeds the <cap>,
// the cache expiration process is performed according to the LRU algorithm.
// It is 0 in default which means no limits.
cap int
data map[interface{}]memCacheItem // Underlying cache data which is stored in a hash table.
expireTimes map[interface{}]int64 // Expiring key mapping to its timestamp, which is used for quick indexing and deleting.
expireSets map[int64]*gset.Set // Expiring timestamp mapping to its key set, which is used for quick indexing and deleting.
lru *memCacheLru // LRU缓存限制(只有限定cap池大小时才启用)
lruGetList *glist.List // Get操作的LRU记录
eventList *glist.List // 异步处理队列
closed *gtype.Bool // 关闭事件通知
lru *memCacheLru // LRU object, which is enabled when <cap> > 0.
lruGetList *glist.List // LRU history according with Get function.
eventList *glist.List // Asynchronous event list for internal data synchronization.
closed *gtype.Bool // Is this cache closed or not.
}
// 缓存数据项

View File

@ -9,57 +9,265 @@
package gcache_test
import (
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/grpool"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
//clear 用于清除全局缓存因gcache api 暂未暴露 Clear 方法
//暂定所有测试用例key的集合为1,2,3避免不同测试用例间因全局cache共享带来的问题每个测试用例在测试gcache.XXX之前先调用clear()
func clear() {
gcache.Removes(g.Slice{1, 2, 3})
}
func TestCache_Set(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.Set(1, 11, 0)
gtest.Assert(cache.Get(1), 11)
})
gtest.Case(t, func() {
cache := gcache.New()
cache.Set(1, 11, 0)
gtest.Assert(cache.Get(1), 11)
gtest.Assert(cache.Contains(1), true)
clear()
gcache.Set(1, 11, 0)
gtest.Assert(gcache.Get(1), 11)
gtest.Assert(gcache.Contains(1), true)
})
}
func TestCache_Set_Expire(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.Set(2, 22, 100)
gtest.Assert(cache.Get(2), 22)
time.Sleep(200*time.Millisecond)
gtest.Assert(cache.Get(2), nil)
time.Sleep(3*time.Second)
gtest.Assert(cache.Size(), 0)
})
gtest.Case(t, func() {
cache := gcache.New()
cache.Set(2, 22, 100)
gtest.Assert(cache.Get(2), 22)
time.Sleep(200 * time.Millisecond)
gtest.Assert(cache.Get(2), nil)
time.Sleep(3 * time.Second)
gtest.Assert(cache.Size(), 0)
cache.Close()
})
}
func TestCache_Keys_Values(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
for i := 0; i < 10; i++ {
cache.Set(i, i*10, 0)
}
gtest.Assert(len(cache.Keys()), 10)
gtest.Assert(len(cache.Values()), 10)
gtest.AssertIN(0, cache.Keys())
gtest.AssertIN(90, cache.Values())
})
gtest.Case(t, func() {
cache := gcache.New()
for i := 0; i < 10; i++ {
cache.Set(i, i*10, 0)
}
gtest.Assert(len(cache.Keys()), 10)
gtest.Assert(len(cache.Values()), 10)
gtest.AssertIN(0, cache.Keys())
gtest.AssertIN(90, cache.Values())
})
}
func TestCache_LRU(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New(2)
for i := 0; i < 10; i++ {
cache.Set(i, i, 0)
}
gtest.Case(t, func() {
cache := gcache.New(2)
for i := 0; i < 10; i++ {
cache.Set(i, i, 0)
}
gtest.Assert(cache.Size(), 10)
gtest.Assert(cache.Get(6), 6)
time.Sleep(4 * time.Second)
gtest.Assert(cache.Size(), 2)
gtest.Assert(cache.Get(6), 6)
gtest.Assert(cache.Get(1), nil)
cache.Close()
})
}
gtest.Assert(cache.Size(), 10)
gtest.Assert(cache.Get(6), 6)
time.Sleep(4*time.Second)
gtest.Assert(cache.Size(), 2)
gtest.Assert(cache.Get(6), 6)
gtest.Assert(cache.Get(1), nil)
})
}
func TestCache_LRU_expire(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New(2)
cache.Set(1, nil, 1000)
gtest.Assert(cache.Size(), 1)
gtest.Assert(cache.Get(1), nil)
})
}
func TestCache_SetIfNotExist(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.SetIfNotExist(1, 11, 0)
gtest.Assert(cache.Get(1), 11)
cache.SetIfNotExist(1, 22, 0)
gtest.Assert(cache.Get(1), 11)
cache.SetIfNotExist(2, 22, 0)
gtest.Assert(cache.Get(2), 22)
clear()
gcache.SetIfNotExist(1, 11, 0)
gtest.Assert(gcache.Get(1), 11)
gcache.SetIfNotExist(1, 22, 0)
gtest.Assert(gcache.Get(1), 11)
})
}
func TestCache_Sets(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
gtest.Assert(cache.Get(1), 11)
clear()
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
gtest.Assert(gcache.Get(1), 11)
})
}
func TestCache_GetOrSet(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.GetOrSet(1, 11, 0)
gtest.Assert(cache.Get(1), 11)
cache.GetOrSet(1, 111, 0)
gtest.Assert(cache.Get(1), 11)
clear()
gcache.GetOrSet(1, 11, 0)
gtest.Assert(gcache.Get(1), 11)
gcache.GetOrSet(1, 111, 0)
gtest.Assert(gcache.Get(1), 11)
})
}
func TestCache_GetOrSetFunc(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.GetOrSetFunc(1, func() interface{} {
return 11
}, 0)
gtest.Assert(cache.Get(1), 11)
cache.GetOrSetFunc(1, func() interface{} {
return 111
}, 0)
gtest.Assert(cache.Get(1), 11)
clear()
gcache.GetOrSetFunc(1, func() interface{} {
return 11
}, 0)
gtest.Assert(gcache.Get(1), 11)
gcache.GetOrSetFunc(1, func() interface{} {
return 111
}, 0)
gtest.Assert(gcache.Get(1), 11)
})
}
func TestCache_GetOrSetFuncLock(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.GetOrSetFuncLock(1, func() interface{} {
return 11
}, 0)
gtest.Assert(cache.Get(1), 11)
cache.GetOrSetFuncLock(1, func() interface{} {
return 111
}, 0)
gtest.Assert(cache.Get(1), 11)
clear()
gcache.GetOrSetFuncLock(1, func() interface{} {
return 11
}, 0)
gtest.Assert(gcache.Get(1), 11)
gcache.GetOrSetFuncLock(1, func() interface{} {
return 111
}, 0)
gtest.Assert(gcache.Get(1), 11)
})
}
func TestCache_Clear(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
cache.Clear()
gtest.Assert(cache.Size(), 0)
})
}
func TestCache_SetConcurrency(t *testing.T) {
gtest.Case(t, func() {
cache := gcache.New()
pool := grpool.New(4)
go func() {
for {
pool.Add(func() {
cache.SetIfNotExist(1, 11, 10)
})
}
}()
select {
case <-time.After(2 * time.Second):
t.Log("first part end")
}
go func() {
for {
pool.Add(func() {
cache.SetIfNotExist(1, nil, 10)
})
}
}()
select {
case <-time.After(2 * time.Second):
t.Log("second part end")
}
})
}
func TestCache_Basic(t *testing.T) {
gtest.Case(t, func() {
{
cache := gcache.New()
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
gtest.Assert(cache.Contains(1), true)
gtest.Assert(cache.Get(1), 11)
data := cache.Data()
gtest.Assert(data[1], 11)
gtest.Assert(data[2], 22)
gtest.Assert(data[3], nil)
gtest.Assert(cache.Size(), 2)
keys := cache.Keys()
gtest.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
keyStrs := cache.KeyStrings()
gtest.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
values := cache.Values()
gtest.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
removeData1 := cache.Remove(1)
gtest.Assert(removeData1, 11)
gtest.Assert(cache.Size(), 1)
cache.Removes(g.Slice{2})
gtest.Assert(cache.Size(), 0)
}
clear()
{
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
gtest.Assert(gcache.Contains(1), true)
gtest.Assert(gcache.Get(1), 11)
data := gcache.Data()
gtest.Assert(data[1], 11)
gtest.Assert(data[2], 22)
gtest.Assert(data[3], nil)
gtest.Assert(gcache.Size(), 2)
keys := gcache.Keys()
gtest.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
keyStrs := gcache.KeyStrings()
gtest.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
values := gcache.Values()
gtest.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
removeData1 := gcache.Remove(1)
gtest.Assert(removeData1, 11)
gtest.Assert(gcache.Size(), 1)
gcache.Removes(g.Slice{2})
gtest.Assert(gcache.Size(), 0)
}
})
}

43
g/os/genv/genv_test.go Normal file
View File

@ -0,0 +1,43 @@
package genv_test
import (
"github.com/gogf/gf/g/os/genv"
"github.com/gogf/gf/g/test/gtest"
"os"
"testing"
)
func Test_Genv_All(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(os.Environ(), genv.All())
})
}
func Test_Genv_Get(t *testing.T) {
gtest.Case(t, func() {
key := "TEST_GET_ENV"
err := os.Setenv(key, "TEST")
gtest.Assert(err, nil)
gtest.AssertEQ(genv.Get(key), "TEST")
})
}
func Test_Genv_Set(t *testing.T) {
gtest.Case(t, func() {
key := "TEST_SET_ENV"
err := genv.Set(key, "TEST")
gtest.Assert(err, nil)
gtest.AssertEQ(os.Getenv(key), "TEST")
})
}
func Test_Genv_Remove(t *testing.T) {
gtest.Case(t, func() {
key := "TEST_REMOVE_ENV"
err := os.Setenv(key, "TEST")
gtest.Assert(err, nil)
err = genv.Remove(key)
gtest.Assert(err, nil)
gtest.AssertEQ(os.Getenv(key), "")
})
}

View File

@ -425,8 +425,7 @@ func MainPkgPath() string {
continue
}
// separator of <file> '/' will be converted to Separator.
path = Dir(file)
for path[len(path) - 1] != os.PathSeparator {
for path = Dir(file); len(path) > 1 && Exists(path) && path[len(path) - 1] != os.PathSeparator; {
files, _ := ScanDir(path, "*.go")
for _, v := range files {
if gregex.IsMatchString(`package\s+main`, GetContents(v)) {

View File

@ -243,6 +243,13 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
if len(timeFormat) > 0 {
buffer.WriteString(time.Now().Format(timeFormat))
}
// Lead string.
if len(lead) > 0 {
buffer.WriteString(lead)
if len(value) > 0 {
buffer.WriteByte(' ')
}
}
// Caller path.
callerPath := ""
if l.flags & F_FILE_LONG > 0 {
@ -259,12 +266,6 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
buffer.WriteString(l.prefix + " ")
}
}
if len(lead) > 0 {
buffer.WriteString(lead)
if len(value) > 0 {
buffer.WriteByte(' ')
}
}
for k, v := range value {
if k > 0 {
buffer.WriteByte(' ')

View File

@ -14,10 +14,10 @@ import (
// Goroutine Pool
type Pool struct {
limit int // Max goroutine count limit.
count *gtype.Int // Current running goroutine count.
list *glist.List // Job list.
closed *gtype.Bool // Is pool closed or not.
limit int // Max goroutine count limit.
count *gtype.Int // Current running goroutine count.
list *glist.List // Job list for asynchronous job adding purpose.
closed *gtype.Bool // Is pool closed or not.
}
// Default goroutine pool.
@ -33,7 +33,7 @@ func New(limit...int) *Pool {
list : glist.New(),
closed : gtype.NewBool(),
}
if len(limit) > 0 {
if len(limit) > 0 && limit[0] > 0 {
p.limit = limit[0]
}
return p
@ -72,6 +72,7 @@ func (p *Pool) Add(f func()) {
p.fork()
}
// Size returns current goroutine count of the pool.
func (p *Pool) Size() int {
return p.count.Val()

View File

@ -5,8 +5,6 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gtime provides functionality for measuring and displaying time.
//
// 时间管理.
package gtime
import (

View File

@ -15,6 +15,10 @@ import (
"testing"
)
var (
PatternErr = `([\d+`
)
func Test_Quote(t *testing.T) {
gtest.Case(t, func() {
s1 := `[foo]` //`\[foo\]`
@ -40,6 +44,8 @@ func Test_IsMatch(t *testing.T) {
gtest.Assert(gregex.IsMatch(pattern, s1), false)
s1 = []byte(`sfs:`)
gtest.Assert(gregex.IsMatch(pattern, s1), false)
// error pattern
gtest.Assert(gregex.IsMatch(PatternErr, s1), false)
})
}
@ -52,6 +58,8 @@ func Test_IsMatchString(t *testing.T) {
gtest.Assert(gregex.IsMatchString(pattern, s1), false)
s1 = `sfs:`
gtest.Assert(gregex.IsMatchString(pattern, s1), false)
// error pattern
gtest.Assert(gregex.IsMatchString(PatternErr, s1), false)
})
}
@ -68,6 +76,9 @@ func Test_Match(t *testing.T) {
if string(subs[1]) != "aab" {
t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1], "aab")
}
// error pattern
_, err = gregex.Match(PatternErr, []byte(s))
gtest.AssertNE(err, nil)
})
}
@ -84,6 +95,9 @@ func Test_MatchString(t *testing.T) {
if string(subs[1]) != "aab" {
t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1], "aab")
}
// error pattern
_, err = gregex.MatchString(PatternErr, s)
gtest.AssertNE(err, nil)
})
}
@ -108,6 +122,9 @@ func Test_MatchAll(t *testing.T) {
if string(subs[1][1]) != "aab" {
t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1][1], "aab")
}
// error pattern
_, err = gregex.MatchAll(PatternErr, []byte(s))
gtest.AssertNE(err, nil)
})
}
@ -131,6 +148,9 @@ func Test_MatchAllString(t *testing.T) {
if string(subs[1][1]) != "aab" {
t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1][1], "aab")
}
// error pattern
_, err = gregex.MatchAllString(PatternErr, s)
gtest.AssertNE(err, nil)
})
}
@ -146,6 +166,9 @@ func Test_Replace(t *testing.T) {
if string(replacedStr) != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
// error pattern
_, err = gregex.Replace(PatternErr, []byte(replace), []byte(s))
gtest.AssertNE(err, nil)
})
}
@ -161,6 +184,9 @@ func Test_ReplaceString(t *testing.T) {
if replacedStr != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
// error pattern
_, err = gregex.ReplaceString(PatternErr, replace, s)
gtest.AssertNE(err, nil)
})
}
@ -182,6 +208,11 @@ func Test_ReplaceFun(t *testing.T) {
if string(replacedStr) != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
// error pattern
_, err = gregex.ReplaceFunc(PatternErr, []byte(s), func(s []byte) []byte {
return []byte("")
})
gtest.AssertNE(err, nil)
})
}
@ -209,6 +240,11 @@ func Test_ReplaceFuncMatch(t *testing.T) {
})
gtest.Assert(e3, nil)
gtest.Assert(s3, []byte("7890"))
// error pattern
_, err := gregex.ReplaceFuncMatch(PatternErr, s, func(match [][]byte) []byte {
return match[3]
})
gtest.AssertNE(err, nil)
})
}
@ -230,6 +266,11 @@ func Test_ReplaceStringFunc(t *testing.T) {
if replacedStr != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
// error pattern
_, err = gregex.ReplaceStringFunc(PatternErr, s, func(s string) string {
return ""
})
gtest.AssertNE(err, nil)
})
}
@ -257,6 +298,11 @@ func Test_ReplaceStringFuncMatch(t *testing.T) {
})
gtest.Assert(e3, nil)
gtest.Assert(s3, "7890")
// error pattern
_, err := gregex.ReplaceStringFuncMatch(PatternErr, s, func(match []string) string {
return ""
})
gtest.AssertNE(err, nil)
})
}
@ -288,6 +334,9 @@ func Test_Split(t *testing.T) {
if items[0] != s {
t.Fatalf("regex:%s,Split(%q) want %q", re, s, item0)
}
// error pattern
items = gregex.Split(PatternErr, s)
gtest.AssertEQ(items, nil)
})
}

View File

@ -157,7 +157,9 @@ func StructDeep(params interface{}, pointer interface{}, mapping...map[string]st
trv := rv.Field(i)
switch trv.Kind() {
case reflect.Struct:
StructDeep(params, trv, mapping...)
if err := StructDeep(params, trv, mapping...); err != nil {
return err
}
}
}
}
@ -239,7 +241,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
switch structFieldValue.Kind() {
// 属性为结构体
case reflect.Struct:
Struct(value, structFieldValue)
if err := Struct(value, structFieldValue); err != nil {
structFieldValue.Set(reflect.ValueOf(value))
}
// 属性为数组类型
case reflect.Slice: fallthrough
@ -253,11 +257,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
for i := 0; i < v.Len(); i++ {
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
Struct(v.Index(i).Interface(), e)
if err := Struct(v.Index(i).Interface(), e); err != nil {
e.Set(reflect.ValueOf(v.Index(i).Interface()))
}
a.Index(i).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
Struct(v.Index(i).Interface(), e)
if err := Struct(v.Index(i).Interface(), e); err != nil {
e.Set(reflect.ValueOf(v.Index(i).Interface()))
}
a.Index(i).Set(e)
}
}
@ -267,11 +275,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
t := a.Index(0).Type()
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
Struct(value, e)
if err := Struct(value, e); err != nil {
e.Set(reflect.ValueOf(value))
}
a.Index(0).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
Struct(value, e)
if err := Struct(value, e); err != nil {
e.Set(reflect.ValueOf(value))
}
a.Index(0).Set(e)
}
}
@ -280,7 +292,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
// 属性为指针类型
case reflect.Ptr:
e := reflect.New(structFieldValue.Type().Elem()).Elem()
Struct(value, e)
if err := Struct(value, e); err != nil {
e.Set(reflect.ValueOf(value))
}
structFieldValue.Set(e.Addr())
default:

View File

@ -7,12 +7,12 @@
package gconv_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
)
func Test_Struct_Basic1(t *testing.T) {
@ -104,6 +104,26 @@ func Test_Struct_Basic2(t *testing.T) {
})
}
// 带有指针的基础类型属性
func Test_Struct_Basic3(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name *string
}
user := new(User)
params := g.Map {
"uid" : 1,
"Name" : "john",
}
if err := gconv.Struct(params, user); err != nil {
gtest.Error(err)
}
gtest.Assert(user.Uid, 1)
gtest.Assert(*user.Name, "john")
})
}
// slice类型属性的赋值
func Test_Struct_Attr_Slice(t *testing.T) {
gtest.Case(t, func() {

View File

@ -5,6 +5,6 @@ import (
)
func main() {
glog.Line().Println("this is the short file name with its line number")
glog.Line(true).Println("lone file name with line number")
glog.Line().Debug("this is the short file name with its line number")
glog.Line(true).Debug("lone file name with line number")
}

View File

@ -1,19 +1,11 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"fmt"
"github.com/gogf/gf/g/os/gtime"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
rs :="GF(Go Frame)是一款模块化、松耦合、生产级的Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、路由注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。"
//此行压测会提示map并发错误 webbench -c 8000 -t 60 http://IP 局域网两台机器测试
r.Response.WriteTplContent(rs, g.Map{
"Contentb": 1,
})
})
s.SetPort(8199)
s.Run()
fmt.Println(gtime.Now().Format("U"))
fmt.Println(gtime.Second())
}

3
go.mod
View File

@ -1 +1,2 @@
module github.com/gogf/gf
module github.com/gogf/gf

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v1.6.17"
const VERSION = "v1.7.0"
const AUTHORS = "john<john@goframe.org>"