Merge pull request #3 from gogf/master

sync master
This commit is contained in:
HaiLaz
2019-07-10 10:30:21 +08:00
committed by GitHub
36 changed files with 1815 additions and 258 deletions

226
README.MD
View File

@ -40,6 +40,7 @@ golang version >= 1.10
# Quick Start
## Hello World
```go
package main
@ -56,8 +57,231 @@ func main() {
s.Run()
}
```
## Rich Router
```go
package main
[View More..](https://goframe.org/start/index)
import (
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g"
)
func main() {
s := g.Server()
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("class"))
r.Response.Writeln(r.Get("course"))
r.Response.Writeln(r.Get("name"))
r.Response.Writeln(r.Get("act"))
})
s.SetPort(8199)
s.Run()
}
```
## Group Routers
```go
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
type Object struct {}
func (o *Object) Show(r *ghttp.Request) {
r.Response.Writeln("Object Show")
}
func (o *Object) Delete(r *ghttp.Request) {
r.Response.Writeln("Object REST Delete")
}
func Handler(r *ghttp.Request) {
r.Response.Writeln("Handler")
}
func HookHandler(r *ghttp.Request) {
r.Response.Writeln("Hook Handler")
}
func main() {
s := g.Server()
obj := new(Object)
group := s.Group("/api")
group.ALL ("*", HookHandler, ghttp.HOOK_BEFORE_SERVE)
group.ALL ("/handler", Handler)
group.ALL ("/obj", obj)
group.GET ("/obj/showit", obj, "Show")
group.REST("/obj/rest", obj)
s.SetPort(8199)
s.Run()
}
```
or
```go
func main() {
s := g.Server()
obj := new(Object)
s.Group("/api").Bind([]ghttp.GroupItem{
{"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE},
{"ALL", "/handler", Handler},
{"ALL", "/obj", obj},
{"GET", "/obj/showit", obj, "Show"},
{"REST", "/obj/rest", obj},
})
s.SetPort(8199)
s.Run()
}
```
## Multi ports & domains
```go
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func Hello1(r *ghttp.Request) {
r.Response.Write("127.0.0.1: Hello1!")
}
func Hello2(r *ghttp.Request) {
r.Response.Write("localhost: Hello2!")
}
func main() {
s := g.Server()
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)
s.SetPort(8100, 8200, 8300)
s.Run()
}
```
## Template Engine
```go
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/template", func(r *ghttp.Request) {
r.Response.WriteTpl("index.tpl", g.Map{
"id": 123,
"name": "john",
})
})
s.SetPort(8199)
s.Run()
}
```
## File Uploading
```go
func Upload(r *ghttp.Request) {
if f, h, e := r.FormFile("upload-file"); e == nil {
defer f.Close()
name := gfile.Basename(h.Filename)
buffer := make([]byte, h.Size)
f.Read(buffer)
gfile.PutBinContents("/tmp/" + name, buffer)
r.Response.Write(name + " uploaded successly")
} else {
r.Response.Write(e.Error())
}
}
```
## ORM Operations
### 1. Retrieving instance
```go
db := g.DB()
db := g.DB("user-center")
```
### 2. Chaining Operations
`Where + string`
```go
// SELECT * FROM user WHERE uid>1 LIMIT 0,10
r, err := db.Table("user").Where("uid > ?", 1).Limit(0, 10).Select()
// SELECT uid,name FROM user WHERE uid>1 LIMIT 0,10
r, err := db.Table("user").Fileds("uid,name").Where("uid > ?", 1).Limit(0, 10).Select()
// SELECT * FROM user WHERE uid=1
r, err := db.Table("user").Where("u.uid=1",).One()
r, err := db.Table("user").Where("u.uid", 1).One()
r, err := db.Table("user").Where("u.uid=?", 1).One()
// SELECT * FROM user WHERE (uid=1) AND (name='john')
r, err := db.Table("user").Where("uid", 1).Where("name", "john").One()
r, err := db.Table("user").Where("uid=?", 1).And("name=?", "john").One()
// SELECT * FROM user WHERE (uid=1) OR (name='john')
r, err := db.Table("user").Where("uid=?", 1).Or("name=?", "john").One()
```
`Where + map`
```go
// SELECT * FROM user WHERE uid=1 AND name='john'
r, err := db.Table("user").Where(g.Map{"uid" : 1, "name" : "john"}).One()
// SELECT * FROM user WHERE uid=1 AND age>18
r, err := db.Table("user").Where(g.Map{"uid" : 1, "age>" : 18}).One()
```
`Where + struct/*struct`
```go
type User struct {
Id int `json:"uid"`
UserName string `gconv:"name"`
}
// SELECT * FROM user WHERE uid =1 AND name='john'
r, err := db.Table("user").Where(User{ Id : 1, UserName : "john"}).One()
// SELECT * FROM user WHERE uid =1
r, err := db.Table("user").Where(&User{ Id : 1}).One()
```
### 3. Update & Delete
```go
// UPDATE user SET name='john guo' WHERE name='john'
r, err := db.Table("user").Data(gdb.Map{"name" : "john guo"}).Where("name=?", "john").Update()
r, err := db.Table("user").Data("name='john guo'").Where("name=?", "john").Update()
// UPDATE user SET status=1 ORDER BY login_time asc LIMIT 10
r, err := db.Table("user").Data("status", 1).OrderBy("login_time asc").Limit(10).Update
// DELETE FROM user WHERE uid=10
r, err := db.Table("user").Where("uid=?", 10).Delete()
// DELETE FROM user ORDER BY login_time asc LIMIT 10
r, err := db.Table("user").OrderBy("login_time asc").Limit(10).Delete()
```
### 4. Insert & Replace & Save
```go
r, err := db.Table("user").Data(g.Map{"name": "john"}).Insert()
r, err := db.Table("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace()
r, err := db.Table("user").Data(g.Map{"uid": 10001, "name": "john"}).Save()
```
### 5. Transaction
```go
if tx, err := db.Begin(); err == nil {
r, err := tx.Save("user", g.Map{
"uid" : 1,
"name" : "john",
})
tx.Commit()
}
```
### 6. Error Handling
```go
func GetOrderInfo(id int) (order *Order, err error) {
err = g.DB().Table("order").Where("id", id).Struct(&order)
if err != nil && err == sql.ErrNoRows {
err = nil
}
return
}
```
[More Features...](https://goframe.org/start/index)
# License

View File

@ -51,7 +51,6 @@
1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用
1. 增加g.Table快捷方法以方便操作数据表但是得考虑后续模型操作设计特别是脚手架的模型管理
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
2. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作

View File

@ -31,6 +31,7 @@ func Test_SortedIntArray1(t *testing.T) {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.Add().Slice(), expect)
}
func Test_SortedIntArray2(t *testing.T) {
@ -44,11 +45,15 @@ func Test_SortedIntArray2(t *testing.T) {
func Test_SortedStringArray1(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedStringArray()
array1 := garray.NewSortedStringArray()
array2 := garray.NewSortedStringArray(true)
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
array1.Add(gconv.String(i))
array2.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
gtest.Assert(array1.Slice(), expect)
gtest.Assert(array2.Slice(), expect)
}
func Test_SortedStringArray2(t *testing.T) {
@ -58,6 +63,8 @@ func Test_SortedStringArray2(t *testing.T) {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
array.Add()
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray1(t *testing.T) {
@ -73,13 +80,18 @@ func Test_SortedArray1(t *testing.T) {
func Test_SortedArray2(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
}
array := garray.NewSortedArray(func1)
array2 := garray.NewSortedArray(func1, true)
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
array2.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.Add().Slice(), expect)
gtest.Assert(array2.Slice(), expect)
}
func TestNewFromCopy(t *testing.T) {
@ -89,6 +101,5 @@ func TestNewFromCopy(t *testing.T) {
gtest.AssertIN(array1.PopRands(2), a1)
gtest.Assert(len(array1.PopRands(1)), 1)
gtest.Assert(len(array1.PopRands(9)), 3)
})
}

View File

@ -9,7 +9,9 @@
package garray_test
import (
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
@ -18,12 +20,15 @@ import (
func Test_IntArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []int{0, 1, 2, 3}
expect2 := []int{}
array := garray.NewIntArrayFrom(expect)
array2 := garray.NewIntArrayFrom(expect2)
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array2.Search(100), -1)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains(100), false)
@ -44,13 +49,16 @@ func TestIntArray_Sort(t *testing.T) {
expect1 := []int{0, 1, 2, 3}
expect2 := []int{3, 2, 1, 0}
array := garray.NewIntArray()
array2 := garray.NewIntArray(true)
for i := 3; i >= 0; i-- {
array.Append(i)
array2.Append(i)
}
array.Sort()
gtest.Assert(array.Slice(), expect1)
array.Sort(true)
gtest.Assert(array.Slice(), expect2)
gtest.Assert(array2.Slice(), expect2)
})
}
@ -98,21 +106,48 @@ func TestIntArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(value1)
array2 := garray.NewIntArrayFrom(value1, true)
gtest.Assert(array1.Range(0, 1), []int{0})
gtest.Assert(array1.Range(1, 2), []int{1})
gtest.Assert(array1.Range(0, 2), []int{0, 1})
gtest.Assert(array1.Range(10, 2), nil)
gtest.Assert(array1.Range(-1, 10), value1)
gtest.Assert(array2.Range(1, 2), []int{1})
})
}
func TestIntArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3}
a2 := []int{4, 5, 6, 7}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []int{0, 1, 2, 3, 4, 5, 6, 7})
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
n1 := []int{0, 1, 2, 3}
n2 := []int{4, 5, 6, 7}
i1 := []interface{}{"1", "2"}
s1 := []string{"a", "b", "c"}
s2 := []string{"e", "f"}
a1 := garray.NewIntArrayFrom(n1)
a2 := garray.NewIntArrayFrom(n2)
a3 := garray.NewArrayFrom(i1)
a4 := garray.NewStringArrayFrom(s1)
a5 := garray.NewSortedStringArrayFrom(s2)
a6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a7 := garray.NewSortedStringArrayFrom(s1)
a8 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
gtest.Assert(a1.Merge(a2).Slice(), []int{0, 1, 2, 3, 4, 5, 6, 7})
gtest.Assert(a1.Merge(a3).Len(), 10)
gtest.Assert(a1.Merge(a4).Len(), 13)
gtest.Assert(a1.Merge(a5).Len(), 15)
gtest.Assert(a1.Merge(a6).Len(), 18)
gtest.Assert(a1.Merge(a7).Len(), 21)
gtest.Assert(a1.Merge(a8).Len(), 23)
})
}
@ -124,6 +159,7 @@ func TestIntArray_Fill(t *testing.T) {
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []int{0, 100, 100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []int{100, 100})
gtest.Assert(array2.Fill(-1, 2, 100).Slice(), []int{100, 100})
})
}
@ -136,6 +172,7 @@ func TestIntArray_Chunk(t *testing.T) {
gtest.Assert(chunks[0], []int{1, 2})
gtest.Assert(chunks[1], []int{3, 4})
gtest.Assert(chunks[2], []int{5})
gtest.Assert(array1.Chunk(0), nil)
})
}
@ -153,6 +190,7 @@ func TestIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a1, true)
gtest.Assert(array1.SubSlice(6), []int{6})
gtest.Assert(array1.SubSlice(5), []int{5, 6})
gtest.Assert(array1.SubSlice(8), nil)
@ -168,6 +206,7 @@ func TestIntArray_SubSlice(t *testing.T) {
gtest.Assert(array1.SubSlice(-9, 3), nil)
gtest.Assert(array1.SubSlice(1, -1), []int{0})
gtest.Assert(array1.SubSlice(1, -3), nil)
gtest.Assert(array2.SubSlice(0, 2), []int{0, 1})
})
}
@ -193,7 +232,6 @@ func TestIntArray_PopRands(t *testing.T) {
ns2 := array.PopRands(7)
gtest.AssertIN(len(ns2), 6)
gtest.AssertIN(ns2, []int{100, 200, 300, 400, 500, 600})
})
}
@ -226,6 +264,7 @@ func TestNewSortedIntArrayFrom(t *testing.T) {
a1 := []int{0, 3, 2, 1, 4, 5, 6}
array1 := garray.NewSortedIntArrayFrom(a1, true)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
gtest.Assert(array1.Slice(), a1)
})
}
@ -260,7 +299,6 @@ func TestSortedIntArray_Sort(t *testing.T) {
gtest.Assert(array2.Len(), 4)
gtest.Assert(array2, []int{0, 1, 2, 3})
})
}
@ -271,7 +309,6 @@ func TestSortedIntArray_Get(t *testing.T) {
gtest.Assert(array1.Get(0), 0)
gtest.Assert(array1.Get(1), 1)
gtest.Assert(array1.Get(3), 5)
})
}
@ -296,7 +333,6 @@ func TestSortedIntArray_Remove(t *testing.T) {
i3 = array2.Remove(1)
gtest.Assert(array2.Search(4), -1)
gtest.Assert(i3, 4)
})
}
@ -308,7 +344,6 @@ func TestSortedIntArray_PopLeft(t *testing.T) {
gtest.Assert(i1, 1)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Search(1), -1)
})
}
@ -348,7 +383,6 @@ func TestSortedIntArray_PopRands(t *testing.T) {
gtest.Assert(array2.Len(), 0)
gtest.Assert(len(ns2), 4)
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
})
}
@ -365,7 +399,6 @@ func TestSortedIntArray_PopLefts(t *testing.T) {
ns2 := array2.PopLefts(5)
gtest.Assert(array2.Len(), 0)
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
})
}
@ -389,6 +422,7 @@ func TestSortedIntArray_Range(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2, 6, 7}
array1 := garray.NewSortedIntArrayFrom(a1)
array2 := garray.NewSortedIntArrayFrom(a1, true)
ns1 := array1.Range(1, 4)
gtest.Assert(len(ns1), 3)
gtest.Assert(ns1, []int{2, 3, 5})
@ -401,7 +435,7 @@ func TestSortedIntArray_Range(t *testing.T) {
nsl := array1.Range(5, 8)
gtest.Assert(len(nsl), 1)
gtest.Assert(array2.Range(1, 2), []int{2})
})
}
@ -418,7 +452,6 @@ func TestSortedIntArray_Contains(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
//gtest.Assert(array1.Contains(3),true) //todo 这一行应该返回true
gtest.Assert(array1.Contains(4), false)
})
}
@ -439,7 +472,6 @@ func TestSortedIntArray_Clear(t *testing.T) {
array1 := garray.NewSortedIntArrayFrom(a1)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
@ -453,7 +485,6 @@ func TestSortedIntArray_Chunk(t *testing.T) {
gtest.Assert(ns1[0], []int{1, 2})
gtest.Assert(ns1[2], []int{5})
gtest.Assert(len(ns2), 0)
})
}
@ -461,6 +492,7 @@ func TestSortedIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
array2 := garray.NewSortedIntArrayFrom(a1, true)
ns1 := array1.SubSlice(1, 2)
gtest.Assert(len(ns1), 2)
gtest.Assert(ns1, []int{2, 3})
@ -475,6 +507,10 @@ func TestSortedIntArray_SubSlice(t *testing.T) {
ns4 := array1.SubSlice(3, 1)
gtest.Assert(len(ns4), 1)
gtest.Assert(ns4, []int{4})
gtest.Assert(array1.SubSlice(-1, 1), []int{5})
gtest.Assert(array1.SubSlice(-9, 1), nil)
gtest.Assert(array1.SubSlice(1, -9), nil)
gtest.Assert(array2.SubSlice(1, 2), []int{2, 3})
})
}
@ -519,7 +555,6 @@ func TestSortedIntArray_SetUnique(t *testing.T) {
array1.SetUnique(true)
gtest.Assert(array1.Len(), 5)
gtest.Assert(array1, []int{1, 2, 3, 4, 5})
})
}
@ -531,7 +566,6 @@ func TestIntArray_SetArray(t *testing.T) {
array1.SetArray(a2)
gtest.Assert(array1.Len(), 2)
gtest.Assert(array1, []int{6, 7})
})
}
@ -621,3 +655,171 @@ func TestIntArray_Remove(t *testing.T) {
gtest.Assert(array1.Len(), 2)
})
}
func TestIntArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []int{1, 2, 3, 4}
a1 := garray.NewIntArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []int) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = 6
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains(6), true)
})
}
func TestIntArray_SortFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []int{1, 4, 3, 2}
a1 := garray.NewIntArrayFrom(s1)
func1 := func(v1, v2 int) bool {
return v1 < v2
}
a11 := a1.SortFunc(func1)
gtest.Assert(a11, []int{1, 2, 3, 4})
})
}
func TestIntArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []int{1, 2, 3, 4}
a1 := garray.NewIntArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
//go1
go a1.RLockFunc(func(n1 []int) { //读锁
time.Sleep(2 * time.Second) //暂停1秒
n1[2] = 6
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候并没有阻塞。
gtest.Assert(a1.Contains(6), true)
})
}
func TestSortedIntArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []int{1, 2, 3, 4}
a1 := garray.NewSortedIntArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []int) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = 6
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains(6), true)
})
}
func TestSortedIntArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []int{1, 2, 3, 4}
a1 := garray.NewSortedIntArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
//go1
go a1.RLockFunc(func(n1 []int) { //读锁
time.Sleep(2 * time.Second) //暂停1秒
n1[2] = 6
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候并没有阻塞。
gtest.Assert(a1.Contains(6), true)
})
}
func TestSortedIntArray_Merge(t *testing.T) {
gtest.Case(t, func() {
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
i0 := []int{1, 2, 3, 4}
s2 := []string{"e", "f"}
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
i2 := garray.NewArrayFrom([]interface{}{3})
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
s5 := garray.NewSortedStringArrayFrom(s2)
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a1 := garray.NewSortedIntArrayFrom(i0)
gtest.Assert(a1.Merge(s2).Len(), 6)
gtest.Assert(a1.Merge(i1).Len(), 9)
gtest.Assert(a1.Merge(i2).Len(), 10)
gtest.Assert(a1.Merge(s3).Len(), 12)
gtest.Assert(a1.Merge(s4).Len(), 14)
gtest.Assert(a1.Merge(s5).Len(), 16)
gtest.Assert(a1.Merge(s6).Len(), 19)
})
}

View File

@ -14,28 +14,36 @@ import (
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
"time"
)
func Test_Array_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
array2 := garray.NewArrayFrom(expect)
array3 := garray.NewArrayFrom([]interface{}{})
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array3.Search(100), -1)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array2.Remove(3), 3)
gtest.Assert(array2.Remove(1), 1)
gtest.Assert(array.Contains(100), false)
array.Append(4)
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, 100)
array.InsertAfter(0, 200)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 4})
gtest.Assert(array.Slice(), []interface{}{100, 200, 2, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 300, 4, 400})
gtest.Assert(array.Slice(), []interface{}{100, 200, 2, 2, 3, 300, 4, 400})
gtest.Assert(array.Clear().Len(), 0)
})
}
@ -111,20 +119,48 @@ func TestArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(value1)
array2 := garray.NewArrayFrom(value1, true)
gtest.Assert(array1.Range(0, 1), []interface{}{0})
gtest.Assert(array1.Range(1, 2), []interface{}{1})
gtest.Assert(array1.Range(0, 2), []interface{}{0, 1})
gtest.Assert(array1.Range(-1, 10), value1)
gtest.Assert(array1.Range(10, 2), nil)
gtest.Assert(array2.Range(1, 3), []interface{}{1, 2})
})
}
func TestArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3}
a2 := []interface{}{4, 5, 6, 7}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
i1 := []interface{}{0, 1, 2, 3}
i2 := []interface{}{4, 5, 6, 7}
array1 := garray.NewArrayFrom(i1)
array2 := garray.NewArrayFrom(i2)
gtest.Assert(array1.Merge(array2).Slice(), []interface{}{0, 1, 2, 3, 4, 5, 6, 7})
//s1 := []string{"a", "b", "c", "d"}
s2 := []string{"e", "f"}
i3 := garray.NewIntArrayFrom([]int{1, 2, 3})
i4 := garray.NewArrayFrom([]interface{}{3})
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
s5 := garray.NewSortedStringArrayFrom(s2)
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a1 := garray.NewArrayFrom(i1)
gtest.Assert(a1.Merge(s2).Len(), 6)
gtest.Assert(a1.Merge(i3).Len(), 9)
gtest.Assert(a1.Merge(i4).Len(), 10)
gtest.Assert(a1.Merge(s3).Len(), 12)
gtest.Assert(a1.Merge(s4).Len(), 14)
gtest.Assert(a1.Merge(s5).Len(), 16)
gtest.Assert(a1.Merge(s6).Len(), 19)
})
}
@ -133,9 +169,10 @@ func TestArray_Fill(t *testing.T) {
a1 := []interface{}{0}
a2 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
array2 := garray.NewArrayFrom(a2, true)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []interface{}{0, 100, 100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []interface{}{100, 100})
gtest.Assert(array2.Fill(-1, 2, 100).Slice(), []interface{}{100, 100})
})
}
@ -148,6 +185,7 @@ func TestArray_Chunk(t *testing.T) {
gtest.Assert(chunks[0], []interface{}{1, 2})
gtest.Assert(chunks[1], []interface{}{3, 4})
gtest.Assert(chunks[2], []interface{}{5})
gtest.Assert(array1.Chunk(0), nil)
})
}
@ -165,9 +203,15 @@ func TestArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a1, true)
gtest.Assert(array1.SubSlice(0, 2), []interface{}{0, 1})
gtest.Assert(array1.SubSlice(2, 2), []interface{}{2, 3})
gtest.Assert(array1.SubSlice(5, 8), []interface{}{5, 6})
gtest.Assert(array1.SubSlice(9, 1), nil)
gtest.Assert(array1.SubSlice(-2, 2), []interface{}{5, 6})
gtest.Assert(array1.SubSlice(-9, 2), nil)
gtest.Assert(array1.SubSlice(1, -2), nil)
gtest.Assert(array2.SubSlice(0, 2), []interface{}{0, 1})
})
}
@ -179,6 +223,14 @@ func TestArray_Rand(t *testing.T) {
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
})
gtest.Case(t, func() {
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewArrayFrom(s1)
i1 := a1.Rand()
gtest.Assert(a1.Contains(i1), true)
gtest.Assert(a1.Len(), 4)
})
}
func TestArray_Shuffle(t *testing.T) {
@ -496,6 +548,7 @@ func TestSortedArray_Range(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array2 := garray.NewSortedArrayFrom(a1, func1, true)
i1 := array1.Range(2, 5)
gtest.Assert(i1, []interface{}{"c", "d", "e"})
gtest.Assert(array1.Len(), 6)
@ -509,6 +562,8 @@ func TestSortedArray_Range(t *testing.T) {
gtest.Assert(len(i2), 2)
gtest.Assert(i2, []interface{}{"e", "f"})
gtest.Assert(array2.Range(1, 3), []interface{}{"b", "c"})
})
}
@ -587,6 +642,7 @@ func TestSortedArray_SubSlice(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array2 := garray.NewSortedArrayFrom(a1, func1, true)
i1 := array1.SubSlice(2, 3)
gtest.Assert(len(i1), 3)
gtest.Assert(i1, []interface{}{"c", "d", "e"})
@ -598,6 +654,13 @@ func TestSortedArray_SubSlice(t *testing.T) {
i1 = array1.SubSlice(7, 2)
gtest.Assert(len(i1), 0)
s1 := array1.SubSlice(1, -2)
gtest.Assert(s1, nil)
s1 = array1.SubSlice(-9, 2)
gtest.Assert(s1, nil)
gtest.Assert(array2.SubSlice(1, 3), []interface{}{"b", "c", "d"})
})
}
@ -676,3 +739,168 @@ func TestSortedArray_SetUnique(t *testing.T) {
gtest.Assert(array1, []interface{}{"a", "c", "d"})
})
}
func TestSortedArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewSortedArrayFrom(s1, func1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []interface{}) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestSortedArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewSortedArrayFrom(s1, func1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.RLockFunc(func(n1 []interface{}) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候不会被阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestSortedArray_Merge(t *testing.T) {
gtest.Case(t, func() {
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
s1 := []interface{}{"a", "b", "c", "d"}
s2 := []string{"e", "f"}
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
i2 := garray.NewArrayFrom([]interface{}{3})
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
s5 := garray.NewSortedStringArrayFrom(s2)
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a1 := garray.NewSortedArrayFrom(s1, func1)
gtest.Assert(a1.Merge(s2).Len(), 6)
gtest.Assert(a1.Merge(i1).Len(), 9)
gtest.Assert(a1.Merge(i2).Len(), 10)
gtest.Assert(a1.Merge(s3).Len(), 12)
gtest.Assert(a1.Merge(s4).Len(), 14)
gtest.Assert(a1.Merge(s5).Len(), 16)
gtest.Assert(a1.Merge(s6).Len(), 19)
})
}
func TestArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []interface{}) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
//go1
go a1.RLockFunc(func(n1 []interface{}) { //读锁
time.Sleep(2 * time.Second) //暂停1秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候并没有阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}

View File

@ -14,12 +14,15 @@ import (
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
"time"
)
func Test_StringArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
array2 := garray.NewStringArrayFrom(expect, true)
array3 := garray.NewStringArrayFrom([]string{})
gtest.Assert(array.Slice(), expect)
array.Set(0, "100")
gtest.Assert(array.Get(0), 100)
@ -37,6 +40,8 @@ func Test_StringArray_Basic(t *testing.T) {
array.InsertAfter(6, "400")
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "300", "4", "400"})
gtest.Assert(array.Clear().Len(), 0)
gtest.Assert(array2.Slice(), expect)
gtest.Assert(array3.Search("100"), -1)
})
}
@ -99,20 +104,48 @@ func TestString_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(value1)
array2 := garray.NewStringArrayFrom(value1, true)
gtest.Assert(array1.Range(0, 1), []interface{}{"0"})
gtest.Assert(array1.Range(1, 2), []interface{}{"1"})
gtest.Assert(array1.Range(0, 2), []interface{}{"0", "1"})
gtest.Assert(array1.Range(-1, 10), value1)
gtest.Assert(array1.Range(10, 1), nil)
gtest.Assert(array2.Range(0, 1), []interface{}{"0"})
})
}
func TestStringArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3"}
a2 := []string{"4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
a11 := []string{"0", "1", "2", "3"}
a21 := []string{"4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a11)
array2 := garray.NewStringArrayFrom(a21)
gtest.Assert(array1.Merge(array2).Slice(), []string{"0", "1", "2", "3", "4", "5", "6", "7"})
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
s1 := []string{"a", "b", "c", "d"}
s2 := []string{"e", "f"}
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
i2 := garray.NewArrayFrom([]interface{}{3})
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
s5 := garray.NewSortedStringArrayFrom(s2)
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a1 := garray.NewStringArrayFrom(s1)
gtest.Assert(a1.Merge(s2).Len(), 6)
gtest.Assert(a1.Merge(i1).Len(), 9)
gtest.Assert(a1.Merge(i2).Len(), 10)
gtest.Assert(a1.Merge(s3).Len(), 12)
gtest.Assert(a1.Merge(s4).Len(), 14)
gtest.Assert(a1.Merge(s5).Len(), 16)
gtest.Assert(a1.Merge(s6).Len(), 19)
})
}
@ -139,7 +172,6 @@ func TestStringArray_Chunk(t *testing.T) {
gtest.Assert(chunks[1], []string{"3", "4"})
gtest.Assert(chunks[2], []string{"5"})
gtest.Assert(len(array1.Chunk(0)), 0)
})
}
@ -157,9 +189,15 @@ func TestStringArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a1, true)
gtest.Assert(array1.SubSlice(0, 2), []string{"0", "1"})
gtest.Assert(array1.SubSlice(2, 2), []string{"2", "3"})
gtest.Assert(array1.SubSlice(5, 8), []string{"5", "6"})
gtest.Assert(array1.SubSlice(8, 2), nil)
gtest.Assert(array1.SubSlice(1, -2), nil)
gtest.Assert(array1.SubSlice(-5, 2), []string{"2", "3"})
gtest.Assert(array1.SubSlice(-10, 1), nil)
gtest.Assert(array2.SubSlice(0, 2), []string{"0", "1"})
})
}
@ -172,7 +210,6 @@ func TestStringArray_Rand(t *testing.T) {
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.Assert(len(array1.Rand()), 1)
gtest.AssertIN(array1.Rand(), a1)
})
}
@ -181,10 +218,9 @@ func TestStringArray_PopRands(t *testing.T) {
a1 := []string{"a", "b", "c", "d", "e", "f", "g"}
a2 := []string{"1", "2", "3", "4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a1)
//todo gtest.AssertIN(array1.PopRands(1),a1)
gtest.AssertIN(array1.PopRands(1), strings.Join(a1, ","))
gtest.AssertNI(array1.PopRands(1), strings.Join(a2, ","))
gtest.Assert(len(array1.PopRands(10)), 5)
})
}
@ -275,26 +311,6 @@ func TestStringArray_Sum(t *testing.T) {
})
}
//func TestStringArray_SortFunc(t *testing.T) {
// gtest.Case(t, func() {
// a1 := []string{"0","1","2","3","4","5","6"}
// //a2 := []string{"0","a","3","4","5","6"}
// array1 := garray.NewStringArrayFrom(a1)
//
// lesss:=func(v1,v2 string)bool{
// if v1>v2{
// return true
// }
// return false
// }
// gtest.Assert(array1.Len(),7)
// gtest.Assert(lesss("1","2"),false)
// gtest.Assert(array1.SortFunc(lesss("1","2")) ,false)
//
//
// })
//}
func TestStringArray_PopRand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
@ -324,7 +340,6 @@ func TestStringArray_CountValues(t *testing.T) {
gtest.Assert(len(m1), 6)
gtest.Assert(m1["2"], 1)
gtest.Assert(m1["4"], 2)
})
}
@ -357,7 +372,6 @@ func TestSortedStringArray_SetArray(t *testing.T) {
gtest.Assert(array1.Contains("d"), false)
gtest.Assert(array1.Contains("b"), false)
gtest.Assert(array1.Contains("g"), true)
})
}
@ -367,7 +381,7 @@ func TestSortedStringArray_Sort(t *testing.T) {
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1, []string{"a", "b", "c", "d"})
array1.Sort() //todo 这个SortedStringArray.sort这个方法没有必要
array1.Sort()
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1.Contains("c"), true)
gtest.Assert(array1, []string{"a", "b", "c", "d"})
@ -380,7 +394,6 @@ func TestSortedStringArray_Get(t *testing.T) {
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1.Get(2), "c")
gtest.Assert(array1.Get(0), "a")
})
}
@ -400,7 +413,6 @@ func TestSortedStringArray_Remove(t *testing.T) {
// 此时array1里的元素只剩下2个
gtest.Assert(array1.Remove(1), "d")
gtest.Assert(array1.Len(), 1)
})
}
@ -486,6 +498,7 @@ func TestSortedStringArray_Range(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := garray.NewSortedStringArrayFrom(a1, true)
s1 := array1.Range(2, 4)
gtest.Assert(len(s1), 2)
gtest.Assert(s1, []string{"c", "d"})
@ -497,6 +510,11 @@ func TestSortedStringArray_Range(t *testing.T) {
s1 = array1.Range(4, 8)
gtest.Assert(len(s1), 3)
gtest.Assert(s1, []string{"e", "f", "g"})
gtest.Assert(array1.Range(10, 2), nil)
s2 := array2.Range(2, 4)
gtest.Assert(s2, []string{"c", "d"})
})
}
@ -528,7 +546,6 @@ func TestSortedStringArray_Clear(t *testing.T) {
array1 := garray.NewSortedStringArrayFrom(a1)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
@ -536,6 +553,7 @@ func TestSortedStringArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := garray.NewSortedStringArrayFrom(a1, true)
s1 := array1.SubSlice(1, 3)
gtest.Assert(len(s1), 3)
gtest.Assert(s1, []string{"b", "c", "d"})
@ -547,6 +565,16 @@ func TestSortedStringArray_SubSlice(t *testing.T) {
s3 := array1.SubSlice(10, 2)
gtest.Assert(len(s3), 0)
s3 = array1.SubSlice(-5, 2)
gtest.Assert(s3, []string{"c", "d"})
s3 = array1.SubSlice(-10, 2)
gtest.Assert(s3, nil)
s3 = array1.SubSlice(1, -2)
gtest.Assert(s3, nil)
gtest.Assert(array2.SubSlice(1, 3), []string{"b", "c", "d"})
})
}
@ -564,7 +592,6 @@ func TestSortedStringArray_Rand(t *testing.T) {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.AssertIN(array1.Rand(), []string{"e", "a", "d"})
})
}
@ -611,6 +638,7 @@ func TestSortedStringArray_Chunk(t *testing.T) {
gtest.Assert(len(array2), 3)
gtest.Assert(len(array2[0]), 2)
gtest.Assert(array2[1], []string{"c", "d"})
gtest.Assert(array1.Chunk(0), nil)
})
}
@ -634,6 +662,174 @@ func TestStringArray_Remove(t *testing.T) {
s1 = array1.Remove(3)
gtest.Assert(s1, "c")
gtest.Assert(array1.Len(), 3)
})
}
func TestStringArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []string{"a", "b", "c", "d"}
a1 := garray.NewStringArrayFrom(s1, true)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
//go1
go a1.RLockFunc(func(n1 []string) { //读锁
time.Sleep(2 * time.Second) //暂停1秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候并没有阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestSortedStringArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []string{"a", "b", "c", "d"}
a1 := garray.NewSortedStringArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []string) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestSortedStringArray_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []string{"a", "b", "c", "d"}
a1 := garray.NewSortedStringArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
//go1
go a1.RLockFunc(func(n1 []string) { //读锁
time.Sleep(2 * time.Second) //暂停1秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertLT(t2-t1, 20) //go1加的读锁所go2读的时候并没有阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}
func TestSortedStringArray_Merge(t *testing.T) {
gtest.Case(t, func() {
func1 := func(v1, v2 interface{}) int {
if gconv.Int(v1) < gconv.Int(v2) {
return 0
}
return 1
}
s1 := []string{"a", "b", "c", "d"}
s2 := []string{"e", "f"}
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
i2 := garray.NewArrayFrom([]interface{}{3})
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
s5 := garray.NewSortedStringArrayFrom(s2)
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
a1 := garray.NewSortedStringArrayFrom(s1)
gtest.Assert(a1.Merge(s2).Len(), 6)
gtest.Assert(a1.Merge(i1).Len(), 9)
gtest.Assert(a1.Merge(i2).Len(), 10)
gtest.Assert(a1.Merge(s3).Len(), 12)
gtest.Assert(a1.Merge(s4).Len(), 14)
gtest.Assert(a1.Merge(s5).Len(), 16)
gtest.Assert(a1.Merge(s6).Len(), 19)
})
}
func TestStringArray_SortFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []string{"a", "d", "c", "b"}
a1 := garray.NewStringArrayFrom(s1)
func1 := func(v1, v2 string) bool {
return v1 < v2
}
a11 := a1.SortFunc(func1)
gtest.Assert(a11, []string{"a", "b", "c", "d"})
})
}
func TestStringArray_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s1 := []string{"a", "b", "c", "d"}
a1 := garray.NewStringArrayFrom(s1)
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 3)
//go1
go a1.LockFunc(func(n1 []string) { //读写锁
time.Sleep(2 * time.Second) //暂停2秒
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
//go2
go func() {
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后再开始执行.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
}()
t1 := <-ch1
t2 := <-ch1
<-ch2 //等待go1完成
// 防止ci抖动,以豪秒为单位
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁所go2读的时候被阻塞。
gtest.Assert(a1.Contains("g"), true)
})
}

View File

@ -1,7 +1,14 @@
// 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 gpool_test
import (
"errors"
"github.com/gogf/gf/g"
"testing"
"time"
@ -30,7 +37,7 @@ func Test_Gpool(t *testing.T) {
//test won't be timeout
v1, err1 := p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, 1)
gtest.AssertIN(v1, g.Slice{1, 2})
//test clear
p1.Clear()
gtest.Assert(p1.Size(), 0)
@ -43,13 +50,12 @@ func Test_Gpool(t *testing.T) {
p1.Put(4)
v1, err1 = p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, 3)
gtest.AssertIN(v1, g.Slice{3, 4})
//test close
p1.Close()
v1, err1 = p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, "hello")
})
gtest.Case(t, func() {

View File

@ -58,33 +58,6 @@ func (v *Var) Interface() interface{} {
return v.Val()
}
// Time converts and returns <v> as time.Time.
// The parameter <format> specifies the format of the time string using gtime,
// eg: Y-m-d H:i:s.
func (v *Var) Time(format ...string) time.Time {
return gconv.Time(v.Val(), format...)
}
// Duration converts and returns <v> as time.Duration.
// If value of <v> is string, then it uses time.ParseDuration for conversion.
func (v *Var) Duration() time.Duration {
return gconv.Duration(v.Val())
}
// GTime converts and returns <v> as *gtime.Time.
// The parameter <format> specifies the format of the time string using gtime,
// eg: Y-m-d H:i:s.
func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// Struct maps value of <v> to <objPointer>.
// The parameter <objPointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// IsNil checks whether <v> is nil.
func (v *Var) IsNil() bool {
return v.Val() == nil
@ -184,3 +157,57 @@ func (v *Var) Strings() []string {
func (v *Var) Interfaces() []interface{} {
return gconv.Interfaces(v.Val())
}
// Time converts and returns <v> as time.Time.
// The parameter <format> specifies the format of the time string using gtime,
// eg: Y-m-d H:i:s.
func (v *Var) Time(format ...string) time.Time {
return gconv.Time(v.Val(), format...)
}
// Duration converts and returns <v> as time.Duration.
// If value of <v> is string, then it uses time.ParseDuration for conversion.
func (v *Var) Duration() time.Duration {
return gconv.Duration(v.Val())
}
// GTime converts and returns <v> as *gtime.Time.
// The parameter <format> specifies the format of the time string using gtime,
// eg: Y-m-d H:i:s.
func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// Map converts <v> to map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapDeep converts <v> to map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts <v> to given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts <v> to given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}

View File

@ -511,21 +511,18 @@ func (md *Model) Structs(objPointerSlice interface{}) error {
// 链式操作将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (md *Model) Scan(objPointer interface{}) error {
t := reflect.TypeOf(objPointer)
func (md *Model) Scan(pointer interface{}) error {
t := reflect.TypeOf(pointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
switch t.Elem().Kind() {
case reflect.Array:
case reflect.Slice:
return md.Structs(objPointer)
case reflect.Struct:
return md.Struct(objPointer)
return md.Structs(pointer)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
return md.Struct(pointer)
}
return nil
}

View File

@ -102,14 +102,14 @@ func (r Result) ToUintRecord(key string) map[uint]Record {
}
// 将结果列表转换为指定对象的slice。
func (r Result) ToStructs(objPointerSlice interface{}) (err error) {
func (r Result) ToStructs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return sql.ErrNoRows
}
t := reflect.TypeOf(objPointerSlice)
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", t.Kind())
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
}
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
@ -128,6 +128,6 @@ func (r Result) ToStructs(objPointerSlice interface{}) (err error) {
array.Index(i).Set(e)
}
}
reflect.ValueOf(objPointerSlice).Elem().Set(array)
reflect.ValueOf(pointer).Elem().Set(array)
return nil
}

View File

@ -358,6 +358,40 @@ func TestModel_Struct(t *testing.T) {
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
// Auto creating struct object.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := (*User)(nil)
err := db.Table("user").Where("id=1").Struct(&user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
// Just using Scan.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := (*User)(nil)
err := db.Table("user").Where("id=1").Scan(&user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
@ -396,6 +430,7 @@ func TestModel_Structs(t *testing.T) {
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
// Auto create struct slice.
gtest.Case(t, func() {
type User struct {
Id int
@ -418,7 +453,29 @@ func TestModel_Structs(t *testing.T) {
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
// Just using Scan.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
@ -595,6 +652,21 @@ func TestModel_Where(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// slice
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? AND nickname=?", g.Slice{3, "T3"}...).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("passport like ? and nickname like ?", g.Slice{"t3", "T3"}...).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
// map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id": 3, "nickname": "T3"}).One()

View File

@ -22,10 +22,7 @@ func Encode(src []byte) []byte {
func Decode(dst []byte) ([]byte, error) {
src := make([]byte, base64.StdEncoding.DecodedLen(len(dst)))
n, err := base64.StdEncoding.Decode(src, dst)
if err != nil {
return nil, err
}
return src[:n], nil
return src[:n], err
}
// EncodeString encodes bytes with BASE64 algorithm.

View File

@ -266,11 +266,32 @@ func (j *Json) GetToVar(pattern string, pointer interface{}) error {
return nil
}
// GetToStruct gets the value by specified <pattern>,
// and converts it to specified object <objPointer>.
// The <objPointer> should be the pointer to an object.
func (j *Json) GetToStruct(pattern string, pointer interface{}) error {
return gconv.Struct(j.Get(pattern), pointer)
// GetStruct gets the value by specified <pattern>,
// and converts it to specified object <pointer>.
// The <pointer> should be the pointer to an object.
func (j *Json) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(j.Get(pattern), pointer, mapping...)
}
// GetStructDeep does GetStruct recursively.
func (j *Json) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(j.Get(pattern), pointer, mapping...)
}
// GetStructs converts any slice to given struct slice.
func (j *Json) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.Structs(j.Get(pattern), pointer, mapping...)
}
// GetStructsDeep converts any slice to given struct slice recursively.
func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.StructsDeep(j.Get(pattern), pointer, mapping...)
}
// GetToStruct is alias of GetStruct.
// Deprecated.
func (j *Json) GetToStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
return j.GetStruct(pattern, pointer, mapping...)
}
// ToMap converts current Json object to map[string]interface{}.
@ -290,7 +311,7 @@ func (j *Json) ToArray() []interface{} {
}
// ToStruct converts current Json object to specified object.
// The <objPointer> should be a pointer type.
// The <pointer> should be a pointer type.
func (j *Json) ToStruct(pointer interface{}) error {
j.mu.RLock()
defer j.mu.RUnlock()

View File

@ -145,11 +145,32 @@ func (p *Parser) GetToVar(pattern string, pointer interface{}) error {
return p.json.GetToVar(pattern, pointer)
}
// GetToStruct gets the value by specified <pattern>,
// GetStruct gets the value by specified <pattern>,
// and converts it to specified object <pointer>.
// The <pointer> should be the pointer to a struct.
func (p *Parser) GetToStruct(pattern string, pointer interface{}) error {
return p.json.GetToStruct(pattern, pointer)
// The <pointer> should be the pointer to an object.
func (p *Parser) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
return p.json.GetStruct(pattern, pointer, mapping...)
}
// GetStructDeep does GetStruct recursively.
func (p *Parser) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return p.json.GetStructDeep(pattern, pointer, mapping...)
}
// GetStructs converts any slice to given struct slice.
func (p *Parser) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error {
return p.json.GetStructs(pattern, pointer, mapping...)
}
// GetStructsDeep converts any slice to given struct slice recursively.
func (p *Parser) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return p.json.GetStructsDeep(pattern, pointer, mapping...)
}
// GetToStruct is alias of GetStruct.
// Deprecated.
func (p *Parser) GetToStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
return p.json.GetStruct(pattern, pointer, mapping...)
}
// Set sets value with specified <pattern>.

View File

@ -87,75 +87,34 @@ func Database(name ...string) gdb.DB {
glog.Error(`database init failed: "database" node not found, is config file or configuration missing?`)
return nil
}
for group, v := range m {
// Parse <m> as map-slice.
for group, groupConfig := range m {
cg := gdb.ConfigGroup{}
if list, ok := v.([]interface{}); ok {
for _, nodeValue := range list {
node := gdb.ConfigNode{}
nodeMap := nodeValue.(map[string]interface{})
if value, ok := nodeMap["host"]; ok {
node.Host = gconv.String(value)
switch value := groupConfig.(type) {
case []interface{}:
for _, v := range value {
if node := parseDBConfigNode(v); node != nil {
cg = append(cg, *node)
}
if value, ok := nodeMap["port"]; ok {
node.Port = gconv.String(value)
}
if value, ok := nodeMap["user"]; ok {
node.User = gconv.String(value)
}
if value, ok := nodeMap["pass"]; ok {
node.Pass = gconv.String(value)
}
if value, ok := nodeMap["name"]; ok {
node.Name = gconv.String(value)
}
if value, ok := nodeMap["type"]; ok {
node.Type = gconv.String(value)
}
if value, ok := nodeMap["role"]; ok {
node.Role = gconv.String(value)
}
if value, ok := nodeMap["charset"]; ok {
node.Charset = gconv.String(value)
}
if value, ok := nodeMap["priority"]; ok {
node.Priority = gconv.Int(value)
}
// Deprecated
if value, ok := nodeMap["linkinfo"]; ok {
node.LinkInfo = gconv.String(value)
}
// Deprecated
if value, ok := nodeMap["link-info"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["linkInfo"]; ok {
node.LinkInfo = gconv.String(value)
}
// Deprecated
if value, ok := nodeMap["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxIdle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
// Deprecated
if value, ok := nodeMap["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxOpen"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
// Deprecated
if value, ok := nodeMap["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
if value, ok := nodeMap["maxLifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
cg = append(cg, node)
}
case map[string]interface{}:
if node := parseDBConfigNode(value); node != nil {
cg = append(cg, *node)
}
}
gdb.AddConfigGroup(group, cg)
if len(cg) > 0 {
gdb.AddConfigGroup(group, cg)
}
}
// Parse <m> as a single node configuration.
if node := parseDBConfigNode(m); node != nil {
cg := gdb.ConfigGroup{}
if node.LinkInfo != "" || node.Host != "" {
cg = append(cg, *node)
}
if len(cg) > 0 {
gdb.AddConfigGroup(group, cg)
}
}
addConfigMonitor(key, config)
}
@ -172,6 +131,81 @@ func Database(name ...string) gdb.DB {
return nil
}
// 解析数据库配置节点项
func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
nodeMap, ok := value.(map[string]interface{})
if !ok {
return nil
}
node := &gdb.ConfigNode{}
if value, ok := nodeMap["host"]; ok {
node.Host = gconv.String(value)
}
if value, ok := nodeMap["port"]; ok {
node.Port = gconv.String(value)
}
if value, ok := nodeMap["user"]; ok {
node.User = gconv.String(value)
}
if value, ok := nodeMap["pass"]; ok {
node.Pass = gconv.String(value)
}
if value, ok := nodeMap["name"]; ok {
node.Name = gconv.String(value)
}
if value, ok := nodeMap["type"]; ok {
node.Type = gconv.String(value)
}
if value, ok := nodeMap["role"]; ok {
node.Role = gconv.String(value)
}
if value, ok := nodeMap["charset"]; ok {
node.Charset = gconv.String(value)
}
if value, ok := nodeMap["priority"]; ok {
node.Priority = gconv.Int(value)
}
if value, ok := nodeMap["linkinfo"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["link-info"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["linkInfo"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["link"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxIdle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodeMap["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxOpen"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodeMap["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
if value, ok := nodeMap["maxLifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
// Parse link syntax.
if node.LinkInfo != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.LinkInfo)
if len(match) == 3 {
node.Type = match[1]
node.LinkInfo = match[2]
}
}
return node
}
// Redis操作对象使用了连接池
func Redis(name ...string) *gredis.Redis {
config := Config()

View File

@ -154,5 +154,5 @@ func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]str
for k, v := range r.GetPostMap() {
params[k] = v
}
return gconv.Struct(params, pointer, tagMap)
return gconv.StructDeep(params, pointer, tagMap)
}

View File

@ -489,14 +489,41 @@ func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time {
return nil
}
func (c *Config) GetToStruct(pattern string, pointer interface{}, def ...interface{}) error {
func (c *Config) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetToStruct(pattern, pointer)
return j.GetStruct(pattern, pointer, mapping...)
}
return errors.New("config file not found")
}
// Deprecated. See Clear.
func (c *Config) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetStructDeep(pattern, pointer, mapping...)
}
return errors.New("config file not found")
}
func (c *Config) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetStructs(pattern, pointer, mapping...)
}
return errors.New("config file not found")
}
func (c *Config) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetStructsDeep(pattern, pointer, mapping...)
}
return errors.New("config file not found")
}
// Deprecated.
func (c *Config) GetToStruct(pattern string, pointer interface{}) error {
return c.GetStruct(pattern, pointer)
}
// Reload is alias of Clear.
// Deprecated.
func (c *Config) Reload() {
c.jsons.Clear()
}

View File

@ -11,11 +11,8 @@ import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"io"
"io/ioutil"
"os"
"os/exec"
"os/user"
@ -24,6 +21,11 @@ import (
"sort"
"strings"
"time"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
)
const (
@ -142,29 +144,109 @@ func Rename(src string, dst string) error {
return Move(src, dst)
}
// Copy file from <src> to <dst>.
// Copy file/directory from <src> to <dst>.
//
// @TODO directory copy support.
// If <src> is file, it calls CopyFile to implements copy feature,
// or else it calls CopyDir.
func Copy(src string, dst string) error {
srcFile, err := Open(src)
if IsFile(src) {
return CopyFile(src, dst)
}
return CopyDir(src, dst)
}
// CopyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
// Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
func CopyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer func() {
if e := in.Close(); e != nil {
err = e
}
}()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
if e := out.Close(); e != nil {
err = e
}
}()
_, err = io.Copy(out, in)
if err != nil {
return
}
err = out.Sync()
if err != nil {
return
}
si, err := os.Stat(src)
if err != nil {
return
}
err = os.Chmod(dst, si.Mode())
if err != nil {
return
}
return
}
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
si, err := os.Stat(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := Create(dst)
if err != nil {
return err
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
err = dstFile.Sync()
if err != nil {
return err
if err == nil {
return fmt.Errorf("destination already exists")
}
return nil
err = os.MkdirAll(dst, si.Mode())
if err != nil {
return
}
entries, err := ioutil.ReadDir(src)
if err != nil {
return
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}
// DirNames returns sub-file names of given directory <path>.

View File

@ -1,12 +1,13 @@
package gfile_test
import (
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
)
func TestIsDir(t *testing.T) {
@ -677,3 +678,71 @@ func TestMainPkgPath(t *testing.T) {
gtest.Assert(reads, "")
})
}
func TestCopyFile(t *testing.T) {
gtest.Case(t, func() {
var (
paths string = "/testfile_copyfile1.txt"
topath string = "/testfile_copyfile2.txt"
)
createTestFile(paths, "")
defer delTestFiles(paths)
gtest.Assert(gfile.CopyFile(testpath()+paths, testpath()+topath), nil)
defer delTestFiles(topath)
gtest.Assert(gfile.IsFile(testpath()+topath), true)
gtest.AssertNE(gfile.CopyFile("", ""), nil)
})
}
func TestCopyDir(t *testing.T) {
gtest.Case(t, func() {
var (
dirpath1 string = "/testcopydir1"
dirpath2 string = "/testcopydir2"
)
havelist1 := []string{
"t1.txt",
"t2.txt",
}
createDir(dirpath1)
for _, v := range havelist1 {
createTestFile(dirpath1+"/"+v, "")
}
defer delTestFiles(dirpath1)
yfolder := testpath() + dirpath1
tofolder := testpath() + dirpath2
if gfile.IsDir(tofolder) {
gtest.Assert(gfile.Remove(tofolder), nil)
gtest.Assert(gfile.Remove(""), nil)
}
gtest.Assert(gfile.CopyDir(yfolder, tofolder), nil)
defer delTestFiles(tofolder)
// 检查复制后的旧文件夹是否真实存在
gtest.Assert(gfile.IsDir(yfolder), true)
// 检查复制后的旧文件夹中的文件是否真实存在
for _, v := range havelist1 {
gtest.Assert(gfile.IsFile(yfolder+"/"+v), true)
}
// 检查复制后的新文件夹是否真实存在
gtest.Assert(gfile.IsDir(tofolder), true)
// 检查复制后的新文件夹中的文件是否真实存在
for _, v := range havelist1 {
gtest.Assert(gfile.IsFile(tofolder+"/"+v), true)
}
gtest.Assert(gfile.Remove(tofolder), nil)
gtest.Assert(gfile.Remove(""), nil)
})
}

View File

@ -7,10 +7,53 @@
package gconv
import (
"github.com/gogf/gf/g/text/gstr"
"errors"
"fmt"
"reflect"
"github.com/gogf/gf/g/text/gstr"
)
// SliceInt is alias of Ints.
func SliceInt(i interface{}) []int {
return Ints(i)
}
// SliceStr is alias of Strings.
func SliceStr(i interface{}) []string {
return Strings(i)
}
// SliceAny is alias of Interfaces.
func SliceAny(i interface{}) []interface{} {
return Interfaces(i)
}
// SliceFloat is alias of Floats.
func SliceFloat(i interface{}) []float64 {
return Floats(i)
}
// SliceMap is alias of Maps.
func SliceMap(i interface{}) []map[string]interface{} {
return Maps(i)
}
// SliceMapDeep is alias of MapsDeep.
func SliceMapDeep(i interface{}) []map[string]interface{} {
return MapsDeep(i)
}
// SliceStruct is alias of Structs.
func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Structs(params, pointer, mapping...)
}
// SliceStructDeep is alias of StructsDeep.
func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return StructsDeep(params, pointer, mapping...)
}
// Ints converts <i> to []int.
func Ints(i interface{}) []int {
if i == nil {
@ -306,9 +349,7 @@ func Interfaces(i interface{}) []interface{} {
kind = rv.Kind()
}
switch kind {
case reflect.Slice:
fallthrough
case reflect.Array:
case reflect.Slice, reflect.Array:
for i := 0; i < rv.Len(); i++ {
array = append(array, rv.Index(i).Interface())
}
@ -348,3 +389,99 @@ func Maps(i interface{}) []map[string]interface{} {
return list
}
}
// MapsDeep converts <i> to []map[string]interface{} recursively.
func MapsDeep(i interface{}) []map[string]interface{} {
if i == nil {
return nil
}
if r, ok := i.([]map[string]interface{}); ok {
return r
} else {
array := Interfaces(i)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = MapDeep(v)
}
return list
}
}
// Structs converts any slice to given struct slice.
func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, false, mapping...)
}
// StructsDeep converts any slice to given struct slice recursively.
func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, true, mapping...)
}
// doStructs converts any slice to given struct slice.
//
// The parameter <params> should be type of slice.
//
// The parameter <pointer> should be type of pointer to slice of struct.
// Note that if <pointer> is a pointer to another pointer of type of slice of struct,
// it will create the struct/pointer internally.
func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
if params == nil {
return errors.New("params cannot be nil")
}
if pointer == nil {
return errors.New("object pointer cannot be nil")
}
pointerRt := reflect.TypeOf(pointer)
if kind := pointerRt.Kind(); kind != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", kind)
}
rv := reflect.ValueOf(params)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice, reflect.Array:
array := reflect.MakeSlice(pointerRt.Elem(), rv.Len(), rv.Len())
itemType := array.Index(0).Type()
for i := 0; i < rv.Len(); i++ {
if itemType.Kind() == reflect.Ptr {
// Slice element is type pointer.
e := reflect.New(itemType.Elem()).Elem()
if deep {
if err = StructDeep(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
} else {
if err = Struct(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
}
array.Index(i).Set(e.Addr())
} else {
// Slice element is not type of pointer.
e := reflect.New(itemType).Elem()
if deep {
if err = StructDeep(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
} else {
if err = Struct(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
}
array.Index(i).Set(e)
}
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil
default:
return fmt.Errorf("params should be type of slice, but got: %v", kind)
}
}

View File

@ -52,6 +52,13 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
return errors.New("object pointer cannot be nil")
}
elem = rv.Elem()
// Auto create struct object.
// For example, if <pointer> is **User, then <elem> is *User, which is a pointer to User.
if elem.Type().Kind() == reflect.Ptr && (!elem.IsValid() || elem.IsNil()) {
e := reflect.New(elem.Type().Elem()).Elem()
elem.Set(e.Addr())
elem = e
}
}
// It only performs one converting to the same attribute.
// doneMap is used to check repeated converting.

View File

@ -7,10 +7,11 @@
package gconv_test
import (
"testing"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
)
func Test_Slice(t *testing.T) {
@ -35,3 +36,50 @@ func Test_Slice_PrivateAttribute(t *testing.T) {
gtest.Assert(gconv.Interfaces(user), g.Slice{1})
})
}
func Test_Slice_Structs(t *testing.T) {
type Base struct {
Age int
}
type User struct {
Id int
Name string
Base
}
gtest.Case(t, func() {
users := make([]User, 0)
params := []g.Map{
{"id": 1, "name": "john", "age": 18},
{"id": 2, "name": "smith", "age": 20},
}
err := gconv.Structs(params, &users)
gtest.Assert(err, nil)
gtest.Assert(len(users), 2)
gtest.Assert(users[0].Id, params[0]["id"])
gtest.Assert(users[0].Name, params[0]["name"])
gtest.Assert(users[0].Age, 0)
gtest.Assert(users[1].Id, params[1]["id"])
gtest.Assert(users[1].Name, params[1]["name"])
gtest.Assert(users[1].Age, 0)
})
gtest.Case(t, func() {
users := make([]User, 0)
params := []g.Map{
{"id": 1, "name": "john", "age": 18},
{"id": 2, "name": "smith", "age": 20},
}
err := gconv.StructsDeep(params, &users)
gtest.Assert(err, nil)
gtest.Assert(len(users), 2)
gtest.Assert(users[0].Id, params[0]["id"])
gtest.Assert(users[0].Name, params[0]["name"])
gtest.Assert(users[0].Age, params[0]["age"])
gtest.Assert(users[1].Id, params[1]["id"])
gtest.Assert(users[1].Name, params[1]["name"])
gtest.Assert(users[1].Age, params[1]["age"])
})
}

View File

@ -26,9 +26,8 @@ func Test_Struct_Basic1(t *testing.T) {
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user := (*User)(nil)
// 使用默认映射规则绑定属性值到对象
user = new(User)
user := new(User)
params1 := g.Map{
"uid": 1,
"Name": "john",
@ -339,6 +338,29 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
}
func Test_Struct_Deep(t *testing.T) {
gtest.Case(t, func() {
type Base struct {
Age int
}
type User struct {
Id int
Name string
Base
}
user := new(User)
params := g.Map{
"id": 1,
"name": "john",
"age": 18,
}
err := gconv.StructDeep(params, user)
gtest.Assert(err, nil)
gtest.Assert(user.Id, params["id"])
gtest.Assert(user.Name, params["name"])
gtest.Assert(user.Age, params["age"])
})
gtest.Case(t, func() {
type Ids struct {
Id int `json:"id"`
@ -363,7 +385,8 @@ func Test_Struct_Deep(t *testing.T) {
"create_time": "2019",
}
user := new(User)
gconv.StructDeep(data, user)
err := gconv.StructDeep(data, user)
gtest.Assert(err, nil)
gtest.Assert(user.Id, 100)
gtest.Assert(user.Uid, 101)
gtest.Assert(user.Nickname, "T1")
@ -432,3 +455,38 @@ func Test_Struct_Time(t *testing.T) {
gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
})
}
// Auto create struct when given pointer.
func Test_Struct_Create(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"Name": "john",
}
err := gconv.Struct(params, &user)
gtest.Assert(err, nil)
gtest.Assert(user.Uid, 1)
gtest.Assert(user.Name, "john")
})
gtest.Case(t, func() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"Name": "john",
}
err := gconv.Struct(params, user)
gtest.AssertNE(err, nil)
gtest.Assert(user, nil)
})
}

View File

@ -0,0 +1,4 @@
# MySQL数据库配置
[database]
link = "mysql:root:8692651@tcp(192.168.1.11:3306)/test"

View File

@ -0,0 +1,7 @@
# MySQL数据库配置
[database]
[database.default]
link = "mysql:root:8692651@tcp(192.168.1.11:3306)/test"
[database.user]
link = "mysql:root:8692651@tcp(192.168.1.11:3306)/test"

View File

@ -6,7 +6,6 @@ import (
)
func main() {
g.Config().AddPath("/home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/geg/frame")
if r, err := g.DB().Table("user").Where("uid=?", 1).One(); err == nil {
fmt.Println(r["uid"].Int())
fmt.Println(r["name"].String())

View File

@ -0,0 +1,16 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
)
func main() {
g.Config().SetFileName("config2.toml")
if r, err := g.DB().Table("user").Where("uid=?", 1).One(); err == nil {
fmt.Println(r["uid"].Int())
fmt.Println(r["name"].String())
} else {
fmt.Println(err)
}
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
)
func main() {
g.Config().SetFileName("config3.toml")
if r, err := g.DB().Table("user").Where("uid=?", 1).One(); err == nil {
fmt.Println(r["uid"].Int())
fmt.Println(r["name"].String())
} else {
fmt.Println(err)
}
if r, err := g.DB("user").Table("user").Where("uid=?", 1).One(); err == nil {
fmt.Println(r["uid"].Int())
fmt.Println(r["name"].String())
} else {
fmt.Println(err)
}
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
)
func main() {
db := g.DB()
// 开启调试模式以便于记录所有执行的SQL
db.SetDebug(true)
type User struct {
Uid int
Name string
}
user := (*User)(nil)
fmt.Println(user)
err := db.Table("test").Where("id=1").Struct(&user)
fmt.Println(err)
fmt.Println(user)
}

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"github.com/gogf/gf/g"
)

View File

@ -7,7 +7,7 @@ import (
func main() {
//path := `D:\temp`
path := "/home/john/temp"
path := "/Users/john/Temp"
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Println(event)
})

View File

@ -1,40 +1,19 @@
package main
import (
"encoding/base64"
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/encoding/gparser"
"github.com/gogf/gf/g/encoding/gbase64"
)
func main() {
type User struct {
Uid int
Name string
SiteUrl string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
g.Dump(gconv.Map(User{
Uid: 100,
Name: "john",
SiteUrl: "https://goframe.org",
Pass1: "123",
Pass2: "456",
}))
s, err := gparser.VarToJsonString(User{
Uid: 100,
Name: "john",
SiteUrl: "https://goframe.org",
Pass1: "123",
Pass2: "456",
})
data := "HwHsGhXMaGc==="
datab, err := gbase64.Decode([]byte(data))
fmt.Println(err)
fmt.Println(s)
fmt.Println(datab)
fmt.Println(string(datab))
s, e := base64.StdEncoding.DecodeString(data)
fmt.Println(e)
fmt.Println(string(s))
}

View File

@ -0,0 +1,18 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Id int `json:"uid"`
Name string `my-tag:"nick-name" json:"name"`
}
user := &User{
Id: 1,
Name: "john",
}
g.Dump(gconv.Map(user, "my-tag"))
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"name": "john",
}
err := gconv.Struct(params, &user)
if err != nil {
panic(err)
}
g.Dump(user)
}

1
go.mod
View File

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

View File

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