Compare commits

..

39 Commits

Author SHA1 Message Date
7e95058cb5 donator updates 2020-06-08 22:34:48 +08:00
f33753e0cd add example for package ghttp/glog 2020-06-08 20:21:35 +08:00
ac71e1d753 rename Dump to RawDump for ghttp.ClientResponse 2020-06-08 19:26:14 +08:00
8151b6efd6 add more example for package gredis 2020-06-08 19:17:24 +08:00
94de306c93 add zero time filtering for package gdb 2020-06-08 13:46:45 +08:00
51f8ea26de add example for package gredis 2020-06-06 18:12:42 +08:00
29d9bb17cd improve function Unscoped for package gdb 2020-06-06 16:28:16 +08:00
30501a882d improve function Struct for package gconv 2020-06-06 15:31:04 +08:00
f2f98e1d16 rename testing file names for package gdb 2020-06-06 14:38:05 +08:00
cbf465eeea improve body interface implements for ghttp.Reqest/Response 2020-06-06 13:34:58 +08:00
79c400e912 Merge branch 'master' of https://github.com/gogf/gf 2020-06-06 10:10:05 +08:00
cc4c49b5b0 add unit testing case for pointer attribute mapping for function Parse 2020-06-06 10:09:19 +08:00
96ea2c911d Merge pull request #696 from sanrentai/master
the sqlserver type money  should be converted to float64
2020-06-05 21:08:49 +08:00
131b11680a readme update 2020-06-05 20:36:05 +08:00
3c6ab96283 fix issue in function Walk and add more example for package gset 2020-06-04 21:46:22 +08:00
d2c4fa921a add more example for package gmap 2020-06-04 20:45:18 +08:00
dbb51a9328 improve package glist 2020-06-04 20:13:33 +08:00
55bfc3fa73 add more examples for package garray 2020-06-04 18:28:33 +08:00
d9c5182d1a gitignore updates 2020-06-04 17:56:32 +08:00
899fcbf2da add mor example for package garray 2020-06-04 17:55:43 +08:00
6e2c0d8706 comment and readme update 2020-06-04 17:29:16 +08:00
c4e599ce63 improve random bytes buffer hanlding for package grand 2020-06-04 14:45:56 +08:00
9cad9e4467 add more unit testing cases for package gstr 2020-06-04 00:03:30 +08:00
48019444ea README and DONATOR update 2020-06-03 23:49:12 +08:00
76d31a7fbb improve slice result handling by treating it as string slice for package gredis 2020-06-03 23:44:21 +08:00
facb9d93c0 fix issue of multiple slice arguments handling in function where 2020-06-03 21:36:16 +08:00
1d6bd46c5e README updates 2020-06-03 20:33:21 +08:00
8646dc1825 Merge pull request #702 from misitebao/master
更新 readme 中文版
2020-06-03 18:49:35 +08:00
2992bdeed7 修改之前的捐赠名称 2020-06-03 11:38:21 +08:00
c5601e6c88 更新 readme 中文版 2020-06-03 10:59:20 +08:00
ef1d9a561c improve the route feature for ghttp.Server 2020-06-03 00:09:51 +08:00
2d3b32c94a money 类型转换错误 2020-05-29 15:41:37 +08:00
269378aa0d fix issue in unit testing case for package gsession; version updates 2020-05-28 20:28:07 +08:00
6889542801 improve package guid 2020-05-27 11:26:05 +08:00
8e6f1f1740 improve performance of random buffer usage for package grand 2020-05-25 23:39:09 +08:00
b6ab1a992c improve empty checks for common interfaces implementer 2020-05-25 14:26:08 +08:00
a5a267567c comment update for package garray 2020-05-22 12:04:58 +08:00
f05f855c07 improve default max header bytes from 1KB to 10KB for ghttp.Server 2020-05-21 20:10:38 +08:00
bc5f773ba6 make WriteTimeout default to 0 for ghttp.Server 2020-05-19 19:16:47 +08:00
103 changed files with 1532 additions and 457 deletions

View File

@ -1,8 +1,9 @@
# MySQL数据库配置
[database]
# debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
MaxOpen = 100
#[database]
# [[database.default]]

View File

@ -2,12 +2,34 @@ package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"path/filepath"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func SendXmlFile(gameId int, areaName string, filePath string) error {
path := filepath.FromSlash(filePath)
fmt.Println(path)
data := g.Map{
"gameName": gameId,
"area": areaName,
"file": "@file:" + path,
"contentType": "json",
}
if r, err := ghttp.Post("http://127.0.0.1:8199/upload", data); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println("ok")
}
return nil
}
func main() {
SendXmlFile(1, "xxx", "/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/net/ghttp/server/session.go")
return
path := "/home/john/Workspace/Go/github.com/gogf/gf/version.go"
r, e := ghttp.Post("http://127.0.0.1:8199/upload", "upload-file=@file:"+path)
if e != nil {

View File

@ -8,7 +8,7 @@ import (
// Upload uploads files to /tmp .
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
files := r.GetUploadFiles("file")
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}

View File

@ -5,21 +5,18 @@ import (
"github.com/gogf/gf/net/ghttp"
)
type User struct {
}
type User struct{}
func (c *User) Index(r *ghttp.Request) {
r.Response.Write("Index")
}
// 不符合规范,不会被注册
func (c *User) Test(r *ghttp.Request, value interface{}) {
func (c *User) Test(r *ghttp.Request) {
r.Response.Write("Test")
}
func main() {
s := g.Server()
s.BindObject("/user", new(User))
u := new(User)
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/db-{table}/{id}", u, "Test")
})
s.SetPort(8199)
s.Run()
}

View File

@ -1,25 +0,0 @@
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
# Logger.
[logger]
Path = "/tmp/log/gf-app"
Level = "all"
Stdout = true
[viewer]
delimiters = ["${", "}"]
autoencode = true
[server]
Address = ":8800"
ServerRoot = "/Users/john/Downloads"
ServerAgent = "gf-app"
# LogPath = "./log/gf-app/server"

View File

@ -1,13 +0,0 @@
package main
import (
"fmt"
"github.com/gogf/gf/encoding/gjson"
)
func main() {
body := "{\"id\": 413231383385427875}"
if dat, err := gjson.DecodeToJson(body); err == nil {
fmt.Println(dat.MustToJsonString())
}
}

View File

@ -1,11 +0,0 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.Run()
}

View File

@ -10,4 +10,7 @@ func main() {
for i := 0; i < 100; i++ {
fmt.Println(grand.S(16))
}
for i := 0; i < 100; i++ {
fmt.Println(grand.N(0, 99999999))
}
}

View File

@ -6,7 +6,7 @@ import (
)
func main() {
for i := 0; i < 1000; i++ {
for i := 0; i < 100; i++ {
s := guid.S()
fmt.Println(s, len(s))
}

View File

@ -7,9 +7,14 @@ import (
)
func main() {
fmt.Println(strconv.FormatUint(4589634556, 36))
fmt.Println(strconv.FormatUint(math.MaxUint64-2, 36))
fmt.Println(strconv.FormatUint(math.MaxUint32-1, 36))
fmt.Println(strconv.FormatUint(math.MaxUint32, 36))
fmt.Println(strconv.FormatUint(2000000, 36))
// 36*36^2+36*36+36
var s string
fmt.Println(strconv.ParseUint("zzz", 36, 3))
fmt.Println(1 << 1)
// MaxInt64
s = strconv.FormatUint(math.MaxUint64, 16)
fmt.Println(s, len(s))
// PID
s = strconv.FormatInt(1000000, 36)
fmt.Println(s, len(s))
}

View File

@ -6,17 +6,17 @@ import (
)
func main() {
for i := 0; i < 10; i++ {
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 10; i++ {
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"), []byte("456"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 10; i++ {
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"), []byte("456"), []byte("789"))
fmt.Println(s, len(s))
}

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ cbuild
**/.DS_Store
.vscode/
go.sum
.example/other/

View File

@ -56,7 +56,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|🚶|wechat|¥6.88| 喝杯冰阔落
|a*l|wechat|¥10.00| gf
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00|
|*包|wechat|¥9.99|
|[米司特包](https://github.com/misitebao)|wechat|¥9.99|
|重庆宝尔威科技|wechat|¥6.66|
|琦玉-QPT|wechat|¥6.66|
|sailsea|wechat|¥11.00|
@ -64,7 +64,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|*华|wechat|¥6.66| 感谢郭强的热心
|[Playhi](https://github.com/Playhi)|alipay|¥10.00|
|北京京纬互动科技|alipay|¥200.00|
|米司特包|wechat|¥99.99|
|[米司特包](https://github.com/misitebao)|wechat|¥99.99|
|金毛|alipay|¥100.00|
|1*1x|wechat|¥100.00|
|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66|
@ -73,13 +73,24 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|蔡蔡|wechat|¥666.00| gf真强大让项目省心
|jack|wechat|¥100.00|
|sbilly|wechat|¥100.00| 祝好!
|米司|wechat|¥166.66| 大佬加油!
|[米司特包](https://github.com/misitebao)|wechat|¥166.66| 大佬加油!
|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
|faddei|qq|¥9.99|
|*天|wechat|¥10.00|
|*.|wechat|¥20.00| 学生一个微薄之力,冲
|李勇|wechat|¥50.00|
|[米司特包](https://github.com/misitebao)|wechat|¥66.66|
|千年|wechat|¥1.08|
|[szzxing](https://github.com/szzxing)|wechat|¥50.00|
|伟客互联|wechat|¥66.66|
|sbilly|wechat|¥99.00|
|**阳|alipay|¥100.01|
|**亮|alipay|¥10.00|
|[mingzaily](https://github.com/mingzaily)|alipay|¥30.00|
|[seny0929](https://gitee.com/seny0929)|alipay|¥9.90|
|Fly的狐狸|alipay|¥100.00|
<img src="https://goframe.org/images/donate.png"/>

View File

@ -32,13 +32,13 @@ golang version >= 1.11
```
# Packages
1. **Primary**
1. **Primary Package**
The `gf` repository maintains some basic and most commonly used packages, keeping it as lightweight and simple as possible.
1. **Community**
1. **Community Package**
The community packages are contrinuted and maintained by community members, which are reposited in `gogf` organization. Some of the community packages are seperated from th `gf` repository, which are not of common usage or are with heavy dependecies.
The community packages are contributed and maintained by community members, which are hosted in `gogf` organization. Some of the community packages are separated from the `gf` repository, which are not of common usage or are with heavy dependencies.
# Architecture
<div align=center>
@ -115,18 +115,24 @@ The concurrency starts from `100` to `10000`.
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Known Users
Logos are not authorized to be shown due to trademark copyrights.
# Contributors
This project exists thanks to all the people who contribute. [[Contributors](https://github.com/gogf/gf/graphs/contributors)].
<a href="https://github.com/gogf/gf/graphs/contributors"><img src="https://opencollective.com/goframe/contributors.svg?width=890&button=false" /></a>
<!--
# Donators
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)?
# Sponsors
We appreciate any kind of sponsorship for `GF` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
-->
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
@ -138,3 +144,7 @@ We appreciate any kind of sponsorship for `GF` development. If you've got some i

View File

@ -53,7 +53,7 @@ golang版本 >= 1.11
1. **社区模块**
社区模块主要由社区贡献并维护,大部分也是由`gf`主仓库的贡献者提供及维护,存放于`gogf`组织下,与`gf`主仓库处于同一级别。有的社区模块是从`gf`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。
社区模块主要由社区贡献并维护,大部分也是由`gf`主仓库的贡献者提供及维护,存放于`gogf`空间下,与`gf`主仓库处于同一级别。有的社区模块是从`gf`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。
# 架构
@ -127,12 +127,16 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
- WX交流群微信添加`389961817`备注`GF`
- 主库ISSUEhttps://github.com/gogf/gf/issues
> 建议通过阅读`Gorame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
> 建议通过阅读`GoFrame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
# 协议
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 用户
由于商标版权缘故,未经厂商商务部授权允许无法用于宣传展示。
# 贡献
感谢所有参与`GoFrame`开发的贡献者。 [[贡献者列表](https://github.com/gogf/gf/graphs/contributors)].
@ -149,4 +153,5 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 微信 `389961817` / 邮件 `john@goframe.org`。
# 感谢
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>

View File

@ -4,5 +4,5 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package garray provides concurrent-safe/unsafe arrays.
// Package garray provides most commonly used array containers which also support concurrent-safe/unsafe switch feature.
package garray

View File

@ -22,6 +22,8 @@ import (
)
// Array is a golang array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type Array struct {
mu rwmutex.RWMutex
array []interface{}

View File

@ -20,6 +20,8 @@ import (
)
// IntArray is a golang int array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type IntArray struct {
mu rwmutex.RWMutex
array []int

View File

@ -22,6 +22,8 @@ import (
)
// StrArray is a golang string array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type StrArray struct {
mu rwmutex.RWMutex
array []string

View File

@ -22,7 +22,10 @@ import (
)
// SortedArray is a golang sorted array with rich features.
// It's using increasing order in default.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedArray struct {
mu rwmutex.RWMutex
array []interface{}

View File

@ -19,7 +19,10 @@ import (
)
// SortedIntArray is a golang sorted int array with rich features.
// It's using increasing order in default.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedIntArray struct {
mu rwmutex.RWMutex
array []int

View File

@ -20,7 +20,10 @@ import (
)
// SortedStrArray is a golang sorted string array with rich features.
// It's using increasing order in default.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedStrArray struct {
mu rwmutex.RWMutex
array []string

View File

@ -72,8 +72,50 @@ func Example_basic() {
// []
}
func Example_iterate() {
array := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"})
// Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order
// with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
array.Iterator(func(k int, v string) bool {
fmt.Println(k, v)
return true
})
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
array.IteratorDesc(func(k int, v string) bool {
fmt.Println(k, v)
return true
})
// Output:
// 0 a
// 1 b
// 2 c
// 2 c
// 1 b
// 0 a
}
func Example_reverse() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Reverse makes array with elements in reverse order.
fmt.Println(array.Reverse().Slice())
// Output:
// [9 8 7 6 5 4 3 2 1]
}
func Example_shuffle() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Shuffle randomly shuffles the array.
fmt.Println(array.Shuffle().Slice())
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Randomly retrieve and return 2 items from the array.
// It does not delete the items from array.
@ -84,6 +126,26 @@ func Example_rand() {
fmt.Println(array.PopRand())
}
func Example_join() {
array := garray.NewFrom(g.Slice{"a", "b", "c", "d"})
fmt.Println(array.Join(","))
// Output:
// a,b,c,d
}
func Example_chunk() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
fmt.Println(array.Chunk(2))
// Output:
// [[1 2] [3 4] [5 6] [7 8] [9]]
}
func Example_popItem() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
@ -101,10 +163,38 @@ func Example_popItem() {
// [7 8]
}
func Example_walk() {
var array garray.StrArray
tables := g.SliceStr{"user", "user_detail"}
prefix := "gf_"
array.Append(tables...)
// Add prefix for given table names.
array.Walk(func(value string) string {
return prefix + value
})
fmt.Println(array.Slice())
// Output:
// [gf_user gf_user_detail]
}
func Example_contains() {
var array garray.StrArray
array.Append("a")
fmt.Println(array.Contains("a"))
fmt.Println(array.Contains("A"))
fmt.Println(array.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_mergeArray() {
array1 := garray.NewFrom([]interface{}{1, 2})
array2 := garray.NewFrom([]interface{}{3, 4})
slice1 := []interface{}{5, 6}
array1 := garray.NewFrom(g.Slice{1, 2})
array2 := garray.NewFrom(g.Slice{3, 4})
slice1 := g.Slice{5, 6}
slice2 := []int{7, 8}
slice3 := []string{"9", "0"}
fmt.Println(array1.Slice())

View File

@ -5,25 +5,26 @@
// You can obtain one at https://github.com/gogf/gf.
//
// Package glist provides a concurrent-safe/unsafe doubly linked list.
// Package glist provides most commonly used doubly linked list container which also supports concurrent-safe/unsafe switch feature.
package glist
import (
"bytes"
"container/list"
"encoding/json"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/internal/rwmutex"
)
type (
// List is a doubly linked list containing a concurrent-safe/unsafe switch.
// The switch should be set when its initialization and cannot be changed then.
List struct {
mu rwmutex.RWMutex
list list.List
list *list.List
}
// Element the item type of the list.
Element = list.Element
)
@ -31,7 +32,7 @@ type (
func New(safe ...bool) *List {
return &List{
mu: rwmutex.Create(safe...),
list: list.List{},
list: list.New(),
}
}
@ -39,7 +40,7 @@ func New(safe ...bool) *List {
// The parameter <safe> is used to specify whether using list in concurrent-safety,
// which is false in default.
func NewFrom(array []interface{}, safe ...bool) *List {
l := list.List{}
l := list.New()
for _, v := range array {
l.PushBack(v)
}
@ -52,6 +53,9 @@ func NewFrom(array []interface{}, safe ...bool) *List {
// PushFront inserts a new element <e> with value <v> at the front of list <l> and returns <e>.
func (l *List) PushFront(v interface{}) (e *Element) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
e = l.list.PushFront(v)
l.mu.Unlock()
return
@ -60,6 +64,9 @@ func (l *List) PushFront(v interface{}) (e *Element) {
// PushBack inserts a new element <e> with value <v> at the back of list <l> and returns <e>.
func (l *List) PushBack(v interface{}) (e *Element) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
e = l.list.PushBack(v)
l.mu.Unlock()
return
@ -68,6 +75,9 @@ func (l *List) PushBack(v interface{}) (e *Element) {
// PushFronts inserts multiple new elements with values <values> at the front of list <l>.
func (l *List) PushFronts(values []interface{}) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
for _, v := range values {
l.list.PushFront(v)
}
@ -77,6 +87,9 @@ func (l *List) PushFronts(values []interface{}) {
// PushBacks inserts multiple new elements with values <values> at the back of list <l>.
func (l *List) PushBacks(values []interface{}) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
for _, v := range values {
l.list.PushBack(v)
}
@ -86,20 +99,28 @@ func (l *List) PushBacks(values []interface{}) {
// PopBack removes the element from back of <l> and returns the value of the element.
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
if e := l.list.Back(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
// PopFront removes the element from front of <l> and returns the value of the element.
func (l *List) PopFront() (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
@ -107,6 +128,11 @@ func (l *List) PopFront() (value interface{}) {
// and returns values of the removed elements as slice.
func (l *List) PopBacks(max int) (values []interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
@ -117,7 +143,6 @@ func (l *List) PopBacks(max int) (values []interface{}) {
values[i] = l.list.Remove(l.list.Back())
}
}
l.mu.Unlock()
return
}
@ -125,6 +150,11 @@ func (l *List) PopBacks(max int) (values []interface{}) {
// and returns values of the removed elements as slice.
func (l *List) PopFronts(max int) (values []interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
@ -135,7 +165,6 @@ func (l *List) PopFronts(max int) (values []interface{}) {
values[i] = l.list.Remove(l.list.Front())
}
}
l.mu.Unlock()
return
}
@ -154,6 +183,10 @@ func (l *List) PopFrontAll() []interface{} {
// FrontAll copies and returns values of all elements from front of <l> as slice.
func (l *List) FrontAll() (values []interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
@ -161,13 +194,16 @@ func (l *List) FrontAll() (values []interface{}) {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// BackAll copies and returns values of all elements from back of <l> as slice.
func (l *List) BackAll() (values []interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
@ -175,43 +211,54 @@ func (l *List) BackAll() (values []interface{}) {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// FrontValue returns value of the first element of <l> or nil if the list is empty.
func (l *List) FrontValue() (value interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
if e := l.list.Front(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// BackValue returns value of the last element of <l> or nil if the list is empty.
func (l *List) BackValue() (value interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
if e := l.list.Back(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// Front returns the first element of list <l> or nil if the list is empty.
func (l *List) Front() (e *Element) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
e = l.list.Front()
l.mu.RUnlock()
return
}
// Back returns the last element of list <l> or nil if the list is empty.
func (l *List) Back() (e *Element) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
e = l.list.Back()
l.mu.RUnlock()
return
}
@ -219,8 +266,11 @@ func (l *List) Back() (e *Element) {
// The complexity is O(1).
func (l *List) Len() (length int) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length = l.list.Len()
l.mu.RUnlock()
return
}
@ -234,8 +284,11 @@ func (l *List) Size() int {
// The element and <p> must not be nil.
func (l *List) MoveBefore(e, p *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveBefore(e, p)
l.mu.Unlock()
}
// MoveAfter moves element <e> to its new position after <p>.
@ -243,8 +296,11 @@ func (l *List) MoveBefore(e, p *Element) {
// The element and <p> must not be nil.
func (l *List) MoveAfter(e, p *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveAfter(e, p)
l.mu.Unlock()
}
// MoveToFront moves element <e> to the front of list <l>.
@ -252,8 +308,11 @@ func (l *List) MoveAfter(e, p *Element) {
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveToFront(e)
l.mu.Unlock()
}
// MoveToBack moves element <e> to the back of list <l>.
@ -261,8 +320,11 @@ func (l *List) MoveToFront(e *Element) {
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveToBack(e)
l.mu.Unlock()
}
// PushBackList inserts a copy of an other list at the back of list <l>.
@ -273,8 +335,11 @@ func (l *List) PushBackList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushBackList(&other.list)
l.mu.Unlock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.PushBackList(other.list)
}
// PushFrontList inserts a copy of an other list at the front of list <l>.
@ -285,8 +350,11 @@ func (l *List) PushFrontList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushFrontList(&other.list)
l.mu.Unlock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.PushFrontList(other.list)
}
// InsertAfter inserts a new element <e> with value <v> immediately after <p> and returns <e>.
@ -294,8 +362,11 @@ func (l *List) PushFrontList(other *List) {
// The <p> must not be nil.
func (l *List) InsertAfter(p *Element, v interface{}) (e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
}
@ -304,8 +375,11 @@ func (l *List) InsertAfter(p *Element, v interface{}) (e *Element) {
// The <p> must not be nil.
func (l *List) InsertBefore(p *Element, v interface{}) (e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
}
@ -314,25 +388,31 @@ func (l *List) InsertBefore(p *Element, v interface{}) (e *Element) {
// The element must not be nil.
func (l *List) Remove(e *Element) (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
value = l.list.Remove(e)
l.mu.Unlock()
return
}
// Removes removes multiple elements <es> from <l> if <es> are elements of list <l>.
func (l *List) Removes(es []*Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
for _, e := range es {
l.list.Remove(e)
}
l.mu.Unlock()
return
}
// RemoveAll removes all elements from list <l>.
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.List{}
l.list = list.New()
l.mu.Unlock()
}
@ -345,14 +425,19 @@ func (l *List) Clear() {
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(&l.list)
if l.list != nil {
f(l.list)
}
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (l *List) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
f(&l.list)
if l.list == nil {
l.list = list.New()
}
f(l.list)
}
// Iterator is alias of IteratorAsc.
@ -365,6 +450,9 @@ func (l *List) Iterator(f func(e *Element) bool) {
func (l *List) IteratorAsc(f func(e *Element) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
@ -380,6 +468,9 @@ func (l *List) IteratorAsc(f func(e *Element) bool) {
func (l *List) IteratorDesc(f func(e *Element) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
@ -394,17 +485,14 @@ func (l *List) IteratorDesc(f func(e *Element) bool) {
func (l *List) Join(glue string) string {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return ""
}
buffer := bytes.NewBuffer(nil)
length := l.list.Len()
if length > 0 {
s := ""
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
s = gconv.String(e.Value)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
buffer.WriteString(gconv.String(e.Value))
if i != length-1 {
buffer.WriteString(glue)
}
@ -427,6 +515,9 @@ func (l *List) MarshalJSON() ([]byte, error) {
func (l *List) UnmarshalJSON(b []byte) error {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
var array []interface{}
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -439,6 +530,9 @@ func (l *List) UnmarshalJSON(b []byte) error {
func (l *List) UnmarshalValue(value interface{}) (err error) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
var array []interface{}
switch value.(type) {
case string, []byte:

View File

@ -10,6 +10,7 @@ import (
"container/list"
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/glist"
)
@ -96,5 +97,30 @@ func Example_iterate() {
//10987654321
//12345678910
//10987654321
//[1,2,3,4,5,"M",7,8,9,10]
//[1,2,3,4,5,M,7,8,9,10]
}
func Example_popItem() {
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
fmt.Println(l.PopBack())
fmt.Println(l.PopBacks(2))
fmt.Println(l.PopFront())
fmt.Println(l.PopFronts(2))
// Output:
// 9
// [8 7]
// 1
// [2 3]
}
func Example_join() {
var l glist.List
l.PushBacks(g.Slice{"a", "b", "c", "d"})
fmt.Println(l.Join(","))
// Output:
// a,b,c,d
}

View File

@ -15,7 +15,6 @@ import (
"testing"
)
// 检查链表长度
func checkListLen(t *gtest.T, l *List, len int) bool {
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
@ -24,7 +23,6 @@ func checkListLen(t *gtest.T, l *List, len int) bool {
return true
}
// 检查指针地址
func checkListPointers(t *gtest.T, l *List, es []*Element) {
if !checkListLen(t, l, len(es)) {
return
@ -605,6 +603,17 @@ func TestList_Removes(t *testing.T) {
})
}
func TestList_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
t.Assert(l.PopBack(), 9)
t.Assert(l.PopBacks(2), []interface{}{8, 7})
t.Assert(l.PopFront(), 1)
t.Assert(l.PopFronts(2), []interface{}{2, 3})
})
}
func TestList_Clear(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := New()
@ -672,15 +681,15 @@ func TestList_Iterator(t *testing.T) {
func TestList_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, "a", `"b"`, `\c`})
t.Assert(l.Join(","), `1,2,"a","\"b\"","\\c"`)
t.Assert(l.Join("."), `1.2."a"."\"b\""."\\c"`)
t.Assert(l.Join(","), `1,2,a,"b",\c`)
t.Assert(l.Join("."), `1.2.a."b".\c`)
})
}
func TestList_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, "a", `"b"`, `\c`})
t.Assert(l.String(), `[1,2,"a","\"b\"","\\c"]`)
t.Assert(l.String(), `[1,2,a,"b",\c]`)
})
}

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// Package gmap provides concurrent-safe/unsafe map containers.
// Package gmap provides most commonly used map container which also support concurrent-safe/unsafe switch feature.
package gmap
type (

View File

@ -98,6 +98,7 @@ func (m *AnyAnyMap) MapStrAny() map[string]interface{} {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *AnyAnyMap) FilterEmpty() {
m.mu.Lock()
defer m.mu.Unlock()
@ -108,6 +109,17 @@ func (m *AnyAnyMap) FilterEmpty() {
}
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *AnyAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *AnyAnyMap) Set(key interface{}, value interface{}) {
m.mu.Lock()

View File

@ -98,6 +98,7 @@ func (m *IntAnyMap) MapCopy() map[int]interface{} {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntAnyMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -108,6 +109,17 @@ func (m *IntAnyMap) FilterEmpty() {
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *IntAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *IntAnyMap) Set(key int, val interface{}) {
m.mu.Lock()

View File

@ -96,6 +96,7 @@ func (m *IntIntMap) MapCopy() map[int]int {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntIntMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {

View File

@ -96,6 +96,7 @@ func (m *IntStrMap) MapCopy() map[int]string {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntStrMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {

View File

@ -92,6 +92,7 @@ func (m *StrAnyMap) MapCopy() map[string]interface{} {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrAnyMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -102,6 +103,17 @@ func (m *StrAnyMap) FilterEmpty() {
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *StrAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *StrAnyMap) Set(key string, val interface{}) {
m.mu.Lock()

View File

@ -96,6 +96,7 @@ func (m *StrIntMap) MapCopy() map[string]int {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrIntMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {

View File

@ -97,6 +97,7 @@ func (m *StrStrMap) MapCopy() map[string]string {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrStrMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {

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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/gmap"
)
@ -59,13 +66,97 @@ func Example_normalBasic() {
m.Clear()
fmt.Println(m.Size())
}
func Example_keysValues() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Keys())
fmt.Println(m.Values())
// May Output:
// [k1 k2 k3 k4]
// [v2 v3 v4 v1]
}
func Example_flip() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
m.Flip()
fmt.Println(m.Map())
// May Output:
// map[v1:k1 v2:k2]
}
func Example_pop() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Pop())
fmt.Println(m.Pops(2))
fmt.Println(m.Size())
// May Output:
// k1 v1
// map[k2:v2 k4:v4]
// 1
}
func Example_filter() {
m1 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m2 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m1.FilterEmpty()
m2.FilterNil()
fmt.Println(m1.Map())
fmt.Println(m2.Map())
// Output:
// map[k4:1]
// map[k1: k3:0 k4:1]
}
func Example_setIfNotExist() {
var m gmap.Map
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.Map())
// Output:
// true
// false
// map[k1:v1]
}
func Example_normalMerge() {
m1 := gmap.New()
m2 := gmap.New()
var m1, m2 gmap.Map
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
m1.Merge(&m2)
fmt.Println(m1.Map())
// May Output:
// map[key1:val1 key2:val2]
}

View File

@ -9,12 +9,12 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/test/gtest"
"time"
)
func Test_AnyAnyMap_Var(t *testing.T) {
@ -218,6 +218,18 @@ func Test_AnyAnyMap_FilterEmpty(t *testing.T) {
t.Assert(m.Get(1), nil)
t.Assert(m.Get(2), 2)
})
gtest.C(t, func(t *gtest.T) {
m := gmap.NewAnyAnyMap()
m.Set(1, 0)
m.Set("time1", time.Time{})
m.Set("time2", time.Now())
t.Assert(m.Get(1), 0)
t.Assert(m.Get("time1"), time.Time{})
m.FilterEmpty()
t.Assert(m.Get(1), nil)
t.Assert(m.Get("time1"), nil)
t.AssertNE(m.Get("time2"), nil)
})
}
func Test_AnyAnyMap_Json(t *testing.T) {

View File

@ -61,12 +61,12 @@ func (set *Set) Iterator(f func(v interface{}) bool) {
}
// Add adds one or multiple items to the set.
func (set *Set) Add(item ...interface{}) {
func (set *Set) Add(items ...interface{}) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
for _, v := range item {
for _, v := range items {
set.data[v] = struct{}{}
}
set.mu.Unlock()
@ -456,9 +456,11 @@ func (set *Set) Pops(size int) []interface{} {
func (set *Set) Walk(f func(item interface{}) interface{}) *Set {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[interface{}]struct{}, len(set.data))
for k, v := range set.data {
set.data[f(k)] = v
m[f(k)] = v
}
set.data = m
return set
}

View File

@ -416,9 +416,11 @@ func (set *IntSet) Pops(size int) []int {
func (set *IntSet) Walk(f func(item int) int) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[int]struct{}, len(set.data))
for k, v := range set.data {
set.data[f(k)] = v
m[f(k)] = v
}
set.data = m
return set
}

View File

@ -444,9 +444,11 @@ func (set *StrSet) Pops(size int) []string {
func (set *StrSet) Walk(f func(item string) string) *StrSet {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[string]struct{}, len(set.data))
for k, v := range set.data {
set.data[f(k)] = v
m[f(k)] = v
}
set.data = m
return set
}

View File

@ -0,0 +1,119 @@
// Copyright 2020 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gset_test
import (
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/frame/g"
)
func Example_intersectDiffUnionComplement() {
s1 := gset.NewFrom(g.Slice{1, 2, 3})
s2 := gset.NewFrom(g.Slice{4, 5, 6})
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
fmt.Println(s3.Intersect(s1).Slice())
fmt.Println(s3.Diff(s1).Slice())
fmt.Println(s1.Union(s2).Slice())
fmt.Println(s1.Complement(s3).Slice())
// May Output:
// [2 3 1]
// [5 6 7 4]
// [6 1 2 3 4 5]
// [4 5 6 7]
}
func Example_isSubsetOf() {
var s1, s2 gset.Set
s1.Add(g.Slice{1, 2, 3}...)
s2.Add(g.Slice{2, 3}...)
fmt.Println(s1.IsSubsetOf(&s2))
fmt.Println(s2.IsSubsetOf(&s1))
// Output:
// false
// true
}
func Example_addIfNotExist() {
var set gset.Set
fmt.Println(set.AddIfNotExist(1))
fmt.Println(set.AddIfNotExist(1))
fmt.Println(set.Slice())
// Output:
// true
// false
// [1]
}
func Example_pop() {
var set gset.Set
set.Add(1, 2, 3, 4)
fmt.Println(set.Pop())
fmt.Println(set.Pops(2))
fmt.Println(set.Size())
// May Output:
// 1
// [2 3]
// 1
}
func Example_join() {
var set gset.Set
set.Add("a", "b", "c", "d")
fmt.Println(set.Join(","))
// May Output:
// a,b,c,d
}
func Example_contains() {
var set gset.StrSet
set.Add("a")
fmt.Println(set.Contains("a"))
fmt.Println(set.Contains("A"))
fmt.Println(set.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_Contains() {
var set gset.StrSet
set.Add("a")
fmt.Println(set.Contains("a"))
fmt.Println(set.Contains("A"))
fmt.Println(set.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_walk() {
var (
set gset.StrSet
names = g.SliceStr{"user", "user_detail"}
prefix = "gf_"
)
set.Add(names...)
// Add prefix for given table names.
set.Walk(func(item string) string {
return prefix + item
})
fmt.Println(set.Slice())
// May Output:
// [gf_user gf_user_detail]
}

View File

@ -389,6 +389,19 @@ func TestSet_AddIfNotExistFunc(t *testing.T) {
})
}
func TestSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var set gset.Set
set.Add(g.Slice{1, 2}...)
set.Walk(func(item interface{}) interface{} {
return gconv.Int(item) + 10
})
t.Assert(set.Size(), 2)
t.Assert(set.Contains(11), true)
t.Assert(set.Contains(12), true)
})
}
func TestSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)

View File

@ -377,6 +377,19 @@ func TestIntSet_Json(t *testing.T) {
})
}
func TestIntSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var set gset.IntSet
set.Add(g.SliceInt{1, 2}...)
set.Walk(func(item int) int {
return item + 10
})
t.Assert(set.Size(), 2)
t.Assert(set.Contains(11), true)
t.Assert(set.Contains(12), true)
})
}
func TestIntSet_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -423,6 +423,24 @@ func TestStrSet_Json(t *testing.T) {
})
}
func TestStrSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
set gset.StrSet
names = g.SliceStr{"user", "user_detail"}
prefix = "gf_"
)
set.Add(names...)
// Add prefix for given table names.
set.Walk(func(item string) string {
return prefix + item
})
t.Assert(set.Size(), 2)
t.Assert(set.Contains("gf_user"), true)
t.Assert(set.Contains("gf_user_detail"), true)
})
}
func TestStrSet_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -22,7 +22,7 @@ func (v *Var) MapStrStr(tags ...string) map[string]string {
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}
@ -45,7 +45,7 @@ func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}

View File

@ -39,10 +39,10 @@ type ConfigNode struct {
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration // (Optional) Max connection TTL configuration for underlying connection pool.
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool.
}
// configs is internal used configuration object.

View File

@ -411,11 +411,15 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
// underlying driver.
func handleArguments(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
newSql = sql
// insertHolderCount is used to calculate the inserting position for the '?' holder.
insertHolderCount := 0
// Handles the slice arguments.
if len(args) > 0 {
for index, arg := range args {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
var (
rv = reflect.ValueOf(arg)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -431,17 +435,25 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
// It the '?' holder count equals the length of the slice,
// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(args) == 1 && gstr.Count(newSql, "?") == rv.Len() {
break
}
// counter is used to finding the inserting position for the '?' holder.
counter := 0
var (
counter = 0
replaced = false
)
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
if replaced {
return s
}
counter++
if counter == index+1 {
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += rv.Len() - 1
return "?" + strings.Repeat(",?", rv.Len()-1)
}
return s

View File

@ -12,14 +12,10 @@ import (
"github.com/gogf/gf/os/gtime"
)
// Unscoped enables/disables the soft deleting feature.
func (m *Model) Unscoped(unscoped ...bool) *Model {
// Unscoped disables the soft deleting feature.
func (m *Model) Unscoped() *Model {
model := m.getModel()
if len(unscoped) > 0 {
model.unscoped = unscoped[0]
} else {
model.unscoped = true
}
model.unscoped = true
return model
}

View File

@ -13,6 +13,7 @@ import (
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
@ -30,6 +31,7 @@ func (m *Model) Fields(fields string) *Model {
}
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
// Note that this function supports only single table operations.
func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")

View File

@ -63,10 +63,18 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
if r.IsZero() {
continue
}
case *time.Time:
if r.IsZero() {
continue
}
case gtime.Time:
if r.IsZero() {
continue
}
case *gtime.Time:
if r.IsZero() {
continue
}
}
tempMap[k] = v
}

View File

@ -41,8 +41,7 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
"smallint",
"medium_int",
"mediumint",
"serial",
"smallmoney":
"serial":
if gstr.ContainsI(fieldType, "unsigned") {
gconv.Uint(string(fieldValue))
}
@ -51,8 +50,7 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
case
"big_int",
"bigint",
"bigserial",
"money":
"bigserial":
if gstr.ContainsI(fieldType, "unsigned") {
gconv.Uint64(string(fieldValue))
}
@ -65,7 +63,9 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
"float",
"double",
"decimal",
"numeric":
"money",
"numeric",
"smallmoney":
return gconv.Float64(string(fieldValue))
case "bit":

View File

@ -0,0 +1,40 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test
import (
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
)
func Example_transaction() {
db.Transaction(func(tx *gdb.TX) error {
// user
result, err := tx.Insert("user", g.Map{
"passport": "john",
"password": "12345678",
"nickname": "JohnGuo",
})
if err != nil {
return err
}
// user_detail
id, err := result.LastInsertId()
if err != nil {
return err
}
_, err = tx.Insert("user_detail", g.Map{
"uid": id,
"site": "https://johng.cn",
"true_name": "GuoQiang",
})
if err != nil {
return err
}
return nil
})
}

View File

@ -1023,15 +1023,6 @@ func Test_Model_Where(t *testing.T) {
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where(g.Map{
"id": g.Slice{1, 2, 3},
"passport": g.Slice{"user_2", "user_3"},
}).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One()
t.Assert(err, nil)
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where("id=3", g.Slice{}).One()
t.Assert(err, nil)
@ -1343,7 +1334,7 @@ func Test_Model_WherePri(t *testing.T) {
}).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One()
t.Assert(err, nil)
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
t.Assert(result["id"].Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).WherePri("id=3", g.Slice{}).One()
@ -1833,6 +1824,32 @@ func Test_Model_Option_Where(t *testing.T) {
})
}
func Test_Model_Where_MultiSliceArguments(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.Table(table).Where(g.Map{
"id": g.Slice{1, 2, 3, 4},
"passport": g.Slice{"user_2", "user_3", "user_4"},
"nickname": g.Slice{"name_2", "name_4"},
"id >= 4": nil,
}).All()
t.Assert(err, nil)
t.Assert(len(r), 1)
t.Assert(r[0]["id"], 4)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where(g.Map{
"id": g.Slice{1, 2, 3},
"passport": g.Slice{"user_2", "user_3"},
}).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One()
t.Assert(err, nil)
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 2)
})
}
func Test_Model_FieldsEx(t *testing.T) {
table := createInitTable()
defer dropTable(table)

View File

@ -210,11 +210,15 @@ func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
return conn.Do(command, args...)
}
// DoVar returns value from Do as gvar.Var.
// DoVar returns value from Do as *gvar.Var.
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
v, err := r.Do(command, args...)
if result, ok := v.([]byte); ok {
return gvar.New(gconv.UnsafeBytesToStr(result)), err
}
// It treats all returned slice as string slice.
if result, ok := v.([]interface{}); ok {
return gvar.New(gconv.Strings(result)), err
}
return gvar.New(v), err
}

View File

@ -0,0 +1,89 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gredis_test
import (
"fmt"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
)
func Example_autoMarshalUnmarshalMap() {
var (
err error
result *gvar.Var
key = "user"
data = g.Map{
"id": 10000,
"name": "john",
}
)
_, err = g.Redis().Do("SET", key, data)
if err != nil {
panic(err)
}
result, err = g.Redis().DoVar("GET", key)
if err != nil {
panic(err)
}
fmt.Println(result.Map())
}
func Example_autoMarshalUnmarshalStruct() {
type User struct {
Id int
Name string
}
var (
err error
result *gvar.Var
key = "user"
user = &User{
Id: 10000,
Name: "john",
}
)
_, err = g.Redis().Do("SET", key, user)
if err != nil {
panic(err)
}
result, err = g.Redis().DoVar("GET", key)
if err != nil {
panic(err)
}
var user2 *User
if err = result.Struct(&user2); err != nil {
panic(err)
}
fmt.Println(user2.Id, user2.Name)
}
func Example_hashSet() {
var (
err error
result *gvar.Var
key = "user"
)
_, err = g.Redis().Do("HSET", key, "id", 10000)
if err != nil {
panic(err)
}
_, err = g.Redis().Do("HSET", key, "name", "john")
if err != nil {
panic(err)
}
result, err = g.Redis().DoVar("HGETALL", key)
if err != nil {
panic(err)
}
fmt.Println(result.Map())
// May Output:
// map[id:10000 name:john]
}

View File

@ -261,14 +261,14 @@ func Test_HSet(t *testing.T) {
})
}
func Test_HGetAll(t *testing.T) {
func Test_HGetAll1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var err error
redis := gredis.New(config)
key := guid.S()
defer redis.Do("DEL", key)
_, err = redis.Do("HSET", key, "id", "100")
_, err = redis.Do("HSET", key, "id", 100)
t.Assert(err, nil)
_, err = redis.Do("HSET", key, "name", "john")
t.Assert(err, nil)
@ -282,6 +282,28 @@ func Test_HGetAll(t *testing.T) {
})
}
func Test_HGetAll2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
key = guid.S()
redis = gredis.New(config)
)
defer redis.Do("DEL", key)
_, err = redis.Do("HSET", key, "id", 100)
t.Assert(err, nil)
_, err = redis.Do("HSET", key, "name", "john")
t.Assert(err, nil)
result, err := redis.DoVar("HGETALL", key)
t.Assert(err, nil)
t.Assert(gconv.Uint(result.MapStrVar()["id"]), 100)
t.Assert(result.MapStrVar()["id"].Uint(), 100)
})
}
func Test_Auto_Marshal(t *testing.T) {
var (
err error

View File

@ -6,7 +6,7 @@
package gbinary
// 实验特性
// NOTE: THIS IS AN EXPERIMENTAL FEATURE!
// 二进制位(0|1)
type Bit int8

View File

@ -8,6 +8,7 @@ package gins
import (
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gutil"
@ -58,6 +59,7 @@ func Database(name ...string) gdb.DB {
}
}
if len(cg) > 0 {
intlog.Printf("%s, %#v", group, cg)
gdb.SetConfigGroup(group, cg)
}
}
@ -69,6 +71,7 @@ func Database(name ...string) gdb.DB {
cg = append(cg, *node)
}
if len(cg) > 0 {
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
}
}

View File

@ -68,7 +68,7 @@ func New(options ...Options) *Manager {
gregex.Quote(opts.Delimiters[1]),
),
}
intlog.Printf(`New: %+v`, m)
intlog.Printf(`New: %#v`, m)
return m
}

View File

@ -11,6 +11,21 @@ import (
"reflect"
)
// apiString is used for type assert api for String().
type apiString interface {
String() string
}
// apiInterfaces is used for type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
// apiMapStrAny is the interface support for converting struct parameter to map.
type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
// IsEmpty checks whether given <value> empty.
// It returns true if <value> is in: 0, nil, false, "", len(slice/map/chan) == 0,
// or else it returns false.
@ -52,6 +67,16 @@ func IsEmpty(value interface{}) bool {
case []rune:
return len(value) == 0
default:
// Common interfaces checks.
if f, ok := value.(apiString); ok {
return f.String() == ""
}
if f, ok := value.(apiInterfaces); ok {
return len(f.Interfaces()) == 0
}
if f, ok := value.(apiMapStrAny); ok {
return len(f.MapStrAny()) == 0
}
// Finally using reflect.
var rv reflect.Value
if v, ok := value.(reflect.Value); ok {

View File

@ -0,0 +1,63 @@
// Copyright 2020 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 utils
import (
"io"
"io/ioutil"
)
// ReadCloser implements the io.ReadCloser interface
// which is used for reading request body content multiple times.
//
// Note that it cannot be closed.
type ReadCloser struct {
index int // Current read position.
content []byte // Content.
repeatable bool
}
// NewRepeatReadCloser creates and returns a RepeatReadCloser object.
func NewReadCloser(content []byte, repeatable bool) io.ReadCloser {
return &ReadCloser{
content: content,
repeatable: repeatable,
}
}
// NewRepeatReadCloserWithReadCloser creates and returns a RepeatReadCloser object
// with given io.ReadCloser.
func NewReadCloserWithReadCloser(r io.ReadCloser, repeatable bool) (io.ReadCloser, error) {
content, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
defer r.Close()
return &ReadCloser{
content: content,
repeatable: repeatable,
}, nil
}
// Read implements the io.ReadCloser interface.
func (b *ReadCloser) Read(p []byte) (n int, err error) {
n = copy(p, b.content[b.index:])
b.index += n
if b.index >= len(b.content) {
// Make it repeatable reading.
if b.repeatable {
b.index = 0
}
return n, io.EOF
}
return n, nil
}
// Close implements the io.ReadCloser interface.
func (b *ReadCloser) Close() error {
return nil
}

View File

@ -0,0 +1,65 @@
// Copyright 2020 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 utils_test
import (
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/test/gtest"
"io/ioutil"
"testing"
)
func Test_ReadCloser(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
n int
b = make([]byte, 3)
body = utils.NewReadCloser([]byte{1, 2, 3, 4}, false)
)
n, _ = body.Read(b)
t.Assert(b[:n], []byte{1, 2, 3})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{4})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{})
})
gtest.C(t, func(t *gtest.T) {
var (
r []byte
body = utils.NewReadCloser([]byte{1, 2, 3, 4}, false)
)
r, _ = ioutil.ReadAll(body)
t.Assert(r, []byte{1, 2, 3, 4})
r, _ = ioutil.ReadAll(body)
t.Assert(r, []byte{})
})
gtest.C(t, func(t *gtest.T) {
var (
n int
r []byte
b = make([]byte, 3)
body = utils.NewReadCloser([]byte{1, 2, 3, 4}, true)
)
n, _ = body.Read(b)
t.Assert(b[:n], []byte{1, 2, 3})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{4})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{1, 2, 3})
n, _ = body.Read(b)
t.Assert(b[:n], []byte{4})
r, _ = ioutil.ReadAll(body)
t.Assert(r, []byte{1, 2, 3, 4})
r, _ = ioutil.ReadAll(body)
t.Assert(r, []byte{1, 2, 3, 4})
})
}

View File

@ -7,68 +7,41 @@
package ghttp
import (
"bytes"
"fmt"
"github.com/gogf/gf/internal/utils"
"io/ioutil"
"net/http"
"net/http/httputil"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
)
// dumpTextFormat is the format of the dumped raw string
const dumpTextFormat = `+---------------------------------------------+
| %s |
| %s |
+---------------------------------------------+
%s
%s
`
// ifDumpBody determines whether to output body according to content-type.
func ifDumpBody(contentType string) bool {
// the body should not be output when the body is html or stream.
if gstr.Contains(contentType, "application/json") ||
gstr.Contains(contentType, "application/xml") ||
gstr.Contains(contentType, "multipart/form-data") ||
gstr.Contains(contentType, "application/x-www-form-urlencoded") ||
gstr.Contains(contentType, "text/plain") {
return true
}
return false
}
// getRequestBody returns the raw text of the request body.
func getRequestBody(req *http.Request) string {
contentType := req.Header.Get("Content-Type")
if !ifDumpBody(contentType) {
if req.Body == nil {
return ""
}
// so that the request body can be read again.
bodyReader, errGetBody := req.GetBody()
if errGetBody != nil {
return ""
}
bytesBody, errReadBody := ioutil.ReadAll(bodyReader)
if errReadBody != nil {
return ""
}
return gconv.UnsafeBytesToStr(bytesBody)
bodyContent, _ := ioutil.ReadAll(req.Body)
req.Body = utils.NewReadCloser(bodyContent, true)
return gconv.UnsafeBytesToStr(bodyContent)
}
// getResponseBody returns the text of the response body.
func getResponseBody(resp *http.Response) string {
contentType := resp.Header.Get("Content-Type")
if !ifDumpBody(contentType) {
func getResponseBody(res *http.Response) string {
if res.Body == nil {
return ""
}
bytesBody, errReadBody := ioutil.ReadAll(resp.Body)
if errReadBody != nil {
return ""
}
// So the response body can be read again.
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bytesBody))
return gconv.UnsafeBytesToStr(bytesBody)
bodyContent, _ := ioutil.ReadAll(res.Body)
res.Body = utils.NewReadCloser(bodyContent, true)
return gconv.UnsafeBytesToStr(bodyContent)
}
// RawRequest returns the raw content of the request.
@ -116,3 +89,8 @@ func (r *ClientResponse) RawResponse() string {
func (r *ClientResponse) Raw() string {
return fmt.Sprintf("%s\n%s", r.RawRequest(), r.RawResponse())
}
// RawDump outputs the raw text of the request and the response to stdout.
func (r *ClientResponse) RawDump() {
fmt.Println(r.Raw())
}

View File

@ -11,7 +11,9 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/internal/utils"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
@ -215,20 +217,25 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
}
// do not return nil even if the request fails
resp = &ClientResponse{}
resp = &ClientResponse{
request: req,
}
// The request body can be reused for dumping
// raw HTTP request-response procedure.
reqBodyContent, _ := ioutil.ReadAll(req.Body)
req.Body = utils.NewReadCloser(reqBodyContent, false)
defer func() {
resp.request.Body = utils.NewReadCloser(reqBodyContent, true)
}()
for {
if resp.Response, err = c.Do(req); err != nil {
if c.retryCount > 0 {
c.retryCount--
time.Sleep(c.retryInterval)
} else {
// we need a copy of the request when the request fails.
resp.request = req
return resp, err
}
} else {
resp.request = resp.Request
break
}
}

View File

@ -9,6 +9,7 @@ package ghttp
import (
"context"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gres"
"github.com/gogf/gf/os/gsession"
"github.com/gogf/gf/os/gview"
@ -79,12 +80,11 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
// Custom session id creating function.
err := request.Session.SetIdFunc(func(ttl time.Duration) string {
var (
agent = request.UserAgent()
address = request.RemoteAddr
cookie = request.Header.Get("Cookie")
header = fmt.Sprintf("%v", request.Header)
)
//intlog.Print(agent, address, cookie)
return guid.S([]byte(agent), []byte(address), []byte(cookie))
intlog.Print(address, header)
return guid.S([]byte(address), []byte(header))
})
if err != nil {
panic(err)

View File

@ -1,34 +0,0 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp
import (
"bytes"
"io/ioutil"
)
// BodyReadCloser implements the io.ReadCloser interface
// which is used for reading request body content multiple times.
type BodyReadCloser struct {
*bytes.Reader
}
// RefillBody refills the request body object after read all of its content.
// It makes the request body reusable for next reading.
func (r *Request) RefillBody() {
if r.bodyContent == nil {
r.bodyContent, _ = ioutil.ReadAll(r.Body)
}
r.Body = &BodyReadCloser{
bytes.NewReader(r.bodyContent),
}
}
// Close implements the io.ReadCloser interface.
func (b *BodyReadCloser) Close() error {
return nil
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/encoding/gurl"
"github.com/gogf/gf/encoding/gxml"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
@ -121,7 +122,7 @@ func (r *Request) GetRawString() string {
func (r *Request) GetBody() []byte {
if r.bodyContent == nil {
r.bodyContent, _ = ioutil.ReadAll(r.Body)
r.RefillBody()
r.Body = utils.NewReadCloser(r.bodyContent, true)
}
return r.bodyContent
}

View File

@ -240,9 +240,9 @@ func Config() ServerConfig {
HTTPSAddr: "",
Handler: nil,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
WriteTimeout: 0, // No timeout.
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1024,
MaxHeaderBytes: 10240, // 10KB
KeepAlive: true,
IndexFiles: []string{"index.html", "index.htm"},
IndexFolder: false,

View File

@ -230,12 +230,40 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte
if newItem.router.Priority < oldItem.router.Priority {
return false
}
// Route type: {xxx} > :xxx > *xxx.
// Eg: /name/act > /{name}/:act
var fuzzyCountFieldNew, fuzzyCountFieldOld int
var fuzzyCountNameNew, fuzzyCountNameOld int
var fuzzyCountAnyNew, fuzzyCountAnyOld int
var fuzzyCountTotalNew, fuzzyCountTotalOld int
// Compare the length of their URI,
// but the fuzzy and named parts of the URI are not calculated to the result.
// Eg:
// /admin-goods-{page} > /admin-{page}
// /{hash}.{type} > /{hash}
var uriNew, uriOld string
uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.router.Uri)
uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.router.Uri)
uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew)
uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld)
uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any".
uriOld, _ = gregex.ReplaceString(`\*[^/]*`, "", uriOld) // Replace "/*" and "/*any".
if len(uriNew) > len(uriOld) {
return true
}
if len(uriNew) < len(uriOld) {
return false
}
// Route type checks: {xxx} > :xxx > *xxx.
// Eg:
// /name/act > /{name}/:act
var (
fuzzyCountFieldNew int
fuzzyCountFieldOld int
fuzzyCountNameNew int
fuzzyCountNameOld int
fuzzyCountAnyNew int
fuzzyCountAnyOld int
fuzzyCountTotalNew int
fuzzyCountTotalOld int
)
for _, v := range newItem.router.Uri {
switch v {
case '{':
@ -282,24 +310,6 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte
return false
}
// It then compares the length of their URI,
// but the fuzzy and named parts of the URI are not calculated to the result.
// Eg: /admin-goods-{page} > /admin-{page}
var uriNew, uriOld string
uriNew, _ = gregex.ReplaceString(`\{[^/]+\}`, "", newItem.router.Uri)
uriNew, _ = gregex.ReplaceString(`:[^/]+`, "", uriNew)
uriNew, _ = gregex.ReplaceString(`\*[^/]+`, "", uriNew)
uriOld, _ = gregex.ReplaceString(`\{[^/]+\}`, "", oldItem.router.Uri)
uriOld, _ = gregex.ReplaceString(`:[^/]+`, "", uriOld)
uriOld, _ = gregex.ReplaceString(`\*[^/]+`, "", uriOld)
if len(uriNew) > len(uriOld) {
return true
}
if len(uriNew) < len(uriOld) {
return false
}
// It then compares the accuracy of their http method,
// the more accurate the more priority.
if newItem.router.Method != gDEFAULT_METHOD {

View File

@ -48,7 +48,7 @@ func Test_Client_Request_13_Dump(t *testing.T) {
r2, err := client2.Post("/hello2", g.Map{"field": "test_for_request_body"})
t.Assert(err, nil)
dumpedText3 := r2.RawRequest()
t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), false)
t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), true)
dumpedText4 := r2.RawResponse()
t.Assert(gstr.Contains(dumpedText4, "test_for_request_body"), false)

View File

@ -18,6 +18,106 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Params_Parse(t *testing.T) {
type User struct {
Id int
Name string
Map map[string]interface{}
}
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Map["id"], user.Map["score"])
}
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), `1100`)
})
}
func Test_Params_Parse_Attr_Pointer(t *testing.T) {
type User struct {
Id *int
Name *string
}
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse1", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name)
}
})
s.BindHandler("/parse2", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user = new(User)
if err := r.Parse(user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name)
}
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.PostContent("/parse1", `{"id":1,"name":"john"}`), `1john`)
t.Assert(client.PostContent("/parse2", `{"id":1,"name":"john"}`), `1john`)
})
}
// It does not support this kind of converting yet.
//func Test_Params_Parse_Attr_SliceSlice(t *testing.T) {
// type User struct {
// Id int
// Name string
// Scores [][]int
// }
// p, _ := ports.PopRand()
// s := g.Server(p)
// s.BindHandler("/parse", func(r *ghttp.Request) {
// if m := r.GetMap(); len(m) > 0 {
// var user *User
// if err := r.Parse(&user); err != nil {
// r.Response.WriteExit(err)
// }
// r.Response.WriteExit(user.Scores)
// }
// })
// s.SetPort(p)
// s.SetDumpRouterMap(false)
// s.Start()
// defer s.Shutdown()
//
// time.Sleep(100 * time.Millisecond)
// gtest.C(t, func(t *gtest.T) {
// client := ghttp.NewClient()
// client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
// t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","scores":[[1,2,3]]}`), `1100`)
// })
//}
func Test_Params_Struct(t *testing.T) {
type User struct {
Id int

View File

@ -16,7 +16,7 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Router_Basic(t *testing.T) {
func Test_Router_Basic1(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/:name", func(r *ghttp.Request) {
@ -50,6 +50,29 @@ func Test_Router_Basic(t *testing.T) {
})
}
func Test_Router_Basic2(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/{hash}", func(r *ghttp.Request) {
r.Response.Write(r.Get("hash"))
})
s.BindHandler("/{hash}.{type}", func(r *ghttp.Request) {
r.Response.Write(r.Get("type"))
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/data"), "data")
t.Assert(client.GetContent("/data.json"), "json")
})
}
// HTTP method register.
func Test_Router_Method(t *testing.T) {
p, _ := ports.PopRand()

View File

@ -0,0 +1,29 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp_test
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func Example_helloWorld() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("hello world")
})
s.SetPort(8999)
s.Run()
}
func Example_clientDump() {
response, err := g.Client().Get("https://goframe.org")
if err != nil {
panic(err)
}
response.RawDump()
}

View File

@ -173,12 +173,12 @@ func (c *Config) SetPath(path string) error {
return nil
}
// SetViolenceCheck sets whether to perform hierarchical conflict check.
// SetViolenceCheck sets whether to perform hierarchical conflict checking.
// This feature needs to be enabled when there is a level symbol in the key name.
// The default is off.
// Turning on this feature is quite expensive,
// and it is not recommended to allow separators in the key names.
// It is best to avoid this on the application side.
// It is off in default.
//
// Note that, turning on this feature is quite expensive, and it is not recommended
// to allow separators in the key names. It is best to avoid this on the application side.
func (c *Config) SetViolenceCheck(check bool) {
c.vc = check
c.Clear()
@ -186,8 +186,12 @@ func (c *Config) SetViolenceCheck(check bool) {
// AddPath adds a absolute or relative path to the search paths.
func (c *Config) AddPath(path string) error {
isDir := false
realPath := ""
var (
isDir = false
realPath = ""
)
// It firstly checks the resource manager,
// and then checks the filesystem for the path.
if file := gres.Get(path); file != nil {
realPath = path
isDir = file.FileInfo().IsDir()

View File

@ -0,0 +1,22 @@
// Copyright 2020 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 glog_test
import (
"context"
"github.com/gogf/gf/frame/g"
)
func Example_context() {
ctx := context.WithValue(context.Background(), "Trace-Id", "123456789")
g.Log().Ctx(ctx).Error("runtime error")
// May Output:
// 2020-06-08 20:17:03.630 [ERRO] {Trace-Id: 123456789} runtime error
// Stack:
// ...
}

View File

@ -17,6 +17,6 @@ func Test_NewSessionId(t *testing.T) {
id1 := NewSessionId()
id2 := NewSessionId()
t.AssertNE(id1, id2)
t.Assert(len(id1), 36)
t.Assert(len(id1), 32)
})
}

View File

@ -5,6 +5,8 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gtime provides functionality for measuring and displaying time.
//
// This package should keep much less dependencies with other packages.
package gtime
import (

View File

@ -12,25 +12,25 @@ import (
"github.com/gogf/gf/os/gtime"
)
func Benchmark_Second(b *testing.B) {
func Benchmark_Timestamp(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.Timestamp()
}
}
func Benchmark_Millisecond(b *testing.B) {
func Benchmark_TimestampMilli(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.TimestampMilli()
}
}
func Benchmark_Microsecond(b *testing.B) {
func Benchmark_TimestampMicro(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.TimestampMicro()
}
}
func Benchmark_Nanosecond(b *testing.B) {
func Benchmark_TimestampNano(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.TimestampNano()
}

View File

@ -342,6 +342,9 @@ func Test_HideStr(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(gstr.HideStr("15928008611", 40, "*"), "159****8611")
t.Assert(gstr.HideStr("john@kohg.cn", 40, "*"), "jo*n@kohg.cn")
t.Assert(gstr.HideStr("张三", 50, "*"), "张*")
t.Assert(gstr.HideStr("张小三", 50, "*"), "张*三")
t.Assert(gstr.HideStr("欧阳小三", 50, "*"), "欧**三")
})
}

View File

@ -5,6 +5,8 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gconv implements powerful and convenient converting functionality for any types of variables.
//
// This package should keep much less dependencies with other packages.
package gconv
import (
@ -19,16 +21,6 @@ import (
"github.com/gogf/gf/encoding/gbinary"
)
// Type assert api for String().
type apiString interface {
String() string
}
// Type assert api for Error().
type apiError interface {
Error() string
}
var (
// Empty strings.
emptyStringMap = map[string]struct{}{

View File

@ -0,0 +1,53 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
// apiString is used for type assert api for String().
type apiString interface {
String() string
}
// apiError is used for type assert api for Error().
type apiError interface {
Error() string
}
// apiInterfaces is used for type assert api for Interfaces().
type apiInterfaces interface {
Interfaces() []interface{}
}
// apiFloats is used for type assert api for Floats().
type apiFloats interface {
Floats() []float64
}
// apiInts is used for type assert api for Ints().
type apiInts interface {
Ints() []int
}
// apiStrings is used for type assert api for Strings().
type apiStrings interface {
Strings() []string
}
// apiUints is used for type assert api for Uints().
type apiUints interface {
Uints() []uint
}
// apiMapStrAny is the interface support for converting struct parameter to map.
type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
// apiUnmarshalValue is the interface for custom defined types customizing value assignment.
// Note that only pointer can implement interface apiUnmarshalValue.
type apiUnmarshalValue interface {
UnmarshalValue(interface{}) error
}

View File

@ -17,11 +17,6 @@ import (
"github.com/gogf/gf/internal/utils"
)
// apiMapStrAny is the interface support for converting struct parameter to map.
type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
// Map converts any variable <value> to map[string]interface{}. If the parameter <value> is not a
// map/struct/*struct type, then the conversion will fail and returns nil.
//
@ -266,7 +261,7 @@ func MapStrStr(value interface{}, tags ...string) map[string]string {
}
m := Map(value, tags...)
if len(m) > 0 {
vMap := make(map[string]string)
vMap := make(map[string]string, len(m))
for k, v := range m {
vMap[k] = String(v)
}
@ -283,7 +278,7 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string {
}
m := MapDeep(value, tags...)
if len(m) > 0 {
vMap := make(map[string]string)
vMap := make(map[string]string, len(m))
for k, v := range m {
vMap[k] = String(v)
}

View File

@ -11,11 +11,6 @@ import (
"reflect"
)
// apiInterfaces is used for type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
// SliceAny is alias of Interfaces.
func SliceAny(i interface{}) []interface{} {
return Interfaces(i)

View File

@ -6,11 +6,6 @@
package gconv
// apiFloats is used for type assert api for Floats.
type apiFloats interface {
Floats() []float64
}
// SliceFloat is alias of Floats.
func SliceFloat(i interface{}) []float64 {
return Floats(i)

View File

@ -6,11 +6,6 @@
package gconv
// apiInts is used for type assert api for Ints.
type apiInts interface {
Ints() []int
}
// SliceInt is alias of Ints.
func SliceInt(i interface{}) []int {
return Ints(i)

View File

@ -6,11 +6,6 @@
package gconv
// apiStrings is used for type assert api for Strings.
type apiStrings interface {
Strings() []string
}
// SliceStr is alias of Strings.
func SliceStr(i interface{}) []string {
return Strings(i)

View File

@ -6,11 +6,6 @@
package gconv
// apiUints is used for type assert api for Uints.
type apiUints interface {
Uints() []uint
}
// SliceUint is alias of Uints.
func SliceUint(i interface{}) []uint {
return Uints(i)

View File

@ -19,12 +19,6 @@ import (
"github.com/gogf/gf/internal/utils"
)
// apiUnmarshalValue is the interface for custom defined types customizing value assignment.
// Note that only pointer can implement interface apiUnmarshalValue.
type apiUnmarshalValue interface {
UnmarshalValue(interface{}) error
}
var (
// replaceCharReg is the regular expression object for replacing chars
// in map keys and attribute names.
@ -56,11 +50,13 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
err = gerror.NewfSkip(1, "%v", e)
}
}()
// paramsMap is the map[string]interface{} type variable for params.
paramsMap := Map(params)
if paramsMap == nil {
return fmt.Errorf("invalid params: %v", params)
}
// Using reflect to do the converting,
// it also supports type of reflect.Value for <pointer>(always in internal usage).
elem, ok := pointer.(reflect.Value)
@ -92,7 +88,8 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
}
}
// It only performs one converting to the same attribute.
// doneMap is used to check repeated converting, its key is the attribute name of the struct.
// doneMap is used to check repeated converting, its key is the real attribute name
// of the struct.
doneMap := make(map[string]struct{})
// It first checks the passed mapping rules.
if len(mapping) > 0 && len(mapping[0]) > 0 {
@ -106,26 +103,13 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
}
}
}
// It secondly checks the tags of attributes.
tagMap := structs.TagMapName(pointer, structTagPriority, true)
for tagK, tagV := range tagMap {
// tagV is the the attribute name of the struct.
if _, ok := doneMap[tagV]; ok {
continue
}
if v, ok := paramsMap[tagK]; ok {
doneMap[tagV] = struct{}{}
if err := bindVarToStructAttr(elem, tagV, v); err != nil {
return err
}
}
}
// It finally do the converting with default rules.
// The key of the map is the attribute name of the struct,
// and the value is its replaced name for later comparison to improve performance.
attrMap := make(map[string]string)
elemType := elem.Type()
tempName := ""
var (
attrMap = make(map[string]string)
elemType = elem.Type()
tempName = ""
)
for i := 0; i < elem.NumField(); i++ {
// Only do converting to public attributes.
if !utils.IsLetterUpper(elemType.Field(i).Name[0]) {
@ -137,12 +121,27 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
if len(attrMap) == 0 {
return nil
}
var attrName, checkName string
var (
attrName string
checkName string
tagMap = structs.TagMapName(pointer, structTagPriority, true)
)
for mapK, mapV := range paramsMap {
attrName = ""
checkName = replaceCharReg.ReplaceAllString(mapK, "")
// Loop to find the matched attribute name with or without
// string cases and chars like '-'/'_'/'.'/' '.
// Matching the parameters to struct tag names.
// The <tagV> is the attribute name of the struct.
for tagK, tagV := range tagMap {
if strings.EqualFold(checkName, tagK) {
attrName = tagV
break
}
}
// Matching the parameters to struct attributes.
for attrK, attrV := range attrMap {
// Eg:
// UserName eq user_name
@ -154,19 +153,15 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
break
}
}
// If the attribute name is already checked converting, then skip it.
if attrName != "" {
if _, ok := doneMap[attrName]; ok {
continue
}
if _, ok := tagMap[attrName]; ok {
continue
}
}
// No matching, give up this attribute converting.
if attrName == "" {
continue
}
// If the attribute name is already checked converting, then skip it.
if _, ok := doneMap[attrName]; ok {
continue
}
// Mark it done.
doneMap[attrName] = struct{}{}
if err := bindVarToStructAttr(elem, attrName, mapV); err != nil {

View File

@ -27,7 +27,6 @@ func Test_Struct_Basic1(t *testing.T) {
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
// 使用默认映射规则绑定属性值到对象
user := new(User)
params1 := g.Map{
"uid": 1,
@ -38,7 +37,7 @@ func Test_Struct_Basic1(t *testing.T) {
"PASS2": "456",
}
if err := gconv.Struct(params1, user); err != nil {
gtest.Error(err)
t.Error(err)
}
t.Assert(user, &User{
Uid: 1,
@ -49,7 +48,6 @@ func Test_Struct_Basic1(t *testing.T) {
Pass2: "456",
})
// 使用struct tag映射绑定属性值到对象
user = new(User)
params2 := g.Map{
"uid": 2,
@ -60,7 +58,7 @@ func Test_Struct_Basic1(t *testing.T) {
"password2": "222",
}
if err := gconv.Struct(params2, user); err != nil {
gtest.Error(err)
t.Error(err)
}
t.Assert(user, &User{
Uid: 2,
@ -73,7 +71,6 @@ func Test_Struct_Basic1(t *testing.T) {
})
}
// 使用默认映射规则绑定属性值到对象
func Test_Struct_Basic2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
@ -92,7 +89,7 @@ func Test_Struct_Basic2(t *testing.T) {
"PASS2": "456",
}
if err := gconv.Struct(params, user); err != nil {
gtest.Error(err)
t.Error(err)
}
t.Assert(user, &User{
Uid: 1,
@ -104,11 +101,10 @@ func Test_Struct_Basic2(t *testing.T) {
})
}
// 带有指针的基础类型属性
func Test_Struct_Basic3(t *testing.T) {
func Test_Struct_Attr_Pointer(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Uid int
Uid *int
Name *string
}
user := new(User)
@ -117,15 +113,14 @@ func Test_Struct_Basic3(t *testing.T) {
"Name": "john",
}
if err := gconv.Struct(params, user); err != nil {
gtest.Error(err)
t.Error(err)
}
t.Assert(user.Uid, 1)
t.Assert(*user.Name, "john")
})
}
// slice类型属性的赋值
func Test_Struct_Attr_Slice(t *testing.T) {
func Test_Struct_Attr_Slice1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int
@ -133,7 +128,7 @@ func Test_Struct_Attr_Slice(t *testing.T) {
scores := []interface{}{99, 100, 60, 140}
user := new(User)
if err := gconv.Struct(g.Map{"Scores": scores}, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(user, &User{
Scores: []int{99, 100, 60, 140},
@ -142,6 +137,24 @@ func Test_Struct_Attr_Slice(t *testing.T) {
})
}
// It does not support this kind of converting yet.
//func Test_Struct_Attr_Slice2(t *testing.T) {
// gtest.C(t, func(t *gtest.T) {
// type User struct {
// Scores [][]int
// }
// scores := []interface{}{[]interface{}{99, 100, 60, 140}}
// user := new(User)
// if err := gconv.Struct(g.Map{"Scores": scores}, user); err != nil {
// t.Error(err)
// } else {
// t.Assert(user, &User{
// Scores: [][]int{{99, 100, 60, 140}},
// })
// }
// })
//}
// 属性为struct对象
func Test_Struct_Attr_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
@ -163,7 +176,7 @@ func Test_Struct_Attr_Struct(t *testing.T) {
// 嵌套struct转换
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(user, &User{
Scores: Score{
@ -175,7 +188,6 @@ func Test_Struct_Attr_Struct(t *testing.T) {
})
}
// 属性为struct对象指针
func Test_Struct_Attr_Struct_Ptr(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Score struct {
@ -196,7 +208,7 @@ func Test_Struct_Attr_Struct_Ptr(t *testing.T) {
// 嵌套struct转换
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(user.Scores, &Score{
Name: "john",
@ -206,7 +218,6 @@ func Test_Struct_Attr_Struct_Ptr(t *testing.T) {
})
}
// 属性为struct对象slice
func Test_Struct_Attr_Struct_Slice1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Score struct {
@ -225,9 +236,8 @@ func Test_Struct_Attr_Struct_Slice1(t *testing.T) {
},
}
// 嵌套struct转换属性为slice类型数值为map类型
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(user.Scores, []Score{
{
@ -239,7 +249,6 @@ func Test_Struct_Attr_Struct_Slice1(t *testing.T) {
})
}
// 属性为struct对象slice
func Test_Struct_Attr_Struct_Slice2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Score struct {
@ -264,9 +273,8 @@ func Test_Struct_Attr_Struct_Slice2(t *testing.T) {
},
}
// 嵌套struct转换属性为slice类型数值为slice map类型
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(user.Scores, []Score{
{
@ -282,7 +290,6 @@ func Test_Struct_Attr_Struct_Slice2(t *testing.T) {
})
}
// 属性为struct对象slice ptr
func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Score struct {
@ -309,7 +316,7 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
// 嵌套struct转换属性为slice类型数值为slice map类型
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
t.Error(err)
} else {
t.Assert(len(user.Scores), 2)
t.Assert(user.Scores[0], &Score{

View File

@ -29,15 +29,21 @@ func init() {
// to produce the random bytes, and a buffer chan to store the random bytes.
// So it has high performance to generate random numbers.
func asyncProducingRandomBufferBytesLoop() {
buffer := make([]byte, 1024)
var step int
for {
buffer := make([]byte, 1024)
if n, err := rand.Read(buffer); err != nil {
panic(err)
} else {
for i := 0; i < n-4; i += 4 {
b := make([]byte, 4)
copy(b, buffer[i:i+4])
bufferChan <- b
// The random buffer from system is very expensive,
// so fully reuse the random buffer by changing
// the step with a different number can
// improve the performance a lot.
// for _, step = range []int{4, 5, 6, 7} {
for _, step = range []int{4} {
for i := 0; i <= n-4; i += step {
bufferChan <- buffer[i : i+4]
}
}
}
}

View File

@ -17,11 +17,25 @@ import (
)
var (
buffer = make([]byte, 8)
strForStr = "我爱GoFrame"
buffer = make([]byte, 8)
randBuffer4 = make([]byte, 4)
randBuffer1024 = make([]byte, 1024)
strForStr = "我爱GoFrame"
)
func Benchmark_Rand_Intn(b *testing.B) {
func Benchmark_Rand_Buffer4(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Read(randBuffer4)
}
}
func Benchmark_Rand_Buffer1024(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Read(randBuffer1024)
}
}
func Benchmark_GRand_Intn(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.N(0, 99)
}

View File

@ -25,7 +25,7 @@ import (
var (
sequence gtype.Uint32 // Sequence for unique purpose of current process.
sequenceMax = uint32(1000000) // Sequence max.
sequenceMax = uint32(46655) // Sequence max("zzz").
randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // Random chars string(36 bytes).
macAddrStr = "0000000" // MAC addresses hash result in 7 bytes.
processIdStr = "0000" // Process id in 4 bytes.
@ -42,47 +42,47 @@ func init() {
}
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
s := strconv.FormatUint(uint64(ghash.DJBHash(macAddrBytes)), 36)
copy(b[7-len(s):], s)
copy(b, s)
macAddrStr = string(b)
}
// Process id in 4 bytes.
{
b := []byte{'0', '0', '0', '0'}
s := strconv.FormatInt(int64(os.Getpid()), 36)
copy(b[4-len(s):], s)
copy(b, s)
processIdStr = string(b)
}
}
// S creates and returns an unique string in 36 bytes that meets most common usages
// without strict UUID algorithm. It returns an unique string using default unique
// algorithm if no <data> is given.
// S creates and returns a global unique string in 32 bytes that meets most common
// usages without strict UUID algorithm. It returns an unique string using default
// unique algorithm if no <data> is given.
//
// The specified <data> can be no more than 3 count. No matter how long each of the <data>
// size is, each of them will be hashed into 7 bytes as part of the result. If given
// <data> count is less than 3, the leftover size of the result bytes will be token by
// random string.
// The specified <data> can be no more than 2 count. No matter how long each of the
// <data> size is, each of them will be hashed into 7 bytes as part of the result.
// If given <data> count is less than 2, the leftover size of the result bytes will
// be token by random string.
//
// The returned string is composed with:
// 1. Default: MAC(7) + PID(4) + Sequence(4) + TimestampNano(12) + RandomString(9)
// 2. CustomData: Data...(7 - 21) + TimestampNano(12) + RandomString(3 - 17)
// 1. Default: MAC(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)
// 2. CustomData: Data(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)
//
// Note that
// 1. The returned length is fixed to 36 bytes for performance purpose.
// 1. The returned length is fixed to 32 bytes for performance purpose.
// 2. The custom parameter <data> composed should have unique attribute in your
// business situation.
func S(data ...[]byte) string {
var (
b = make([]byte, 36)
b = make([]byte, 32)
nanoStr = strconv.FormatInt(time.Now().UnixNano(), 36)
)
if len(data) == 0 {
copy(b, macAddrStr)
copy(b[7:], processIdStr)
copy(b[11:], getSequence())
copy(b[15:], nanoStr)
copy(b[27:], getRandomStr(9))
} else if len(data) <= 3 {
copy(b[11:], nanoStr)
copy(b[23:], getSequence())
copy(b[26:], getRandomStr(6))
} else if len(data) <= 2 {
n := 0
for i, v := range data {
// Ignore empty data item bytes.
@ -92,26 +92,27 @@ func S(data ...[]byte) string {
}
}
copy(b[n:], nanoStr)
copy(b[n+12:], getRandomStr(36-n-12))
copy(b[n+12:], getSequence())
copy(b[n+12+3:], getRandomStr(32-n-12-3))
} else {
panic("data count too long, no more than 3")
panic("data count too long, no more than 2")
}
return gconv.UnsafeBytesToStr(b)
}
// getSequence increases and returns the sequence string in 4 bytes.
// The sequence is less than 1000000.
func getSequence() string {
b := []byte{'0', '0', '0', '0'}
// getSequence increases and returns the sequence string in 3 bytes.
// The sequence is less than "zzz"(46655).
func getSequence() []byte {
b := []byte{'0', '0', '0'}
s := strconv.FormatUint(uint64(sequence.Add(1)%sequenceMax), 36)
copy(b[4-len(s):], s)
return gconv.UnsafeBytesToStr(b)
copy(b, s)
return b
}
// getRandomStr randomly picks and returns <n> count of chars from randomStrBase.
func getRandomStr(n int) string {
func getRandomStr(n int) []byte {
if n <= 0 {
return ""
return []byte{}
}
var (
b = make([]byte, n)
@ -120,13 +121,13 @@ func getRandomStr(n int) string {
for i := range b {
b[i] = randomStrBase[numberBytes[i]%36]
}
return gconv.UnsafeBytesToStr(b)
return b
}
// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes.
func getDataHashStr(data []byte) string {
func getDataHashStr(data []byte) []byte {
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36)
copy(b[7-len(s):], s)
return gconv.UnsafeBytesToStr(b)
copy(b, s)
return b
}

Some files were not shown because too many files have changed in this diff Show More