Compare commits

...

30 Commits

Author SHA1 Message Date
58b2efc900 version update 2019-10-24 21:05:23 +08:00
28326606f5 fix issue in resource file handler for ghttp.Server 2019-10-24 20:22:37 +08:00
91c98bbb60 fix issue in group router feature for domain 2019-10-24 19:44:30 +08:00
c0236d7dfa improve gstr.SplitAndTrim 2019-10-22 13:57:21 +08:00
d4051df5b6 improve gdb/gstr/gconv/garray 2019-10-21 19:13:25 +08:00
88045417ff fix issue in field type check for gdb 2019-10-17 20:57:49 +08:00
500efb5601 fix issue in template for ghttp.Server 2019-10-17 20:31:03 +08:00
97fe8235da fix issue in session expiring for file storage of gsession; add Pop/Pops functions for gset/gmap 2019-10-16 23:33:06 +08:00
e1164e935b improve configuration for gdb; add more unit test case for ghttp.Server 2019-10-15 21:20:38 +08:00
b26330aee1 rename cacheTime to cacheExpire and change its type from int to time.Duration for gdb.Model; fix issue in string support of function Data for gdb 2019-10-15 17:44:47 +08:00
2b083709b5 add gmap.ListMap/TreeMap support for gdb.Where 2019-10-14 23:27:48 +08:00
0ac45dc379 rename file name from link_map to list_map for package gmap 2019-10-14 22:43:31 +08:00
650916c22a version updates 2019-10-14 13:46:45 +08:00
2804183325 fix issue in content-type response for ghttp.Server 2019-10-14 13:46:16 +08:00
2dc2610621 improve gdb; README update 2019-10-13 23:05:48 +08:00
1736e71e6b add debug for unit test case of 2019-10-13 22:40:23 +08:00
69ee5375b9 improve Join/String functions for garray/gset/gmap; add AddIfNotExistFunc/AddIfNotExistFuncLock functions for gset 2019-10-13 22:31:28 +08:00
3ac0a66887 improve unit test cases for gspath 2019-10-13 21:42:04 +08:00
20e873a1fc version updates 2019-10-13 00:40:06 +08:00
142484d89c add support for like statement in map key for Where function, improve error format for gdb 2019-10-13 00:37:25 +08:00
76a9f4ca14 fix security issue in static file feature for ghttp.Server 2019-10-12 23:56:03 +08:00
c4e5679d5c fix issue in missing passing MaxIdle/MaxActive configuration to underlying redis pool for gredis; add extra output for unit test cases of ghttp.Server 2019-10-11 23:33:21 +08:00
b08d7c3c38 change Join function feature for garray 2019-10-11 23:17:29 +08:00
5092d8e6c5 improve status handling and add error logger for ghttp.Server; fix issue in defer error warpping for some packages 2019-10-11 22:54:25 +08:00
74d625ff97 add Get*Int32/Get*Int64/Get*Uint32/Get*Uint64 functions for ghttp.Request 2019-10-10 10:40:38 +08:00
f1119e28e8 add Get*Int32/Get*Int64/Get*Uint32/Get*Uint64 functions for ghttp.Request 2019-10-10 10:33:14 +08:00
3082c7f761 fix issue in StrLimit for gstr 2019-10-10 00:04:38 +08:00
5f36614dd7 remove UseNumber for json decoding in package gjson 2019-10-09 20:33:26 +08:00
1dcc7a4887 improve http status handling for middleware of ghttp.Server 2019-10-09 15:26:50 +08:00
41e9d35487 improve middleware feature for ghttp.Server; fix issue memory usage in big file downloading 2019-10-09 00:33:58 +08:00
146 changed files with 3332 additions and 1195 deletions

View File

@ -1,5 +1,6 @@
# MySQL数据库配置
[database]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"

View File

@ -0,0 +1,24 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
// error!
r, err := g.DB().Table("user").Where(g.Map{
"or": g.Map{
"nickname": "jim",
"create_time > ": "2019-10-01",
},
"and": g.Map{
"nickname": "tom",
"create_time > ": "2019-10-01",
},
}).All()
if err != nil {
panic(err)
}
g.Dump(r)
}

View File

@ -7,7 +7,7 @@ import (
func main() {
db := g.DB()
db.SetDebug(true)
//db.SetDebug(true)
type User struct {
Id int

View File

@ -0,0 +1,34 @@
package main
import (
"github.com/gogf/gf/frame/g"
"time"
)
func test1() {
db := g.DB()
db.SetDebug(true)
time.Sleep(1 * time.Minute)
r, e := db.Table("test").Where("id", 10000).Count()
if e != nil {
panic(e)
}
g.Dump(r)
}
func test2() {
db := g.DB()
db.SetDebug(true)
dao := db.Table("test").Safe()
time.Sleep(1 * time.Minute)
r, e := dao.Where("id", 10000).Count()
if e != nil {
panic(e)
}
g.Dump(r)
}
func main() {
test1()
test2()
}

View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/gogf/gf/encoding/gjson"
)
func main() {
s := `
{"apiVersion":"v1","kind":"Service","metadata":{"labels":{"name":"http-daemon"},"name":"http-daemon","namespace":"default"},"spec":{"ports":[{"name":"http-daemon","port":8080,"protocol":"TCP","targetPort":9212}],"selector":{"app":"http-daemon","version":"v0930-082326"}}}
`
js, err := gjson.DecodeToJson(s)
if err != nil {
panic(err)
}
//g.Dump(js.ToMap())
y, _ := js.ToYamlString()
fmt.Println(y)
}

View File

@ -0,0 +1,60 @@
// This is auto-generated by gf cli tool. You may not really want to edit it.
package defaults
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
)
import (
"github.com/gogf/gf/os/gtime"
)
// User is the golang structure for table user.
type User struct {
Id int `orm:"id,primary" json:"id"`
Passport string `orm:"passport" json:"passport"`
Password string `orm:"password" json:"password"`
Nickname string `orm:"nickname,unique" json:"nickname"`
CreateTime *gtime.Time `orm:"create_time" json:"create_time"`
}
var (
// TableUser is the table name of user.
TableUser = "user"
// ModelUser is the model object of user.
ModelUser = g.DB("default").Table(TableUser).Safe()
)
// Inserts does "INSERT...INTO..." statement for inserting current object into table.
func (r *User) Insert() (result sql.Result, err error) {
return ModelUser.Data(r).Insert()
}
// Replace does "REPLACE...INTO..." statement for inserting current object into table.
// If there's already another same record in the table (it checks using primary key or unique index),
// it deletes it and insert this one.
func (r *User) Replace() (result sql.Result, err error) {
return ModelUser.Data(r).Replace()
}
// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Save() (result sql.Result, err error) {
return ModelUser.Data(r).Save()
}
// Update does "UPDATE...WHERE..." statement for updating current object from table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Update() (result sql.Result, err error) {
return ModelUser.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
}
// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
func (r *User) Delete() (result sql.Result, err error) {
return ModelUser.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
}

View File

@ -1,4 +1,11 @@
viewpath = "/home/www/templates"
# MySQL数据库配置
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"

View File

@ -1,15 +1,13 @@
package main
import (
_ "github.com/gogf/gf/.example/frame/mvc/controller/demo"
_ "github.com/gogf/gf/.example/frame/mvc/controller/stats"
"github.com/gogf/gf/frame/g"
"fmt"
"github.com/gogf/gf/.example/frame/mvc/app/model/defaults"
"github.com/gogf/gf/database/gdb"
)
func main() {
//g.Server().SetDumpRouteMap(false)
g.Server().SetPort(8199)
g.Server().Run()
u := defaults.User{Id: 1, Nickname: "test"}
fmt.Println(gdb.GetWhereConditionOfStruct(&u))
fmt.Println(u.Replace())
}

View File

@ -1,8 +0,0 @@
package test
import "github.com/gogf/gf/database/gdb"
var (
// ConfigGroup is the configuration group name for this model.
ConfigGroup = gdb.DEFAULT_GROUP_NAME
)

View File

@ -1,92 +0,0 @@
package test
import (
"database/sql"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/os/gtime"
)
// User is the golang structure for table user.
type User struct {
Id int `orm:"id,primary" json:"id"`
Passport string `orm:"passport" json:"passport"`
Password string `orm:"password" json:"password"`
NickName string `orm:"nickname" json:"nick_name"`
CreateTime *gtime.Time `orm:"create_time" json:"create_time"`
}
// UserModel is the model of convenient operations for table user.
type UserModel struct {
*gdb.Model
TableName string
}
var (
// UserTableName is the table name of user.
UserTableName = "user"
)
// ModelUser creates and returns a new model object for table user.
func ModelUser() *UserModel {
return &UserModel{
g.DB(ConfigGroup).Table(UserTableName).Safe(),
UserTableName,
}
}
// Inserts does "INSERT...INTO..." statement for inserting current object into table.
func (r *User) Insert() (result sql.Result, err error) {
return ModelUser().Data(r).Insert()
}
// Replace does "REPLACE...INTO..." statement for inserting current object into table.
// If there's already another same record in the table (it checks using primary key or unique index),
// it deletes it and insert this one.
func (r *User) Replace() (result sql.Result, err error) {
return ModelUser().Data(r).Replace()
}
// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Save() (result sql.Result, err error) {
return ModelUser().Data(r).Save()
}
// Update does "UPDATE...WHERE..." statement for updating current object from table.
// It updates the record if there's already another same record in the table
// (it checks using primary key or unique index).
func (r *User) Update() (result sql.Result, err error) {
return ModelUser().Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
}
// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
func (r *User) Delete() (result sql.Result, err error) {
return ModelUser().Where(gdb.GetWhereConditionOfStruct(r)).Delete()
}
// Select overwrite the Select method from gdb.Model for model
// as retuning all objects with specified structure.
func (m *UserModel) Select() ([]*User, error) {
array := ([]*User)(nil)
if err := m.Scan(&array); err != nil {
return nil, err
}
return array, nil
}
// First does the same logistics as One method from gdb.Model for model
// as retuning first/one object with specified structure.
func (m *UserModel) First() (*User, error) {
list, err := m.Select()
if err != nil {
return nil, err
}
if len(list) > 0 {
return list[0], nil
}
return nil, nil
}

View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindMiddlewareDefault(func(r *ghttp.Request) {
fmt.Println("cors")
r.Response.CORSDefault()
r.Middleware.Next()
})
s.BindHandler("/api/captcha", func(r *ghttp.Request) {
r.Response.Write("captcha")
})
s.SetPort(8010)
s.Run()
}

View File

@ -9,7 +9,7 @@ import (
func main() {
s := g.Server()
s.SetSessionMaxAge(2 * time.Second)
s.SetSessionMaxAge(61 * time.Second)
s.BindHandler("/set", func(r *ghttp.Request) {
r.Session.Set("time", gtime.Second())
r.Response.Write("ok")

View File

@ -0,0 +1,11 @@
package main
import (
"context"
"fmt"
)
func main() {
c := context.WithValue(context.Background(), "key", "value")
fmt.Printf("%v", c)
}

View File

@ -0,0 +1,16 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
tplContent := `
{{"我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人"| strlimit 10 "..."}}
`
content, err := g.View().ParseContent(tplContent, nil)
fmt.Println(err)
fmt.Println(content)
}

View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
s := "我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人我是中国人"
tplContent := `
{{.str | strlimit 10 "..."}}
`
content, err := g.View().ParseContent(tplContent, g.Map{
"str": s,
})
fmt.Println(err)
fmt.Println(content)
}

View File

@ -1,12 +1,47 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
b, _ := json.Marshal([]interface{}{1, 2, 3, 4, 5, 123.456, "a"})
fmt.Println(gconv.String(b))
func loadRouter(domain *ghttp.Domain) {
domain.Group("/", func(g *ghttp.RouterGroup) {
g.Group("/app", func(gApp *ghttp.RouterGroup) {
// 该路由规则仅会在GET请求下有效
gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在GET请求及localhost域名下有效
gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在DELETE请求下有效
gApp.DELETE("/comment/{id}", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
})
// 该路由规则仅会在GET请求下有效
g.GET("/{table}/list/{page}.html", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在GET请求及localhost域名下有效
g.GET("/order/info/{order_id}", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在DELETE请求下有效
g.DELETE("/comment/{id}", func(r *ghttp.Request) {
r.Response.WriteJson(r.Router)
})
})
}
func main() {
s := g.Server()
domain := s.Domain("localhost")
loadRouter(domain)
s.SetPort(8199)
s.Run()
}

View File

@ -1,32 +0,0 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
// 演示slice类型属性的赋值
func main() {
type User struct {
Scores []int
}
user := new(User)
scores := []interface{}{99, 100, 60, 140}
// 通过map映射转换
if err := gconv.Struct(g.Map{"Scores": scores}, user); err != nil {
fmt.Println(err)
} else {
g.Dump(user)
}
// 通过变量映射转换直接slice赋值
if err := gconv.Struct(scores, user); err != nil {
fmt.Println(err)
} else {
g.Dump(user)
}
}

View File

@ -21,8 +21,8 @@ func main() {
user1 := new(User1)
user2 := new(User2)
scores := map[string]interface{}{
"Scores": map[string]interface{}{
scores := g.Map{
"Scores": g.Map{
"Name": "john",
"Result": 100,
},

View File

@ -17,8 +17,8 @@ func main() {
}
user := new(User)
scores := map[string]interface{}{
"Scores": map[string]interface{}{
scores := g.Map{
"Scores": g.Map{
"Name": "john",
"Result": 100,
},

View File

@ -17,13 +17,13 @@ func main() {
}
user := new(User)
scores := map[string]interface{}{
"Scores": []interface{}{
map[string]interface{}{
scores := g.Map{
"Scores": g.Slice{
g.Map{
"Name": "john",
"Result": 100,
},
map[string]interface{}{
g.Map{
"Name": "smith",
"Result": 60,
},

View File

@ -14,7 +14,7 @@ English | [简体中文](README_ZH.MD)
# Installation
```
go get -u github.com/gogf/gf
go get -u -v github.com/gogf/gf
```
suggested using `go.mod`:
```

View File

@ -15,6 +15,7 @@
# 特点
* 模块化、松耦合设计;
* 模块丰富,开箱即用;
* 简便及可维护性为宗旨;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;

View File

@ -370,6 +370,11 @@ func (a *Array) Slice() []interface{} {
}
}
// Interfaces returns current array as []interface{}.
func (a *Array) Interfaces() []interface{} {
return a.Slice()
}
// Clone returns a new array, which is a copy of current array.
func (a *Array) Clone() (newArray *Array) {
a.mu.RLock()
@ -584,14 +589,8 @@ func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
@ -610,9 +609,26 @@ func (a *Array) CountValues() map[interface{}]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *Array) String() string {
return "[" + a.Join(",") + "]"
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.

View File

@ -376,6 +376,17 @@ func (a *IntArray) Slice() []int {
return array
}
// Interfaces returns current array as []interface{}.
func (a *IntArray) Interfaces() []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *IntArray) Clone() (newArray *IntArray) {
a.mu.RLock()
@ -610,7 +621,7 @@ func (a *IntArray) CountValues() map[int]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *IntArray) String() string {
return "[" + a.Join(",") + "]"
}

View File

@ -378,6 +378,17 @@ func (a *StrArray) Slice() []string {
return array
}
// Interfaces returns current array as []interface{}.
func (a *StrArray) Interfaces() []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *StrArray) Clone() (newArray *StrArray) {
a.mu.RLock()
@ -592,7 +603,7 @@ func (a *StrArray) Join(glue string) string {
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(`"` + gstr.QuoteMeta(v, `"\`) + `"`)
buffer.WriteString(v)
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
@ -611,9 +622,20 @@ func (a *StrArray) CountValues() map[string]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *StrArray) String() string {
return "[" + a.Join(",") + "]"
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
for k, v := range a.array {
buffer.WriteString(`"` + gstr.QuoteMeta(v, `"\`) + `"`)
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.

View File

@ -342,6 +342,11 @@ func (a *SortedArray) Slice() []interface{} {
return array
}
// Interfaces returns current array as []interface{}.
func (a *SortedArray) Interfaces() []interface{} {
return a.Slice()
}
// Contains checks whether a value exists in the array.
func (a *SortedArray) Contains(value interface{}) bool {
return a.Search(value) != -1
@ -529,14 +534,8 @@ func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
@ -555,9 +554,26 @@ func (a *SortedArray) CountValues() map[interface{}]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedArray) String() string {
return "[" + a.Join(",") + "]"
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.

View File

@ -328,6 +328,17 @@ func (a *SortedIntArray) Slice() []int {
return array
}
// Interfaces returns current array as []interface{}.
func (a *SortedIntArray) Interfaces() []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
}
// Contains checks whether a value exists in the array.
func (a *SortedIntArray) Contains(value int) bool {
return a.Search(value) != -1
@ -535,7 +546,7 @@ func (a *SortedIntArray) CountValues() map[int]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedIntArray) String() string {
return "[" + a.Join(",") + "]"
}

View File

@ -329,6 +329,17 @@ func (a *SortedStrArray) Slice() []string {
return array
}
// Interfaces returns current array as []interface{}.
func (a *SortedStrArray) Interfaces() []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
}
// Contains checks whether a value exists in the array.
func (a *SortedStrArray) Contains(value string) bool {
return a.Search(value) != -1
@ -517,7 +528,7 @@ func (a *SortedStrArray) Join(glue string) string {
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(`"` + gstr.QuoteMeta(v, `"\`) + `"`)
buffer.WriteString(v)
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
@ -536,9 +547,20 @@ func (a *SortedStrArray) CountValues() map[string]int {
return m
}
// String returns current array as a string.
// String returns current array as a string, which implements like json.Marshal does.
func (a *SortedStrArray) String() string {
return "[" + a.Join(",") + "]"
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
for k, v := range a.array {
buffer.WriteString(`"` + gstr.QuoteMeta(v, `"\`) + `"`)
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.

View File

@ -26,6 +26,7 @@ func Test_Array_Basic(t *testing.T) {
array2 := garray.NewArrayFrom(expect)
array3 := garray.NewArrayFrom([]interface{}{})
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.Interfaces(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
@ -261,7 +262,7 @@ func TestArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, `"a"`, `\a`}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Join("."), `0.1."\"a\""."\\a"`)
gtest.Assert(array1.Join("."), `0.1."a".\a`)
})
}

View File

@ -27,6 +27,7 @@ func Test_IntArray_Basic(t *testing.T) {
array := garray.NewIntArrayFrom(expect)
array2 := garray.NewIntArrayFrom(expect2)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.Interfaces(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)

View File

@ -27,6 +27,7 @@ func Test_StrArray_Basic(t *testing.T) {
array2 := garray.NewStrArrayFrom(expect, true)
array3 := garray.NewStrArrayFrom([]string{})
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.Interfaces(), expect)
array.Set(0, "100")
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
@ -248,12 +249,12 @@ func TestStrArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStrArrayFrom(a1)
gtest.Assert(array1.Join("."), `"0"."1"."2"."3"."4"."5"."6"`)
gtest.Assert(array1.Join("."), `0.1.2.3.4.5.6`)
})
gtest.Case(t, func() {
a1 := []string{"0", "1", `"a"`, `\a`}
array1 := garray.NewStrArrayFrom(a1)
gtest.Assert(array1.Join("."), `"0"."1"."\"a\""."\\a"`)
gtest.Assert(array1.Join("."), `0.1."a".\a`)
})
}

View File

@ -394,14 +394,14 @@ func TestSortedArray_Join(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
gtest.Assert(array1.Join(","), `"a","c","d"`)
gtest.Assert(array1.Join("."), `"a"."c"."d"`)
gtest.Assert(array1.Join(","), `a,c,d`)
gtest.Assert(array1.Join("."), `a.c.d`)
})
gtest.Case(t, func() {
a1 := []interface{}{0, 1, `"a"`, `\a`}
array1 := garray.NewSortedArrayFrom(a1, gutil.ComparatorString)
gtest.Assert(array1.Join("."), `"\"a\"".0.1."\\a"`)
gtest.Assert(array1.Join("."), `"a".0.1.\a`)
})
}
@ -561,6 +561,7 @@ func TestSortedArray_Json(t *testing.T) {
err := json.Unmarshal(b2, &a3)
gtest.Assert(err, nil)
gtest.Assert(a3.Slice(), s1)
gtest.Assert(a3.Interfaces(), s1)
})
gtest.Case(t, func() {

View File

@ -26,6 +26,7 @@ func TestNewSortedIntArrayFrom(t *testing.T) {
array1 := garray.NewSortedIntArrayFrom(a1, true)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
gtest.Assert(array1.Slice(), a1)
gtest.Assert(array1.Interfaces(), a1)
})
}

View File

@ -290,14 +290,14 @@ func TestSortedStrArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStrArrayFrom(a1)
gtest.Assert(array1.Join(","), `"a","d","e"`)
gtest.Assert(array1.Join("."), `"a"."d"."e"`)
gtest.Assert(array1.Join(","), `a,d,e`)
gtest.Assert(array1.Join("."), `a.d.e`)
})
gtest.Case(t, func() {
a1 := []string{"a", `"b"`, `\c`}
array1 := garray.NewSortedStrArrayFrom(a1)
gtest.Assert(array1.Join("."), `"\"b\""."\\c"."a"`)
gtest.Assert(array1.Join("."), `"b".\c.a`)
})
}
@ -448,11 +448,13 @@ func TestSortedStrArray_Json(t *testing.T) {
a2 := garray.NewSortedStrArray()
err1 = json.Unmarshal(b2, &a2)
gtest.Assert(a2.Slice(), s2)
gtest.Assert(a2.Interfaces(), s2)
var a3 garray.SortedStrArray
err := json.Unmarshal(b2, &a3)
gtest.Assert(err, nil)
gtest.Assert(a3.Slice(), s1)
gtest.Assert(a3.Interfaces(), s1)
})
gtest.Case(t, func() {

View File

@ -141,6 +141,41 @@ func (m *AnyAnyMap) Get(key interface{}) interface{} {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *AnyAnyMap) Pop() (key, value interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[interface{}]interface{}, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -321,10 +356,7 @@ func (m *AnyAnyMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *AnyAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -141,6 +141,41 @@ func (m *IntAnyMap) Get(key int) interface{} {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *IntAnyMap) Pop() (key int, value interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *IntAnyMap) Pops(size int) map[int]interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]interface{}, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -321,10 +356,7 @@ func (m *IntAnyMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -139,6 +139,41 @@ func (m *IntIntMap) Get(key int) int {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *IntIntMap) Pop() (key, value int) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *IntIntMap) Pops(size int) map[int]int {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]int, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -298,10 +333,7 @@ func (m *IntIntMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -139,6 +139,41 @@ func (m *IntStrMap) Get(key int) string {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *IntStrMap) Pop() (key int, value string) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *IntStrMap) Pops(size int) map[int]string {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]string, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -298,10 +333,7 @@ func (m *IntStrMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -135,6 +135,41 @@ func (m *StrAnyMap) Get(key string) interface{} {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *StrAnyMap) Pop() (key string, value interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *StrAnyMap) Pops(size int) map[string]interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]interface{}, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -317,10 +352,7 @@ func (m *StrAnyMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -139,6 +139,41 @@ func (m *StrIntMap) Get(key string) int {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *StrIntMap) Pop() (key string, value int) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *StrIntMap) Pops(size int) map[string]int {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]int, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -300,10 +335,7 @@ func (m *StrIntMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -139,6 +139,41 @@ func (m *StrStrMap) Get(key string) string {
return val
}
// Pop retrieves and deletes an item from the map.
func (m *StrStrMap) Pop() (key, value string) {
m.mu.Lock()
defer m.mu.Unlock()
for key, value = range m.data {
delete(m.data, key)
return
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *StrStrMap) Pops(size int) map[string]string {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]string, size)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
@ -300,10 +335,7 @@ func (m *StrStrMap) Size() int {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.

View File

@ -189,6 +189,45 @@ func (m *ListMap) Get(key interface{}) (value interface{}) {
return
}
// Pop retrieves and deletes an item from the map.
func (m *ListMap) Pop() (key, value interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
for k, e := range m.data {
value = e.Value.(*gListMapNode).value
delete(m.data, k)
m.list.Remove(e)
return k, value
}
return
}
// Pops retrieves and deletes <size> items from the map.
// It returns all items if size == -1.
func (m *ListMap) Pops(size int) map[interface{}]interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if size > len(m.data) || size == -1 {
size = len(m.data)
}
if size == 0 {
return nil
}
index := 0
newMap := make(map[interface{}]interface{}, size)
for k, e := range m.data {
value := e.Value.(*gListMapNode).value
delete(m.data, k)
m.list.Remove(e)
newMap[k] = value
index++
if index == size {
break
}
}
return newMap
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"testing"
@ -221,3 +222,56 @@ func Test_AnyAnyMap_Json(t *testing.T) {
gtest.Assert(m.Get("k2"), data["k2"])
})
}
func Test_AnyAnyMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewAnyAnyMapFrom(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{"k1", "k2"})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{"k1", "k2"})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_AnyAnyMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewAnyAnyMapFrom(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -203,3 +204,56 @@ func Test_IntAnyMap_Json(t *testing.T) {
gtest.Assert(m.Get(2), data[2])
})
}
func Test_IntAnyMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntAnyMapFrom(g.MapIntAny{
1: "v1",
2: "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{1, 2})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{1, 2})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_IntAnyMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntAnyMapFrom(g.MapIntAny{
1: "v1",
2: "v2",
3: "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -206,3 +207,56 @@ func Test_IntIntMap_Json(t *testing.T) {
gtest.Assert(m.Get(2), data[2])
})
}
func Test_IntIntMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntIntMapFrom(g.MapIntInt{
1: 11,
2: 22,
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{1, 2})
gtest.AssertIN(v1, g.Slice{11, 22})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{1, 2})
gtest.AssertIN(v2, g.Slice{11, 22})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_IntIntMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntIntMapFrom(g.MapIntInt{
1: 11,
2: 22,
3: 33,
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{11, 22, 33})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{11, 22, 33})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -207,3 +208,56 @@ func Test_IntStrMap_Json(t *testing.T) {
gtest.Assert(m.Get(2), data[2])
})
}
func Test_IntStrMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntStrMapFrom(g.MapIntStr{
1: "v1",
2: "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{1, 2})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{1, 2})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_IntStrMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntStrMapFrom(g.MapIntStr{
1: "v1",
2: "v2",
3: "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{1, 2, 3})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/util/gconv"
"testing"
@ -177,3 +178,56 @@ func Test_ListMap_Json(t *testing.T) {
gtest.Assert(m.Get("k2"), data["k2"])
})
}
func Test_ListMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewListMapFrom(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{"k1", "k2"})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{"k1", "k2"})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_ListMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewListMapFrom(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -215,3 +216,56 @@ func Test_StrAnyMap_Json(t *testing.T) {
gtest.Assert(m.Get("k2"), data["k2"])
})
}
func Test_StrAnyMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrAnyMapFrom(g.MapStrAny{
"k1": "v1",
"k2": "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{"k1", "k2"})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{"k1", "k2"})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_StrAnyMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrAnyMapFrom(g.MapStrAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -218,3 +219,56 @@ func Test_StrIntMap_Json(t *testing.T) {
gtest.Assert(m.Get("k2"), data["k2"])
})
}
func Test_StrIntMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrIntMapFrom(g.MapStrInt{
"k1": 11,
"k2": 22,
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{"k1", "k2"})
gtest.AssertIN(v1, g.Slice{11, 22})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{"k1", "k2"})
gtest.AssertIN(v2, g.Slice{11, 22})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_StrIntMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrIntMapFrom(g.MapStrInt{
"k1": 11,
"k2": 22,
"k3": 33,
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{11, 22, 33})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{11, 22, 33})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"testing"
@ -215,3 +216,56 @@ func Test_StrStrMap_Json(t *testing.T) {
gtest.Assert(m.Get("k2"), data["k2"])
})
}
func Test_StrStrMap_Pop(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrStrMapFrom(g.MapStrStr{
"k1": "v1",
"k2": "v2",
})
gtest.Assert(m.Size(), 2)
k1, v1 := m.Pop()
gtest.AssertIN(k1, g.Slice{"k1", "k2"})
gtest.AssertIN(v1, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 1)
k2, v2 := m.Pop()
gtest.AssertIN(k2, g.Slice{"k1", "k2"})
gtest.AssertIN(v2, g.Slice{"v1", "v2"})
gtest.Assert(m.Size(), 0)
gtest.AssertNE(k1, k2)
gtest.AssertNE(v1, v2)
})
}
func Test_StrStrMap_Pops(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrStrMapFrom(g.MapStrStr{
"k1": "v1",
"k2": "v2",
"k3": "v3",
})
gtest.Assert(m.Size(), 3)
kArray := garray.New()
vArray := garray.New()
for k, v := range m.Pops(1) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 2)
for k, v := range m.Pops(2) {
gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"})
gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"})
kArray.Append(k)
vArray.Append(v)
}
gtest.Assert(m.Size(), 0)
gtest.Assert(kArray.Unique().Len(), 3)
gtest.Assert(vArray.Unique().Len(), 3)
})
}

View File

@ -71,6 +71,48 @@ func (set *Set) Add(item ...interface{}) *Set {
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *Set) AddIfNotExistFunc(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *Set) AddIfNotExistFuncLock(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *Set) doAddWithLockCheck(item interface{}, value interface{}) interface{} {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() interface{}); ok {
item = f()
} else {
item = value
}
}
set.data[item] = struct{}{}
return item
}
// Contains checks whether the set contains <item>.
func (set *Set) Contains(item interface{}) bool {
set.mu.RLock()
@ -121,6 +163,24 @@ func (set *Set) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
for k, _ := range set.data {
buffer.WriteString(gconv.String(k))
if i != l-1 {
buffer.WriteString(glue)
}
i++
}
return buffer.String()
}
// String returns items as a string, which implements like json.Marshal does.
func (set *Set) String() string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
s := ""
l := len(set.data)
i := 0
@ -132,18 +192,14 @@ func (set *Set) Join(glue string) string {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
if i != l-1 {
buffer.WriteString(glue)
buffer.WriteByte(',')
}
i++
}
buffer.WriteByte(']')
return buffer.String()
}
// String returns items as a string, which are joined by char ','.
func (set *Set) String() string {
return "[" + set.Join(",") + "]"
}
// LockFunc locks writing with callback function <f>.
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) {
set.mu.Lock()
@ -317,24 +373,30 @@ func (set *Set) Sum() (sum int) {
// Pops randomly pops an item from set.
func (set *Set) Pop() interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
set.mu.Lock()
defer set.mu.Unlock()
for k, _ := range set.data {
delete(set.data, k)
return k
}
return nil
}
// Pops randomly pops <size> items from set.
// It returns all items if size == -1.
func (set *Set) Pops(size int) []interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.data) {
set.mu.Lock()
defer set.mu.Unlock()
if size > len(set.data) || size == -1 {
size = len(set.data)
}
if size <= 0 {
return nil
}
index := 0
array := make([]interface{}, size)
for k, _ := range set.data {
delete(set.data, k)
array[index] = k
index++
if index == size {

View File

@ -64,6 +64,48 @@ func (set *IntSet) Add(item ...int) *IntSet {
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *IntSet) AddIfNotExistFunc(item int, f func() int) *IntSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() int) *IntSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *IntSet) doAddWithLockCheck(item int, value interface{}) int {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() int); ok {
item = f()
} else {
item = value.(int)
}
}
set.data[item] = struct{}{}
return item
}
// Contains checks whether the set contains <item>.
func (set *IntSet) Contains(item int) bool {
set.mu.RLock()
@ -126,7 +168,7 @@ func (set *IntSet) Join(glue string) string {
return buffer.String()
}
// String returns items as a string, which are joined by char ','.
// String returns items as a string, which implements like json.Marshal does.
func (set *IntSet) String() string {
return "[" + set.Join(",") + "]"
}
@ -304,24 +346,30 @@ func (set *IntSet) Sum() (sum int) {
// Pops randomly pops an item from set.
func (set *IntSet) Pop() int {
set.mu.RLock()
defer set.mu.RUnlock()
set.mu.Lock()
defer set.mu.Unlock()
for k, _ := range set.data {
delete(set.data, k)
return k
}
return 0
}
// Pops randomly pops <size> items from set.
// It returns all items if size == -1.
func (set *IntSet) Pops(size int) []int {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.data) {
set.mu.Lock()
defer set.mu.Unlock()
if size > len(set.data) || size == -1 {
size = len(set.data)
}
if size <= 0 {
return nil
}
index := 0
array := make([]int, size)
for k, _ := range set.data {
delete(set.data, k)
array[index] = k
index++
if index == size {

View File

@ -65,6 +65,48 @@ func (set *StrSet) Add(item ...string) *StrSet {
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *StrSet) AddIfNotExistFunc(item string, f func() string) *StrSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() string) *StrSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *StrSet) doAddWithLockCheck(item string, value interface{}) string {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() string); ok {
item = f()
} else {
item = value.(string)
}
}
set.data[item] = struct{}{}
return item
}
// Contains checks whether the set contains <item>.
func (set *StrSet) Contains(item string) bool {
set.mu.RLock()
@ -119,11 +161,7 @@ func (set *StrSet) Join(glue string) string {
l := len(set.data)
i := 0
for k, _ := range set.data {
if gstr.IsNumeric(k) {
buffer.WriteString(k)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(k, `"\`) + `"`)
}
buffer.WriteString(k)
if i != l-1 {
buffer.WriteString(glue)
}
@ -132,9 +170,21 @@ func (set *StrSet) Join(glue string) string {
return buffer.String()
}
// String returns items as a string, which are joined by char ','.
// String returns items as a string, which implements like json.Marshal does.
func (set *StrSet) String() string {
return "[" + set.Join(",") + "]"
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
for k, _ := range set.data {
buffer.WriteString(`"` + gstr.QuoteMeta(k, `"\`) + `"`)
if i != l-1 {
buffer.WriteByte(',')
}
i++
}
return buffer.String()
}
// LockFunc locks writing with callback function <f>.
@ -310,24 +360,30 @@ func (set *StrSet) Sum() (sum int) {
// Pops randomly pops an item from set.
func (set *StrSet) Pop() string {
set.mu.RLock()
defer set.mu.RUnlock()
set.mu.Lock()
defer set.mu.Unlock()
for k, _ := range set.data {
delete(set.data, k)
return k
}
return ""
}
// Pops randomly pops <size> items from set.
// It returns all items if size == -1.
func (set *StrSet) Pops(size int) []string {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.data) {
set.mu.Lock()
defer set.mu.Unlock()
if size > len(set.data) || size == -1 {
size = len(set.data)
}
if size <= 0 {
return nil
}
index := 0
array := make([]string, size)
for k, _ := range set.data {
delete(set.data, k)
array[index] = k
index++
if index == size {

View File

@ -220,8 +220,8 @@ func TestSet_Join(t *testing.T) {
s1 := gset.New(true)
s1.Add("a").Add(`"b"`).Add(`\c`)
str1 := s1.Join(",")
gtest.Assert(strings.Contains(str1, `\"b\"`), true)
gtest.Assert(strings.Contains(str1, `\\c`), true)
gtest.Assert(strings.Contains(str1, `"b"`), true)
gtest.Assert(strings.Contains(str1, `\c`), true)
gtest.Assert(strings.Contains(str1, `a`), true)
})
}
@ -261,19 +261,35 @@ func TestSet_Sum(t *testing.T) {
func TestSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
gtest.AssertIN(s1.Pop(), []int{1, 2, 3, 4})
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(s.Pop(), []int{1, 2, 3, 4})
gtest.Assert(s.Size(), 3)
})
}
func TestSet_Pops(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
gtest.AssertIN(s1.Pops(1), []int{1, 2, 3, 4})
gtest.AssertIN(s1.Pops(6), []int{1, 2, 3, 4})
gtest.Assert(len(s1.Pops(2)), 2)
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s.Size(), 4)
gtest.Assert(s.Pops(0), nil)
gtest.AssertIN(s.Pops(1), []int{1, 2, 3, 4})
gtest.Assert(s.Size(), 3)
a := s.Pops(6)
gtest.Assert(len(a), 3)
gtest.AssertIN(a, []int{1, 2, 3, 4})
gtest.Assert(s.Size(), 0)
})
gtest.Case(t, func() {
s := gset.New(true)
a := []interface{}{1, 2, 3, 4}
s.Add(a...)
gtest.Assert(s.Size(), 4)
gtest.Assert(s.Pops(-2), nil)
gtest.AssertIN(s.Pops(-1), a)
})
}
@ -305,3 +321,43 @@ func TestSet_Json(t *testing.T) {
gtest.Assert(a3.Contains("e"), false)
})
}
func TestSet_AddIfNotExistFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.New(true)
s.Add(1)
gtest.Assert(s.Contains(1), true)
gtest.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() interface{} {
return 3
})
gtest.Assert(s.Contains(2), false)
gtest.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() interface{} {
return 4
})
gtest.Assert(s.Contains(3), true)
gtest.Assert(s.Contains(4), false)
})
gtest.Case(t, func() {
s := gset.New(true)
s.Add(1)
gtest.Assert(s.Contains(1), true)
gtest.Assert(s.Contains(2), false)
s.AddIfNotExistFuncLock(2, func() interface{} {
return 3
})
gtest.Assert(s.Contains(2), false)
gtest.Assert(s.Contains(3), true)
s.AddIfNotExistFuncLock(3, func() interface{} {
return 4
})
gtest.Assert(s.Contains(3), true)
gtest.Assert(s.Contains(4), false)
})
}

View File

@ -223,11 +223,36 @@ func TestIntSet_Sum(t *testing.T) {
func TestIntSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s1.Add(4).Add(2).Add(3)
gtest.AssertIN(s1.Pop(), []int{4, 2, 3})
gtest.AssertIN(s1.Pop(), []int{4, 2, 3})
gtest.Assert(s1.Size(), 3)
s := gset.NewIntSet()
s.Add(4).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
gtest.AssertIN(s.Pop(), []int{4, 2, 3})
gtest.AssertIN(s.Pop(), []int{4, 2, 3})
gtest.Assert(s.Size(), 1)
})
}
func TestIntSet_Pops(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(4).Add(2).Add(3)
gtest.Assert(s.Size(), 4)
gtest.Assert(s.Pops(0), nil)
gtest.AssertIN(s.Pops(1), []int{1, 4, 2, 3})
gtest.Assert(s.Size(), 3)
a := s.Pops(2)
gtest.Assert(len(a), 2)
gtest.AssertIN(a, []int{1, 4, 2, 3})
gtest.Assert(s.Size(), 1)
})
gtest.Case(t, func() {
s := gset.NewIntSet(true)
a := []int{1, 2, 3, 4}
s.Add(a...)
gtest.Assert(s.Size(), 4)
gtest.Assert(s.Pops(-2), nil)
gtest.AssertIN(s.Pops(-1), a)
})
}
@ -259,3 +284,43 @@ func TestIntSet_Json(t *testing.T) {
gtest.Assert(a2.Contains(5), false)
})
}
func TestIntSet_AddIfNotExistFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet(true)
s.Add(1)
gtest.Assert(s.Contains(1), true)
gtest.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() int {
return 3
})
gtest.Assert(s.Contains(2), false)
gtest.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() int {
return 4
})
gtest.Assert(s.Contains(3), true)
gtest.Assert(s.Contains(4), false)
})
gtest.Case(t, func() {
s := gset.NewIntSet(true)
s.Add(1)
gtest.Assert(s.Contains(1), true)
gtest.Assert(s.Contains(2), false)
s.AddIfNotExistFuncLock(2, func() int {
return 3
})
gtest.Assert(s.Contains(2), false)
gtest.Assert(s.Contains(3), true)
s.AddIfNotExistFuncLock(3, func() int {
return 4
})
gtest.Assert(s.Contains(3), true)
gtest.Assert(s.Contains(4), false)
})
}

View File

@ -207,8 +207,8 @@ func TestStrSet_Join(t *testing.T) {
s1 := gset.NewStrSet()
s1.Add("a").Add(`"b"`).Add(`\c`)
str1 := s1.Join(",")
gtest.Assert(strings.Contains(str1, `\"b\"`), true)
gtest.Assert(strings.Contains(str1, `\\c`), true)
gtest.Assert(strings.Contains(str1, `"b"`), true)
gtest.Assert(strings.Contains(str1, `\c`), true)
gtest.Assert(strings.Contains(str1, `a`), true)
})
}
@ -259,20 +259,36 @@ func TestStrSet_Remove(t *testing.T) {
func TestStrSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true)
str1 := s1.Pop()
gtest.Assert(strings.Contains("a,b,c", str1), true)
a := []string{"a", "b", "c", "d"}
s := gset.NewStrSetFrom(a, true)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(s.Pop(), a)
gtest.Assert(s.Size(), 3)
gtest.AssertIN(s.Pop(), a)
gtest.Assert(s.Size(), 2)
})
}
func TestStrSet_Pops(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true)
strs1 := s1.Pops(2)
gtest.AssertIN(strs1, []string{"a", "b", "c"})
gtest.Assert(len(strs1), 2)
str2 := s1.Pops(7)
gtest.AssertIN(str2, []string{"a", "b", "c"})
a := []string{"a", "b", "c", "d"}
s := gset.NewStrSetFrom(a, true)
array := s.Pops(2)
gtest.Assert(len(array), 2)
gtest.Assert(s.Size(), 2)
gtest.AssertIN(array, a)
gtest.Assert(s.Pops(0), nil)
gtest.AssertIN(s.Pops(2), a)
gtest.Assert(s.Size(), 0)
})
gtest.Case(t, func() {
s := gset.NewStrSet(true)
a := []string{"1", "2", "3", "4"}
s.Add(a...)
gtest.Assert(s.Size(), 4)
gtest.Assert(s.Pops(-2), nil)
gtest.AssertIN(s.Pops(-1), a)
})
}
@ -304,3 +320,43 @@ func TestStrSet_Json(t *testing.T) {
gtest.Assert(a3.Contains("e"), false)
})
}
func TestStrSet_AddIfNotExistFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStrSet(true)
s.Add("1")
gtest.Assert(s.Contains("1"), true)
gtest.Assert(s.Contains("2"), false)
s.AddIfNotExistFunc("2", func() string {
return "3"
})
gtest.Assert(s.Contains("2"), false)
gtest.Assert(s.Contains("3"), true)
s.AddIfNotExistFunc("3", func() string {
return "4"
})
gtest.Assert(s.Contains("3"), true)
gtest.Assert(s.Contains("4"), false)
})
gtest.Case(t, func() {
s := gset.NewStrSet(true)
s.Add("1")
gtest.Assert(s.Contains("1"), true)
gtest.Assert(s.Contains("2"), false)
s.AddIfNotExistFuncLock("2", func() string {
return "3"
})
gtest.Assert(s.Contains("2"), false)
gtest.Assert(s.Contains("3"), true)
s.AddIfNotExistFuncLock("3", func() string {
return "4"
})
gtest.Assert(s.Contains("3"), true)
gtest.Assert(s.Contains("4"), false)
})
}

View File

@ -13,7 +13,6 @@ import (
"io"
"os"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gconv"
)
@ -43,9 +42,7 @@ func EncryptFile(path string) (encrypt string, err error) {
if err != nil {
return "", err
}
defer func() {
err = gerror.Wrap(f.Close(), "file closing error")
}()
defer f.Close()
h := md5.New()
_, err = io.Copy(h, f)
if err != nil {

View File

@ -13,7 +13,6 @@ import (
"io"
"os"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gconv"
)
@ -30,9 +29,7 @@ func EncryptFile(path string) (encrypt string, err error) {
if err != nil {
return "", err
}
defer func() {
err = gerror.Wrap(f.Close(), "file closing error")
}()
defer f.Close()
h := sha1.New()
_, err = io.Copy(h, f)
if err != nil {

View File

@ -99,9 +99,8 @@ type DB interface {
getChars() (charLeft string, charRight string)
getDebug() bool
quoteWord(s string) string
setSchema(sqlDb *sql.DB, schema string) error
doSetSchema(sqlDb *sql.DB, schema string) error
filterFields(table string, data map[string]interface{}) map[string]interface{}
formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{})
convertValue(fieldValue []byte, fieldType string) interface{}
rowsToResult(rows *sql.Rows) (Result, error)
handleSqlBeforeExec(sql string) string
@ -131,11 +130,12 @@ type dbBase struct {
// 执行的SQL对象
type Sql struct {
Sql string // SQL语句(可能带有预处理占位符)
Args []interface{} // 预处理参数值列表
Error error // 执行结果(nil为成功)
Start int64 // 执行开始时间(毫秒)
End int64 // 执行结束时间(毫秒)
Sql string // SQL语句(可能带有预处理占位符)
Args []interface{} // 预处理参数值列表
Format string // 格式化后的SQL语句仅供参考
Error error // 执行结果(nil为成功)
Start int64 // 执行开始时间(毫秒)
End int64 // 执行结束时间(毫秒)
}
// 表字段结构信息
@ -230,7 +230,7 @@ func New(name ...string) (db DB, err error) {
// which is DEFAULT_GROUP_NAME in default.
func Instance(name ...string) (db DB, err error) {
group := configs.defaultGroup
if len(name) > 0 {
if len(name) > 0 && name[0] != "" {
group = name[0]
}
v := instances.GetOrSetFuncLock(group, func() interface{} {
@ -344,9 +344,9 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
}
if bs.maxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(bs.maxConnLifetime) * time.Second)
sqlDb.SetConnMaxLifetime(bs.maxConnLifetime * time.Second)
} else if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
}
return sqlDb
}, 0)
@ -359,7 +359,7 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
}
// 是否手动选择数据库
if v := bs.schema.Val(); v != "" {
if e := bs.db.setSchema(sqlDb, v); e != nil {
if e := bs.db.doSetSchema(sqlDb, v); e != nil {
err = e
}
}

View File

@ -8,7 +8,6 @@
package gdb
import (
"bytes"
"database/sql"
"errors"
"fmt"
@ -31,8 +30,8 @@ const (
)
var (
wordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
wordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // Regular expression object for a word.
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) // Regular expression object for a string which has operator at its tail.
)
// 获取最近一条执行的sql
@ -68,23 +67,24 @@ func (bs *dbBase) PrintQueriedSqls() {
sqlSlice := bs.GetQueriedSqls()
for k, v := range sqlSlice {
fmt.Println(len(sqlSlice)-k, ":")
fmt.Println(" Sql :", v.Sql)
fmt.Println(" Args :", v.Args)
fmt.Println(" Error:", v.Error)
fmt.Println(" Start:", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
fmt.Println(" Cost :", v.End-v.Start, "ms")
fmt.Println(" Sql :", v.Sql)
fmt.Println(" Args :", v.Args)
fmt.Println(" Format :", v.Format)
fmt.Println(" Error :", v.Error)
fmt.Println(" Start :", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
fmt.Println(" Cost :", v.End-v.Start, "ms")
}
}
// 打印SQL对象(仅在debug=true时有效)
func (bs *dbBase) printSql(v *Sql) {
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, bindArgsToQuery(v.Sql, v.Args))
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
} else {
bs.logger.Debug(s)
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
}
}
@ -106,11 +106,12 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
rows, err = link.Query(query, args...)
mTime2 := gtime.Millisecond()
s := &Sql{
Sql: query,
Args: args,
Error: err,
Start: mTime1,
End: mTime2,
Sql: query,
Args: args,
Format: bindArgsToQuery(query, args),
Error: err,
Start: mTime1,
End: mTime2,
}
bs.sqls.Put(s)
bs.printSql(s)
@ -143,11 +144,12 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
result, err = link.Exec(query, args...)
mTime2 := gtime.Millisecond()
s := &Sql{
Sql: query,
Args: args,
Error: err,
Start: mTime1,
End: mTime2,
Sql: query,
Args: args,
Format: bindArgsToQuery(query, args),
Error: err,
Start: mTime1,
End: mTime2,
}
bs.sqls.Put(s)
bs.printSql(s)
@ -354,7 +356,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
case reflect.Slice, reflect.Array:
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map, reflect.Struct:
dataMap = structToMap(data)
dataMap = varToMapDeep(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
@ -434,10 +436,10 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = structToMap(rv.Index(i).Interface())
listMap[i] = varToMapDeep(rv.Index(i).Interface())
}
case reflect.Map, reflect.Struct:
listMap = List{structToMap(list)}
listMap = List{varToMapDeep(list)}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
@ -521,7 +523,7 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
// CURD操作:数据更新统一采用sql预处理。
// data参数支持string/map/struct/*struct类型。
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := bs.db.formatWhere(condition, args)
newWhere, newArgs := formatWhere(bs.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
@ -542,11 +544,9 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
}
params := []interface{}(nil)
switch kind {
case reflect.Map:
fallthrough
case reflect.Struct:
case reflect.Map, reflect.Struct:
var fields []string
for k, v := range structToMap(data) {
for k, v := range varToMapDeep(data) {
fields = append(fields, bs.db.quoteWord(k)+"=?")
params = append(params, convertParam(v))
}
@ -571,7 +571,7 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
// CURD操作:删除数据
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
newWhere, newArgs := bs.db.formatWhere(condition, args)
newWhere, newArgs := formatWhere(bs.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
@ -643,87 +643,6 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
return records, nil
}
// 格式化Where查询条件。
func (bs *dbBase) formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
// 使用反射进行类型判断
rv := reflect.ValueOf(where)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// map/struct类型
case reflect.Map:
fallthrough
case reflect.Struct:
for key, value := range structToMap(where) {
// 字段安全符号判断
key = bs.db.quoteWord(key)
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
// 支持slice键值/属性,如果只有一个?占位符号那么作为IN查询否则打散作为多个查询参数
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice:
fallthrough
case reflect.Array:
count := gstr.Count(key, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
newArgs = append(newArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
newArgs = append(newArgs, value)
} else {
buffer.WriteString(key)
// 如果键名/属性名称中带有多个?占位符号,那么将参数打散
newArgs = append(newArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
} else {
// 支持key带操作符号
if gstr.Pos(key, "?") == -1 {
if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 {
buffer.WriteString(key + "=?")
} else {
buffer.WriteString(key + "?")
}
} else {
buffer.WriteString(key)
}
newArgs = append(newArgs, value)
}
}
}
default:
buffer.WriteString(gconv.String(where))
}
// 没有任何条件查询参数,直接返回
if buffer.Len() == 0 {
return "", args
}
newArgs = append(newArgs, args...)
newWhere = buffer.String()
if len(newArgs) > 0 {
// 支持例如 Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1) 这种格式
if gstr.Pos(newWhere, "?") == -1 {
if lastOperatorReg.MatchString(newWhere) {
newWhere += "?"
} else if wordReg.MatchString(newWhere) {
newWhere += "=?"
}
}
}
return handlerSliceArguments(newWhere, newArgs)
}
// 使用关键字操作符转义给定字符串。
// 如果给定的字符串不为单词,那么不转义,直接返回该字符串。
func (bs *dbBase) quoteWord(s string) string {
@ -735,7 +654,7 @@ func (bs *dbBase) quoteWord(s string) string {
}
// 动态切换数据库
func (bs *dbBase) setSchema(sqlDb *sql.DB, schema string) error {
func (bs *dbBase) doSetSchema(sqlDb *sql.DB, schema string) error {
_, err := sqlDb.Exec("USE " + schema)
return err
}

View File

@ -7,9 +7,11 @@
package gdb
import (
"bytes"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/empty"
"reflect"
"strings"
"time"
@ -21,11 +23,21 @@ import (
"github.com/gogf/gf/util/gconv"
)
// Type assert api for String().
// Type assert api for String.
type apiString interface {
String() string
}
// Type assert api for Iterator.
type apiIterator interface {
Iterator(f func(key, value interface{}) bool)
}
// Type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
const (
ORM_TAG_FOR_STRUCT = "orm"
ORM_TAG_FOR_UNIQUE = "unique"
@ -63,6 +75,173 @@ func formatQuery(query string, args []interface{}) (newQuery string, newArgs []i
return handlerSliceArguments(query, args)
}
// 格式化Where查询条件。
// TODO []interface{} type support for parameter <where> does not completed yet.
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
buffer := bytes.NewBuffer(nil)
rv := reflect.ValueOf(where)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Array, reflect.Slice:
newArgs = formatWhereInterfaces(db, gconv.Interfaces(where), buffer, newArgs)
case reflect.Map:
for key, value := range varToMapDeep(where) {
if omitEmpty && empty.IsEmpty(value) {
continue
}
newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value)
}
case reflect.Struct:
// If <where> struct implements apiIterator interface,
// it then uses its Iterate function to iterates its key-value pairs.
// For example, ListMap and TreeMap are ordered map,
// which implement apiIterator interface and are index-friendly for where conditions.
if iterator, ok := where.(apiIterator); ok {
iterator.Iterator(func(key, value interface{}) bool {
if omitEmpty && empty.IsEmpty(value) {
return true
}
newArgs = formatWhereKeyValue(db, buffer, newArgs, gconv.String(key), value)
return true
})
break
}
// TODO garray support.
for key, value := range varToMapDeep(where) {
if omitEmpty && empty.IsEmpty(value) {
continue
}
newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value)
}
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
return "", args
}
newArgs = append(newArgs, args...)
newWhere = buffer.String()
if len(newArgs) > 0 {
// It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1)
if gstr.Pos(newWhere, "?") == -1 {
if lastOperatorReg.MatchString(newWhere) {
newWhere += "?"
} else if wordReg.MatchString(newWhere) {
newWhere += "=?"
}
}
}
return handlerSliceArguments(newWhere, newArgs)
}
// formatWhereInterfaces formats <where> as []interface{}.
// TODO []interface{} type support for parameter <where> does not completed yet.
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
var str string
var array []interface{}
var holderCount int
for i := 0; i < len(where); {
if holderCount > 0 {
array = gconv.Interfaces(where[i])
newArgs = append(newArgs, array...)
holderCount -= len(array)
} else {
str = gconv.String(where[i])
holderCount = gstr.Count(str, "?")
buffer.WriteString(str)
}
}
return newArgs
}
// formatWhereKeyValue handles each key-value pair of the parameter map.
func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} {
key = db.quoteWord(key)
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
// 支持slice键值/属性,如果只有一个?占位符号那么作为IN查询否则打散作为多个查询参数
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice, reflect.Array:
count := gstr.Count(key, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
newArgs = append(newArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
newArgs = append(newArgs, value)
} else {
buffer.WriteString(key)
// 如果键名/属性名称中带有多个?占位符号,那么将参数打散
newArgs = append(newArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
} else {
// 支持key带操作符号注意like也算是操作符号
key = gstr.Trim(key)
if gstr.Pos(key, "?") == -1 {
like := " like"
if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) {
buffer.WriteString(key + " ?")
} else if lastOperatorReg.MatchString(key) {
buffer.WriteString(key + " ?")
} else {
buffer.WriteString(key + "=?")
}
} else {
buffer.WriteString(key)
}
newArgs = append(newArgs, value)
}
}
return newArgs
}
// 将对象转换为map如果对象带有继承对象那么执行递归转换。
// 该方法用于将变量传递给数据库执行之前。
func varToMapDeep(obj interface{}) map[string]interface{} {
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
for key, value := range data {
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Struct:
// 底层数据库引擎支持 time.Time/*time.Time 类型
if _, ok := value.(time.Time); ok {
continue
}
if _, ok := value.(*time.Time); ok {
continue
}
// 如果执行String方法那么执行字符串转换
if s, ok := value.(apiString); ok {
data[key] = s.String()
continue
}
delete(data, key)
for k, v := range varToMapDeep(value) {
data[k] = v
}
}
}
return data
}
// 处理预处理占位符与slice类型的参数。
// 需要注意的是,
// 如果是链式操作,在条件参数中也会调用该方法处理查询参数,
@ -146,12 +325,7 @@ func convertParam(value interface{}) interface{} {
// 格式化错误信息
func formatError(err error, query string, args ...interface{}) error {
if err != nil && err != sql.ErrNoRows {
errStr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errStr += fmt.Sprintf("DB QUERY: %s\n", query)
if len(args) > 0 {
errStr += fmt.Sprintf("DB PARAM: %v\n", args)
}
err = errors.New(errStr)
return errors.New(fmt.Sprintf("%s, %s\n", err.Error(), bindArgsToQuery(query, args)))
}
return err
}
@ -169,55 +343,27 @@ func getInsertOperationByOption(option int) string {
return operator
}
// 将对象转换为map如果对象带有继承对象那么执行递归转换。
// 该方法用于将变量传递给数据库执行之前。
func structToMap(obj interface{}) map[string]interface{} {
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
for key, value := range data {
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Struct:
// 底层数据库引擎支持 time.Time/*time.Time 类型
if _, ok := value.(time.Time); ok {
continue
}
if _, ok := value.(*time.Time); ok {
continue
}
// 如果执行String方法那么执行字符串转换
if s, ok := value.(apiString); ok {
data[key] = s.String()
continue
}
delete(data, key)
for k, v := range structToMap(value) {
data[k] = v
}
}
}
return data
}
// 将参数绑定到SQL语句中仅用于调试打印。
func bindArgsToQuery(query string, args []interface{}) string {
index := -1
newQuery, _ := gregex.ReplaceStringFunc(`\?`, query, func(s string) string {
index++
if len(args) > index {
if args[index] == nil {
return "null"
}
rv := reflect.ValueOf(args[index])
kind := rv.Kind()
if kind == reflect.Ptr {
if rv.IsNil() || !rv.IsValid() {
return "null"
}
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
return "'" + gstr.QuoteMeta(gconv.String(args[index]), "'") + "'"
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
}
return gconv.String(args[index])
}

View File

@ -12,6 +12,7 @@ import (
"fmt"
"reflect"
"strings"
"time"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
@ -23,33 +24,44 @@ import (
// 数据库链式操作模型对象
type Model struct {
db DB // 数据库操作对象
tx *TX // 数据库事务对象
linkType int // 连接对象类型(用于主从集群时开发者自定义操作对象)
tablesInit string // 初始化Model时的表名称(可以是多个)
tables string // 数据库操作表
fields string // 操作字段
where string // 操作条件
whereArgs []interface{} // 操作条件参数
groupBy string // 分组语句
orderBy string // 排序语句
start int // 分页开始
limit int // 分页条数
option int // 操作选项
offset int // 查询偏移量(OFFSET语法)
data interface{} // 操作数据(注意仅支持Map/List/string类型)
batch int // 批量操作条数
filter bool // 是否按照表字段过滤data参
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
safe bool // 当前模型是否安全模式(默认非安全表示链式操作直接修改当前模型属性;否则每一次链式操作都是返回新的模型对象)
db DB // 数据库操作对象
tx *TX // 数据库事务对象
linkType int // 连接对象类型(用于主从集群时开发者自定义操作对象)
tablesInit string // 初始化Model时的表名称(可以是多个)
tables string // 数据库操作表
fields string // 操作字段
where string // 操作条件
whereArgs []interface{} // 操作条件参数
whereHolder []*whereHolder // 操作条件预处理
groupBy string // 分组语句
orderBy string // 排序语句
start int // 分页开始
limit int // 分页条数
option int // 操作选项
offset int // 查询偏移量(OFFSET语法)
data interface{} // 操作数据(注意仅支持Map/List/string类型)
batch int // 批量操作条
filter bool // 是否按照表字段过滤data参数
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheExpire time.Duration // 查询缓存时间
cacheName string // 查询缓存名称
safe bool // 当前模型是否安全模式(默认非安全表示链式操作直接修改当前模型属性;否则每一次链式操作都是返回新的模型对象)
}
// whereHolder is the holder for where condition preparing.
type whereHolder struct {
operator int // Operator for this holder.
where interface{} // Where parameter.
args []interface{} // Arguments for where parameter.
}
const (
gLINK_TYPE_MASTER = 1
gLINK_TYPE_SLAVE = 2
OPTION_OMITEMPTY = 1 << iota
gLINK_TYPE_MASTER = 1
gLINK_TYPE_SLAVE = 2
gWHERE_HOLDER_WHERE = 1
gWHERE_HOLDER_AND = 2
gWHERE_HOLDER_OR = 3
OPTION_OMITEMPTY = 1 << iota
OPTION_ALLOWEMPTY
)
@ -178,6 +190,11 @@ func (md *Model) Option(option int) *Model {
return model
}
// 链式操作,设置 OPTION_OMITEMPTY 常用选项
func (md *Model) OptionOmitEmpty() *Model {
return md.Option(OPTION_OMITEMPTY)
}
// 链式操作,过滤字段
func (md *Model) Filter() *Model {
model := md.getModel()
@ -185,42 +202,46 @@ func (md *Model) Filter() *Model {
return model
}
// 链式操作condition支持string & gdb.Map.
// 链式操作condition支持string/map/gmap/struct/*struct.
// 注意多个Where调用时会自动转换为And条件调用。
func (md *Model) Where(where interface{}, args ...interface{}) *Model {
model := md.getModel()
if model.where != "" {
return md.And(where, args...)
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
newWhere, newArgs := md.db.formatWhere(where, args)
model.where = newWhere
model.whereArgs = newArgs
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_WHERE,
where: where,
args: args,
})
return model
}
// 链式操作添加AND条件到Where中
func (md *Model) And(where interface{}, args ...interface{}) *Model {
model := md.getModel()
newWhere, newArgs := md.db.formatWhere(where, args)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere)
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereArgs = append(model.whereArgs, newArgs...)
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_AND,
where: where,
args: args,
})
return model
}
// 链式操作添加OR条件到Where中
func (md *Model) Or(where interface{}, args ...interface{}) *Model {
model := md.getModel()
newWhere, newArgs := md.db.formatWhere(where, args)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere)
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereArgs = append(model.whereArgs, newArgs...)
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_OR,
where: where,
args: args,
})
return model
}
@ -281,12 +302,17 @@ func (md *Model) Batch(batch int) *Model {
}
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
// 当time < 0时表示清除缓存 time=0时表示不过期, time > 0时表示过期时间time过期时间单位
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ...string) *Model {
// 1. 当 expire < 0时表示清除缓存expire=0 时表示不过期, expire > 0时表示过期时间
// 2. expire参数类型为interface{},这是一个兼容旧版本的方式,该参数支持 int/time.Duration 类型当传递类型为int时表示缓存多少秒。
// 3. name表示自定义的缓存名称注意不要出现重复便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,在特定的业务逻辑中清理缓存时可以给定缓存名称进行精准清理。
func (md *Model) Cache(expire interface{}, name ...string) *Model {
model := md.getModel()
model.cacheTime = time
if d, ok := expire.(time.Duration); ok {
model.cacheExpire = d
} else {
model.cacheExpire = gconv.Duration(expire) * time.Second
}
if len(name) > 0 {
model.cacheName = name[0]
}
@ -328,11 +354,11 @@ func (md *Model) Data(data ...interface{}) *Model {
case reflect.Slice, reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = structToMap(rv.Index(i).Interface())
list[i] = varToMapDeep(rv.Index(i).Interface())
}
model.data = list
case reflect.Map, reflect.Struct:
model.data = structToMap(data[0])
model.data = varToMapDeep(data[0])
default:
model.data = data[0]
}
@ -352,7 +378,7 @@ func (md *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
} else if m, ok := md.data.(Map); ok {
return md.doFilterDataMapForInsertOrUpdate(m, true)
}
return nil
return data
}
// doFilterDataMapForInsertOrUpdate does the filter features for map.
@ -361,11 +387,13 @@ func (md *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
if md.filter {
data = md.db.filterFields(md.tables, data)
}
// Remove key-value pairs of which the value is empty.
if allowOmitEmpty && md.option&OPTION_OMITEMPTY > 0 {
m := gmap.NewStrAnyMapFrom(data)
m.FilterEmpty()
data = m.Map()
}
// Keep specified fields.
if len(md.fields) > 0 && md.fields != "*" {
set := gset.NewStrSet()
for _, v := range gstr.SplitAndTrimSpace(md.fields, ",") {
@ -647,10 +675,10 @@ func (md *Model) getAll(query string, args ...interface{}) (result Result, err e
result, err = md.db.doGetAll(md.getLink(), query, args...)
// 查询缓存保存处理
if len(cacheKey) > 0 && err == nil {
if md.cacheTime < 0 {
if md.cacheExpire < 0 {
md.db.getCache().Remove(cacheKey)
} else {
md.db.getCache().Set(cacheKey, result, md.cacheTime*1000)
md.db.getCache().Set(cacheKey, result, md.cacheExpire)
}
}
return result, err
@ -658,13 +686,51 @@ func (md *Model) getAll(query string, args ...interface{}) (result Result, err e
// 检查是否需要查询查询缓存
func (md *Model) checkAndRemoveCache() {
if md.cacheEnabled && md.cacheTime < 0 && len(md.cacheName) > 0 {
if md.cacheEnabled && md.cacheExpire < 0 && len(md.cacheName) > 0 {
md.db.getCache().Remove(md.cacheName)
}
}
// 格式化当前输入参数返回SQL条件语句不带参数
func (md *Model) getConditionSql() string {
if len(md.whereHolder) > 0 {
for _, v := range md.whereHolder {
switch v.operator {
case gWHERE_HOLDER_WHERE:
if md.where == "" {
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
md.where = newWhere
md.whereArgs = newArgs
}
continue
}
fallthrough
case gWHERE_HOLDER_AND:
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if md.where[0] == '(' {
md.where = fmt.Sprintf(`%s AND (%s)`, md.where, newWhere)
} else {
md.where = fmt.Sprintf(`(%s) AND (%s)`, md.where, newWhere)
}
md.whereArgs = append(md.whereArgs, newArgs...)
}
case gWHERE_HOLDER_OR:
newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if md.where[0] == '(' {
md.where = fmt.Sprintf(`%s OR (%s)`, md.where, newWhere)
} else {
md.where = fmt.Sprintf(`(%s) OR (%s)`, md.where, newWhere)
}
md.whereArgs = append(md.whereArgs, newArgs...)
}
}
}
}
s := ""
if md.where != "" {
s += " WHERE " + md.where

View File

@ -218,7 +218,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
case reflect.Map:
fallthrough
case reflect.Struct:
dataMap = structToMap(data)
dataMap = varToMapDeep(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
@ -330,12 +330,12 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = structToMap(rv.Index(i).Interface())
listMap[i] = varToMapDeep(rv.Index(i).Interface())
}
case reflect.Map:
fallthrough
case reflect.Struct:
listMap = List{Map(structToMap(list))}
listMap = List{Map(varToMapDeep(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}

View File

@ -28,13 +28,13 @@ func (bs *dbBase) convertValue(fieldValue []byte, fieldType string) interface{}
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
return fieldValue
case "int", "tinyint", "small_int", "medium_int":
case "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint":
if gstr.ContainsI(fieldType, "unsigned") {
gconv.Uint(string(fieldValue))
}
return gconv.Int(string(fieldValue))
case "big_int":
case "big_int", "bigint":
if gstr.ContainsI(fieldType, "unsigned") {
gconv.Uint64(string(fieldValue))
}

View File

@ -165,7 +165,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul
// CURD操作:数据更新统一采用sql预处理,
// data参数支持字符串或者关联数组类型内部会自行做判断处理.
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := tx.db.formatWhere(condition, args)
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
@ -179,7 +179,7 @@ func (tx *TX) doUpdate(table string, data interface{}, condition string, args ..
// CURD操作:删除数据
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := tx.db.formatWhere(condition, args)
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}

View File

@ -25,7 +25,7 @@ func (r Record) Xml(rootTag ...string) string {
return string(content)
}
// 将Record转换为Map其中最主要的区别是里面的键值被强制转换为string类型方便json处理
// 将Record转换为Map类型
func (r Record) Map() Map {
m := make(map[string]interface{})
for k, v := range r {
@ -34,7 +34,7 @@ func (r Record) Map() Map {
return m
}
// 将Record转换为gmap.StrAnyMap类型
// 将Record转换为常用的gmap.StrAnyMap类型
func (r Record) GMap() *gmap.StrAnyMap {
return gmap.NewStrAnyMapFrom(r.Map())
}

View File

@ -9,6 +9,8 @@ package gdb_test
import (
"database/sql"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/util/gutil"
"testing"
"github.com/gogf/gf/database/gdb"
@ -225,6 +227,14 @@ func Test_Model_Update(t *testing.T) {
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
// Update + Data(string)
gtest.Case(t, func() {
result, err := db.Table(table).Data("passport='user_33'").Where("passport='user_3'").Update()
gtest.Assert(err, nil)
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
}
func Test_Model_Clone(t *testing.T) {
@ -656,6 +666,16 @@ func Test_Model_Where(t *testing.T) {
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
// map like
gtest.Case(t, func() {
result, err := db.Table(table).Where(g.Map{
"passport like": "user_1%",
}).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 2)
gtest.Assert(result[0].GMap().Get("id"), 1)
gtest.Assert(result[1].GMap().Get("id"), 10)
})
// map + slice parameter
gtest.Case(t, func() {
result, err := db.Table(table).Where(g.Map{
@ -746,6 +766,46 @@ func Test_Model_Where(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// gmap.Map
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// gmap.Map key operator
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// list map
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// list map key operator
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// tree map
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// tree map key operator
gtest.Case(t, func() {
result, err := db.Table(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// complicated where 1
gtest.Case(t, func() {
//db.SetDebug(true)
@ -992,15 +1052,15 @@ func Test_Model_Option_Map(t *testing.T) {
_, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": ""}).Where("id", 2).Update()
gtest.AssertNE(err, nil)
r, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()
r, err = db.Table(table).OptionOmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()
gtest.Assert(err, nil)
n, _ = r.RowsAffected()
gtest.Assert(n, 1)
_, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update()
_, err = db.Table(table).OptionOmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update()
gtest.AssertNE(err, nil)
r, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).
r, err = db.Table(table).OptionOmitEmpty().
Fields("password").Data(g.Map{
"nickname": "",
"passport": "123",
@ -1020,75 +1080,95 @@ func Test_Model_Option_Map(t *testing.T) {
func Test_Model_Option_List(t *testing.T) {
gtest.Case(t, func() {
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
r, err := db.Table(table).Fields("id, password").Data(g.List{
g.Map{
"id": 1,
"passport": "1",
"password": "1",
"nickname": "1",
},
g.Map{
"id": 2,
"passport": "2",
"password": "2",
"nickname": "2",
},
}).Save()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
gtest.Assert(list[0]["id"].String(), "1")
gtest.Assert(list[0]["nickname"].String(), "")
gtest.Assert(list[0]["passport"].String(), "")
gtest.Assert(list[0]["password"].String(), "1")
table := createTable()
defer dropTable(table)
r, err := db.Table(table).Fields("id, password").Data(g.List{
g.Map{
"id": 1,
"passport": "1",
"password": "1",
"nickname": "1",
},
g.Map{
"id": 2,
"passport": "2",
"password": "2",
"nickname": "2",
},
}).Save()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
gtest.Assert(list[0]["id"].String(), "1")
gtest.Assert(list[0]["nickname"].String(), "")
gtest.Assert(list[0]["passport"].String(), "")
gtest.Assert(list[0]["password"].String(), "1")
gtest.Assert(list[1]["id"].String(), "2")
gtest.Assert(list[1]["nickname"].String(), "")
gtest.Assert(list[1]["passport"].String(), "")
gtest.Assert(list[1]["password"].String(), "2")
})
gtest.Assert(list[1]["id"].String(), "2")
gtest.Assert(list[1]["nickname"].String(), "")
gtest.Assert(list[1]["passport"].String(), "")
gtest.Assert(list[1]["password"].String(), "2")
})
gtest.Case(t, func() {
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
r, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Fields("id, password").Data(g.List{
g.Map{
"id": 1,
"passport": "1",
"password": 0,
"nickname": "1",
},
g.Map{
"id": 2,
"passport": "2",
"password": "2",
"nickname": "2",
},
}).Save()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
g.Dump(list)
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
gtest.Assert(list[0]["id"].String(), "1")
gtest.Assert(list[0]["nickname"].String(), "")
gtest.Assert(list[0]["passport"].String(), "")
gtest.Assert(list[0]["password"].String(), "0")
table := createTable()
defer dropTable(table)
r, err := db.Table(table).OptionOmitEmpty().Fields("id, password").Data(g.List{
g.Map{
"id": 1,
"passport": "1",
"password": 0,
"nickname": "1",
},
g.Map{
"id": 2,
"passport": "2",
"password": "2",
"nickname": "2",
},
}).Save()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
list, err := db.Table(table).OrderBy("id asc").All()
g.Dump(list)
gtest.Assert(err, nil)
gtest.Assert(len(list), 2)
gtest.Assert(list[0]["id"].String(), "1")
gtest.Assert(list[0]["nickname"].String(), "")
gtest.Assert(list[0]["passport"].String(), "")
gtest.Assert(list[0]["password"].String(), "0")
gtest.Assert(list[1]["id"].String(), "2")
gtest.Assert(list[1]["nickname"].String(), "")
gtest.Assert(list[1]["passport"].String(), "")
gtest.Assert(list[1]["password"].String(), "2")
gtest.Assert(list[1]["id"].String(), "2")
gtest.Assert(list[1]["nickname"].String(), "")
gtest.Assert(list[1]["passport"].String(), "")
gtest.Assert(list[1]["password"].String(), "2")
})
})
}
func Test_Model_Option_Where(t *testing.T) {
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
r, err := db.Table(table).OptionOmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).Update()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, INIT_DATA_SIZE)
})
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
r, err := db.Table(table).OptionOmitEmpty().Data("nickname", 1).Where(g.Map{"id": 1, "passport": ""}).Update()
gtest.Assert(err, nil)
n, _ := r.RowsAffected()
gtest.Assert(n, 1)
v, err := db.Table(table).Where("id", 1).Fields("nickname").Value()
gtest.Assert(err, nil)
gtest.Assert(v.String(), "1")
})
}

View File

@ -75,6 +75,8 @@ func New(config Config) *Redis {
pool: pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
return &redis.Pool{
IdleTimeout: config.IdleTimeout,
MaxActive: config.MaxActive,
MaxIdle: config.MaxIdle,
MaxConnLifetime: config.MaxConnLifetime,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))

View File

@ -103,7 +103,10 @@ func Decode(data interface{}) (interface{}, error) {
// The <v> should be a pointer type.
func DecodeTo(data interface{}, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(gconv.Bytes(data)))
decoder.UseNumber()
// Do not use number, it converts float64 to json.Number type,
// which actually a string type. It causes converting issue for other data formats,
// for example: yaml.
//decoder.UseNumber()
return decoder.Decode(v)
}
@ -184,7 +187,10 @@ func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
// Do not use number, it converts float64 to json.Number type,
// which actually a string type. It causes converting issue for other data formats,
// for example: yaml.
//decoder.UseNumber()
if err := decoder.Decode(&result); err != nil {
return nil, err
}

View File

@ -15,7 +15,7 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Load_JSON(t *testing.T) {
func Test_Load_JSON1(t *testing.T) {
data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`)
// JSON
gtest.Case(t, func() {
@ -42,6 +42,19 @@ func Test_Load_JSON(t *testing.T) {
})
}
func Test_Load_JSON2(t *testing.T) {
data := []byte(`{"n":123456789000000000000, "m":{"k":"v"}, "a":[1,2,3]}`)
gtest.Case(t, func() {
j, err := gjson.LoadContent(data)
gtest.Assert(err, nil)
gtest.Assert(j.Get("n"), "123456789000000000000")
gtest.Assert(j.Get("m"), g.Map{"k": "v"})
gtest.Assert(j.Get("m.k"), "v")
gtest.Assert(j.Get("a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("a.1"), 2)
})
}
func Test_Load_XML(t *testing.T) {
data := []byte(`<doc><a>1</a><a>2</a><a>3</a><m><k>v</k></m><n>123456789</n></doc>`)
// XML

View File

@ -30,17 +30,18 @@ type MapIntBool = map[int]bool
// Frequently-used slice type alias.
type List = []Map
type ListAnyStr = []map[interface{}]string
type ListAnyInt = []map[interface{}]int
type ListStrAny = []map[string]interface{}
type ListStrStr = []map[string]string
type ListStrInt = []map[string]int
type ListIntAny = []map[int]interface{}
type ListIntStr = []map[int]string
type ListIntInt = []map[int]int
type ListAnyBool = []map[interface{}]bool
type ListStrBool = []map[string]bool
type ListIntBool = []map[int]bool
type ListAnyAny = []Map
type ListAnyStr = []MapAnyStr
type ListAnyInt = []MapAnyInt
type ListStrAny = []MapStrAny
type ListStrStr = []MapStrStr
type ListStrInt = []MapStrInt
type ListIntAny = []MapIntAny
type ListIntStr = []MapIntStr
type ListIntInt = []MapIntInt
type ListAnyBool = []MapAnyBool
type ListStrBool = []MapStrBool
type ListIntBool = []MapIntBool
// Frequently-used slice type alias.
type Slice = []interface{}

View File

@ -27,7 +27,6 @@ import (
const (
gFRAME_CORE_COMPONENT_NAME_REDIS = "gf.core.component.redis"
gFRAME_CORE_COMPONENT_NAME_GKVDB = "gf.core.component.gkvdb"
gFRAME_CORE_COMPONENT_NAME_DATABASE = "gf.core.component.database"
)
@ -160,66 +159,13 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
return nil
}
node := &gdb.ConfigNode{}
if value, ok := nodeMap["host"]; ok {
node.Host = gconv.String(value)
}
if value, ok := nodeMap["port"]; ok {
node.Port = gconv.String(value)
}
if value, ok := nodeMap["user"]; ok {
node.User = gconv.String(value)
}
if value, ok := nodeMap["pass"]; ok {
node.Pass = gconv.String(value)
}
if value, ok := nodeMap["name"]; ok {
node.Name = gconv.String(value)
}
if value, ok := nodeMap["type"]; ok {
node.Type = gconv.String(value)
}
if value, ok := nodeMap["role"]; ok {
node.Role = gconv.String(value)
}
if value, ok := nodeMap["debug"]; ok {
node.Debug = gconv.Bool(value)
}
if value, ok := nodeMap["charset"]; ok {
node.Charset = gconv.String(value)
}
if value, ok := nodeMap["weight"]; ok {
node.Weight = gconv.Int(value)
}
if value, ok := nodeMap["linkinfo"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["link-info"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["linkInfo"]; ok {
node.LinkInfo = gconv.String(value)
err := gconv.Struct(nodeMap, node)
if err != nil {
glog.Error(err)
}
if value, ok := nodeMap["link"]; ok {
node.LinkInfo = gconv.String(value)
}
if value, ok := nodeMap["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxIdle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodeMap["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodeMap["maxOpen"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodeMap["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Duration(value)
}
if value, ok := nodeMap["maxLifetime"]; ok {
node.MaxConnLifetime = gconv.Duration(value)
}
// Parse link syntax.
if node.LinkInfo != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.LinkInfo)

View File

@ -118,7 +118,7 @@ func (m *Manager) Translate(content string, language ...string) string {
m.mu.RLock()
defer m.mu.RUnlock()
var data map[string]string
if len(language) > 0 {
if len(language) > 0 && language[0] != "" {
data = m.data[language[0]]
} else {
data = m.data[m.options.Language]

View File

@ -62,6 +62,14 @@ func (r *Request) GetDeleteInt(key string, def ...interface{}) int {
return r.GetDeleteVar(key, def...).Int()
}
func (r *Request) GetDeleteInt32(key string, def ...interface{}) int32 {
return r.GetDeleteVar(key, def...).Int32()
}
func (r *Request) GetDeleteInt64(key string, def ...interface{}) int64 {
return r.GetDeleteVar(key, def...).Int64()
}
func (r *Request) GetDeleteInts(key string, def ...interface{}) []int {
return r.GetDeleteVar(key, def...).Ints()
}
@ -70,6 +78,14 @@ func (r *Request) GetDeleteUint(key string, def ...interface{}) uint {
return r.GetDeleteVar(key, def...).Uint()
}
func (r *Request) GetDeleteUint32(key string, def ...interface{}) uint32 {
return r.GetDeleteVar(key, def...).Uint32()
}
func (r *Request) GetDeleteUint64(key string, def ...interface{}) uint64 {
return r.GetDeleteVar(key, def...).Uint64()
}
func (r *Request) GetDeleteFloat32(key string, def ...interface{}) float32 {
return r.GetDeleteVar(key, def...).Float32()
}

View File

@ -95,6 +95,14 @@ func (r *Request) GetPostInt(key string, def ...interface{}) int {
return r.GetPostVar(key, def...).Int()
}
func (r *Request) GetPostInt32(key string, def ...interface{}) int32 {
return r.GetPostVar(key, def...).Int32()
}
func (r *Request) GetPostInt64(key string, def ...interface{}) int64 {
return r.GetPostVar(key, def...).Int64()
}
func (r *Request) GetPostInts(key string, def ...interface{}) []int {
return r.GetPostVar(key, def...).Ints()
}
@ -103,6 +111,14 @@ func (r *Request) GetPostUint(key string, def ...interface{}) uint {
return r.GetPostVar(key, def...).Uint()
}
func (r *Request) GetPostUint32(key string, def ...interface{}) uint32 {
return r.GetPostVar(key, def...).Uint32()
}
func (r *Request) GetPostUint64(key string, def ...interface{}) uint64 {
return r.GetPostVar(key, def...).Uint64()
}
func (r *Request) GetPostFloat32(key string, def ...interface{}) float32 {
return r.GetPostVar(key, def...).Float32()
}

View File

@ -61,6 +61,14 @@ func (r *Request) GetPutInt(key string, def ...interface{}) int {
return r.GetPutVar(key, def...).Int()
}
func (r *Request) GetPutInt32(key string, def ...interface{}) int32 {
return r.GetPutVar(key, def...).Int32()
}
func (r *Request) GetPutInt64(key string, def ...interface{}) int64 {
return r.GetPutVar(key, def...).Int64()
}
func (r *Request) GetPutInts(key string, def ...interface{}) []int {
return r.GetPutVar(key, def...).Ints()
}
@ -69,6 +77,14 @@ func (r *Request) GetPutUint(key string, def ...interface{}) uint {
return r.GetPutVar(key, def...).Uint()
}
func (r *Request) GetPutUint32(key string, def ...interface{}) uint32 {
return r.GetPutVar(key, def...).Uint32()
}
func (r *Request) GetPutUint64(key string, def ...interface{}) uint64 {
return r.GetPutVar(key, def...).Uint64()
}
func (r *Request) GetPutFloat32(key string, def ...interface{}) float32 {
return r.GetPutVar(key, def...).Float32()
}

View File

@ -66,6 +66,14 @@ func (r *Request) GetQueryInt(key string, def ...interface{}) int {
return r.GetQueryVar(key, def...).Int()
}
func (r *Request) GetQueryInt32(key string, def ...interface{}) int32 {
return r.GetQueryVar(key, def...).Int32()
}
func (r *Request) GetQueryInt64(key string, def ...interface{}) int64 {
return r.GetQueryVar(key, def...).Int64()
}
func (r *Request) GetQueryInts(key string, def ...interface{}) []int {
return r.GetQueryVar(key, def...).Ints()
}
@ -74,6 +82,14 @@ func (r *Request) GetQueryUint(key string, def ...interface{}) uint {
return r.GetQueryVar(key, def...).Uint()
}
func (r *Request) GetQueryUint32(key string, def ...interface{}) uint32 {
return r.GetQueryVar(key, def...).Uint32()
}
func (r *Request) GetQueryUint64(key string, def ...interface{}) uint64 {
return r.GetQueryVar(key, def...).Uint64()
}
func (r *Request) GetQueryFloat32(key string, def ...interface{}) float32 {
return r.GetQueryVar(key, def...).Float32()
}

View File

@ -15,33 +15,33 @@ import (
"github.com/gogf/gf/util/gutil"
)
// 中间件对象
// Middleware is the plugin for request handling.
type Middleware struct {
served bool // 是否带有请求服务函数,用以识别是否404
request *Request // 请求对象
served bool // Is the request served, which is used for checking response status 404.
request *Request // The request object pointer.
}
// 执行下一个请求流程处理函数
// Next calls the next workflow handler.
func (m *Middleware) Next() {
item := (*handlerParsedItem)(nil)
loop := true
for loop {
// 是否停止请求执行
// Check whether the request is exited.
if m.request.IsExited() || m.request.handlerIndex >= len(m.request.handlers) {
return
break
}
item = m.request.handlers[m.request.handlerIndex]
m.request.handlerIndex++
// 中间件执行时不执行钩子函数,由另外的逻辑进行控制
// Filter the HOOK handlers, which are designed to be called in another standalone procedure.
if item.handler.itemType == gHANDLER_TYPE_HOOK {
continue
}
// 路由参数赋值
// Router values switching.
for k, v := range item.values {
m.request.routerMap[k] = v
}
m.request.Router = item.handler.router
// 执行函数处理
gutil.TryCatch(func() {
switch item.handler.itemType {
case gHANDLER_TYPE_CONTROLLER:
@ -98,8 +98,8 @@ func (m *Middleware) Next() {
niceCallFunc(func() {
item.handler.itemFunc(m.request)
})
// 中间件默认不会进一步执行,
// 需要内部调用Next方法决定是否进一步执行以便于请求流程控制。
// It does not continue calling next middleware after another middleware done.
// There should be a "Next" function to be called in the middleware in order to manage the workflow.
loop = false
}
}, func(exception interface{}) {
@ -107,4 +107,12 @@ func (m *Middleware) Next() {
m.request.Response.WriteStatus(http.StatusInternalServerError, exception)
})
}
// Check the http status code after all handler and middleware done.
if m.request.Response.Status == 0 {
if m.request.Middleware.served || m.request.Response.buffer.Len() > 0 {
m.request.Response.WriteHeader(http.StatusOK)
} else {
m.request.Response.WriteHeader(http.StatusNotFound)
}
}
}

View File

@ -67,6 +67,14 @@ func (r *Request) GetRequestInt(key string, def ...interface{}) int {
return r.GetRequestVar(key, def...).Int()
}
func (r *Request) GetRequestInt32(key string, def ...interface{}) int32 {
return r.GetRequestVar(key, def...).Int32()
}
func (r *Request) GetRequestInt64(key string, def ...interface{}) int64 {
return r.GetRequestVar(key, def...).Int64()
}
func (r *Request) GetRequestInts(key string, def ...interface{}) []int {
return r.GetRequestVar(key, def...).Ints()
}
@ -75,6 +83,14 @@ func (r *Request) GetRequestUint(key string, def ...interface{}) uint {
return r.GetRequestVar(key, def...).Uint()
}
func (r *Request) GetRequestUint32(key string, def ...interface{}) uint32 {
return r.GetRequestVar(key, def...).Uint32()
}
func (r *Request) GetRequestUint64(key string, def ...interface{}) uint64 {
return r.GetRequestVar(key, def...).Uint64()
}
func (r *Request) GetRequestFloat32(key string, def ...interface{}) float32 {
return r.GetRequestVar(key, def...).Float32()
}

View File

@ -20,8 +20,8 @@ import (
"github.com/gogf/gf/util/gconv"
)
// 服务端请求返回对象。
// 注意该对象并没有实现http.ResponseWriter接口而是依靠ghttp.ResponseWriter实现。
// Response is the writer for response buffer.
// Note that it implements the http.ResponseWriter interface with buffering feature.
type Response struct {
*ResponseWriter // Underlying ResponseWriter.
Server *Server // Parent server.
@ -29,7 +29,7 @@ type Response struct {
Request *Request // According request.
}
// 创建一个ghttp.Response对象指针
// newResponse creates and returns a new Response object.
func newResponse(s *Server, w http.ResponseWriter) *Response {
r := &Response{
Server: s,
@ -42,12 +42,12 @@ func newResponse(s *Server, w http.ResponseWriter) *Response {
return r
}
// 返回信息任何变量自动转换为bytes
// Write writes <content> to the response buffer.
func (r *Response) Write(content ...interface{}) {
if len(content) == 0 {
return
}
if r.Status == 0 && r.Request.hasServeHandler {
if r.Status == 0 {
r.Status = http.StatusOK
}
for _, v := range content {
@ -62,27 +62,32 @@ func (r *Response) Write(content ...interface{}) {
}
}
// 返回信息支持自定义format格式
// WriteOver overwrites the response buffer with <content>.
func (r *Response) WriteOver(content ...interface{}) {
r.ClearBuffer()
r.Write(content...)
}
// Writef writes the response with fmt.Sprintf.
func (r *Response) Writef(format string, params ...interface{}) {
r.Write(fmt.Sprintf(format, params...))
}
// 返回信息,末尾增加换行标识符"\n"
// Writef writes the response with <content> and new line.
func (r *Response) Writeln(content ...interface{}) {
if len(content) == 0 {
r.Write("\n")
return
}
content = append(content, "\n")
r.Write(content...)
r.Write(append(content, "\n")...)
}
// 返回信息,末尾增加换行标识符"\n"
// Writefln writes the response with fmt.Sprintf and new line.
func (r *Response) Writefln(format string, params ...interface{}) {
r.Writeln(fmt.Sprintf(format, params...))
}
// 返回JSON
// WriteJson writes <content> to the response with JSON format.
func (r *Response) WriteJson(content interface{}) error {
if b, err := json.Marshal(content); err != nil {
return err
@ -93,7 +98,8 @@ func (r *Response) WriteJson(content interface{}) error {
return nil
}
// 返回JSONP
// WriteJson writes <content> to the response with JSONP format.
// Note that there should be a "callback" parameter in the request for JSONP format.
func (r *Response) WriteJsonP(content interface{}) error {
if b, err := json.Marshal(content); err != nil {
return err
@ -112,7 +118,7 @@ func (r *Response) WriteJsonP(content interface{}) error {
return nil
}
// 返回XML
// WriteJson writes <content> to the response with XML format.
func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
if b, err := gparser.VarToXml(content, rootTag...); err != nil {
return err
@ -123,36 +129,21 @@ func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
return nil
}
// 返回HTTP Code状态码
// WriteStatus writes HTTP <status> and <content> to the response.
func (r *Response) WriteStatus(status int, content ...interface{}) {
if r.buffer.Len() == 0 {
// 状态码注册回调函数处理
if status != http.StatusOK {
if f := r.Request.Server.getStatusHandler(status, r.Request); f != nil {
niceCallFunc(func() {
f(r.Request)
})
// 防止多次设置(http: multiple response.WriteHeader calls)
if r.Status == 0 {
r.WriteHeader(status)
}
return
}
}
if r.Header().Get("Content-Type") == "" {
r.Header().Set("Content-Type", "text/plain; charset=utf-8")
//r.Header().Set("X-Content-Type-Options", "nosniff")
}
if len(content) > 0 {
r.Write(content...)
} else {
r.Write(http.StatusText(status))
}
}
r.WriteHeader(status)
if len(content) > 0 {
r.Write(content...)
} else {
r.Write(http.StatusText(status))
}
if r.Header().Get("Content-Type") == "" {
r.Header().Set("Content-Type", "text/plain; charset=utf-8")
//r.Header().Set("X-Content-Type-Options", "nosniff")
}
}
// 静态文件处理
// ServeFile serves the file to the response.
func (r *Response) ServeFile(path string, allowIndex ...bool) {
serveFile := (*staticServeFile)(nil)
if file := gres.Get(path); file != nil {
@ -171,7 +162,7 @@ func (r *Response) ServeFile(path string, allowIndex ...bool) {
r.Server.serveFile(r.Request, serveFile, allowIndex...)
}
// 静态文件下载处理
// ServeFileDownload serves file as file downloading to the response.
func (r *Response) ServeFileDownload(path string, name ...string) {
serveFile := (*staticServeFile)(nil)
downloadName := ""
@ -203,46 +194,45 @@ func (r *Response) ServeFileDownload(path string, name ...string) {
r.Server.serveFile(r.Request, serveFile)
}
// 返回location标识引导客户端跳转。
// 注意这里要先把设置的cookie输出否则会被忽略。
// RedirectTo redirects client to another location.
func (r *Response) RedirectTo(location string) {
r.Header().Set("Location", location)
r.WriteHeader(http.StatusFound)
r.Request.Exit()
}
// 返回location标识引导客户端跳转到来源页面
// RedirectBack redirects client back to referer.
func (r *Response) RedirectBack() {
r.RedirectTo(r.Request.GetReferer())
}
// 获取当前缓冲区中的数据
// BufferString returns the buffer content as []byte.
func (r *Response) Buffer() []byte {
return r.buffer.Bytes()
}
// 获取当前缓冲区中的数据(string)
// BufferString returns the buffer content as string.
func (r *Response) BufferString() string {
return r.buffer.String()
}
// 获取当前缓冲区中的数据大小
// BufferLength returns the length of the buffer content.
func (r *Response) BufferLength() int {
return r.buffer.Len()
}
// 手动设置缓冲区内容
// SetBuffer overwrites the buffer with <data>.
func (r *Response) SetBuffer(data []byte) {
r.buffer.Reset()
r.buffer.Write(data)
}
// 清空缓冲区内容
// ClearBuffer clears the response buffer.
func (r *Response) ClearBuffer() {
r.buffer.Reset()
}
// 输出缓冲区数据到客户端.
// Output outputs the buffer content to the client.
func (r *Response) Output() {
r.Header().Set("Server", r.Server.config.ServerAgent)
//r.handleGzip()

View File

@ -58,7 +58,7 @@ func (r *Response) ParseTplContent(content string, params ...gview.Params) (stri
// 内置变量/对象
func (r *Response) buildInVars(params ...map[string]interface{}) map[string]interface{} {
vars := map[string]interface{}(nil)
if len(params) > 0 {
if len(params) > 0 && params[0] != nil {
vars = params[0]
} else {
vars = make(map[string]interface{})

View File

@ -16,9 +16,15 @@ import (
// Custom ResponseWriter, which is used for controlling the output buffer.
type ResponseWriter struct {
Status int // HTTP status.
writer http.ResponseWriter // The underlying ResponseWriter.
buffer *bytes.Buffer // The output buffer.
Status int // HTTP status.
writer http.ResponseWriter // The underlying ResponseWriter.
buffer *bytes.Buffer // The output buffer.
wroteHeader bool // Is header wrote, avoiding error: superfluous/multiple response.WriteHeader call.
}
// RawWriter returns the underlying ResponseWriter.
func (w *ResponseWriter) RawWriter() http.ResponseWriter {
return w.writer
}
// Header implements the interface function of http.ResponseWriter.Header.
@ -44,9 +50,13 @@ func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// OutputBuffer outputs the buffer to client.
func (w *ResponseWriter) OutputBuffer() {
if w.Status != 0 {
if w.Status != 0 && !w.wroteHeader {
w.writer.WriteHeader(w.Status)
}
// Default status text output.
if w.Status != http.StatusOK && w.buffer.Len() == 0 {
w.buffer.WriteString(http.StatusText(w.Status))
}
if w.buffer.Len() > 0 {
w.writer.Write(w.buffer.Bytes())
w.buffer.Reset()

View File

@ -0,0 +1,23 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp
import (
"bytes"
"github.com/gogf/gf/os/glog"
)
// errorLogger is the error logging logger for underlying net/http.Server.
type errorLogger struct {
logger *glog.Logger
}
// Write implements the io.Writer interface.
func (l *errorLogger) Write(p []byte) (n int, err error) {
l.logger.Skip(1).Error(string(bytes.TrimRight(p, "\r\n")))
return len(p), nil
}

View File

@ -11,6 +11,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
@ -53,6 +54,7 @@ func (s *Server) newHttpServer(itemFunc string) *http.Server {
WriteTimeout: s.config.WriteTimeout,
IdleTimeout: s.config.IdleTimeout,
MaxHeaderBytes: s.config.MaxHeaderBytes,
ErrorLog: log.New(&errorLogger{logger: s.logger}, "", 0),
}
server.SetKeepAlivesEnabled(s.config.KeepAlive)
return server

View File

@ -66,15 +66,6 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
defer func() {
// 设置请求完成时间
request.LeaveTime = gtime.Microsecond()
// 如果没有产生异常状态那么设置返回状态为200
if request.Response.Status == 0 {
if request.Middleware.served || request.Response.buffer.Len() > 0 {
request.Response.Status = http.StatusOK
} else {
request.Response.WriteStatus(http.StatusNotFound)
}
}
// error log
if request.error != nil {
s.handleErrorLog(request.error, request)
@ -84,7 +75,6 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
s.handleErrorLog(gerror.Newf("%v", exception), request)
}
}
// access log
s.handleAccessLog(request)
}()
@ -104,12 +94,10 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
}
// 动态服务检索
if serveFile == nil || serveFile.dir {
request.handlers, request.hasHookHandler, request.hasServeHandler = s.getHandlersWithCache(request)
}
request.handlers, request.hasHookHandler, request.hasServeHandler = s.getHandlersWithCache(request)
// 判断最终对该请求提供的服务方式
if serveFile != nil && serveFile.dir && request.handlers != nil {
if serveFile != nil && serveFile.dir && request.hasServeHandler {
request.isFileRequest = false
}
@ -122,7 +110,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
// 静态服务
s.serveFile(request, serveFile)
} else {
if request.hasServeHandler {
if len(request.handlers) > 0 {
// 动态服务
request.Middleware.Next()
} else {
@ -133,7 +121,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
if len(request.Response.Header()) == 0 &&
request.Response.Status == 0 &&
request.Response.BufferLength() == 0 {
request.Response.WriteStatus(http.StatusNotFound)
request.Response.WriteHeader(http.StatusNotFound)
}
}
}
@ -149,6 +137,25 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
if !request.IsExited() {
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
}
// HTTP status checking.
if request.Response.Status == 0 {
if request.Middleware.served || request.Response.buffer.Len() > 0 {
request.Response.WriteHeader(http.StatusOK)
} else {
request.Response.WriteHeader(http.StatusNotFound)
}
}
// HTTP status handler.
if request.Response.Status != http.StatusOK {
if f := s.getStatusHandler(request.Response.Status, request); f != nil {
// Call custom status handler.
niceCallFunc(func() {
f(request)
})
}
}
// 设置Session Id到Cookie中
if request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
request.Cookie.SetSessionId(request.Session.Id())
@ -167,18 +174,18 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
// 查找静态文件的绝对路径
func (s *Server) searchStaticFile(uri string) *staticServeFile {
// 优先查找URI映射关系
var file *gres.File
var path string
var dir bool
// Firstly search the StaticPaths mapping.
if len(s.config.StaticPaths) > 0 {
for _, item := range s.config.StaticPaths {
if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0:len(item.prefix)]) {
// 防止类似 /static/style 映射到 /static/style.css 的情况
// To avoid case like: /static/style -> /static/style.css
if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' {
continue
}
// 优先检索资源管理器
// Firstly searching resource manager.
file = gres.GetWithIndex(item.path+uri[len(item.prefix):], s.config.IndexFiles)
if file != nil {
return &staticServeFile{
@ -186,7 +193,7 @@ func (s *Server) searchStaticFile(uri string) *staticServeFile {
dir: file.FileInfo().IsDir(),
}
}
// 其次检索文件系统
// Secondly searching the file system.
path, dir = gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...)
if path != "" {
return &staticServeFile{
@ -242,7 +249,8 @@ func (s *Server) serveFile(r *Request, f *staticServeFile, allowIndex ...bool) {
}
} else {
info := f.file.FileInfo()
http.ServeContent(r.Response.Writer, r.Request, info.Name(), info.ModTime(), f.file)
r.Response.wroteHeader = true
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), f.file)
}
return
}
@ -253,6 +261,11 @@ func (s *Server) serveFile(r *Request, f *staticServeFile, allowIndex ...bool) {
return
}
defer file.Close()
// Clear the response buffer before file serving.
// It ignores all custom buffer content and uses the file content.
r.Response.ClearBuffer()
info, _ := file.Stat()
if info.IsDir() {
if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
@ -261,7 +274,8 @@ func (s *Server) serveFile(r *Request, f *staticServeFile, allowIndex ...bool) {
r.Response.WriteStatus(http.StatusForbidden)
}
} else {
http.ServeContent(r.Response.Writer, r.Request, info.Name(), info.ModTime(), file)
r.Response.wroteHeader = true
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), file)
}
}

View File

@ -254,6 +254,10 @@ func (g *RouterGroup) doBind(bindType string, pattern string, object interface{}
if err != nil {
glog.Fatalf("invalid pattern: %s", pattern)
}
// If there'a already a domain, unset the domain field in the pattern.
if g.domain != nil {
domain = ""
}
if bindType == "REST" {
pattern = prefix + "/" + strings.TrimLeft(path, "/")
} else {

View File

@ -208,8 +208,11 @@ func (item *handlerParsedItem) MarshalJSON() ([]byte, error) {
// 生成回调方法查询的Key
func (s *Server) serveHandlerKey(method, path, domain string) string {
if method == "" {
return path + "@" + strings.ToLower(domain)
if len(domain) > 0 {
domain = "@" + domain
}
return strings.ToUpper(method) + ":" + path + "@" + strings.ToLower(domain)
if method == "" {
return path + strings.ToLower(domain)
}
return strings.ToUpper(method) + ":" + path + strings.ToLower(domain)
}

View File

@ -0,0 +1,42 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
)
func Test_Client_Basic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/hello", func(r *ghttp.Request) {
r.Response.Write("hello")
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
url := fmt.Sprintf("http://127.0.0.1:%d", p)
client := ghttp.NewClient()
client.SetPrefix(url)
gtest.Assert(ghttp.GetContent(""), ``)
gtest.Assert(client.GetContent("/hello"), `hello`)
_, err := ghttp.Post("")
gtest.AssertNE(err, nil)
})
}

View File

@ -33,8 +33,7 @@ func Test_Cookie(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)

View File

@ -8,6 +8,9 @@ package ghttp_test
import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/os/gfile"
"net/http"
"testing"
"time"
@ -46,14 +49,13 @@ func Test_BindMiddleware_Basic1(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/test"), "Not Found")
gtest.Assert(client.GetContent("/test"), "1342")
gtest.Assert(client.GetContent("/test/test"), "57test86")
})
}
@ -87,15 +89,14 @@ func Test_BindMiddleware_Basic2(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/test"), "Not Found")
gtest.Assert(client.PutContent("/test"), "Not Found")
gtest.Assert(client.PutContent("/test"), "1342")
gtest.Assert(client.PostContent("/test"), "Not Found")
gtest.Assert(client.GetContent("/test/test"), "test")
gtest.Assert(client.PutContent("/test/test"), "test")
@ -103,6 +104,153 @@ func Test_BindMiddleware_Basic2(t *testing.T) {
})
}
func Test_BindMiddleware_Must_Be_Called(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/", func(g *ghttp.RouterGroup) {
g.Middleware(func(r *ghttp.Request) {
r.Response.Write("1")
r.Middleware.Next()
})
g.Middleware(func(r *ghttp.Request) {
r.Middleware.Next()
r.Response.Write("2")
})
g.ALL("/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "12")
gtest.Assert(client.GetContent("/test"), "1test2")
gtest.Assert(client.PutContent("/test/none"), "12")
})
}
func Test_Middleware_With_Static(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/", func(g *ghttp.RouterGroup) {
g.Middleware(func(r *ghttp.Request) {
r.Response.Write("1")
r.Middleware.Next()
r.Response.Write("2")
})
g.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Write("list")
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.SetServerRoot(gfile.Join(gdebug.CallerDirectory(), "testdata", "static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/test.html"), "test")
gtest.Assert(client.GetContent("/none"), "12")
gtest.Assert(client.GetContent("/user/list"), "1list2")
})
}
func Test_Middleware_Status(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/", func(g *ghttp.RouterGroup) {
g.Middleware(func(r *ghttp.Request) {
r.Middleware.Next()
r.Response.WriteOver(r.Response.Status)
})
g.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Write("list")
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "404")
gtest.Assert(client.GetContent("/user/list"), "200")
resp, err := client.Get("/")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}
func Test_Middleware_Hook_With_Static(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
a := garray.New(true)
s.Group("/", func(g *ghttp.RouterGroup) {
g.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
a.Append(1)
fmt.Println("HOOK_BEFORE_SERVE")
r.Response.Write("a")
})
g.Hook("/*", ghttp.HOOK_AFTER_SERVE, func(r *ghttp.Request) {
a.Append(1)
fmt.Println("HOOK_AFTER_SERVE")
r.Response.Write("b")
})
g.Middleware(func(r *ghttp.Request) {
r.Response.Write("1")
r.Middleware.Next()
r.Response.Write("2")
})
g.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Write("list")
})
})
s.SetPort(p)
//s.SetDumpRouteMap(false)
s.SetServerRoot(gfile.Join(gdebug.CallerDirectory(), "testdata", "static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
// The length assert sometimes fails, so I added time.Sleep here for debug purpose.
gtest.Assert(client.GetContent("/"), "index")
time.Sleep(100 * time.Millisecond)
gtest.Assert(a.Len(), 2)
gtest.Assert(client.GetContent("/test.html"), "test")
time.Sleep(100 * time.Millisecond)
gtest.Assert(a.Len(), 4)
gtest.Assert(client.GetContent("/none"), "a12b")
time.Sleep(100 * time.Millisecond)
gtest.Assert(a.Len(), 6)
gtest.Assert(client.GetContent("/user/list"), "a1list2b")
time.Sleep(100 * time.Millisecond)
gtest.Assert(a.Len(), 8)
})
}
func Test_BindMiddleware_Status(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -117,8 +265,7 @@ func Test_BindMiddleware_Status(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -151,13 +298,12 @@ func Test_BindMiddlewareDefault_Basic1(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/"), "1342")
gtest.Assert(client.GetContent("/test/test"), "13test42")
})
}
@ -183,15 +329,14 @@ func Test_BindMiddlewareDefault_Basic2(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.PutContent("/"), "Not Found")
gtest.Assert(client.GetContent("/test/test"), "Not Found")
gtest.Assert(client.GetContent("/"), "1342")
gtest.Assert(client.PutContent("/"), "1342")
gtest.Assert(client.GetContent("/test/test"), "1342")
gtest.Assert(client.PutContent("/test/test"), "13test42")
})
}
@ -215,13 +360,12 @@ func Test_BindMiddlewareDefault_Basic3(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/"), "12")
gtest.Assert(client.GetContent("/test/test"), "1test2")
})
}
@ -245,13 +389,12 @@ func Test_BindMiddlewareDefault_Basic4(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/"), "21")
gtest.Assert(client.GetContent("/test/test"), "2test1")
})
}
@ -275,13 +418,12 @@ func Test_BindMiddlewareDefault_Basic5(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/"), "12")
gtest.Assert(client.GetContent("/test/test"), "12test")
})
}
@ -300,8 +442,7 @@ func Test_BindMiddlewareDefault_Status(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -352,18 +493,17 @@ func Test_BindMiddlewareDefault_Basic6(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "13100Object Index20042")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/init"), "1342")
gtest.Assert(client.GetContent("/shut"), "1342")
gtest.Assert(client.GetContent("/index"), "13100Object Index20042")
gtest.Assert(client.GetContent("/show"), "13100Object Show20042")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "1342")
})
}
@ -400,13 +540,12 @@ func Test_Hook_Middleware_Basic1(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "acbd")
gtest.Assert(client.GetContent("/"), "ac1342bd")
gtest.Assert(client.GetContent("/test/test"), "ac13test42bd")
})
}
@ -438,13 +577,13 @@ func Test_Middleware_CORSAndAuth(t *testing.T) {
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/api.v2"), "Not Found")
gtest.Assert(client.GetContent("/api.v2"), "Forbidden")
gtest.Assert(client.GetContent("/api.v2/user/list"), "Forbidden")
gtest.Assert(client.GetContent("/api.v2/user/list", "token=123456"), "list")
})

View File

@ -94,8 +94,7 @@ func Test_Params_Json(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -56,8 +56,7 @@ func Test_Params_Struct(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -222,8 +222,7 @@ func Test_Params_Basic(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -40,8 +40,7 @@ func Test_Router_Basic(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -67,8 +66,7 @@ func Test_Router_Method(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -116,8 +114,7 @@ func Test_Router_Status(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -164,8 +161,7 @@ func Test_Router_CustomStatusHandler(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -191,8 +187,7 @@ func Test_Router_404(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -69,8 +69,7 @@ func Test_Router_ControllerRest(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -53,8 +53,7 @@ func Test_Router_Controller1(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -84,8 +83,7 @@ func Test_Router_Controller2(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -111,8 +109,7 @@ func Test_Router_ControllerMethod(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 基本路由功能以及优先级测试
package ghttp_test
import (
@ -17,7 +16,6 @@ import (
"github.com/gogf/gf/test/gtest"
)
// 基本路由功能测试
func Test_Router_DomainBasic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -42,8 +40,7 @@ func Test_Router_DomainBasic(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -70,7 +67,6 @@ func Test_Router_DomainBasic(t *testing.T) {
})
}
// 测试HTTP Method注册.
func Test_Router_DomainMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -86,8 +82,7 @@ func Test_Router_DomainMethod(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -164,7 +159,6 @@ func Test_Router_DomainMethod(t *testing.T) {
})
}
// 测试状态返回.
func Test_Router_DomainStatus(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -186,8 +180,7 @@ func Test_Router_DomainStatus(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -262,7 +255,6 @@ func Test_Router_DomainStatus(t *testing.T) {
})
}
// 自定义状态码处理.
func Test_Router_DomainCustomStatusHandler(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -278,8 +270,7 @@ func Test_Router_DomainCustomStatusHandler(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -303,7 +294,6 @@ func Test_Router_DomainCustomStatusHandler(t *testing.T) {
})
}
// 测试不存在的路由.
func Test_Router_Domain404(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
@ -316,8 +306,7 @@ func Test_Router_Domain404(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -337,3 +326,45 @@ func Test_Router_Domain404(t *testing.T) {
gtest.Assert(client.GetContent("/"), "hello")
})
}
func Test_Router_DomainGroup(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.Group("/", func(g *ghttp.RouterGroup) {
g.Group("/app", func(gApp *ghttp.RouterGroup) {
gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) {
r.Response.Write(r.Get("table"), "&", r.Get("page"))
})
gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) {
r.Response.Write(r.Get("order_id"))
})
gApp.DELETE("/comment/{id}", func(r *ghttp.Request) {
r.Response.Write(r.Get("id"))
})
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
gtest.Case(t, func() {
client1 := ghttp.NewClient()
client1.SetPrefix(fmt.Sprintf("http://local:%d", p))
client2 := ghttp.NewClient()
client2.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client1.GetContent("/app/t/list/2.html"), "t&2")
gtest.Assert(client2.GetContent("/app/t/list/2.html"), "Not Found")
gtest.Assert(client1.GetContent("/app/order/info/2"), "2")
gtest.Assert(client2.GetContent("/app/order/info/2"), "Not Found")
gtest.Assert(client1.GetContent("/app/comment/20"), "Not Found")
gtest.Assert(client2.GetContent("/app/comment/20"), "Not Found")
gtest.Assert(client1.DeleteContent("/app/comment/20"), "20")
gtest.Assert(client2.DeleteContent("/app/comment/20"), "Not Found")
})
}

View File

@ -69,8 +69,7 @@ func Test_Router_DomainControllerRest(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -51,8 +51,7 @@ func Test_Router_DomainController1(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -102,8 +101,7 @@ func Test_Router_DomainController2(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
@ -156,8 +154,7 @@ func Test_Router_DomainControllerMethod(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

View File

@ -64,8 +64,7 @@ func Test_Router_DomainObjectRest(t *testing.T) {
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(200 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))

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