mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55308cc37c | |||
| 4d9db6edf0 | |||
| 38111a64e3 | |||
| bd8d3fca08 | |||
| 89ccaa3915 | |||
| 7c2cff7d99 | |||
| 788e15dbb6 | |||
| a0172d9d7e | |||
| bc1a7a1644 | |||
| c98234d3e6 | |||
| 45a94d23d5 | |||
| 351de5ee6a | |||
| 3559436d1e | |||
| 189f4c1637 | |||
| caead810e2 | |||
| ebdc5d9c9d | |||
| 4164059211 | |||
| bd27258c46 | |||
| ddf0605bc4 | |||
| 991dbe50e0 | |||
| 8050efb835 | |||
| 9355bc73a2 | |||
| acc2a6a353 | |||
| 09e83e7b8d | |||
| c0df8a3d80 | |||
| 33e890d225 | |||
| 750e5df962 | |||
| 74c65439fc | |||
| f290bd7170 | |||
| d398b749d4 | |||
| 2fd5a1574a | |||
| a7504f0629 | |||
| 3c35bb85ee | |||
| 6621edb04c | |||
| b0c722e297 | |||
| 47a6284274 | |||
| 150b8edb70 | |||
| aa5d3285eb | |||
| 993d6e3076 | |||
| 78ad9d8c2d | |||
| feff3ddce3 | |||
| 80fddad64d | |||
| 1e6dd0be02 | |||
| 22ecf4f1b6 | |||
| 5d21148657 |
14
.example/os/glog/glog_ctx.go
Normal file
14
.example/os/glog/glog_ctx.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.SetCtxKeys("Trace-Id", "Span-Id", "Test")
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
|
||||
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
|
||||
|
||||
glog.Ctx(ctx).Print(1, 2, 3)
|
||||
}
|
||||
@ -15,4 +15,11 @@
|
||||
|
||||
[viewer]
|
||||
delimiters = ["${", "}"]
|
||||
autoencode = true
|
||||
autoencode = true
|
||||
|
||||
|
||||
[server]
|
||||
Address = ":8800"
|
||||
ServerRoot = "/Users/john/Downloads"
|
||||
ServerAgent = "gf-app"
|
||||
# LogPath = "./log/gf-app/server"
|
||||
@ -1,29 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/encoding/gbase64"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
)
|
||||
|
||||
// bytesToHexString converts binary content to hex string content.
|
||||
func bytesToHexStr(b []byte) string {
|
||||
dst := make([]byte, hex.EncodedLen(len(b)))
|
||||
hex.Encode(dst, b)
|
||||
return gconv.UnsafeBytesToStr(dst)
|
||||
}
|
||||
|
||||
func main() {
|
||||
b := make([]byte, 1024)
|
||||
for i := 0; i < 1024; i++ {
|
||||
b[i] = byte(grand.N(0, 255))
|
||||
body := "{\"id\": 413231383385427875}"
|
||||
if dat, err := gjson.DecodeToJson(body); err == nil {
|
||||
fmt.Println(dat.MustToJsonString())
|
||||
}
|
||||
|
||||
fmt.Println(bytesToHexStr(b))
|
||||
fmt.Println(len(b))
|
||||
fmt.Println(len(bytesToHexStr(b)))
|
||||
fmt.Println(gbase64.EncodeToString(b))
|
||||
fmt.Println(len(gbase64.EncodeToString(b)))
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := ghttp.PostContent("http://127.0.0.1:8199/test", `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`)
|
||||
fmt.Println(r)
|
||||
s := g.Server()
|
||||
s.SetIndexFolder(true)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -8,6 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 100; i++ {
|
||||
fmt.Println(grand.Rand(0, 99999))
|
||||
fmt.Println(grand.S(16))
|
||||
}
|
||||
}
|
||||
|
||||
13
.example/util/guid/guid.go
Normal file
13
.example/util/guid/guid.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
s := guid.S()
|
||||
fmt.Println(s, len(s))
|
||||
}
|
||||
}
|
||||
15
.example/util/guid/guid_length.go
Normal file
15
.example/util/guid/guid_length.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
23
.example/util/guid/guid_unique.go
Normal file
23
.example/util/guid/guid_unique.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
s := guid.S([]byte("123"))
|
||||
fmt.Println(s, len(s))
|
||||
}
|
||||
fmt.Println()
|
||||
for i := 0; i < 10; i++ {
|
||||
s := guid.S([]byte("123"), []byte("456"))
|
||||
fmt.Println(s, len(s))
|
||||
}
|
||||
fmt.Println()
|
||||
for i := 0; i < 10; i++ {
|
||||
s := guid.S([]byte("123"), []byte("456"), []byte("789"))
|
||||
fmt.Println(s, len(s))
|
||||
}
|
||||
}
|
||||
13
.example/util/gvalid/gvalid_custom_message.go
Normal file
13
.example/util/gvalid/gvalid_custom_message.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g.I18n().SetLanguage("cn")
|
||||
err := gvalid.Check("", "required", nil)
|
||||
fmt.Println(err.String())
|
||||
}
|
||||
14
.example/util/gvalid/i18n/cn.toml
Normal file
14
.example/util/gvalid/i18n/cn.toml
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
"gf.gvalid.required" = "字段不能为空"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -77,6 +77,9 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
|
||||
|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
|
||||
|faddei|qq|¥9.99|
|
||||
|*天|wechat|¥10.00|
|
||||
|*.|wechat|¥20.00| 学生一个微薄之力,冲
|
||||
|李勇|wechat|¥50.00|
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
|
||||
12
README.MD
12
README.MD
@ -31,6 +31,15 @@ require github.com/gogf/gf latest
|
||||
golang version >= 1.11
|
||||
```
|
||||
|
||||
# Packages
|
||||
1. **Primary**
|
||||
|
||||
The `gf` repository maintains some basic and most commonly used packages, keeping it as lightweight and simple as possible.
|
||||
|
||||
1. **Community**
|
||||
|
||||
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.
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=11"/>
|
||||
@ -110,13 +119,14 @@ The concurrency starts from `100` to `10000`.
|
||||
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>
|
||||
|
||||
11
README_ZH.MD
11
README_ZH.MD
@ -45,6 +45,17 @@ require github.com/gogf/gf latest
|
||||
golang版本 >= 1.11
|
||||
```
|
||||
|
||||
# 模块
|
||||
|
||||
1. **核心模块**
|
||||
|
||||
`GoFrame`提供了一些基础的、常用的模块,简单、易用和轻量级,并保持极少的外部依赖,这些模块由`gf`主仓库细致维护。
|
||||
|
||||
1. **社区模块**
|
||||
|
||||
社区模块主要由社区贡献并维护,大部分也是由`gf`主仓库的贡献者提供及维护,存放于`gogf`组织下,与`gf`主仓库处于同一级别。有的社区模块是从`gf`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。
|
||||
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=11"/>
|
||||
|
||||
@ -288,7 +288,7 @@ func (a *Array) PopRight() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return nil, false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -488,6 +488,7 @@ func (a *Array) Search(value interface{}) int {
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
// Example: [1,1,2,3,2] -> [1,2,3]
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
@ -717,7 +718,8 @@ func (a *Array) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *Array) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a Array) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -264,7 +264,7 @@ func (a *IntArray) PopRight() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return 0, false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -504,6 +504,7 @@ func (a *IntArray) Search(value int) int {
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
// Example: [1,1,2,3,2] -> [1,2,3]
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
@ -716,7 +717,8 @@ func (a *IntArray) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *IntArray) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a IntArray) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -252,7 +252,7 @@ func (a *StrArray) PopRight() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return "", false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -508,6 +508,7 @@ func (a *StrArray) Search(value string) int {
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
// Example: [1,1,2,3,2] -> [1,2,3]
|
||||
func (a *StrArray) Unique() *StrArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
@ -731,7 +732,8 @@ func (a *StrArray) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *StrArray) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a StrArray) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -224,7 +224,7 @@ func (a *SortedArray) PopRight() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return nil, false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -666,7 +666,8 @@ func (a *SortedArray) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *SortedArray) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a SortedArray) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -209,7 +209,7 @@ func (a *SortedIntArray) PopRight() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return 0, false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -640,7 +640,8 @@ func (a *SortedIntArray) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *SortedIntArray) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a SortedIntArray) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -195,7 +195,7 @@ func (a *SortedStrArray) PopRight() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
if index <= 0 {
|
||||
if index < 0 {
|
||||
return "", false
|
||||
}
|
||||
value = a.array[index]
|
||||
@ -653,7 +653,8 @@ func (a *SortedStrArray) String() string {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (a *SortedStrArray) MarshalJSON() ([]byte, error) {
|
||||
// Note that do not use pointer as its receiver here.
|
||||
func (a SortedStrArray) MarshalJSON() ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return json.Marshal(a.array)
|
||||
|
||||
@ -137,6 +137,65 @@ func TestArray_PopRands(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopLeft(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3})
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopRight(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3})
|
||||
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopLefts(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3})
|
||||
t.Assert(array.PopLefts(2), g.Slice{1, 2})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{3})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopRights(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3})
|
||||
t.Assert(array.PopRights(2), g.Slice{2, 3})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{1})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopLeftsAndPopRights(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New()
|
||||
@ -501,6 +560,7 @@ func TestArray_RLockFunc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestArray_Json(t *testing.T) {
|
||||
// pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []interface{}{"a", "b", "d", "c"}
|
||||
a1 := garray.NewArrayFrom(s1)
|
||||
@ -519,7 +579,26 @@ func TestArray_Json(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// value.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []interface{}{"a", "b", "d", "c"}
|
||||
a1 := *garray.NewArrayFrom(s1)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.New()
|
||||
err2 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(err2, nil)
|
||||
t.Assert(a2.Slice(), s1)
|
||||
|
||||
var a3 garray.Array
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -532,6 +611,25 @@ func TestArray_Json(t *testing.T) {
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
// value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.Array
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []int{99, 100, 98},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -230,6 +230,65 @@ func TestIntArray_Fill(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopLeft(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopRight(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopLefts(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
t.Assert(array.PopLefts(2), g.Slice{1, 2})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{3})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopRights(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
t.Assert(array.PopRights(2), g.Slice{2, 3})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{1})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Chunk(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
@ -546,6 +605,7 @@ func TestIntArray_RLockFunc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIntArray_Json(t *testing.T) {
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []int{1, 4, 3, 2}
|
||||
a1 := garray.NewIntArrayFrom(s1)
|
||||
@ -563,7 +623,25 @@ func TestIntArray_Json(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []int{1, 4, 3, 2}
|
||||
a1 := *garray.NewIntArrayFrom(s1)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.NewIntArray()
|
||||
err1 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(a2.Slice(), s1)
|
||||
|
||||
var a3 garray.IntArray
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -576,6 +654,25 @@ func TestIntArray_Json(t *testing.T) {
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.IntArray
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []int{99, 100, 98},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -129,6 +129,65 @@ func TestStrArray_PushAndPop(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_PopLeft(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_PopRight(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_PopLefts(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
t.Assert(array.PopLefts(2), g.Slice{"1", "2"})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{"3"})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_PopRights(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
t.Assert(array.PopRights(2), g.Slice{"2", "3"})
|
||||
t.Assert(array.Len(), 1)
|
||||
t.Assert(array.PopLefts(2), g.Slice{"1"})
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrArray_PopLeftsAndPopRights(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewStrArray()
|
||||
@ -545,6 +604,7 @@ func TestStrArray_LockFunc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStrArray_Json(t *testing.T) {
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []string{"a", "b", "d", "c"}
|
||||
a1 := garray.NewStrArrayFrom(s1)
|
||||
@ -562,7 +622,25 @@ func TestStrArray_Json(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []string{"a", "b", "d", "c"}
|
||||
a1 := *garray.NewStrArrayFrom(s1)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.NewStrArray()
|
||||
err1 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(a2.Slice(), s1)
|
||||
|
||||
var a3 garray.StrArray
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -575,6 +653,25 @@ func TestStrArray_Json(t *testing.T) {
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.Assert(user.Scores, data["Scores"])
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.StrArray
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []string{"A+", "A", "A"},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -148,34 +148,62 @@ func TestSortedArray_Remove(t *testing.T) {
|
||||
|
||||
func TestSortedArray_PopLeft(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array1 := garray.NewSortedArrayFrom(
|
||||
[]interface{}{"a", "d", "c", "b"},
|
||||
gutil.ComparatorString,
|
||||
)
|
||||
i1, ok := array1.PopLeft()
|
||||
t.Assert(ok, true)
|
||||
t.Assert(gconv.String(i1), "a")
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1, []interface{}{"b", "c", "d"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedArrayFrom(g.Slice{1, 2, 3}, gutil.ComparatorInt)
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRight(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array1 := garray.NewSortedArrayFrom(
|
||||
[]interface{}{"a", "d", "c", "b"},
|
||||
gutil.ComparatorString,
|
||||
)
|
||||
i1, ok := array1.PopRight()
|
||||
t.Assert(ok, true)
|
||||
t.Assert(gconv.String(i1), "d")
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1, []interface{}{"a", "b", "c"})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedArrayFrom(g.Slice{1, 2, 3}, gutil.ComparatorInt)
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRand(t *testing.T) {
|
||||
@ -249,7 +277,6 @@ func TestSortedArray_PopLefts(t *testing.T) {
|
||||
t.Assert(len(i2), 4)
|
||||
t.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
|
||||
t.Assert(array1.Len(), 0)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -267,7 +294,7 @@ func TestSortedArray_PopRights(t *testing.T) {
|
||||
|
||||
i2 := array1.PopRights(10)
|
||||
t.Assert(len(i2), 4)
|
||||
|
||||
t.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -612,6 +639,7 @@ func TestSortedArray_Merge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSortedArray_Json(t *testing.T) {
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []interface{}{"a", "b", "d", "c"}
|
||||
s2 := []interface{}{"a", "b", "c", "d"}
|
||||
@ -631,7 +659,27 @@ func TestSortedArray_Json(t *testing.T) {
|
||||
t.Assert(a3.Slice(), s1)
|
||||
t.Assert(a3.Interfaces(), s1)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []interface{}{"a", "b", "d", "c"}
|
||||
s2 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := *garray.NewSortedArrayFrom(s1, gutil.ComparatorString)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.NewSortedArray(gutil.ComparatorString)
|
||||
err1 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(a2.Slice(), s2)
|
||||
|
||||
var a3 garray.SortedArray
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
t.Assert(a3.Interfaces(), s1)
|
||||
})
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -663,6 +711,42 @@ func TestSortedArray_Json(t *testing.T) {
|
||||
t.AssertIN(v, data["Scores"])
|
||||
t.Assert(ok, true)
|
||||
|
||||
v, ok = user.Scores.PopLeft()
|
||||
t.Assert(v, nil)
|
||||
t.Assert(ok, false)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.SortedArray
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []int{99, 100, 98},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.AssertNE(user.Scores, nil)
|
||||
t.Assert(user.Scores.Len(), 3)
|
||||
|
||||
v, ok := user.Scores.PopLeft()
|
||||
t.AssertIN(v, data["Scores"])
|
||||
t.Assert(ok, true)
|
||||
|
||||
v, ok = user.Scores.PopLeft()
|
||||
t.AssertIN(v, data["Scores"])
|
||||
t.Assert(ok, true)
|
||||
|
||||
v, ok = user.Scores.PopLeft()
|
||||
t.AssertIN(v, data["Scores"])
|
||||
t.Assert(ok, true)
|
||||
|
||||
v, ok = user.Scores.PopLeft()
|
||||
t.Assert(v, nil)
|
||||
t.Assert(ok, false)
|
||||
|
||||
@ -133,6 +133,21 @@ func TestSortedIntArray_PopLeft(t *testing.T) {
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1.Search(1), -1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRight(t *testing.T) {
|
||||
@ -145,6 +160,23 @@ func TestSortedIntArray_PopRight(t *testing.T) {
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1.Search(5), -1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2, 3})
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRand(t *testing.T) {
|
||||
@ -504,6 +536,7 @@ func TestSortedIntArray_Merge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Json(t *testing.T) {
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []int{1, 4, 3, 2}
|
||||
s2 := []int{1, 2, 3, 4}
|
||||
@ -522,7 +555,26 @@ func TestSortedIntArray_Json(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []int{1, 4, 3, 2}
|
||||
s2 := []int{1, 2, 3, 4}
|
||||
a1 := *garray.NewSortedIntArrayFrom(s1)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.NewSortedIntArray()
|
||||
err1 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(a2.Slice(), s2)
|
||||
|
||||
var a3 garray.SortedIntArray
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
})
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -535,6 +587,25 @@ func TestSortedIntArray_Json(t *testing.T) {
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.Assert(user.Scores, []int{98, 99, 100})
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.SortedIntArray
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []int{99, 100, 98},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -137,6 +137,21 @@ func TestSortedStrArray_PopLeft(t *testing.T) {
|
||||
t.Assert(array1.Len(), 4)
|
||||
t.Assert(array1.Contains("a"), false)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
v, ok := array.PopLeft()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
v, ok = array.PopLeft()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStrArray_PopRight(t *testing.T) {
|
||||
@ -149,6 +164,23 @@ func TestSortedStrArray_PopRight(t *testing.T) {
|
||||
t.Assert(array1.Len(), 4)
|
||||
t.Assert(array1.Contains("e"), false)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2", "3"})
|
||||
v, ok := array.PopRight()
|
||||
t.Assert(v, 3)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 2)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
v, ok = array.PopRight()
|
||||
t.Assert(v, 1)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStrArray_PopRand(t *testing.T) {
|
||||
@ -210,6 +242,7 @@ func TestSortedStrArray_PopLefts(t *testing.T) {
|
||||
s1 = array1.PopLefts(4)
|
||||
t.Assert(len(s1), 3)
|
||||
t.Assert(s1, []string{"c", "d", "e"})
|
||||
t.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -523,6 +556,7 @@ func TestSortedStrArray_Merge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSortedStrArray_Json(t *testing.T) {
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []string{"a", "b", "d", "c"}
|
||||
s2 := []string{"a", "b", "c", "d"}
|
||||
@ -543,7 +577,28 @@ func TestSortedStrArray_Json(t *testing.T) {
|
||||
t.Assert(a3.Slice(), s1)
|
||||
t.Assert(a3.Interfaces(), s1)
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []string{"a", "b", "d", "c"}
|
||||
s2 := []string{"a", "b", "c", "d"}
|
||||
a1 := *garray.NewSortedStrArrayFrom(s1)
|
||||
b1, err1 := json.Marshal(a1)
|
||||
b2, err2 := json.Marshal(s1)
|
||||
t.Assert(b1, b2)
|
||||
t.Assert(err1, err2)
|
||||
|
||||
a2 := garray.NewSortedStrArray()
|
||||
err1 = json.Unmarshal(b2, &a2)
|
||||
t.Assert(a2.Slice(), s2)
|
||||
t.Assert(a2.Interfaces(), s2)
|
||||
|
||||
var a3 garray.SortedStrArray
|
||||
err := json.Unmarshal(b2, &a3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a3.Slice(), s1)
|
||||
t.Assert(a3.Interfaces(), s1)
|
||||
})
|
||||
// array pointer
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
@ -556,6 +611,25 @@ func TestSortedStrArray_Json(t *testing.T) {
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Name, data["Name"])
|
||||
t.Assert(user.Scores, []string{"A", "A", "A+"})
|
||||
})
|
||||
// array value
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Scores garray.SortedStrArray
|
||||
}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Scores": []string{"A+", "A", "A"},
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
err = json.Unmarshal(b, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -60,26 +60,26 @@ func (v *Var) Maps(tags ...string) []map[string]interface{} {
|
||||
return gconv.Maps(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapToMap converts map type variable <params> to another map type variable <pointer>.
|
||||
// MapToMap converts any map type variable <params> to another map type variable <pointer>.
|
||||
// See gconv.MapToMap.
|
||||
func (v *Var) MapToMap(pointer interface{}) (err error) {
|
||||
return gconv.MapToMap(v.Val(), pointer)
|
||||
}
|
||||
|
||||
// MapToMapDeep converts map type variable <params> to another map type variable
|
||||
// MapToMapDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// See gconv.MapToMapDeep.
|
||||
func (v *Var) MapToMapDeep(pointer interface{}) (err error) {
|
||||
return gconv.MapToMapDeep(v.Val(), pointer)
|
||||
}
|
||||
|
||||
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
|
||||
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
// See gconv.MapToMaps.
|
||||
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMaps(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapsDeep converts map type variable <params> to another map type variable
|
||||
// MapToMapsDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// See gconv.MapToMapsDeep.
|
||||
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
|
||||
@ -734,9 +734,11 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
columnTypes[k] = v.DatabaseTypeName()
|
||||
columnNames[k] = v.Name()
|
||||
}
|
||||
values := make([]sql.RawBytes, len(columnNames))
|
||||
records := make(Result, 0)
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
var (
|
||||
values = make([]sql.RawBytes, len(columnNames))
|
||||
records = make(Result, 0)
|
||||
scanArgs = make([]interface{}, len(values))
|
||||
)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
|
||||
@ -248,6 +248,9 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
// The internal handleArguments function might be called twice during the SQL procedure,
|
||||
// but do not worry about it, it's safe and efficient.
|
||||
func formatSql(sql string, args []interface{}) (newQuery string, newArgs []interface{}) {
|
||||
sql = gstr.Trim(sql)
|
||||
sql = gstr.Replace(sql, "\n", " ")
|
||||
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
return handleArguments(sql, args)
|
||||
}
|
||||
|
||||
|
||||
@ -25,22 +25,47 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
t = strings.ToLower(t)
|
||||
switch t {
|
||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
||||
case
|
||||
"binary",
|
||||
"varbinary",
|
||||
"blob",
|
||||
"tinyblob",
|
||||
"mediumblob",
|
||||
"longblob":
|
||||
return fieldValue
|
||||
|
||||
case "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint":
|
||||
case
|
||||
"int",
|
||||
"tinyint",
|
||||
"small_int",
|
||||
"smallint",
|
||||
"medium_int",
|
||||
"mediumint",
|
||||
"serial",
|
||||
"smallmoney":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
gconv.Uint(string(fieldValue))
|
||||
}
|
||||
return gconv.Int(string(fieldValue))
|
||||
|
||||
case "big_int", "bigint":
|
||||
case
|
||||
"big_int",
|
||||
"bigint",
|
||||
"bigserial",
|
||||
"money":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
gconv.Uint64(string(fieldValue))
|
||||
}
|
||||
return gconv.Int64(string(fieldValue))
|
||||
|
||||
case "float", "double", "decimal":
|
||||
case "real":
|
||||
return gconv.Float32(string(fieldValue))
|
||||
|
||||
case
|
||||
"float",
|
||||
"double",
|
||||
"decimal",
|
||||
"numeric":
|
||||
return gconv.Float64(string(fieldValue))
|
||||
|
||||
case "bit":
|
||||
@ -61,17 +86,19 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
t, _ := gtime.StrToTime(string(fieldValue))
|
||||
return t.Format("Y-m-d")
|
||||
|
||||
case "datetime", "timestamp":
|
||||
case
|
||||
"datetime",
|
||||
"timestamp":
|
||||
t, _ := gtime.StrToTime(string(fieldValue))
|
||||
return t.String()
|
||||
|
||||
default:
|
||||
// Auto detect field type, using key match.
|
||||
switch {
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char") || strings.Contains(t, "character"):
|
||||
return string(fieldValue)
|
||||
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double") || strings.Contains(t, "numeric"):
|
||||
return gconv.Float64(string(fieldValue))
|
||||
|
||||
case strings.Contains(t, "bool"):
|
||||
|
||||
@ -96,6 +96,150 @@ func Test_Model_Inherit_MapToStruct(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_Pointer_Attribute(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id *int
|
||||
Passport *string
|
||||
Password *string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
user := new(User)
|
||||
err = one.Struct(user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(*user.Id, 1)
|
||||
t.Assert(*user.Passport, "user_1")
|
||||
t.Assert(*user.Password, "pass_1")
|
||||
t.Assert(user.Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
err := db.Table(table).Struct(user, "id=1")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(*user.Id, 1)
|
||||
t.Assert(*user.Passport, "user_1")
|
||||
t.Assert(*user.Password, "pass_1")
|
||||
t.Assert(user.Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Table(table).Struct(&user, "id=1")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(*user.Id, 1)
|
||||
t.Assert(*user.Passport, "user_1")
|
||||
t.Assert(*user.Password, "pass_1")
|
||||
t.Assert(user.Nickname, "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Structs_Pointer_Attribute(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id *int
|
||||
Passport *string
|
||||
Password *string
|
||||
Nickname string
|
||||
}
|
||||
// All
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).All("id < 3")
|
||||
t.Assert(err, nil)
|
||||
users := make([]User, 0)
|
||||
err = one.Structs(&users)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).All("id < 3")
|
||||
t.Assert(err, nil)
|
||||
users := make([]*User, 0)
|
||||
err = one.Structs(&users)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
one, err := db.Table(table).All("id < 3")
|
||||
t.Assert(err, nil)
|
||||
err = one.Structs(&users)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*User
|
||||
one, err := db.Table(table).All("id < 3")
|
||||
t.Assert(err, nil)
|
||||
err = one.Structs(&users)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
// Structs
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
users := make([]User, 0)
|
||||
err := db.Table(table).Structs(&users, "id < 3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
users := make([]*User, 0)
|
||||
err := db.Table(table).Structs(&users, "id < 3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
err := db.Table(table).Structs(&users, "id < 3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*User
|
||||
err := db.Table(table).Structs(&users, "id < 3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
t.Assert(*users[0].Passport, "user_1")
|
||||
t.Assert(*users[0].Password, "pass_1")
|
||||
t.Assert(users[0].Nickname, "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_Empty(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -6,9 +6,44 @@
|
||||
|
||||
package gredis
|
||||
|
||||
import "github.com/gogf/gf/container/gvar"
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// DoVar returns value from Do as gvar.Var.
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// It uses json.Marshal for struct/slice/map type values before committing them to redis.
|
||||
func (c *Conn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
for k, v := range args {
|
||||
reflectValue = reflect.ValueOf(v)
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case
|
||||
reflect.Struct,
|
||||
reflect.Map,
|
||||
reflect.Slice,
|
||||
reflect.Array:
|
||||
// Ignore slice type of: []byte.
|
||||
if _, ok := v.([]byte); !ok {
|
||||
if args[k], err = json.Marshal(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.Conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
// DoVar retrieves and returns the result from command as gvar.Var.
|
||||
func (c *Conn) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
|
||||
v, err := c.Do(command, args...)
|
||||
return gvar.New(v), err
|
||||
|
||||
@ -8,7 +8,7 @@ package gredis_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/util/guuid"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -234,7 +234,7 @@ func Test_Bool(t *testing.T) {
|
||||
func Test_Int(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
key := guid.S()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err := redis.Do("SET", key, 1)
|
||||
@ -249,7 +249,7 @@ func Test_Int(t *testing.T) {
|
||||
func Test_HSet(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
key := guid.S()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err := redis.Do("HSET", key, "name", "john")
|
||||
@ -265,7 +265,7 @@ func Test_HGetAll(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var err error
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
key := guid.S()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err = redis.Do("HSET", key, "id", "100")
|
||||
@ -281,3 +281,39 @@ func Test_HGetAll(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Auto_Marshal(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
redis = gredis.New(config)
|
||||
key = guid.S()
|
||||
)
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{
|
||||
Id: 10000,
|
||||
Name: "john",
|
||||
}
|
||||
|
||||
_, err = redis.Do("SET", key, user)
|
||||
t.Assert(err, nil)
|
||||
|
||||
r, err := redis.DoVar("GET", key)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Map(), g.MapStrAny{
|
||||
"Id": user.Id,
|
||||
"Name": user.Name,
|
||||
})
|
||||
|
||||
var user2 *User
|
||||
t.Assert(r.Struct(&user2), nil)
|
||||
t.Assert(user2.Id, user.Id)
|
||||
t.Assert(user2.Name, user.Name)
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package ghash provides some popular hash functions(uint32/uint64) in go.
|
||||
// Package ghash provides some classic hash functions(uint32/uint64) in go.
|
||||
package ghash
|
||||
|
||||
// BKDR Hash Function
|
||||
@ -160,7 +160,7 @@ func DJBHash(str []byte) uint32 {
|
||||
return hash
|
||||
}
|
||||
|
||||
// DJB Hash Function 64
|
||||
// DJB Hash Function 64.
|
||||
func DJBHash64(str []byte) uint64 {
|
||||
var hash uint64 = 5381
|
||||
for i := 0; i < len(str); i++ {
|
||||
|
||||
@ -203,7 +203,7 @@ func checkDataType(content []byte) string {
|
||||
return "yml"
|
||||
} else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false {
|
||||
return "ini"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=\s*.+`, content) {
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-\."]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-\."]+\s*=\s*.+`, content) {
|
||||
return "toml"
|
||||
} else {
|
||||
return ""
|
||||
|
||||
@ -8,6 +8,7 @@ package gjson_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"testing"
|
||||
@ -44,5 +45,41 @@ func Test_ToJson(t *testing.T) {
|
||||
t.Assert(gstr.Contains(content, `"id":"g0936lt1u0f"`), true)
|
||||
t.Assert(gstr.Contains(content, `"new":"4"`), true)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_MapAttributeConvert(t *testing.T) {
|
||||
var data = `
|
||||
{
|
||||
"title": {"l1":"标签1","l2":"标签2"}
|
||||
}
|
||||
`
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j, err := gjson.LoadContent(data)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
tx := struct {
|
||||
Title map[string]interface{}
|
||||
}{}
|
||||
|
||||
err = j.ToStruct(&tx)
|
||||
gtest.Assert(err, nil)
|
||||
t.Assert(tx.Title, g.Map{
|
||||
"l1": "标签1", "l2": "标签2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j, err := gjson.LoadContent(data)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
tx := struct {
|
||||
Title map[string]string
|
||||
}{}
|
||||
|
||||
err = j.ToStruct(&tx)
|
||||
gtest.Assert(err, nil)
|
||||
t.Assert(tx.Title, g.Map{
|
||||
"l1": "标签1", "l2": "标签2",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@ -8,13 +8,12 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/gqcn/structs v1.1.1
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
)
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
package gi18n
|
||||
|
||||
var (
|
||||
// defaultManager is the default i18n instance for package functions.
|
||||
defaultManager = Instance()
|
||||
)
|
||||
|
||||
@ -26,7 +27,7 @@ func SetDelimiters(left, right string) {
|
||||
defaultManager.SetDelimiters(left, right)
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
// T is alias of Translate for convenience.
|
||||
func T(content string, language ...string) string {
|
||||
return defaultManager.T(content, language...)
|
||||
}
|
||||
@ -36,3 +37,9 @@ func T(content string, language ...string) string {
|
||||
func Translate(content string, language ...string) string {
|
||||
return defaultManager.Translate(content, language...)
|
||||
}
|
||||
|
||||
// GetValue retrieves and returns the configured content for given key and specified language.
|
||||
// It returns an empty string if not found.
|
||||
func GetContent(key string, language ...string) string {
|
||||
return defaultManager.GetContent(key, language...)
|
||||
}
|
||||
|
||||
@ -14,7 +14,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// Instances map.
|
||||
// instances is the instances map for management
|
||||
// for multiple i18n instance by name.
|
||||
instances = gmap.NewStrAnyMap(true)
|
||||
)
|
||||
|
||||
|
||||
@ -43,11 +43,13 @@ type Options struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultDelimiters defines the key variable delimiters.
|
||||
// defaultDelimiters defines the default key variable delimiters.
|
||||
defaultDelimiters = []string{"{#", "}"}
|
||||
)
|
||||
|
||||
// New creates and returns a new i18n manager.
|
||||
// The optional parameter <option> specifies the custom options for i18n manager.
|
||||
// It uses a default one if it's not passed.
|
||||
func New(options ...Options) *Manager {
|
||||
var opts Options
|
||||
if len(options) > 0 {
|
||||
@ -70,20 +72,23 @@ func New(options ...Options) *Manager {
|
||||
return m
|
||||
}
|
||||
|
||||
// DefaultOptions returns the default options for i18n manager.
|
||||
// DefaultOptions creates and returns a default options for i18n manager.
|
||||
func DefaultOptions() Options {
|
||||
path := "i18n"
|
||||
realPath, _ := gfile.Search(path)
|
||||
var (
|
||||
path = "i18n"
|
||||
realPath, _ = gfile.Search(path)
|
||||
)
|
||||
if realPath != "" {
|
||||
path = realPath
|
||||
// To avoid of the source of GF: github.com/gogf/i18n/gi18n
|
||||
// To avoid of the source path of GF: github.com/gogf/i18n/gi18n
|
||||
if gfile.Exists(path + gfile.Separator + "gi18n") {
|
||||
path = ""
|
||||
}
|
||||
}
|
||||
return Options{
|
||||
Path: path,
|
||||
Delimiters: []string{"{#", "}"},
|
||||
Language: "en",
|
||||
Delimiters: defaultDelimiters,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +119,7 @@ func (m *Manager) SetDelimiters(left, right string) {
|
||||
intlog.Printf(`SetDelimiters: %v`, m.pattern)
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
// T is alias of Translate for convenience.
|
||||
func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
@ -140,20 +145,41 @@ func (m *Manager) Translate(content string, language ...string) string {
|
||||
return v
|
||||
}
|
||||
// Parse content as variables container.
|
||||
result, _ := gregex.ReplaceStringFuncMatch(m.pattern, content, func(match []string) string {
|
||||
if v, ok := data[match[1]]; ok {
|
||||
return v
|
||||
}
|
||||
return match[0]
|
||||
})
|
||||
result, _ := gregex.ReplaceStringFuncMatch(
|
||||
m.pattern, content,
|
||||
func(match []string) string {
|
||||
if v, ok := data[match[1]]; ok {
|
||||
return v
|
||||
}
|
||||
return match[0]
|
||||
})
|
||||
intlog.Printf(`Translate for language: %s`, transLang)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetValue retrieves and returns the configured content for given key and specified language.
|
||||
// It returns an empty string if not found.
|
||||
func (m *Manager) GetContent(key string, language ...string) string {
|
||||
m.init()
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
transLang := m.options.Language
|
||||
if len(language) > 0 && language[0] != "" {
|
||||
transLang = language[0]
|
||||
} else {
|
||||
transLang = m.options.Language
|
||||
}
|
||||
if data, ok := m.data[transLang]; ok {
|
||||
return data[key]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// init initializes the manager for lazy initialization design.
|
||||
// The i18n manager is only initialized once.
|
||||
func (m *Manager) init() {
|
||||
m.mu.RLock()
|
||||
// If the data is not nil, means it's already initialized.
|
||||
if m.data != nil {
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
@ -165,10 +191,12 @@ func (m *Manager) init() {
|
||||
if gres.Contains(m.options.Path) {
|
||||
files := gres.ScanDirFile(m.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var name string
|
||||
var lang string
|
||||
var array []string
|
||||
var (
|
||||
path string
|
||||
name string
|
||||
lang string
|
||||
array []string
|
||||
)
|
||||
m.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
name = file.Name()
|
||||
@ -193,36 +221,45 @@ func (m *Manager) init() {
|
||||
}
|
||||
} else if m.options.Path != "" {
|
||||
files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var lang string
|
||||
var array []string
|
||||
m.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
path = file[len(m.options.Path)+1:]
|
||||
array = strings.Split(path, gfile.Separator)
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if m.data[lang] == nil {
|
||||
m.data[lang] = make(map[string]string)
|
||||
}
|
||||
if j, err := gjson.LoadContent(gfile.GetBytes(file)); err == nil {
|
||||
for k, v := range j.ToMap() {
|
||||
m.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
} else {
|
||||
glog.Errorf("load i18n file '%s' failed: %v", file, err)
|
||||
}
|
||||
}
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
m.mu.Lock()
|
||||
m.data = nil
|
||||
m.mu.Unlock()
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
if len(files) == 0 {
|
||||
intlog.Printf(
|
||||
"no i18n files found in configured directory: %s",
|
||||
m.options.Path,
|
||||
)
|
||||
return
|
||||
}
|
||||
var (
|
||||
path string
|
||||
lang string
|
||||
array []string
|
||||
)
|
||||
m.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
path = file[len(m.options.Path)+1:]
|
||||
array = strings.Split(path, gfile.Separator)
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if m.data[lang] == nil {
|
||||
m.data[lang] = make(map[string]string)
|
||||
}
|
||||
if j, err := gjson.LoadContent(gfile.GetBytes(file)); err == nil {
|
||||
for k, v := range j.ToMap() {
|
||||
m.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
} else {
|
||||
glog.Errorf("load i18n file '%s' failed: %v", file, err)
|
||||
}
|
||||
}
|
||||
// Monitor changes of i18n files for hot reload feature.
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
// Any changes of i18n files, clear the data.
|
||||
m.mu.Lock()
|
||||
m.data = nil
|
||||
m.mu.Unlock()
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,10 +121,10 @@ func Test_Instance(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(g.I18n().T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
// Default language is: en
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gi18n.Instance(gconv.String(gtime.TimestampNano()))
|
||||
t.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(m.T("{#hello}{#world}"), "HelloWorld")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package empty provides checks for empty variables.
|
||||
// Package empty provides functions for checking empty variables.
|
||||
package empty
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package intlog provides internal logging for GF development usage only.
|
||||
// Package intlog provides internal logging for GoFrame development usage only.
|
||||
package intlog
|
||||
|
||||
import (
|
||||
|
||||
@ -9,7 +9,7 @@ package mutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// Mutex is a sync.Mutex with a switch of concurrent safe feature.
|
||||
// Mutex is a sync.Mutex with a switch for concurrent safe feature.
|
||||
type Mutex struct {
|
||||
sync.Mutex
|
||||
safe bool
|
||||
|
||||
@ -9,7 +9,7 @@ package rwmutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// RWMutex is a sync.RWMutex with a switch of concurrent safe feature.
|
||||
// RWMutex is a sync.RWMutex with a switch for concurrent safe feature.
|
||||
// If its attribute *sync.RWMutex is not nil, it means it's in concurrent safety usage.
|
||||
// Its attribute *sync.RWMutex is nil in default, which makes this struct mush lightweight.
|
||||
type RWMutex struct {
|
||||
|
||||
@ -10,11 +10,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gres"
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -75,6 +76,19 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
|
||||
request.Middleware = &Middleware{
|
||||
request: 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")
|
||||
)
|
||||
//intlog.Print(agent, address, cookie)
|
||||
return guid.S([]byte(agent), []byte(address), []byte(cookie))
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
|
||||
@ -35,6 +35,10 @@ var (
|
||||
//
|
||||
// The parameter <pointer> can be type of: *struct/**struct/*[]struct/*[]*struct.
|
||||
//
|
||||
// It supports single and multiple struct convertion:
|
||||
// 1. Single struct, post content like: {"id":1, "name":"john"}
|
||||
// 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
|
||||
//
|
||||
// TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages.
|
||||
func (r *Request) Parse(pointer interface{}) error {
|
||||
var (
|
||||
@ -52,15 +56,20 @@ func (r *Request) Parse(pointer interface{}) error {
|
||||
reflectKind2 = reflectVal2.Kind()
|
||||
)
|
||||
switch reflectKind2 {
|
||||
// Single struct, post content like:
|
||||
// {"id":1, "name":"john"}
|
||||
case reflect.Ptr, reflect.Struct:
|
||||
// Struct conversion.
|
||||
// Conversion.
|
||||
if err := r.GetStruct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
// Struct validation.
|
||||
// Validation.
|
||||
if err := gvalid.CheckStruct(pointer, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Multiple struct, post content like:
|
||||
// [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
|
||||
case reflect.Array, reflect.Slice:
|
||||
// If struct slice conversion, it might post JSON/XML content,
|
||||
// so it uses gjson for the conversion.
|
||||
|
||||
@ -13,27 +13,29 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// COOKIE对象,非并发安全。
|
||||
// Cookie for HTTP COOKIE management.
|
||||
type Cookie struct {
|
||||
data map[string]CookieItem // 数据项
|
||||
path string // 默认的cookie path
|
||||
domain string // 默认的cookie domain
|
||||
maxage time.Duration // 默认的cookie maxage
|
||||
server *Server // 所属Server
|
||||
request *Request // 所属HTTP请求对象
|
||||
response *Response // 所属HTTP返回对象
|
||||
data map[string]CookieItem // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxage time.Duration // The default cookie maxage.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// cookie项
|
||||
// CookieItem is cookie item stored in Cookie management object.
|
||||
type CookieItem struct {
|
||||
value string
|
||||
domain string // 有效域名
|
||||
path string // 有效路径
|
||||
expireAt int64 // 过期时间
|
||||
value string // Cookie value.
|
||||
domain string // Cookie domain.
|
||||
path string // Cookie path.
|
||||
expireAt int64 // Cookie expiration timestamp.
|
||||
httpOnly bool
|
||||
}
|
||||
|
||||
// 获取或者创建一个COOKIE对象,与传入的请求对应(延迟初始化)
|
||||
// GetCookie creates or retrieves a cookie object with given request.
|
||||
// It retrieves and returns an existing cookie object if it already exists with given request.
|
||||
// It creates and returns a new cookie object if it does not exist with given request.
|
||||
func GetCookie(r *Request) *Cookie {
|
||||
if r.Cookie != nil {
|
||||
return r.Cookie
|
||||
@ -44,7 +46,7 @@ func GetCookie(r *Request) *Cookie {
|
||||
}
|
||||
}
|
||||
|
||||
// 从请求流中初始化,无锁,延迟初始化
|
||||
// init does lazy initialization for cookie object.
|
||||
func (c *Cookie) init() {
|
||||
if c.data == nil {
|
||||
c.data = make(map[string]CookieItem)
|
||||
@ -64,7 +66,7 @@ func (c *Cookie) init() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有的Cookie并构造成map[string]string返回.
|
||||
// Map returns the cookie items as map[string]string.
|
||||
func (c *Cookie) Map() map[string]string {
|
||||
c.init()
|
||||
m := make(map[string]string)
|
||||
@ -74,7 +76,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
// 判断Cookie中是否存在制定键名(并且没有过期)
|
||||
// Contains checks if given key exists and not expired in cookie.
|
||||
func (c *Cookie) Contains(key string) bool {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
@ -85,12 +87,14 @@ func (c *Cookie) Contains(key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 设置cookie,使用默认参数
|
||||
// Set sets cookie item with default domain, path and expiration age.
|
||||
func (c *Cookie) Set(key, value string) {
|
||||
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
|
||||
}
|
||||
|
||||
// 设置cookie,带详细cookie参数
|
||||
// SetCookie sets cookie item given given domain, path and expiration age.
|
||||
// The optional parameter <httpOnly> specifies if the cookie item is only available in HTTP,
|
||||
// which is usually empty.
|
||||
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) {
|
||||
c.init()
|
||||
isHttpOnly := false
|
||||
@ -102,17 +106,18 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration
|
||||
}
|
||||
}
|
||||
|
||||
// 获得客户端提交的SessionId
|
||||
// GetSessionId retrieves and returns the session id from cookie.
|
||||
func (c *Cookie) GetSessionId() string {
|
||||
return c.Get(c.server.GetSessionIdName())
|
||||
}
|
||||
|
||||
// 设置SessionId到Cookie中
|
||||
// SetSessionId sets session id in the cookie.
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
}
|
||||
|
||||
// 查询cookie
|
||||
// Get retrieves and returns the value with specified key.
|
||||
// It returns <def> if specified key does not exist and <def> is given.
|
||||
func (c *Cookie) Get(key string, def ...string) string {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
@ -126,24 +131,26 @@ func (c *Cookie) Get(key string, def ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 删除COOKIE,使用默认的domain&path
|
||||
// Remove deletes specified key and its value from cookie using default domain and path.
|
||||
// It actually tells the http client that the cookie is expired, do not send it to server next time.
|
||||
func (c *Cookie) Remove(key string) {
|
||||
c.SetCookie(key, "", c.domain, c.path, -86400)
|
||||
}
|
||||
|
||||
// 标记该cookie在对应的域名和路径失效
|
||||
// 删除cookie的重点是需要通知浏览器客户端cookie已过期
|
||||
// RemoveCookie deletes specified key and its value from cookie using given domain and path.
|
||||
// It actually tells the http client that the cookie is expired, do not send it to server next time.
|
||||
func (c *Cookie) RemoveCookie(key, domain, path string) {
|
||||
c.SetCookie(key, "", domain, path, -86400)
|
||||
}
|
||||
|
||||
// 输出到客户端
|
||||
// Output outputs the cookie items to client.
|
||||
func (c *Cookie) Output() {
|
||||
if len(c.data) == 0 {
|
||||
return
|
||||
}
|
||||
for k, v := range c.data {
|
||||
// 只有 expire != 0 的才是服务端在本次请求中设置的cookie
|
||||
// Cookie item matches expire != 0 means it is set in this request,
|
||||
// which should be outputted to client.
|
||||
if v.expireAt == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -10,13 +10,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 域名管理器对象
|
||||
// Domain is used for route register for domains.
|
||||
type Domain struct {
|
||||
server *Server // 所属Server
|
||||
domains map[string]struct{} // 多域名
|
||||
server *Server // Belonged server
|
||||
domains map[string]struct{} // Support multiple domains.
|
||||
}
|
||||
|
||||
// 生成一个域名对象, 参数 domains 支持给定多个域名。
|
||||
// Domain creates and returns a domain object for management for one or more domains.
|
||||
func (s *Server) Domain(domains string) *Domain {
|
||||
d := &Domain{
|
||||
server: s,
|
||||
|
||||
@ -19,19 +19,20 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// 优雅的Web Server对象封装
|
||||
// gracefulServer wraps the net/http.Server with graceful reload/restart feature.
|
||||
type gracefulServer struct {
|
||||
server *Server // Belonged server.
|
||||
fd uintptr // 热重启时传递的socket监听文件句柄
|
||||
address string // 监听地址信息
|
||||
httpServer *http.Server // 底层http.Server
|
||||
rawListener net.Listener // 原始listener
|
||||
listener net.Listener // 接口化封装的listener
|
||||
isHttps bool // 是否HTTPS
|
||||
status int // 当前Server状态(关闭/运行)
|
||||
fd uintptr // File descriptor for passing to child process when graceful reload.
|
||||
address string // Listening address like:":80", ":8080".
|
||||
httpServer *http.Server // Underlying http.Server.
|
||||
rawListener net.Listener // Underlying net.Listener.
|
||||
listener net.Listener // Wrapped net.Listener.
|
||||
isHttps bool // Is HTTPS.
|
||||
status int // Status of current server.
|
||||
}
|
||||
|
||||
// 创建一个优雅的Http Server
|
||||
// newGracefulServer creates and returns a graceful http server with given address.
|
||||
// The optional parameter <fd> specifies the file descriptor which is passed from parent server.
|
||||
func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer {
|
||||
// Change port to address like: 80 -> :80
|
||||
if gstr.IsNumeric(address) {
|
||||
@ -42,14 +43,13 @@ func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer {
|
||||
address: address,
|
||||
httpServer: s.newHttpServer(address),
|
||||
}
|
||||
// 是否有继承的文件描述符
|
||||
if len(fd) > 0 && fd[0] > 0 {
|
||||
gs.fd = uintptr(fd[0])
|
||||
}
|
||||
return gs
|
||||
}
|
||||
|
||||
// 生成一个底层的Web Server对象
|
||||
// newGracefulServer creates and returns a underlying http.Server with given address.
|
||||
func (s *Server) newHttpServer(address string) *http.Server {
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
@ -64,7 +64,7 @@ func (s *Server) newHttpServer(address string) *http.Server {
|
||||
return server
|
||||
}
|
||||
|
||||
// 执行HTTP监听
|
||||
// ListenAndServe starts listening on configured address.
|
||||
func (s *gracefulServer) ListenAndServe() error {
|
||||
ln, err := s.getNetListener()
|
||||
if err != nil {
|
||||
@ -75,7 +75,8 @@ func (s *gracefulServer) ListenAndServe() error {
|
||||
return s.doServe()
|
||||
}
|
||||
|
||||
// 获得文件描述符
|
||||
// Fd retrieves and returns the file descriptor of current server.
|
||||
// It is available ony in *nix like operation systems like: linux, unix, darwin.
|
||||
func (s *gracefulServer) Fd() uintptr {
|
||||
if s.rawListener != nil {
|
||||
file, err := s.rawListener.(*net.TCPListener).File()
|
||||
@ -86,12 +87,14 @@ func (s *gracefulServer) Fd() uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 设置自定义fd
|
||||
// setFd sets the file descriptor for current server.
|
||||
func (s *gracefulServer) setFd(fd int) {
|
||||
s.fd = uintptr(fd)
|
||||
}
|
||||
|
||||
// 执行HTTPS监听
|
||||
// ListenAndServeTLS starts listening on configured address with HTTPS.
|
||||
// The parameter <certFile> and <keyFile> specify the necessary certification and key files for HTTPS.
|
||||
// The optional parameter <tlsConfig> specifies the custom TLS configuration.
|
||||
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig ...*tls.Config) error {
|
||||
var config *tls.Config
|
||||
if len(tlsConfig) > 0 && tlsConfig[0] != nil {
|
||||
@ -122,7 +125,7 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig .
|
||||
return s.doServe()
|
||||
}
|
||||
|
||||
// 获取服务协议字符串
|
||||
// getProto retrieves and returns the proto string of current server.
|
||||
func (s *gracefulServer) getProto() string {
|
||||
proto := "http"
|
||||
if s.isHttps {
|
||||
@ -131,7 +134,7 @@ func (s *gracefulServer) getProto() string {
|
||||
return proto
|
||||
}
|
||||
|
||||
// 开始执行Web Server服务处理
|
||||
// doServe does staring the serving.
|
||||
func (s *gracefulServer) doServe() error {
|
||||
action := "started"
|
||||
if s.fd != 0 {
|
||||
@ -147,7 +150,7 @@ func (s *gracefulServer) doServe() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 自定义的net.Listener
|
||||
// getNetListener retrieves and returns the wrapped net.Listener.
|
||||
func (s *gracefulServer) getNetListener() (net.Listener, error) {
|
||||
var ln net.Listener
|
||||
var err error
|
||||
@ -167,7 +170,7 @@ func (s *gracefulServer) getNetListener() (net.Listener, error) {
|
||||
return ln, err
|
||||
}
|
||||
|
||||
// 执行请求优雅关闭
|
||||
// shutdown shuts down the server gracefully.
|
||||
func (s *gracefulServer) shutdown() {
|
||||
if s.status == SERVER_STATUS_STOPPED {
|
||||
return
|
||||
@ -180,7 +183,7 @@ func (s *gracefulServer) shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// 执行请求强制关闭
|
||||
// close shuts down the server forcibly.
|
||||
func (s *gracefulServer) close() {
|
||||
if s.status == SERVER_STATUS_STOPPED {
|
||||
return
|
||||
|
||||
@ -24,38 +24,42 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行
|
||||
// ServeHTTP is the default handler for http request.
|
||||
// It should not create new goroutine handling the request as
|
||||
// it's called by am already created new goroutine from http.Server.
|
||||
//
|
||||
// This function also make serve implementing the interface of http.Handler.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Max body size limit.
|
||||
if s.config.ClientMaxBodySize > 0 {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, s.config.ClientMaxBodySize)
|
||||
}
|
||||
// 重写规则判断
|
||||
|
||||
// Rewrite feature checks.
|
||||
if len(s.config.Rewrites) > 0 {
|
||||
if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok {
|
||||
r.URL.Path = rewrite
|
||||
}
|
||||
}
|
||||
|
||||
// 去掉末尾的"/"号
|
||||
// Remove char '/' in the tail of URI.
|
||||
if r.URL.Path != "/" {
|
||||
for len(r.URL.Path) > 0 && r.URL.Path[len(r.URL.Path)-1] == '/' {
|
||||
r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// URI默认值
|
||||
// Default URI value if it's empty.
|
||||
if r.URL.Path == "" {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
|
||||
// 创建请求处理对象
|
||||
// Create a new request object.
|
||||
request := newRequest(s, r, w)
|
||||
|
||||
defer func() {
|
||||
// 设置请求完成时间
|
||||
request.LeaveTime = gtime.TimestampMilli()
|
||||
// error log
|
||||
// error log handling.
|
||||
if request.error != nil {
|
||||
s.handleErrorLog(request.error, request)
|
||||
} else {
|
||||
@ -64,18 +68,20 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleErrorLog(gerror.Newf("%v", exception), request)
|
||||
}
|
||||
}
|
||||
// access log
|
||||
// access log handling.
|
||||
s.handleAccessLog(request)
|
||||
// 关闭当前Session,并更新会话超时时间
|
||||
// Close the session, which automatically update the TTL
|
||||
// of the session if it exists.
|
||||
request.Session.Close()
|
||||
}()
|
||||
|
||||
// ============================================================
|
||||
// 优先级控制:
|
||||
// 静态文件 > 动态服务 > 静态目录
|
||||
// Priority:
|
||||
// Static File > Dynamic Service > Static Directory
|
||||
// ============================================================
|
||||
|
||||
// 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理)
|
||||
// Search the static file with most high priority,
|
||||
// which also handle the index files feature.
|
||||
if s.config.FileServerEnabled {
|
||||
request.StaticFile = s.searchStaticFile(r.URL.Path)
|
||||
if request.StaticFile != nil {
|
||||
@ -83,29 +89,29 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 动态服务检索
|
||||
// Search the dynamic service handler.
|
||||
request.handlers, request.hasHookHandler, request.hasServeHandler = s.getHandlersWithCache(request)
|
||||
|
||||
// 判断最终对该请求提供的服务方式
|
||||
// Check the service type static or dynamic for current request.
|
||||
if request.StaticFile != nil && request.StaticFile.IsDir && request.hasServeHandler {
|
||||
request.isFileRequest = false
|
||||
}
|
||||
|
||||
// 事件 - BeforeServe
|
||||
// HOOK - BeforeServe
|
||||
s.callHookHandler(HOOK_BEFORE_SERVE, request)
|
||||
|
||||
// 执行静态文件服务/回调控制器/执行对象/方法
|
||||
// Core serving handling.
|
||||
if !request.IsExited() {
|
||||
if request.isFileRequest {
|
||||
// 静态服务
|
||||
// Static file service.
|
||||
s.serveFile(request, request.StaticFile)
|
||||
} else {
|
||||
if len(request.handlers) > 0 {
|
||||
// 动态服务
|
||||
// Dynamic service.
|
||||
request.Middleware.Next()
|
||||
} else {
|
||||
if request.StaticFile != nil && request.StaticFile.IsDir {
|
||||
// 静态目录
|
||||
// Serve the directory.
|
||||
s.serveFile(request, request.StaticFile)
|
||||
} else {
|
||||
if len(request.Response.Header()) == 0 &&
|
||||
@ -118,12 +124,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 事件 - AfterServe
|
||||
// HOOK - AfterServe
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_SERVE, request)
|
||||
}
|
||||
|
||||
// 事件 - BeforeOutput
|
||||
// HOOK - BeforeOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
|
||||
}
|
||||
@ -146,15 +152,16 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置Session Id到Cookie中
|
||||
// Automatically set the session id to cookie
|
||||
// if it creates a new session id in this request.
|
||||
if request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
|
||||
request.Cookie.SetSessionId(request.Session.Id())
|
||||
}
|
||||
// 输出Cookie
|
||||
// Output the cookie content to client.
|
||||
request.Cookie.Output()
|
||||
// 输出缓冲区
|
||||
// Output the buffer content to client.
|
||||
request.Response.Output()
|
||||
// 事件 - AfterOutput
|
||||
// HOOK - AfterOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
}
|
||||
@ -222,9 +229,10 @@ func (s *Server) searchStaticFile(uri string) *StaticFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
// http server静态文件处理,path可以为相对路径也可以为绝对路径
|
||||
// serveFile serves the static file for client.
|
||||
// The optional parameter <allowIndex> specifies if allowing directory listing if <f> is directory.
|
||||
func (s *Server) serveFile(r *Request, f *StaticFile, allowIndex ...bool) {
|
||||
// 使用资源文件
|
||||
// Use resource file from memory.
|
||||
if f.File != nil {
|
||||
if f.IsDir {
|
||||
if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
|
||||
@ -239,7 +247,7 @@ func (s *Server) serveFile(r *Request, f *StaticFile, allowIndex ...bool) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// 使用磁盘文件
|
||||
// Use file from dist.
|
||||
file, err := os.Open(f.Path)
|
||||
if err != nil {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
@ -264,14 +272,23 @@ func (s *Server) serveFile(r *Request, f *StaticFile, allowIndex ...bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示目录列表
|
||||
// listDir lists the sub files of specified directory as HTML content to client.
|
||||
func (s *Server) listDir(r *Request, f http.File) {
|
||||
files, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
r.Response.WriteStatus(http.StatusInternalServerError, "Error reading directory")
|
||||
return
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
|
||||
// The folder type has the most priority than file.
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
if files[i].IsDir() && !files[j].IsDir() {
|
||||
return true
|
||||
}
|
||||
if !files[i].IsDir() && files[j].IsDir() {
|
||||
return false
|
||||
}
|
||||
return files[i].Name() < files[j].Name()
|
||||
})
|
||||
if r.Response.Header().Get("Content-Type") == "" {
|
||||
r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ func (s *Server) Logger() *glog.Logger {
|
||||
return s.config.Logger
|
||||
}
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
// handleAccessLog handles the access logging for server.
|
||||
func (s *Server) handleAccessLog(r *Request) {
|
||||
if !s.IsAccessLogEnabled() {
|
||||
return
|
||||
@ -37,14 +37,13 @@ func (s *Server) handleAccessLog(r *Request) {
|
||||
)
|
||||
}
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
// handleErrorLog handles the error logging for server.
|
||||
func (s *Server) handleErrorLog(err error, r *Request) {
|
||||
// 错误输出默认是开启的
|
||||
// It does nothing if error logging is custom disabled.
|
||||
if !s.IsErrorLogEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// 错误日志信息
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 绑定指定的hook回调函数, pattern参数同BindHandler,支持命名路由;hook参数的值由ghttp server设定,参数不区分大小写
|
||||
// BindHookHandler registers handler for specified hook.
|
||||
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) {
|
||||
s.doBindHookHandler(pattern, hook, handler, "")
|
||||
}
|
||||
@ -26,23 +26,22 @@ func (s *Server) doBindHookHandler(pattern string, hook string, handler HandlerF
|
||||
})
|
||||
}
|
||||
|
||||
// 通过map批量绑定回调函数
|
||||
func (s *Server) BindHookHandlerByMap(pattern string, hookMap map[string]HandlerFunc) {
|
||||
for k, v := range hookMap {
|
||||
s.BindHookHandler(pattern, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 事件回调处理,内部使用了缓存处理.
|
||||
// 并按照指定hook回调函数的优先级及注册顺序进行调用
|
||||
// callHookHandler calls the hook handler by their registered sequences.
|
||||
func (s *Server) callHookHandler(hook string, r *Request) {
|
||||
hookItems := r.getHookHandlers(hook)
|
||||
if len(hookItems) > 0 {
|
||||
// 备份原有的router变量
|
||||
// Backup the old router variable map.
|
||||
oldRouterMap := r.routerMap
|
||||
for _, item := range hookItems {
|
||||
r.routerMap = item.values
|
||||
// 不使用hook的router对象,保留路由注册服务的router对象,不能覆盖
|
||||
// DO NOT USE the router of the hook handler,
|
||||
// which can overwrite the router of serving handler.
|
||||
// r.Router = item.handler.router
|
||||
if err := s.niceCallHookHandler(item.handler.itemFunc, r); err != nil {
|
||||
switch err {
|
||||
@ -58,12 +57,12 @@ func (s *Server) callHookHandler(hook string, r *Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 恢复原有的router变量
|
||||
// Restore the old router variable map.
|
||||
r.routerMap = oldRouterMap
|
||||
}
|
||||
}
|
||||
|
||||
// 获得当前请求,指定类型的的钩子函数列表
|
||||
// getHookHandlers retrieves and returns the hook handlers of specified hook.
|
||||
func (r *Request) getHookHandlers(hook string) []*handlerParsedItem {
|
||||
if !r.hasHookHandler {
|
||||
return nil
|
||||
@ -79,7 +78,9 @@ func (r *Request) getHookHandlers(hook string) []*handlerParsedItem {
|
||||
return parsedItems
|
||||
}
|
||||
|
||||
// 友好地调用方法
|
||||
// niceCallHookHandler nicely calls the hook handler function,
|
||||
// which means it automatically catches and returns the possible panic error to
|
||||
// avoid goroutine crash.
|
||||
func (s *Server) niceCallHookHandler(f HandlerFunc, r *Request) (err interface{}) {
|
||||
defer func() {
|
||||
err = recover()
|
||||
|
||||
@ -16,9 +16,13 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// 绑定控制器,控制器需要实现 gmvc.Controller 接口,
|
||||
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话,
|
||||
// 第三个参数methods用以指定需要注册的方法,支持多个方法名称,多个方法以英文“,”号分隔,区分大小写.
|
||||
// BindController registers controller to server routes with specified pattern. The controller
|
||||
// needs to implement the gmvc.Controller interface. Each request of the controller bound in
|
||||
// this way will initialize a new controller object for processing, corresponding to different
|
||||
// request sessions.
|
||||
//
|
||||
// The optional parameter <method> is used to specify the method to be registered, which
|
||||
// supports multiple method names, multiple methods are separated by char ',', case sensitive.
|
||||
func (s *Server) BindController(pattern string, controller Controller, method ...string) {
|
||||
bindMethod := ""
|
||||
if len(method) > 0 {
|
||||
@ -27,15 +31,23 @@ func (s *Server) BindController(pattern string, controller Controller, method ..
|
||||
s.doBindController(pattern, controller, bindMethod, nil, "")
|
||||
}
|
||||
|
||||
// 绑定路由到指定的方法执行, 第三个参数method仅支持一个方法注册,不支持多个,并且区分大小写。
|
||||
// BindControllerMethod registers specified method to server routes with specified pattern.
|
||||
//
|
||||
// The optional parameter <method> is used to specify the method to be registered, which
|
||||
// does not supports multiple method names but only one, case sensitive.
|
||||
func (s *Server) BindControllerMethod(pattern string, controller Controller, method string) {
|
||||
s.doBindControllerMethod(pattern, controller, method, nil, "")
|
||||
}
|
||||
|
||||
// 绑定控制器(RESTFul),控制器需要实现gmvc.Controller接口
|
||||
// 方法会识别HTTP方法,并做REST绑定处理,例如:Post方法会绑定到HTTP POST的方法请求处理,Delete方法会绑定到HTTP DELETE的方法请求处理
|
||||
// 因此只会绑定HTTP Method对应的方法,其他方法不会自动注册绑定
|
||||
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
|
||||
// BindControllerRest registers controller in REST API style to server with specified pattern.
|
||||
// The controller needs to implement the gmvc.Controller interface. Each request of the controller
|
||||
// bound in this way will initialize a new controller object for processing, corresponding to
|
||||
// different request sessions.
|
||||
// The method will recognize the HTTP method and do REST binding, for example:
|
||||
// The method "Post" of controller will be bound to the HTTP POST method request processing,
|
||||
// and the method "Delete" will be bound to the HTTP DELETE method request processing.
|
||||
// Therefore, only the method corresponding to the HTTP Method will be bound, other methods will
|
||||
// not automatically register the binding.
|
||||
func (s *Server) BindControllerRest(pattern string, controller Controller) {
|
||||
s.doBindControllerRest(pattern, controller, nil, "")
|
||||
}
|
||||
@ -52,7 +64,6 @@ func (s *Server) doBindController(
|
||||
methodMap[strings.TrimSpace(v)] = true
|
||||
}
|
||||
}
|
||||
// 当pattern中的method为all时,去掉该method,以便于后续方法判断
|
||||
domain, method, path, err := s.parsePattern(pattern)
|
||||
if err != nil {
|
||||
s.Logger().Fatal(err)
|
||||
@ -61,7 +72,7 @@ func (s *Server) doBindController(
|
||||
if strings.EqualFold(method, gDEFAULT_METHOD) {
|
||||
pattern = s.serveHandlerKey("", path, domain)
|
||||
}
|
||||
// 遍历控制器,获取方法列表,并构造成uri
|
||||
// Retrieve a list of methods, create construct corresponding URI.
|
||||
m := make(map[string]*handlerItem)
|
||||
v := reflect.ValueOf(controller)
|
||||
t := v.Type()
|
||||
@ -82,13 +93,17 @@ func (s *Server) doBindController(
|
||||
}
|
||||
if _, ok := v.Method(i).Interface().(func()); !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
s.Logger().Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, methodName, v.Method(i).Type().String())
|
||||
// If registering with specified method, print error.
|
||||
s.Logger().Errorf(
|
||||
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
|
||||
)
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
s.Logger().Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func()" for controller registry`,
|
||||
pkgPath, ctlName, methodName, v.Method(i).Type().String())
|
||||
// Else, just print debug information.
|
||||
s.Logger().Debugf(
|
||||
`ignore route method: %s.%s.%s defined as "%s", no match "func()" for controller registry`,
|
||||
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -103,10 +118,13 @@ func (s *Server) doBindController(
|
||||
middleware: middleware,
|
||||
source: source,
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI,
|
||||
// 例如: pattern为/user, 那么会同时注册/user及/user/index,
|
||||
// 这里处理新增/user路由绑定。
|
||||
// 注意,当pattern带有内置变量时,不会自动加该路由。
|
||||
// If there's "Index" method, then an additional route is automatically added
|
||||
// to match the main URI, for example:
|
||||
// If pattern is "/user", then "/user" and "/user/index" are both automatically
|
||||
// registered.
|
||||
//
|
||||
// Note that if there's built-in variables in pattern, this route will not be added
|
||||
// automatically.
|
||||
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
|
||||
p := gstr.PosRI(key, "/index")
|
||||
k := key[0:p] + key[p+6:]
|
||||
@ -176,13 +194,11 @@ func (s *Server) doBindControllerRest(
|
||||
pattern string, controller Controller,
|
||||
middleware []HandlerFunc, source string,
|
||||
) {
|
||||
// 遍历控制器,获取方法列表,并构造成uri
|
||||
m := make(map[string]*handlerItem)
|
||||
v := reflect.ValueOf(controller)
|
||||
t := v.Type()
|
||||
pkgPath := t.Elem().PkgPath()
|
||||
structName := t.Elem().Name()
|
||||
// 如果存在与HttpMethod对应名字的方法,那么绑定这些方法
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
methodName := t.Method(i).Name
|
||||
if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
|
||||
|
||||
@ -14,14 +14,14 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
|
||||
// BindHandler registers a handler function to server with given pattern.
|
||||
func (s *Server) BindHandler(pattern string, handler HandlerFunc) {
|
||||
s.doBindHandler(pattern, handler, nil, "")
|
||||
}
|
||||
|
||||
// 绑定URI到操作函数/方法
|
||||
// pattern的格式形如:/user/list, put:/user, delete:/user, post:/user@johng.cn
|
||||
// 支持RESTful的请求格式,具体业务逻辑由绑定的处理方法来执行
|
||||
// doBindHandler registers a handler function to server with given pattern.
|
||||
// The parameter <pattern> is like:
|
||||
// /user/list, put:/user, delete:/user, post:/user@goframe.org
|
||||
func (s *Server) doBindHandler(
|
||||
pattern string, handler HandlerFunc,
|
||||
middleware []HandlerFunc, source string,
|
||||
@ -35,17 +35,20 @@ func (s *Server) doBindHandler(
|
||||
})
|
||||
}
|
||||
|
||||
// 通过映射map绑定URI到操作函数/方法
|
||||
// bindHandlerByMap registers handlers to server using map.
|
||||
func (s *Server) bindHandlerByMap(m map[string]*handlerItem) {
|
||||
for p, h := range m {
|
||||
s.setHandler(p, h)
|
||||
}
|
||||
}
|
||||
|
||||
// 将内置的名称按照设定的规则合并到pattern中,内置名称按照{.xxx}规则命名。
|
||||
// 规则1:pattern中的URI包含{.struct}关键字,则替换该关键字为结构体名称;
|
||||
// 规则2:pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
|
||||
// 规则2:如果不满足规则1,那么直接将防发明附加到pattern中的URI后面;
|
||||
// mergeBuildInNameToPattern merges build-in names into the pattern according to the following
|
||||
// rules, and the built-in names are named like "{.xxx}".
|
||||
// Rule 1: The URI in pattern contains the {.struct} keyword, it then replaces the keyword with the struct name;
|
||||
// Rule 2: The URI in pattern contains the {.method} keyword, it then replaces the keyword with the method name;
|
||||
// Rule 2: If Rule 1 is not met, it then adds the method name directly to the URI in the pattern;
|
||||
//
|
||||
// The parameter <allowAppend> specifies whether allowing appending method name to the tail of pattern.
|
||||
func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string, allowAppend bool) string {
|
||||
structName = s.nameToUri(structName)
|
||||
methodName = s.nameToUri(methodName)
|
||||
@ -53,27 +56,25 @@ func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodNam
|
||||
if strings.Index(pattern, "{.method}") != -1 {
|
||||
return strings.Replace(pattern, "{.method}", methodName, -1)
|
||||
}
|
||||
// 不允许将方法名称append到路由末尾
|
||||
if !allowAppend {
|
||||
return pattern
|
||||
}
|
||||
// 检测域名后缀
|
||||
// Check domain parameter.
|
||||
array := strings.Split(pattern, "@")
|
||||
// 分离URI(其实可能包含HTTP Method)
|
||||
uri := array[0]
|
||||
uri = strings.TrimRight(uri, "/") + "/" + methodName
|
||||
// 加上指定域名后缀
|
||||
// Append the domain parameter to URI.
|
||||
if len(array) > 1 {
|
||||
return uri + "@" + array[1]
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
// 将给定的名称转换为URL规范格式。
|
||||
// 规则0: 全部转换为小写,方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词;
|
||||
// 规则1: 不处理名称,以原有名称构建成URI
|
||||
// 规则2: 仅转为小写,单词间不使用连接符号
|
||||
// 规则3: 采用驼峰命名方式
|
||||
// nameToUri converts the given name to URL format using following rules:
|
||||
// Rule 0: Convert all method names to lowercase, add char '-' between words.
|
||||
// Rule 1: Do not convert the method name, construct the URI with the original method name.
|
||||
// Rule 2: Convert all method names to lowercase, no connecting symbols between words.
|
||||
// Rule 3: Use camel case naming.
|
||||
func (s *Server) nameToUri(name string) string {
|
||||
switch s.config.NameToUriType {
|
||||
case URI_TYPE_FULLNAME:
|
||||
|
||||
@ -16,8 +16,12 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面
|
||||
// 第三个参数methods用以指定需要注册的方法,支持多个方法名称,多个方法以英文“,”号分隔,区分大小写
|
||||
// BindObject registers object to server routes with given pattern.
|
||||
//
|
||||
// The optional parameter <method> is used to specify the method to be registered, which
|
||||
// supports multiple method names, multiple methods are separated by char ',', case sensitive.
|
||||
//
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObject(pattern string, object interface{}, method ...string) {
|
||||
bindMethod := ""
|
||||
if len(method) > 0 {
|
||||
@ -26,14 +30,18 @@ func (s *Server) BindObject(pattern string, object interface{}, method ...string
|
||||
s.doBindObject(pattern, object, bindMethod, nil, "")
|
||||
}
|
||||
|
||||
// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面,
|
||||
// 第三个参数method仅支持一个方法注册,不支持多个,并且区分大小写。
|
||||
// BindObjectMethod registers specified method of object to server routes with given pattern.
|
||||
//
|
||||
// The optional parameter <method> is used to specify the method to be registered, which
|
||||
// does not supports multiple method names but only one, case sensitive.
|
||||
//
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) {
|
||||
s.doBindObjectMethod(pattern, object, method, nil, "")
|
||||
}
|
||||
|
||||
// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面,
|
||||
// 需要注意对象方法的定义必须按照 ghttp.HandlerFunc 来定义
|
||||
// BindObjectRest registers object in REST API style to server with specified pattern.
|
||||
// Note that the route method should be defined as ghttp.HandlerFunc.
|
||||
func (s *Server) BindObjectRest(pattern string, object interface{}) {
|
||||
s.doBindObjectRest(pattern, object, nil, "")
|
||||
}
|
||||
@ -88,13 +96,11 @@ func (s *Server) doBindObject(
|
||||
itemFunc, ok := v.Method(i).Interface().(func(*Request))
|
||||
if !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
s.Logger().Errorf(
|
||||
`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, methodName, v.Method(i).Type().String(),
|
||||
)
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
s.Logger().Debugf(
|
||||
`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)" for object registry`,
|
||||
pkgPath, objName, methodName, v.Method(i).Type().String(),
|
||||
@ -112,8 +118,13 @@ func (s *Server) doBindObject(
|
||||
middleware: middleware,
|
||||
source: source,
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI。
|
||||
// 注意,当pattern带有内置变量时,不会自动加该路由。
|
||||
// If there's "Index" method, then an additional route is automatically added
|
||||
// to match the main URI, for example:
|
||||
// If pattern is "/user", then "/user" and "/user/index" are both automatically
|
||||
// registered.
|
||||
//
|
||||
// Note that if there's built-in variables in pattern, this route will not be added
|
||||
// automatically.
|
||||
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
|
||||
p := gstr.PosRI(key, "/index")
|
||||
k := key[0:p] + key[p+6:]
|
||||
@ -134,8 +145,6 @@ func (s *Server) doBindObject(
|
||||
s.bindHandlerByMap(m)
|
||||
}
|
||||
|
||||
// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面,
|
||||
// 第三个参数method仅支持一个方法注册,不支持多个,并且区分大小写。
|
||||
func (s *Server) doBindObjectMethod(
|
||||
pattern string, object interface{}, method string,
|
||||
middleware []HandlerFunc, source string,
|
||||
@ -166,8 +175,10 @@ func (s *Server) doBindObjectMethod(
|
||||
}
|
||||
itemFunc, ok := methodValue.Interface().(func(*Request))
|
||||
if !ok {
|
||||
s.Logger().Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, methodName, methodValue.Type().String())
|
||||
s.Logger().Errorf(
|
||||
`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, methodName, methodValue.Type().String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
|
||||
@ -213,8 +224,10 @@ func (s *Server) doBindObjectRest(
|
||||
}
|
||||
itemFunc, ok := v.Method(i).Interface().(func(*Request))
|
||||
if !ok {
|
||||
s.Logger().Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, methodName, v.Method(i).Type().String())
|
||||
s.Logger().Errorf(
|
||||
`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, methodName, v.Method(i).Type().String(),
|
||||
)
|
||||
continue
|
||||
}
|
||||
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
|
||||
|
||||
@ -8,4 +8,6 @@ package ghttp
|
||||
|
||||
import "github.com/gogf/gf/os/gsession"
|
||||
|
||||
// Session is actually a alias of gsession.Session,
|
||||
// which is bound to a single request.
|
||||
type Session = gsession.Session
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// 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
|
||||
|
||||
@ -11,7 +10,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// 查询状态码回调函数
|
||||
// getStatusHandler retrieves and returns the handler for given status code.
|
||||
func (s *Server) getStatusHandler(status int, r *Request) HandlerFunc {
|
||||
domains := []string{r.GetHost(), gDEFAULT_DOMAIN}
|
||||
for _, domain := range domains {
|
||||
@ -22,23 +21,23 @@ func (s *Server) getStatusHandler(status int, r *Request) HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 不同状态码下的回调方法处理
|
||||
// pattern格式:domain#status
|
||||
// setStatusHandler sets the handler for given status code.
|
||||
// The parameter <pattern> is like: domain#status
|
||||
func (s *Server) setStatusHandler(pattern string, handler HandlerFunc) {
|
||||
s.statusHandlerMap[pattern] = handler
|
||||
}
|
||||
|
||||
// 生成状态码回调函数map存储键名
|
||||
// statusHandlerKey creates and returns key for given status and domain.
|
||||
func (s *Server) statusHandlerKey(status int, domain string) string {
|
||||
return fmt.Sprintf("%s#%d", domain, status)
|
||||
}
|
||||
|
||||
// 绑定指定的状态码回调函数
|
||||
// BindStatusHandler registers handler for given status code.
|
||||
func (s *Server) BindStatusHandler(status int, handler HandlerFunc) {
|
||||
s.setStatusHandler(s.statusHandlerKey(status, gDEFAULT_DOMAIN), handler)
|
||||
}
|
||||
|
||||
// 通过map批量绑定状态码回调函数
|
||||
// BindStatusHandlerByMap registers handler for given status code using map.
|
||||
func (s *Server) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) {
|
||||
for k, v := range handlerMap {
|
||||
s.BindStatusHandler(k, v)
|
||||
|
||||
@ -8,6 +8,8 @@ package ghttp
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
|
||||
// WebSocket wraps the underlying websocket connection
|
||||
// and provides convenient functions.
|
||||
type WebSocket struct {
|
||||
*websocket.Conn
|
||||
}
|
||||
|
||||
@ -81,8 +81,8 @@ func Test_Params_Struct(t *testing.T) {
|
||||
t.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
t.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
t.Assert(client.PostContent("/struct2", ``), ``)
|
||||
t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
|
||||
t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `字段长度为2到20个字符; 密码强度不足`)
|
||||
t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The value length must be between 2 and 20; 密码强度不足`)
|
||||
t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The value length must be between 2 and 20; 密码强度不足`)
|
||||
t.Assert(client.GetContent("/parse", `id=1&name=john&password1=123&password2=456`), `密码强度不足`)
|
||||
t.Assert(client.GetContent("/parse", `id=1&name=john&password1=123Abc!@#&password2=123Abc!@#`), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
|
||||
@ -10,58 +10,12 @@ package gipv4
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
// Validate checks whether given <ip> a valid IPv4 address.
|
||||
func Validate(ip string) bool {
|
||||
return gregex.IsMatchString(`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`, ip)
|
||||
}
|
||||
|
||||
// GetHostByName returns the IPv4 address corresponding to a given Internet host name.
|
||||
func GetHostByName(hostname string) (string, error) {
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
return v.String(), nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// GetHostsByName returns a list of IPv4 addresses corresponding to a given Internet host name.
|
||||
func GetHostsByName(hostname string) ([]string, error) {
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
var ipStrings []string
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
ipStrings = append(ipStrings, v.String())
|
||||
}
|
||||
}
|
||||
return ipStrings, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetNameByAddr returns the Internet host name corresponding to a given IP address.
|
||||
func GetNameByAddr(ipAddress string) (string, error) {
|
||||
names, err := net.LookupAddr(ipAddress)
|
||||
if names != nil {
|
||||
return strings.TrimRight(names[0], "."), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Ip2long converts ip address to an uint32 integer.
|
||||
func Ip2long(ip string) uint32 {
|
||||
netIp := net.ParseIP(ip)
|
||||
@ -78,14 +32,9 @@ func Long2ip(long uint32) string {
|
||||
return net.IP(ipByte).String()
|
||||
}
|
||||
|
||||
// GetSegment returns the segment of given ip address.
|
||||
// Eg: 192.168.2.102 -> 192.168.2
|
||||
func GetSegment(ip string) string {
|
||||
match, err := gregex.MatchString(`^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$`, ip)
|
||||
if err != nil || len(match) < 4 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s.%s.%s", match[1], match[2], match[3])
|
||||
// Validate checks whether given <ip> a valid IPv4 address.
|
||||
func Validate(ip string) bool {
|
||||
return gregex.IsMatchString(`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`, ip)
|
||||
}
|
||||
|
||||
// ParseAddress parses <address> to its ip and port.
|
||||
@ -99,99 +48,12 @@ func ParseAddress(address string) (string, int) {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// IntranetIP returns the first intranet ip of current machine.
|
||||
func IntranetIP() (ip string, err error) {
|
||||
ips, err := IntranetIPArray()
|
||||
if err != nil {
|
||||
return "", err
|
||||
// GetSegment returns the segment of given ip address.
|
||||
// Eg: 192.168.2.102 -> 192.168.2
|
||||
func GetSegment(ip string) string {
|
||||
match, err := gregex.MatchString(`^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$`, ip)
|
||||
if err != nil || len(match) < 4 {
|
||||
return ""
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return "", errors.New("no intranet ip found")
|
||||
}
|
||||
return ips[0], nil
|
||||
}
|
||||
|
||||
// IntranetIPArray returns the intranet ip list of current machine.
|
||||
func IntranetIPArray() (ips []string, err error) {
|
||||
ips = make([]string, 0)
|
||||
ifaces, e := net.Interfaces()
|
||||
if e != nil {
|
||||
return ips, e
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
// interface down
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
// loopback interface
|
||||
continue
|
||||
}
|
||||
// ignore warden bridge
|
||||
if strings.HasPrefix(iface.Name, "w-") {
|
||||
continue
|
||||
}
|
||||
addresses, e := iface.Addrs()
|
||||
if e != nil {
|
||||
return ips, e
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
// not an ipv4 address
|
||||
continue
|
||||
}
|
||||
ipStr := ip.String()
|
||||
if IsIntranet(ipStr) {
|
||||
ips = append(ips, ipStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// IsIntranet checks and returns whether given ip an intranet ip.
|
||||
//
|
||||
// Local: 127.0.0.1
|
||||
// A: 10.0.0.0--10.255.255.255
|
||||
// B: 172.16.0.0--172.31.255.255
|
||||
// C: 192.168.0.0--192.168.255.255
|
||||
func IsIntranet(ip string) bool {
|
||||
if ip == "127.0.0.1" {
|
||||
return true
|
||||
}
|
||||
array := strings.Split(ip, ".")
|
||||
if len(array) != 4 {
|
||||
return false
|
||||
}
|
||||
// A
|
||||
if array[0] == "10" || (array[0] == "192" && array[1] == "168") {
|
||||
return true
|
||||
}
|
||||
// C
|
||||
if array[0] == "192" && array[1] == "168" {
|
||||
return true
|
||||
}
|
||||
// B
|
||||
if array[0] == "172" {
|
||||
second, err := strconv.ParseInt(array[1], 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if second >= 16 && second <= 31 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return fmt.Sprintf("%s.%s.%s", match[1], match[2], match[3])
|
||||
}
|
||||
|
||||
128
net/gipv4/gipv4_ip.go
Normal file
128
net/gipv4/gipv4_ip.go
Normal file
@ -0,0 +1,128 @@
|
||||
// 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 gipv4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetIpArray retrieves and returns all the ip of current host.
|
||||
func GetIpArray() (ips []string, err error) {
|
||||
interfaceAddr, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, address := range interfaceAddr {
|
||||
ipNet, isValidIpNet := address.(*net.IPNet)
|
||||
if isValidIpNet && !ipNet.IP.IsLoopback() {
|
||||
if ipNet.IP.To4() != nil {
|
||||
ips = append(ips, ipNet.IP.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// GetIntranetIp retrieves and returns the first intranet ip of current machine.
|
||||
func GetIntranetIp() (ip string, err error) {
|
||||
ips, err := GetIntranetIpArray()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return "", errors.New("no intranet ip found")
|
||||
}
|
||||
return ips[0], nil
|
||||
}
|
||||
|
||||
// GetIntranetIpArray retrieves and returns the intranet ip list of current machine.
|
||||
func GetIntranetIpArray() (ips []string, err error) {
|
||||
interFaces, e := net.Interfaces()
|
||||
if e != nil {
|
||||
return ips, e
|
||||
}
|
||||
for _, interFace := range interFaces {
|
||||
if interFace.Flags&net.FlagUp == 0 {
|
||||
// interface down
|
||||
continue
|
||||
}
|
||||
if interFace.Flags&net.FlagLoopback != 0 {
|
||||
// loopback interface
|
||||
continue
|
||||
}
|
||||
// ignore warden bridge
|
||||
if strings.HasPrefix(interFace.Name, "w-") {
|
||||
continue
|
||||
}
|
||||
addresses, e := interFace.Addrs()
|
||||
if e != nil {
|
||||
return ips, e
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
// not an ipv4 address
|
||||
continue
|
||||
}
|
||||
ipStr := ip.String()
|
||||
if IsIntranet(ipStr) {
|
||||
ips = append(ips, ipStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// IsIntranet checks and returns whether given ip an intranet ip.
|
||||
//
|
||||
// Local: 127.0.0.1
|
||||
// A: 10.0.0.0--10.255.255.255
|
||||
// B: 172.16.0.0--172.31.255.255
|
||||
// C: 192.168.0.0--192.168.255.255
|
||||
func IsIntranet(ip string) bool {
|
||||
if ip == "127.0.0.1" {
|
||||
return true
|
||||
}
|
||||
array := strings.Split(ip, ".")
|
||||
if len(array) != 4 {
|
||||
return false
|
||||
}
|
||||
// A
|
||||
if array[0] == "10" || (array[0] == "192" && array[1] == "168") {
|
||||
return true
|
||||
}
|
||||
// C
|
||||
if array[0] == "192" && array[1] == "168" {
|
||||
return true
|
||||
}
|
||||
// B
|
||||
if array[0] == "172" {
|
||||
second, err := strconv.ParseInt(array[1], 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if second >= 16 && second <= 31 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
52
net/gipv4/gipv4_lookup.go
Normal file
52
net/gipv4/gipv4_lookup.go
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 gipv4
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetHostByName returns the IPv4 address corresponding to a given Internet host name.
|
||||
func GetHostByName(hostname string) (string, error) {
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
return v.String(), nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// GetHostsByName returns a list of IPv4 addresses corresponding to a given Internet
|
||||
// host name.
|
||||
func GetHostsByName(hostname string) ([]string, error) {
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
var ipStrings []string
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
ipStrings = append(ipStrings, v.String())
|
||||
}
|
||||
}
|
||||
return ipStrings, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetNameByAddr returns the Internet host name corresponding to a given IP address.
|
||||
func GetNameByAddr(ipAddress string) (string, error) {
|
||||
names, err := net.LookupAddr(ipAddress)
|
||||
if names != nil {
|
||||
return strings.TrimRight(names[0], "."), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
40
net/gipv4/gipv4_mac.go
Normal file
40
net/gipv4/gipv4_mac.go
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 gipv4
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// GetMac retrieves and returns the first mac address of current host.
|
||||
func GetMac() (mac string, err error) {
|
||||
macs, err := GetMacArray()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(macs) > 0 {
|
||||
return macs[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetMacArray retrieves and returns all the mac address of current host.
|
||||
func GetMacArray() (macs []string, err error) {
|
||||
netInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, netInterface := range netInterfaces {
|
||||
macAddr := netInterface.HardwareAddr.String()
|
||||
if len(macAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
macs = append(macs, macAddr)
|
||||
}
|
||||
return macs, nil
|
||||
}
|
||||
@ -7,7 +7,10 @@
|
||||
// Package gcache provides high performance and concurrent-safe in-memory cache for process.
|
||||
package gcache
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
var cache = New()
|
||||
@ -37,6 +40,11 @@ func Get(key interface{}) interface{} {
|
||||
return cache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as *gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
return cache.GetVar(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>,
|
||||
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
|
||||
// The key-value pair expires after <duration>.
|
||||
@ -67,12 +75,14 @@ func Contains(key interface{}) bool {
|
||||
return cache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the <key> in the cache, and returns its value.
|
||||
func Remove(key interface{}) interface{} {
|
||||
return cache.Remove(key)
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
return cache.Remove(keys...)
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func Removes(keys []interface{}) {
|
||||
cache.Removes(keys)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
@ -222,6 +223,11 @@ func (c *memCache) Get(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as *gvar.Var.
|
||||
func (c *memCache) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(c.Get(key))
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>, or sets <key>-<value> pair and returns <value> if <key>
|
||||
// does not exist in the cache. The key-value pair expires after <duration>. It does not expire
|
||||
// if <duration> == 0.
|
||||
@ -268,29 +274,29 @@ func (c *memCache) Contains(key interface{}) bool {
|
||||
return c.Get(key) != nil
|
||||
}
|
||||
|
||||
// Remove deletes the <key> in the cache, and returns its value.
|
||||
func (c *memCache) Remove(key interface{}) (value interface{}) {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok {
|
||||
value = item.v
|
||||
c.dataMu.Lock()
|
||||
delete(c.data, key)
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func (c *memCache) Remove(keys ...interface{}) (value interface{}) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
for _, key := range keys {
|
||||
item, ok := c.data[key]
|
||||
if ok {
|
||||
value = item.v
|
||||
delete(c.data, key)
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func (c *memCache) Removes(keys []interface{}) {
|
||||
for _, key := range keys {
|
||||
c.Remove(key)
|
||||
}
|
||||
c.Remove(keys...)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
@ -431,7 +437,7 @@ func (c *memCache) syncEventAndClearExpired() {
|
||||
}
|
||||
|
||||
// clearByKey deletes the key-value pair with given <key>.
|
||||
// The parameter <force> specifies whether doing this deleting forcedly.
|
||||
// The parameter <force> specifies whether doing this deleting forcibly.
|
||||
func (c *memCache) clearByKey(key interface{}, force ...bool) {
|
||||
c.dataMu.Lock()
|
||||
// Doubly check before really deleting it from cache.
|
||||
|
||||
@ -19,23 +19,36 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
//clear 用于清除全局缓存,因gcache api 暂未暴露 Clear 方法
|
||||
//暂定所有测试用例key的集合为1,2,3,避免不同测试用例间因全局cache共享带来的问题,每个测试用例在测试gcache.XXX之前,先调用clear()
|
||||
func clear() {
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
func TestCache_GCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gcache.Set(1, 11, 0)
|
||||
defer gcache.Removes(g.Slice{1, 2, 3})
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
t.Assert(cache.Contains(1), true)
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
})
|
||||
}
|
||||
|
||||
clear()
|
||||
gcache.Set(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
func TestCache_GetVar(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
t.Assert(c.GetVar(1).Int(), 11)
|
||||
t.Assert(c.GetVar(2).Int(), 0)
|
||||
t.Assert(c.GetVar(2).IsNil(), true)
|
||||
t.Assert(c.GetVar(2).IsEmpty(), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -108,7 +121,7 @@ func TestCache_SetIfNotExist(t *testing.T) {
|
||||
cache.SetIfNotExist(2, 22, 0)
|
||||
t.Assert(cache.Get(2), 22)
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.SetIfNotExist(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.SetIfNotExist(1, 22, 0)
|
||||
@ -122,7 +135,7 @@ func TestCache_Sets(t *testing.T) {
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
})
|
||||
@ -136,7 +149,7 @@ func TestCache_GetOrSet(t *testing.T) {
|
||||
cache.GetOrSet(1, 111, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSet(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.GetOrSet(1, 111, 0)
|
||||
@ -156,7 +169,7 @@ func TestCache_GetOrSetFunc(t *testing.T) {
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFunc(1, func() interface{} {
|
||||
return 11
|
||||
}, 0)
|
||||
@ -180,7 +193,7 @@ func TestCache_GetOrSetFuncLock(t *testing.T) {
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 11
|
||||
}, 0)
|
||||
@ -256,7 +269,7 @@ func TestCache_Basic(t *testing.T) {
|
||||
t.Assert(cache.Size(), 0)
|
||||
}
|
||||
|
||||
clear()
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
|
||||
@ -25,8 +25,11 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// Default perm for file opening.
|
||||
DefaultPerm = os.FileMode(0666)
|
||||
// DefaultPerm is the default perm for file opening.
|
||||
DefaultPermOpen = os.FileMode(0666)
|
||||
|
||||
// DefaultPermCopy is the default perm for file/folder copy.
|
||||
DefaultPermCopy = os.FileMode(0777)
|
||||
|
||||
// The absolute file path for main package.
|
||||
// It can be only checked and set once.
|
||||
@ -92,7 +95,7 @@ func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
// The default <perm> is 0666.
|
||||
// The parameter <flag> is like: O_RDONLY, O_RDWR, O_RDWR|O_CREATE|O_TRUNC, etc.
|
||||
func OpenWithFlag(path string, flag int) (*os.File, error) {
|
||||
f, err := os.OpenFile(path, flag, DefaultPerm)
|
||||
f, err := os.OpenFile(path, flag, DefaultPermOpen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -131,6 +134,7 @@ func Exists(path string) bool {
|
||||
}
|
||||
|
||||
// IsDir checks whether given <path> a directory.
|
||||
// Note that it returns false if the <path> does not exist.
|
||||
func IsDir(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@ -140,8 +144,13 @@ func IsDir(path string) bool {
|
||||
}
|
||||
|
||||
// Pwd returns absolute path of current working directory.
|
||||
// Note that it returns an empty string if retrieving current
|
||||
// working directory failed.
|
||||
func Pwd() string {
|
||||
path, _ := os.Getwd()
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@ -152,6 +161,7 @@ func Chdir(dir string) error {
|
||||
}
|
||||
|
||||
// IsFile checks whether given <path> a file, which means it's not a directory.
|
||||
// Note that it returns false if the <path> does not exist.
|
||||
func IsFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@ -225,14 +235,13 @@ func Glob(pattern string, onlyNames ...bool) ([]string, error) {
|
||||
// Remove deletes all file/directory with <path> parameter.
|
||||
// If parameter <path> is directory, it deletes it recursively.
|
||||
func Remove(path string) error {
|
||||
//intlog.Print(`Remove:`, path)
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// IsReadable checks whether given <path> is readable.
|
||||
func IsReadable(path string) bool {
|
||||
result := true
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, DefaultPerm)
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, DefaultPermOpen)
|
||||
if err != nil {
|
||||
result = false
|
||||
}
|
||||
@ -256,7 +265,7 @@ func IsWritable(path string) bool {
|
||||
}
|
||||
} else {
|
||||
// 如果是文件,那么判断文件是否可打开
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, DefaultPerm)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, DefaultPermOpen)
|
||||
if err != nil {
|
||||
result = false
|
||||
}
|
||||
@ -313,11 +322,17 @@ func SelfDir() string {
|
||||
// Trailing path separators are removed before extracting the last element.
|
||||
// If the path is empty, Base returns ".".
|
||||
// If the path consists entirely of separators, Basename returns a single separator.
|
||||
// Example:
|
||||
// /var/www/file.js -> file.js
|
||||
// file.js -> file.js
|
||||
func Basename(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
// Name returns the last element of path without file extension.
|
||||
// Example:
|
||||
// /var/www/file.js -> file
|
||||
// file.js -> file
|
||||
func Name(path string) string {
|
||||
base := filepath.Base(path)
|
||||
if i := strings.LastIndexByte(base, '.'); i != -1 {
|
||||
@ -384,6 +399,7 @@ func ExtName(path string) string {
|
||||
|
||||
// TempDir retrieves and returns the temporary directory of current system.
|
||||
// It return "/tmp" is current in *nix system, or else it returns os.TempDir().
|
||||
//
|
||||
// The optional parameter <names> specifies the its sub-folders/sub-files,
|
||||
// which will be joined with current system separator and returned with the path.
|
||||
func TempDir(names ...string) string {
|
||||
|
||||
@ -67,25 +67,25 @@ func Truncate(path string, size int) error {
|
||||
// PutContents puts string <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutContents(path string, content string) error {
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultPerm)
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultPermOpen)
|
||||
}
|
||||
|
||||
// PutContentsAppend appends string <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutContentsAppend(path string, content string) error {
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_APPEND, DefaultPerm)
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_APPEND, DefaultPermOpen)
|
||||
}
|
||||
|
||||
// PutBytes puts binary <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutBytes(path string, content []byte) error {
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultPerm)
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultPermOpen)
|
||||
}
|
||||
|
||||
// PutBytesAppend appends binary <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutBytesAppend(path string, content []byte) error {
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_APPEND, DefaultPerm)
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_APPEND, DefaultPermOpen)
|
||||
}
|
||||
|
||||
// GetNextCharOffset returns the file offset for given <char> starting from <start>.
|
||||
@ -110,7 +110,7 @@ func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
|
||||
// GetNextCharOffsetByPath returns the file offset for given <char> starting from <start>.
|
||||
// It opens file of <path> for reading with os.O_RDONLY flag and default perm.
|
||||
func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPerm); err == nil {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPermOpen); err == nil {
|
||||
defer f.Close()
|
||||
return GetNextCharOffset(f, char, start)
|
||||
}
|
||||
@ -134,7 +134,7 @@ func GetBytesTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64)
|
||||
//
|
||||
// Note: Returned value contains the character of the last position.
|
||||
func GetBytesTilCharByPath(path string, char byte, start int64) ([]byte, int64) {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPerm); err == nil {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPermOpen); err == nil {
|
||||
defer f.Close()
|
||||
return GetBytesTilChar(f, char, start)
|
||||
}
|
||||
@ -157,7 +157,7 @@ func GetBytesByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte {
|
||||
// it returns content range as [start, end).
|
||||
// It opens file of <path> for reading with os.O_RDONLY flag and default perm.
|
||||
func GetBytesByTwoOffsetsByPath(path string, start int64, end int64) []byte {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPerm); err == nil {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, DefaultPermOpen); err == nil {
|
||||
defer f.Close()
|
||||
return GetBytesByTwoOffsets(f, start, end)
|
||||
}
|
||||
|
||||
@ -45,6 +45,10 @@ func CopyFile(src, dst string) (err error) {
|
||||
if dst == "" {
|
||||
return errors.New("destination file cannot be empty")
|
||||
}
|
||||
// If src and dst are the same path, it does nothing.
|
||||
if src == dst {
|
||||
return nil
|
||||
}
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
@ -71,11 +75,7 @@ func CopyFile(src, dst string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
err = os.Chmod(dst, DefaultPermCopy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -92,6 +92,10 @@ func CopyDir(src string, dst string) (err error) {
|
||||
if dst == "" {
|
||||
return errors.New("destination directory cannot be empty")
|
||||
}
|
||||
// If src and dst are the same path, it does nothing.
|
||||
if src == dst {
|
||||
return nil
|
||||
}
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
si, err := os.Stat(src)
|
||||
@ -102,7 +106,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return fmt.Errorf("source is not a directory")
|
||||
}
|
||||
if !Exists(dst) {
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
err = os.MkdirAll(dst, DefaultPermCopy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -23,7 +23,31 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, false)
|
||||
list, err := doScanDir(path, pattern, isRecursive, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Strings(list)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ScanDirFunc returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
|
||||
// symbol to separate multiple patterns.
|
||||
//
|
||||
// The parameter <recursive> specifies whether scanning the <path> recursively, which
|
||||
// means it scans its sub-files and appends the files path to result array if the sub-file
|
||||
// is also a folder. It is false in default.
|
||||
//
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list, err := doScanDir(path, pattern, recursive, handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -45,7 +69,12 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, true)
|
||||
list, err := doScanDir(path, pattern, isRecursive, func(path string) string {
|
||||
if IsDir(path) {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -55,16 +84,20 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory
|
||||
// and returns the absolute path list of files that are not sorted.
|
||||
// doScanDir is an internal method which scans directory and returns the absolute path
|
||||
// list of files that are not sorted.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
|
||||
// symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
// The parameter <recursive> specifies whether scanning the <path> recursively, which
|
||||
// means it scans its sub-files and appends the files path to result array if the sub-file
|
||||
// is also a folder. It is false in default.
|
||||
//
|
||||
// It returns only files except folders if <onlyFile> is true.
|
||||
func doScanDir(path string, pattern string, recursive bool, onlyFile bool) ([]string, error) {
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func doScanDir(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list := ([]string)(nil)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@ -75,24 +108,24 @@ func doScanDir(path string, pattern string, recursive bool, onlyFile bool) ([]st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
filePath = ""
|
||||
isDir = false
|
||||
patterns = gstr.SplitAndTrim(pattern, ",")
|
||||
)
|
||||
for _, name := range names {
|
||||
filePath = path + Separator + name
|
||||
isDir = IsDir(filePath)
|
||||
if isDir && recursive {
|
||||
array, _ := doScanDir(filePath, pattern, true, onlyFile)
|
||||
if IsDir(filePath) && recursive {
|
||||
array, _ := doScanDir(filePath, pattern, true, handler)
|
||||
if len(array) > 0 {
|
||||
list = append(list, array...)
|
||||
}
|
||||
}
|
||||
// It returns only files.
|
||||
if isDir && onlyFile {
|
||||
continue
|
||||
// Handler filtering.
|
||||
if handler != nil {
|
||||
filePath = handler(filePath)
|
||||
if filePath == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// If it meets pattern, then add it to the result list.
|
||||
for _, p := range patterns {
|
||||
|
||||
@ -19,25 +19,21 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
// 创建测试文件
|
||||
func createTestFile(filename, content string) error {
|
||||
TempDir := testpath()
|
||||
err := ioutil.WriteFile(TempDir+filename, []byte(content), 0666)
|
||||
return err
|
||||
}
|
||||
|
||||
// 测试完删除文件或目录
|
||||
func delTestFiles(filenames string) {
|
||||
os.RemoveAll(testpath() + filenames)
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
func createDir(paths string) {
|
||||
TempDir := testpath()
|
||||
os.Mkdir(TempDir+paths, 0777)
|
||||
}
|
||||
|
||||
// 统一格式化文件目录为"/"
|
||||
func formatpaths(paths []string) []string {
|
||||
for k, v := range paths {
|
||||
paths[k] = filepath.ToSlash(v)
|
||||
@ -47,14 +43,12 @@ func formatpaths(paths []string) []string {
|
||||
return paths
|
||||
}
|
||||
|
||||
// 统一格式化文件目录为"/"
|
||||
func formatpath(paths string) string {
|
||||
paths = filepath.ToSlash(paths)
|
||||
paths = strings.Replace(paths, "./", "/", 1)
|
||||
return paths
|
||||
}
|
||||
|
||||
// 指定返回要测试的目录
|
||||
func testpath() string {
|
||||
return gstr.TrimRight(os.TempDir(), "\\/")
|
||||
}
|
||||
@ -77,10 +71,10 @@ func Test_GetContents(t *testing.T) {
|
||||
func Test_GetBinContents(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths1 string = "/testfile_t1.txt" // 文件存在时
|
||||
filepaths2 string = testpath() + "/testfile_t1_no.txt" // 文件不存在时
|
||||
filepaths1 = "/testfile_t1.txt"
|
||||
filepaths2 = testpath() + "/testfile_t1_no.txt"
|
||||
readcontent []byte
|
||||
str1 string = "my name is jroam"
|
||||
str1 = "my name is jroam"
|
||||
)
|
||||
createTestFile(filepaths1, str1)
|
||||
defer delTestFiles(filepaths1)
|
||||
@ -95,11 +89,10 @@ func Test_GetBinContents(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// 截断文件为指定的大小
|
||||
func Test_Truncate(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths1 string = "/testfile_GetContentsyyui.txt" //文件存在时
|
||||
filepaths1 = "/testfile_GetContentsyyui.txt"
|
||||
err error
|
||||
files *os.File
|
||||
)
|
||||
@ -108,7 +101,6 @@ func Test_Truncate(t *testing.T) {
|
||||
err = gfile.Truncate(testpath()+filepaths1, 10)
|
||||
t.Assert(err, nil)
|
||||
|
||||
//=========================检查修改文后的大小,是否与期望一致
|
||||
files, err = os.Open(testpath() + filepaths1)
|
||||
defer files.Close()
|
||||
t.Assert(err, nil)
|
||||
@ -116,7 +108,6 @@ func Test_Truncate(t *testing.T) {
|
||||
t.Assert(err2, nil)
|
||||
t.Assert(fileinfo.Size(), 10)
|
||||
|
||||
//====测试当为空时,是否报错
|
||||
err = gfile.Truncate("", 10)
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
@ -126,7 +117,7 @@ func Test_Truncate(t *testing.T) {
|
||||
func Test_PutContents(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_PutContents.txt"
|
||||
filepaths = "/testfile_PutContents.txt"
|
||||
err error
|
||||
readcontent []byte
|
||||
)
|
||||
@ -136,7 +127,6 @@ func Test_PutContents(t *testing.T) {
|
||||
err = gfile.PutContents(testpath()+filepaths, "test!")
|
||||
t.Assert(err, nil)
|
||||
|
||||
//==================判断是否真正写入
|
||||
readcontent, err = ioutil.ReadFile(testpath() + filepaths)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(string(readcontent), "test!")
|
||||
@ -150,7 +140,7 @@ func Test_PutContents(t *testing.T) {
|
||||
func Test_PutContentsAppend(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_PutContents.txt"
|
||||
filepaths = "/testfile_PutContents.txt"
|
||||
err error
|
||||
readcontent []byte
|
||||
)
|
||||
@ -160,7 +150,6 @@ func Test_PutContentsAppend(t *testing.T) {
|
||||
err = gfile.PutContentsAppend(testpath()+filepaths, "hello")
|
||||
t.Assert(err, nil)
|
||||
|
||||
//==================判断是否真正写入
|
||||
readcontent, err = ioutil.ReadFile(testpath() + filepaths)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(string(readcontent), "ahello")
|
||||
@ -175,7 +164,7 @@ func Test_PutContentsAppend(t *testing.T) {
|
||||
func Test_PutBinContents(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_PutContents.txt"
|
||||
filepaths = "/testfile_PutContents.txt"
|
||||
err error
|
||||
readcontent []byte
|
||||
)
|
||||
@ -185,7 +174,6 @@ func Test_PutBinContents(t *testing.T) {
|
||||
err = gfile.PutBytes(testpath()+filepaths, []byte("test!!"))
|
||||
t.Assert(err, nil)
|
||||
|
||||
// 判断是否真正写入
|
||||
readcontent, err = ioutil.ReadFile(testpath() + filepaths)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(string(readcontent), "test!!")
|
||||
@ -199,7 +187,7 @@ func Test_PutBinContents(t *testing.T) {
|
||||
func Test_PutBinContentsAppend(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_PutContents.txt" //原文件内容: yy
|
||||
filepaths = "/testfile_PutContents.txt"
|
||||
err error
|
||||
readcontent []byte
|
||||
)
|
||||
@ -208,7 +196,6 @@ func Test_PutBinContentsAppend(t *testing.T) {
|
||||
err = gfile.PutBytesAppend(testpath()+filepaths, []byte("word"))
|
||||
t.Assert(err, nil)
|
||||
|
||||
// 判断是否真正写入
|
||||
readcontent, err = ioutil.ReadFile(testpath() + filepaths)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(string(readcontent), "test!!word")
|
||||
@ -222,7 +209,7 @@ func Test_PutBinContentsAppend(t *testing.T) {
|
||||
func Test_GetBinContentsByTwoOffsetsByPath(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_GetContents.txt" // 文件内容: abcdefghijk
|
||||
filepaths = "/testfile_GetContents.txt"
|
||||
readcontent []byte
|
||||
)
|
||||
|
||||
@ -242,7 +229,7 @@ func Test_GetBinContentsByTwoOffsetsByPath(t *testing.T) {
|
||||
func Test_GetNextCharOffsetByPath(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
filepaths string = "/testfile_GetContents.txt" // 文件内容: abcdefghijk
|
||||
filepaths = "/testfile_GetContents.txt"
|
||||
localindex int64
|
||||
)
|
||||
createTestFile(filepaths, "abcdefghijk")
|
||||
@ -310,7 +297,7 @@ func Test_GetBinContentsTilCharByPath(t *testing.T) {
|
||||
var (
|
||||
reads []byte
|
||||
indexs int64
|
||||
filepaths string = "/testfile_GetContents.txt"
|
||||
filepaths = "/testfile_GetContents.txt"
|
||||
)
|
||||
|
||||
createTestFile(filepaths, "abcdefghijklmn")
|
||||
@ -338,6 +325,5 @@ func Test_Home(t *testing.T) {
|
||||
reads, err = gfile.Home()
|
||||
t.Assert(err, nil)
|
||||
t.AssertNE(reads, "")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@ import (
|
||||
func Test_Copy(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
paths = "/testfile_copyfile1.txt"
|
||||
topath = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
@ -34,8 +34,8 @@ func Test_Copy(t *testing.T) {
|
||||
func Test_CopyFile(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
paths string = "/testfile_copyfile1.txt"
|
||||
topath string = "/testfile_copyfile2.txt"
|
||||
paths = "/testfile_copyfile1.txt"
|
||||
topath = "/testfile_copyfile2.txt"
|
||||
)
|
||||
|
||||
createTestFile(paths, "")
|
||||
@ -53,13 +53,12 @@ func Test_CopyFile(t *testing.T) {
|
||||
dst := gfile.TempDir(gtime.TimestampNanoStr())
|
||||
srcContent := "1"
|
||||
dstContent := "1"
|
||||
gfile.PutContents(src, srcContent)
|
||||
gfile.PutContents(dst, dstContent)
|
||||
t.Assert(gfile.PutContents(src, srcContent), nil)
|
||||
t.Assert(gfile.PutContents(dst, dstContent), nil)
|
||||
t.Assert(gfile.GetContents(src), srcContent)
|
||||
t.Assert(gfile.GetContents(dst), dstContent)
|
||||
|
||||
err := gfile.CopyFile(src, dst)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gfile.CopyFile(src, dst), nil)
|
||||
t.Assert(gfile.GetContents(src), srcContent)
|
||||
t.Assert(gfile.GetContents(dst), srcContent)
|
||||
})
|
||||
@ -68,23 +67,23 @@ func Test_CopyFile(t *testing.T) {
|
||||
func Test_CopyDir(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
dirpath1 string = "/testcopydir1"
|
||||
dirpath2 string = "/testcopydir2"
|
||||
dirPath1 = "/test-copy-dir1"
|
||||
dirPath2 = "/test-copy-dir2"
|
||||
)
|
||||
|
||||
havelist1 := []string{
|
||||
haveList := []string{
|
||||
"t1.txt",
|
||||
"t2.txt",
|
||||
}
|
||||
|
||||
createDir(dirpath1)
|
||||
for _, v := range havelist1 {
|
||||
createTestFile(dirpath1+"/"+v, "")
|
||||
createDir(dirPath1)
|
||||
for _, v := range haveList {
|
||||
t.Assert(createTestFile(dirPath1+"/"+v, ""), nil)
|
||||
}
|
||||
defer delTestFiles(dirpath1)
|
||||
defer delTestFiles(dirPath1)
|
||||
|
||||
yfolder := testpath() + dirpath1
|
||||
tofolder := testpath() + dirpath2
|
||||
var (
|
||||
yfolder = testpath() + dirPath1
|
||||
tofolder = testpath() + dirPath2
|
||||
)
|
||||
|
||||
if gfile.IsDir(tofolder) {
|
||||
t.Assert(gfile.Remove(tofolder), nil)
|
||||
@ -94,19 +93,15 @@ func Test_CopyDir(t *testing.T) {
|
||||
t.Assert(gfile.CopyDir(yfolder, tofolder), nil)
|
||||
defer delTestFiles(tofolder)
|
||||
|
||||
// 检查复制后的旧文件夹是否真实存在
|
||||
t.Assert(gfile.IsDir(yfolder), true)
|
||||
|
||||
// 检查复制后的旧文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
for _, v := range haveList {
|
||||
t.Assert(gfile.IsFile(yfolder+"/"+v), true)
|
||||
}
|
||||
|
||||
// 检查复制后的新文件夹是否真实存在
|
||||
t.Assert(gfile.IsDir(tofolder), true)
|
||||
|
||||
// 检查复制后的新文件夹中的文件是否真实存在
|
||||
for _, v := range havelist1 {
|
||||
for _, v := range haveList {
|
||||
t.Assert(gfile.IsFile(tofolder+"/"+v), true)
|
||||
}
|
||||
|
||||
@ -117,10 +112,14 @@ func Test_CopyDir(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := gfile.TempDir(gtime.TimestampNanoStr(), gtime.TimestampNanoStr())
|
||||
dst := gfile.TempDir(gtime.TimestampNanoStr(), gtime.TimestampNanoStr())
|
||||
defer func() {
|
||||
gfile.Remove(src)
|
||||
gfile.Remove(dst)
|
||||
}()
|
||||
srcContent := "1"
|
||||
dstContent := "1"
|
||||
gfile.PutContents(src, srcContent)
|
||||
gfile.PutContents(dst, dstContent)
|
||||
t.Assert(gfile.PutContents(src, srcContent), nil)
|
||||
t.Assert(gfile.PutContents(dst, dstContent), nil)
|
||||
t.Assert(gfile.GetContents(src), srcContent)
|
||||
t.Assert(gfile.GetContents(dst), dstContent)
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_ScanDir(t *testing.T) {
|
||||
teatPath := gfile.Dir(gdebug.CallerFilePath()) + gfile.Separator + "testdata"
|
||||
teatPath := gdebug.TestDataPath()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
files, err := gfile.ScanDir(teatPath, "*", false)
|
||||
t.Assert(err, nil)
|
||||
@ -34,8 +34,23 @@ func Test_ScanDir(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanDirFunc(t *testing.T) {
|
||||
teatPath := gdebug.TestDataPath()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
files, err := gfile.ScanDirFunc(teatPath, "*", true, func(path string) string {
|
||||
if gfile.Name(path) != "file1" {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(files), 1)
|
||||
t.Assert(gfile.Name(files[0]), "file1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanDirFile(t *testing.T) {
|
||||
teatPath := gfile.Dir(gdebug.CallerFilePath()) + gfile.Separator + "testdata"
|
||||
teatPath := gdebug.TestDataPath()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
files, err := gfile.ScanDirFile(teatPath, "*", false)
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -610,7 +610,11 @@ func Test_ExtName(t *testing.T) {
|
||||
|
||||
func Test_TempDir(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gfile.TempDir(), "/tmp")
|
||||
if gfile.Separator != "/" || !gfile.Exists("/tmp") {
|
||||
t.Assert(gfile.TempDir(), os.TempDir())
|
||||
} else {
|
||||
t.Assert(gfile.TempDir(), "/tmp")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package glog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
@ -15,6 +16,13 @@ func Expose() *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Ctx is a chaining function,
|
||||
// which sets the context for current logging.
|
||||
// The parameter <keys> specifies the context keys for retrieving values.
|
||||
func Ctx(ctx context.Context, keys ...interface{}) *Logger {
|
||||
return logger.Ctx(ctx, keys...)
|
||||
}
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the sepecified <writer>.
|
||||
func To(writer io.Writer) *Logger {
|
||||
|
||||
@ -99,6 +99,19 @@ func GetFlags() int {
|
||||
return logger.GetFlags()
|
||||
}
|
||||
|
||||
// SetCtxKeys sets the context keys for logger. The keys is used for retrieving values
|
||||
// from context and printing them to logging content.
|
||||
//
|
||||
// Note that multiple calls of this function will overwrite the previous set context keys.
|
||||
func SetCtxKeys(keys ...interface{}) {
|
||||
logger.SetCtxKeys(keys...)
|
||||
}
|
||||
|
||||
// GetCtxKeys retrieves and returns the context keys for logging.
|
||||
func GetCtxKeys() []interface{} {
|
||||
return logger.GetCtxKeys()
|
||||
}
|
||||
|
||||
// PrintStack prints the caller stack,
|
||||
// the optional parameter <skip> specify the skipped stack offset from the end point.
|
||||
func PrintStack(skip ...int) {
|
||||
|
||||
@ -8,6 +8,7 @@ package glog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfpool"
|
||||
@ -29,9 +30,10 @@ import (
|
||||
|
||||
// Logger is the struct for logging management.
|
||||
type Logger struct {
|
||||
rmu sync.Mutex // Mutex for rotation feature.
|
||||
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
|
||||
config Config // Logger configuration.
|
||||
rmu sync.Mutex // Mutex for rotation feature.
|
||||
ctx context.Context // Context for logging.
|
||||
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
|
||||
config Config // Logger configuration.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -95,7 +97,7 @@ func (l *Logger) getFilePath(now time.Time) string {
|
||||
}
|
||||
|
||||
// print prints <s> to defined writer, logging file or passed <std>.
|
||||
func (l *Logger) print(std io.Writer, lead string, value ...interface{}) {
|
||||
func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
|
||||
var (
|
||||
now = time.Now()
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
@ -118,7 +120,7 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) {
|
||||
// Lead string.
|
||||
if len(lead) > 0 {
|
||||
buffer.WriteString(lead)
|
||||
if len(value) > 0 {
|
||||
if len(values) > 0 {
|
||||
buffer.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
@ -141,9 +143,26 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) {
|
||||
}
|
||||
}
|
||||
// Convert value to string.
|
||||
tempStr := ""
|
||||
valueStr := ""
|
||||
for _, v := range value {
|
||||
var (
|
||||
tempStr = ""
|
||||
valueStr = ""
|
||||
)
|
||||
// Context values.
|
||||
if l.ctx != nil && len(l.config.CtxKeys) > 0 {
|
||||
ctxStr := ""
|
||||
for _, key := range l.config.CtxKeys {
|
||||
if v := l.ctx.Value(key); v != nil {
|
||||
if ctxStr != "" {
|
||||
ctxStr += ", "
|
||||
}
|
||||
ctxStr += fmt.Sprintf("%s: %+v", key, v)
|
||||
}
|
||||
}
|
||||
if ctxStr != "" {
|
||||
buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr))
|
||||
}
|
||||
}
|
||||
for _, v := range values {
|
||||
if err, ok := v.(error); ok {
|
||||
tempStr = fmt.Sprintf("%+v", err)
|
||||
} else {
|
||||
|
||||
@ -7,11 +7,28 @@
|
||||
package glog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
)
|
||||
|
||||
// Ctx is a chaining function,
|
||||
// which sets the context for current logging.
|
||||
func (l *Logger) Ctx(ctx context.Context, keys ...interface{}) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.parent == nil {
|
||||
logger = l.Clone()
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
logger.ctx = ctx
|
||||
if len(keys) > 0 {
|
||||
logger.SetCtxKeys(keys...)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the specified <writer>.
|
||||
func (l *Logger) To(writer io.Writer) *Logger {
|
||||
|
||||
@ -29,6 +29,7 @@ type Config struct {
|
||||
StSkip int // Skip count for stack.
|
||||
StStatus int // Stack status(1: enabled - default; 0: disabled)
|
||||
StFilter string // Stack string filter.
|
||||
CtxKeys []interface{} // Context keys for logging, which is used for value retrieving from context.
|
||||
HeaderPrint bool `c:"header"` // Print header or not(true in default).
|
||||
StdoutPrint bool `c:"stdout"` // Output to stdout or not(true in default).
|
||||
LevelPrefixes map[int]string // Logging level to its prefix string mapping.
|
||||
@ -155,6 +156,19 @@ func (l *Logger) SetStackFilter(filter string) {
|
||||
l.config.StFilter = filter
|
||||
}
|
||||
|
||||
// SetCtxKeys sets the context keys for logger. The keys is used for retrieving values
|
||||
// from context and printing them to logging content.
|
||||
//
|
||||
// Note that multiple calls of this function will overwrite the previous set context keys.
|
||||
func (l *Logger) SetCtxKeys(keys ...interface{}) {
|
||||
l.config.CtxKeys = keys
|
||||
}
|
||||
|
||||
// GetCtxKeys retrieves and returns the context keys for logging.
|
||||
func (l *Logger) GetCtxKeys() []interface{} {
|
||||
return l.config.CtxKeys
|
||||
}
|
||||
|
||||
// SetWriter sets the customized logging <writer> for logging.
|
||||
// The <writer> object should implements the io.Writer interface.
|
||||
// Developer can use customized logging <writer> to redirect logging output to another service,
|
||||
|
||||
55
os/glog/glog_z_unit_ctx_test.go
Normal file
55
os/glog/glog_z_unit_ctx_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 glog_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Ctx(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
l := glog.NewWithWriter(w)
|
||||
l.SetCtxKeys("Trace-Id", "Span-Id", "Test")
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
|
||||
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
|
||||
|
||||
l.Ctx(ctx).Print(1, 2, 3)
|
||||
t.Assert(gstr.Count(w.String(), "Trace-Id"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "1234567890"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "Span-Id"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "abcdefg"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "1 2 3"), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Ctx_Config(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
l := glog.NewWithWriter(w)
|
||||
m := map[string]interface{}{
|
||||
"CtxKeys": g.SliceStr{"Trace-Id", "Span-Id", "Test"},
|
||||
}
|
||||
err := l.SetConfigWithMap(m)
|
||||
t.Assert(err, nil)
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
|
||||
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
|
||||
|
||||
l.Ctx(ctx).Print(1, 2, 3)
|
||||
t.Assert(gstr.Count(w.String(), "Trace-Id"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "1234567890"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "Span-Id"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "abcdefg"), 1)
|
||||
t.Assert(gstr.Count(w.String(), "1 2 3"), 1)
|
||||
})
|
||||
}
|
||||
@ -39,8 +39,10 @@ func zipPathWriter(paths string, writer io.Writer, prefix ...string) error {
|
||||
// commonly the destination zip file path.
|
||||
// The unnecessary parameter <prefix> indicates the path prefix for zip file.
|
||||
func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix ...string) error {
|
||||
var err error
|
||||
var files []string
|
||||
var (
|
||||
err error
|
||||
files []string
|
||||
)
|
||||
path, err = gfile.Search(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -99,6 +101,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix
|
||||
// zipFile compresses the file of given <path> and writes the content to <zw>.
|
||||
// The parameter <prefix> indicates the path prefix for zip file.
|
||||
func zipFile(path string, prefix string, zw *zip.Writer) error {
|
||||
prefix = strings.Replace(prefix, "//", "/", -1)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
@ -20,7 +20,7 @@ func (f *File) Close() error {
|
||||
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||
files := f.resource.ScanDir(f.Name(), "*", false)
|
||||
if len(files) > 0 {
|
||||
if count < 0 || count > len(files) {
|
||||
if count <= 0 || count > len(files) {
|
||||
count = len(files)
|
||||
}
|
||||
infos := make([]os.FileInfo, count)
|
||||
|
||||
@ -91,6 +91,7 @@ func (r *Resource) Get(path string) *File {
|
||||
//
|
||||
// GetWithIndex is usually used for http static file service.
|
||||
func (r *Resource) GetWithIndex(path string, indexFiles []string) *File {
|
||||
// Necessary for double char '/' replacement in prefix.
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
@ -173,10 +174,12 @@ func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFi
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
name := ""
|
||||
files := make([]*File, 0)
|
||||
length := len(path)
|
||||
patterns := strings.Split(pattern, ",")
|
||||
var (
|
||||
name = ""
|
||||
files = make([]*File, 0)
|
||||
length = len(path)
|
||||
patterns = strings.Split(pattern, ",")
|
||||
)
|
||||
for i := 0; i < len(patterns); i++ {
|
||||
patterns[i] = strings.TrimSpace(patterns[i])
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ func Test_PackMulti(t *testing.T) {
|
||||
func Test_PackWithPrefix1(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
srcPath := gdebug.TestDataPath("files")
|
||||
goFilePath := gdebug.TestDataPath("testdata.go")
|
||||
goFilePath := gfile.TempDir("testdata.go")
|
||||
pkgName := "testdata"
|
||||
err := gres.PackToGoFile(srcPath, goFilePath, pkgName, "www/gf-site/test")
|
||||
t.Assert(err, nil)
|
||||
@ -83,7 +83,7 @@ func Test_PackWithPrefix1(t *testing.T) {
|
||||
func Test_PackWithPrefix2(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
srcPath := gdebug.TestDataPath("files")
|
||||
goFilePath := gdebug.TestDataPath("testdata.go")
|
||||
goFilePath := gfile.TempDir("testdata.go")
|
||||
pkgName := "testdata"
|
||||
err := gres.PackToGoFile(srcPath, goFilePath, pkgName, "/var/www/gf-site/test")
|
||||
t.Assert(err, nil)
|
||||
|
||||
2
os/gres/testdata/data/data.go
vendored
2
os/gres/testdata/data/data.go
vendored
File diff suppressed because one or more lines are too long
2
os/gres/testdata/testdata.go
vendored
2
os/gres/testdata/testdata.go
vendored
File diff suppressed because one or more lines are too long
@ -9,11 +9,7 @@ package gsession
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -21,7 +17,7 @@ var (
|
||||
)
|
||||
|
||||
// NewSessionId creates and returns a new and unique session id string,
|
||||
// the length of which is 18 bytes.
|
||||
// which is in 36 bytes.
|
||||
func NewSessionId() string {
|
||||
return strings.ToUpper(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6))
|
||||
return guid.S()
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
// Manager for sessions.
|
||||
type Manager struct {
|
||||
ttl time.Duration // TTL for sessions.
|
||||
storage Storage // Storage interface for session storage Set/Get.
|
||||
storage Storage // Storage interface for session storage.
|
||||
sessionData *gcache.Cache // Session data cache for session TTL.
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gsession
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"time"
|
||||
|
||||
@ -16,22 +17,27 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Session struct for storing single session data.
|
||||
// Session struct for storing single session data,
|
||||
// which is bound to a single request.
|
||||
type Session struct {
|
||||
id string // Session id.
|
||||
data *gmap.StrAnyMap // Session data.
|
||||
dirty bool // Used to mark session is modified.
|
||||
start bool // Used to mark session is started.
|
||||
manager *Manager // Parent manager.
|
||||
|
||||
// idFunc is a callback function used for creating custom session id.
|
||||
// This is called if session id is empty ever when session starts.
|
||||
idFunc func(ttl time.Duration) (id string)
|
||||
}
|
||||
|
||||
// init does the delay initialization for session.
|
||||
// init does the lazy initialization for session.
|
||||
// It here initializes real session if necessary.
|
||||
func (s *Session) init() {
|
||||
if s.start {
|
||||
return
|
||||
}
|
||||
if len(s.id) > 0 {
|
||||
if s.id != "" {
|
||||
var err error
|
||||
// Retrieve memory session data from manager.
|
||||
if r := s.manager.sessionData.Get(s.id); r != nil {
|
||||
@ -50,10 +56,16 @@ func (s *Session) init() {
|
||||
s.id = ""
|
||||
}
|
||||
}
|
||||
if len(s.id) == 0 {
|
||||
// Use custom session id creating function.
|
||||
if s.id == "" && s.idFunc != nil {
|
||||
s.id = s.idFunc(s.manager.ttl)
|
||||
}
|
||||
// Use default session id creating function of storage.
|
||||
if s.id == "" {
|
||||
s.id = s.manager.storage.New(s.manager.ttl)
|
||||
}
|
||||
if len(s.id) == 0 {
|
||||
// Use default session id creating function.
|
||||
if s.id == "" {
|
||||
s.id = NewSessionId()
|
||||
}
|
||||
if s.data == nil {
|
||||
@ -67,7 +79,7 @@ func (s *Session) init() {
|
||||
//
|
||||
// NOTE that this function must be called ever after a session request done.
|
||||
func (s *Session) Close() {
|
||||
if s.start && len(s.id) > 0 {
|
||||
if s.start && s.id != "" {
|
||||
size := s.data.Size()
|
||||
if s.manager.storage != nil {
|
||||
if s.dirty {
|
||||
@ -116,7 +128,7 @@ func (s *Session) Sets(data map[string]interface{}) error {
|
||||
|
||||
// Remove removes key along with its value from this session.
|
||||
func (s *Session) Remove(key string) error {
|
||||
if len(s.id) == 0 {
|
||||
if s.id == "" {
|
||||
return nil
|
||||
}
|
||||
s.init()
|
||||
@ -138,7 +150,7 @@ func (s *Session) Clear() error {
|
||||
|
||||
// RemoveAll deletes all key-value pairs from this session.
|
||||
func (s *Session) RemoveAll() error {
|
||||
if len(s.id) == 0 {
|
||||
if s.id == "" {
|
||||
return nil
|
||||
}
|
||||
s.init()
|
||||
@ -160,10 +172,30 @@ func (s *Session) Id() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// SetId sets custom session before session starts.
|
||||
// It returns error if it is called after session starts.
|
||||
func (s *Session) SetId(id string) error {
|
||||
if s.start {
|
||||
return errors.New("session already started")
|
||||
}
|
||||
s.id = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetIdFunc sets custom session id creating function before session starts.
|
||||
// It returns error if it is called after session starts.
|
||||
func (s *Session) SetIdFunc(f func(ttl time.Duration) string) error {
|
||||
if s.start {
|
||||
return errors.New("session already started")
|
||||
}
|
||||
s.idFunc = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map returns all data as map.
|
||||
// Note that it's using value copy internally for concurrent-safe purpose.
|
||||
func (s *Session) Map() map[string]interface{} {
|
||||
if len(s.id) > 0 {
|
||||
if s.id != "" {
|
||||
s.init()
|
||||
if data := s.manager.storage.GetMap(s.id); data != nil {
|
||||
return data
|
||||
@ -175,7 +207,7 @@ func (s *Session) Map() map[string]interface{} {
|
||||
|
||||
// Size returns the size of the session.
|
||||
func (s *Session) Size() int {
|
||||
if len(s.id) > 0 {
|
||||
if s.id != "" {
|
||||
s.init()
|
||||
if size := s.manager.storage.GetSize(s.id); size >= 0 {
|
||||
return size
|
||||
@ -200,7 +232,7 @@ func (s *Session) IsDirty() bool {
|
||||
// It returns <def> if the key does not exist in the session if <def> is given,
|
||||
// or else it return nil.
|
||||
func (s *Session) Get(key string, def ...interface{}) interface{} {
|
||||
if len(s.id) == 0 {
|
||||
if s.id == "" {
|
||||
return nil
|
||||
}
|
||||
s.init()
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Storage is the interface definition for session storage.
|
||||
type Storage interface {
|
||||
// New creates a custom session id.
|
||||
// This function can be used for custom session creation.
|
||||
|
||||
@ -17,6 +17,6 @@ func Test_NewSessionId(t *testing.T) {
|
||||
id1 := NewSessionId()
|
||||
id2 := NewSessionId()
|
||||
t.AssertNE(id1, id2)
|
||||
t.Assert(len(id1), 18)
|
||||
t.Assert(len(id1), 36)
|
||||
})
|
||||
}
|
||||
|
||||
@ -685,35 +685,3 @@ func SearchArray(a []string, s string) int {
|
||||
func InArray(a []string, s string) bool {
|
||||
return SearchArray(a, s) != -1
|
||||
}
|
||||
|
||||
// CompareVersion compares <a> and <b> as standard golang version.
|
||||
// Golang standard version is as: 1.0.0, v1.0.1, v2.10.8, 10.2.0 etc.
|
||||
func CompareVersion(a, b string) int {
|
||||
if a[0] == 'v' {
|
||||
a = a[1:]
|
||||
}
|
||||
if b[0] == 'v' {
|
||||
b = b[1:]
|
||||
}
|
||||
array1 := strings.Split(a, ".")
|
||||
array2 := strings.Split(b, ".")
|
||||
for i := 0; i < len(array2)-len(array1); i++ {
|
||||
array1 = append(array1, "")
|
||||
}
|
||||
for i := 0; i < len(array1)-len(array2); i++ {
|
||||
array2 = append(array2, "")
|
||||
}
|
||||
v1 := 0
|
||||
v2 := 0
|
||||
for i := 0; i < len(array1); i++ {
|
||||
v1 = gconv.Int(array1[i])
|
||||
v2 = gconv.Int(array2[i])
|
||||
if v1 > v2 {
|
||||
return 1
|
||||
}
|
||||
if v1 < v2 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
132
text/gstr/gstr_version.go
Normal file
132
text/gstr/gstr_version.go
Normal file
@ -0,0 +1,132 @@
|
||||
// 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 gstr
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CompareVersion compares <a> and <b> as standard GNU version.
|
||||
// It returns 1 if <a> > <b>.
|
||||
// It returns -1 if <a> < <b>.
|
||||
// It returns 0 if <a> = <b>.
|
||||
// GNU standard version is like:
|
||||
// v1.0
|
||||
// 1
|
||||
// 1.0.0
|
||||
// v1.0.1
|
||||
// v2.10.8
|
||||
// 10.2.0
|
||||
// etc.
|
||||
func CompareVersion(a, b string) int {
|
||||
if a[0] == 'v' {
|
||||
a = a[1:]
|
||||
}
|
||||
if b[0] == 'v' {
|
||||
b = b[1:]
|
||||
}
|
||||
var (
|
||||
array1 = strings.Split(a, ".")
|
||||
array2 = strings.Split(b, ".")
|
||||
diff = 0
|
||||
)
|
||||
diff = len(array2) - len(array1)
|
||||
for i := 0; i < diff; i++ {
|
||||
array1 = append(array1, "0")
|
||||
}
|
||||
diff = len(array1) - len(array2)
|
||||
for i := 0; i < diff; i++ {
|
||||
array2 = append(array2, "0")
|
||||
}
|
||||
v1 := 0
|
||||
v2 := 0
|
||||
for i := 0; i < len(array1); i++ {
|
||||
v1 = gconv.Int(array1[i])
|
||||
v2 = gconv.Int(array2[i])
|
||||
if v1 > v2 {
|
||||
return 1
|
||||
}
|
||||
if v1 < v2 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// CompareVersionGo compares <a> and <b> as standard Golang version.
|
||||
// It returns 1 if <a> > <b>.
|
||||
// It returns -1 if <a> < <b>.
|
||||
// It returns 0 if <a> = <b>.
|
||||
// Golang standard version is like:
|
||||
// 1.0.0
|
||||
// v1.0.1
|
||||
// v2.10.8
|
||||
// 10.2.0
|
||||
// v0.0.0-20190626092158-b2ccc519800e
|
||||
// v4.20.0+incompatible
|
||||
// etc.
|
||||
func CompareVersionGo(a, b string) int {
|
||||
if a[0] == 'v' {
|
||||
a = a[1:]
|
||||
}
|
||||
if b[0] == 'v' {
|
||||
b = b[1:]
|
||||
}
|
||||
if Count(a, "-") > 1 {
|
||||
if i := PosR(a, "-"); i > 0 {
|
||||
a = a[:i]
|
||||
}
|
||||
}
|
||||
if Count(b, "-") > 1 {
|
||||
if i := PosR(b, "-"); i > 0 {
|
||||
b = b[:i]
|
||||
}
|
||||
}
|
||||
if i := Pos(a, "+"); i > 0 {
|
||||
a = a[:i]
|
||||
}
|
||||
if i := Pos(b, "+"); i > 0 {
|
||||
b = b[:i]
|
||||
}
|
||||
a = Replace(a, "-", ".")
|
||||
b = Replace(b, "-", ".")
|
||||
var (
|
||||
array1 = strings.Split(a, ".")
|
||||
array2 = strings.Split(b, ".")
|
||||
diff = 0
|
||||
)
|
||||
// Specially in Golang:
|
||||
// "v1.12.2-0.20200413154443-b17e3a6804fa" < "v1.12.2"
|
||||
if len(array1) > 3 && len(array2) <= 3 {
|
||||
return -1
|
||||
}
|
||||
if len(array1) <= 3 && len(array2) > 3 {
|
||||
return 1
|
||||
}
|
||||
diff = len(array2) - len(array1)
|
||||
for i := 0; i < diff; i++ {
|
||||
array1 = append(array1, "0")
|
||||
}
|
||||
diff = len(array1) - len(array2)
|
||||
for i := 0; i < diff; i++ {
|
||||
array2 = append(array2, "0")
|
||||
}
|
||||
v1 := 0
|
||||
v2 := 0
|
||||
for i := 0; i < len(array1); i++ {
|
||||
v1 = gconv.Int(array1[i])
|
||||
v2 = gconv.Int(array2[i])
|
||||
if v1 > v2 {
|
||||
return 1
|
||||
}
|
||||
if v1 < v2 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
42
text/gstr/gstr_z_unit_version_test.go
Normal file
42
text/gstr/gstr_z_unit_version_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gstr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
func Test_CompareVersion(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertEQ(gstr.CompareVersion("1", "v0.99"), 1)
|
||||
t.AssertEQ(gstr.CompareVersion("v1.0", "v0.99"), 1)
|
||||
t.AssertEQ(gstr.CompareVersion("v1.0.1", "v1.1.0"), -1)
|
||||
t.AssertEQ(gstr.CompareVersion("1.0.1", "v1.1.0"), -1)
|
||||
t.AssertEQ(gstr.CompareVersion("1.0.0", "v0.1.0"), 1)
|
||||
t.AssertEQ(gstr.CompareVersion("1.0.0", "v1.0.0"), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CompareVersionGo(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.AssertEQ(gstr.CompareVersionGo("v1.0.1", "v1.1.0"), -1)
|
||||
t.AssertEQ(gstr.CompareVersionGo("1.0.1", "v1.1.0"), -1)
|
||||
t.AssertEQ(gstr.CompareVersionGo("1.0.0", "v0.1.0"), 1)
|
||||
t.AssertEQ(gstr.CompareVersionGo("1.0.0", "v1.0.0"), 0)
|
||||
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092158-b2ccc519800e", "0.0.0-20190626092158"), 0)
|
||||
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092159-b2ccc519800e", "0.0.0-20190626092158"), 1)
|
||||
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.0"), 0)
|
||||
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.1"), -1)
|
||||
// Note that this comparison a < b.
|
||||
t.AssertEQ(gstr.CompareVersionGo("v1.12.2-0.20200413154443-b17e3a6804fa", "v1.12.2"), -1)
|
||||
})
|
||||
}
|
||||
@ -119,6 +119,16 @@ func Convert(i interface{}, t string, params ...interface{}) interface{} {
|
||||
|
||||
case "Duration", "time.Duration":
|
||||
return Duration(i)
|
||||
|
||||
case "map[string]string":
|
||||
return MapStrStr(i)
|
||||
|
||||
case "map[string]interface{}":
|
||||
return Map(i)
|
||||
|
||||
case "[]map[string]interface{}":
|
||||
return Maps(i)
|
||||
|
||||
default:
|
||||
return i
|
||||
}
|
||||
|
||||
@ -292,21 +292,21 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapToMap converts map type variable <params> to another map type variable <pointer> using
|
||||
// reflect.
|
||||
// MapToMap converts any map type variable <params> to another map type variable <pointer>
|
||||
// using reflect.
|
||||
// See doMapToMap.
|
||||
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMap(params, pointer, false, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapDeep converts map type variable <params> to another map type variable <pointer> using
|
||||
// reflect recursively.
|
||||
// MapToMapDeep converts any map type variable <params> to another map type variable <pointer>
|
||||
// using reflect recursively.
|
||||
// See doMapToMap.
|
||||
func MapToMapDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMap(params, pointer, true, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMap converts map type variable <params> to another map type variable <pointer>.
|
||||
// doMapToMap converts any map type variable <params> to another map type variable <pointer>.
|
||||
//
|
||||
// The parameter <params> can be any type of map, like:
|
||||
// map[string]string, map[string]struct, , map[string]*struct, etc.
|
||||
@ -400,20 +400,20 @@ func doMapToMap(params interface{}, pointer interface{}, deep bool, mapping ...m
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
|
||||
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
// See doMapToMaps.
|
||||
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMaps(params, pointer, false, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapsDeep converts map type variable <params> to another map type variable
|
||||
// MapToMapsDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// See doMapToMaps.
|
||||
func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMaps(params, pointer, true, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMaps converts map type variable <params> to another map type variable <pointer>.
|
||||
// doMapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
//
|
||||
// The parameter <params> can be any type of map, of which the item type is slice map, like:
|
||||
// map[int][]map, map[string][]map.
|
||||
|
||||
@ -31,7 +31,7 @@ var (
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
// Struct maps the params key-value pairs to the corresponding struct object's properties.
|
||||
// Struct maps the params key-value pairs to the corresponding struct object's attributes.
|
||||
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the
|
||||
// custom key name and the attribute name(case sensitive).
|
||||
//
|
||||
@ -347,7 +347,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) (e
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = errors.New(
|
||||
fmt.Sprintf(`cannot convert value "%d" to type "%s"`,
|
||||
fmt.Sprintf(`cannot convert value "%+v" to type "%s"`,
|
||||
value,
|
||||
structFieldValue.Type().String(),
|
||||
),
|
||||
|
||||
@ -4,10 +4,11 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package grand provides high performance random string generation functionality.
|
||||
// Package grand provides high performance random bytes/number/string generation functionality.
|
||||
package grand
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@ -18,14 +19,38 @@ var (
|
||||
characters = letters + digits + symbols // 94
|
||||
)
|
||||
|
||||
// Meet randomly calculate whether the given probability <num>/<total> is met.
|
||||
func Meet(num, total int) bool {
|
||||
return Intn(total) < num
|
||||
// Intn returns a int number which is between 0 and max: [0, max).
|
||||
//
|
||||
// Note that:
|
||||
// 1. The <max> can only be greater than 0, or else it returns <max> directly;
|
||||
// 2. The result is greater than or equal to 0, but less than <max>;
|
||||
// 3. The result number is 32bit and less than math.MaxUint32.
|
||||
func Intn(max int) int {
|
||||
if max <= 0 {
|
||||
return max
|
||||
}
|
||||
n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max
|
||||
if (max > 0 && n < 0) || (max < 0 && n > 0) {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// MeetProb randomly calculate whether the given probability is met.
|
||||
func MeetProb(prob float32) bool {
|
||||
return Intn(1e7) < int(prob*1e7)
|
||||
// B retrieves and returns random bytes of given length <n>.
|
||||
func B(n int) []byte {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
i := 0
|
||||
b := make([]byte, n)
|
||||
for {
|
||||
copy(b[i:], <-bufferChan)
|
||||
i += 4
|
||||
if i >= n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// N returns a random int between min and max: [min, max].
|
||||
@ -55,12 +80,18 @@ func N(min, max int) int {
|
||||
// The optional parameter <symbols> specifies whether the result could contain symbols,
|
||||
// which is false in default.
|
||||
func S(n int, symbols ...bool) string {
|
||||
b := make([]byte, n)
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = B(n)
|
||||
)
|
||||
for i := range b {
|
||||
if len(symbols) > 0 && symbols[0] {
|
||||
b[i] = characters[Intn(94)]
|
||||
b[i] = characters[numberBytes[i]%94]
|
||||
} else {
|
||||
b[i] = characters[Intn(62)]
|
||||
b[i] = characters[numberBytes[i]%62]
|
||||
}
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
@ -69,37 +100,67 @@ func S(n int, symbols ...bool) string {
|
||||
// Str randomly picks and returns <n> count of chars from given string <s>.
|
||||
// It also supports unicode string like Chinese/Russian/Japanese, etc.
|
||||
func Str(s string, n int) string {
|
||||
b := make([]rune, n)
|
||||
runes := []rune(s)
|
||||
for i := range b {
|
||||
b[i] = runes[Intn(len(runes))]
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
b = make([]rune, n)
|
||||
runes = []rune(s)
|
||||
)
|
||||
if len(runes) <= 255 {
|
||||
numberBytes := B(n)
|
||||
for i := range b {
|
||||
b[i] = runes[int(numberBytes[i])%len(runes)]
|
||||
}
|
||||
} else {
|
||||
for i := range b {
|
||||
b[i] = runes[Intn(len(runes))]
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Digits returns a random string which contains only digits, and its length is <n>.
|
||||
func Digits(n int) string {
|
||||
b := make([]byte, n)
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = B(n)
|
||||
)
|
||||
for i := range b {
|
||||
b[i] = digits[Intn(10)]
|
||||
b[i] = digits[numberBytes[i]%10]
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// Letters returns a random string which contains only letters, and its length is <n>.
|
||||
func Letters(n int) string {
|
||||
b := make([]byte, n)
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = B(n)
|
||||
)
|
||||
for i := range b {
|
||||
b[i] = letters[Intn(52)]
|
||||
b[i] = letters[numberBytes[i]%52]
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// Symbols returns a random string which contains only symbols, and its length is <n>.
|
||||
func Symbols(n int) string {
|
||||
b := make([]byte, n)
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = B(n)
|
||||
)
|
||||
for i := range b {
|
||||
b[i] = symbols[Intn(52)]
|
||||
b[i] = symbols[numberBytes[i]%32]
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
@ -115,3 +176,13 @@ func Perm(n int) []int {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Meet randomly calculate whether the given probability <num>/<total> is met.
|
||||
func Meet(num, total int) bool {
|
||||
return Intn(total) < num
|
||||
}
|
||||
|
||||
// MeetProb randomly calculate whether the given probability is met.
|
||||
func MeetProb(prob float32) bool {
|
||||
return Intn(1e7) < int(prob*1e7)
|
||||
}
|
||||
|
||||
44
util/grand/grand_buffer.go
Normal file
44
util/grand/grand_buffer.go
Normal file
@ -0,0 +1,44 @@
|
||||
// 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 grand
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
// Buffer size for uint32 random number.
|
||||
gBUFFER_SIZE = 10000
|
||||
)
|
||||
|
||||
var (
|
||||
// bufferChan is the buffer for random bytes,
|
||||
// every item storing 4 bytes.
|
||||
bufferChan = make(chan []byte, gBUFFER_SIZE)
|
||||
)
|
||||
|
||||
func init() {
|
||||
go asyncProducingRandomBufferBytesLoop()
|
||||
}
|
||||
|
||||
// asyncProducingRandomBufferBytes is a named goroutine, which uses a asynchronous goroutine
|
||||
// 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)
|
||||
for {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,73 +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 grand
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
// Buffer size for uint32 random number.
|
||||
gBUFFER_SIZE = 10000
|
||||
)
|
||||
|
||||
var (
|
||||
// Buffer chan.
|
||||
bufferChan = make(chan uint32, gBUFFER_SIZE)
|
||||
)
|
||||
|
||||
// It uses a asynchronous goroutine to produce the random number,
|
||||
// and a buffer chan to store the random number. So it has high performance
|
||||
// to generate random number.
|
||||
func init() {
|
||||
step := 0
|
||||
buffer := make([]byte, 1024)
|
||||
go func() {
|
||||
for {
|
||||
if n, err := rand.Read(buffer); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
for i := 0; i < n-4; {
|
||||
bufferChan <- binary.LittleEndian.Uint32(buffer[i : i+4])
|
||||
i++
|
||||
}
|
||||
// Reuse the rand buffer.
|
||||
for i := 0; i < n; i++ {
|
||||
step = int(buffer[0]) % 10
|
||||
if step != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if step == 0 {
|
||||
step = 2
|
||||
}
|
||||
for i := 0; i < n-4; {
|
||||
bufferChan <- binary.BigEndian.Uint32(buffer[i : i+4])
|
||||
i += step
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Intn returns a int number which is between 0 and max - [0, max).
|
||||
//
|
||||
// Note:
|
||||
// 1. The <max> can only be geater than 0, or else it return <max> directly;
|
||||
// 2. The result is greater than or equal to 0, but less than <max>;
|
||||
// 3. The result number is 32bit and less than math.MaxUint32.
|
||||
func Intn(max int) int {
|
||||
if max <= 0 {
|
||||
return max
|
||||
}
|
||||
n := int(<-bufferChan) % max
|
||||
if (max > 0 && n < 0) || (max < 0 && n > 0) {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user