mirror of
https://gitee.com/johng/gf
synced 2026-06-20 07:11:35 +08:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87c22d32b0 | |||
| 27dd15b403 | |||
| dd452c19ce | |||
| acc0846cf3 | |||
| 5a6738841f | |||
| bea451c9d6 | |||
| 676e904ec6 | |||
| 49aa5c61bc | |||
| a2272b852c | |||
| 1fa77630f9 | |||
| a841c4cc05 | |||
| 1874808e3b | |||
| 149b67916b | |||
| 7fb6f58162 | |||
| a309114a18 | |||
| 35cbde9530 | |||
| b9211b182a | |||
| 81ec499ae9 | |||
| f2e276eabd | |||
| f6dbaba1f8 | |||
| 65dcff052a | |||
| e3861567c7 | |||
| e89a20c725 | |||
| a8acc6bd28 | |||
| eb5efc735e | |||
| 737af527cd | |||
| 875d2b7e63 | |||
| bf1cb0e1bd | |||
| 2bfeb1b06c | |||
| 820e4302b7 | |||
| 84d761b418 | |||
| e5e27f2ac4 | |||
| efca9b18a8 | |||
| 607821ecbc | |||
| 7cc1b239d4 | |||
| efa8de34da | |||
| bcf45e3c5a | |||
| 0a3cd1d2ab | |||
| 3e621856c8 | |||
| 32e4d64ddb | |||
| 46e45ca84b | |||
| fcb13bd8ee | |||
| eacad9b453 | |||
| ed479e2a13 | |||
| 19937cb75d | |||
| a95093222c | |||
| 7b599d1882 | |||
| 4d501fd2f4 | |||
| c3d3053ded | |||
| 14d5fd3e11 | |||
| 9f79453334 | |||
| c7f1c881c0 | |||
| c39c032b4e | |||
| fa96c18308 | |||
| d90145a47f | |||
| 4871f86346 | |||
| 919eaf1e9a | |||
| 8a84ca16d1 | |||
| 12b4fdd692 | |||
| 3d8451d5d0 | |||
| cf88f28519 | |||
| 15d99eee46 | |||
| 6d68277db8 | |||
| 3e3b5557f7 | |||
| ba1a9d9f8e | |||
| 3e1a7953ec | |||
| 073fb2d717 | |||
| 7f33021184 | |||
| b396096721 | |||
| 0a5c6d832f | |||
| d279566114 | |||
| 2693cbb136 | |||
| f7a9be4292 | |||
| a926033b66 | |||
| dbcdd06b19 | |||
| ff5dab5c70 | |||
| 84b576418f | |||
| 253b124903 | |||
| 33dc5ddf79 | |||
| a456fa537c | |||
| 9e1fb93e08 | |||
| 5d24f702be | |||
| 6cc4747965 | |||
| 437fc04620 | |||
| 937f8e6919 | |||
| 6e08eebcbe | |||
| d9422d00ac |
@ -5,6 +5,7 @@ go:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "1.15.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
20
DONATOR.MD
20
DONATOR.MD
@ -11,7 +11,7 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
| Name | Channel | Amount | Comment
|
||||
|---|---|--- | ---
|
||||
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥301.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥501.00 |
|
||||
|[mg91](https://gitee.com/mg91)|gitee|¥10.00 |
|
||||
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 |
|
||||
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 |
|
||||
@ -97,6 +97,24 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
|六七 ·|wechat|¥88.88| gf越来越好
|
||||
|Tom|wechat|¥500.00| 一点心意 希望GF越来越好
|
||||
|Chao|wechat|¥166.00|
|
||||
|汤sir|wechat|¥10.00| 强哥,喝杯咖啡☕️
|
||||
|秋叶、|wechat|¥66.66| GF带我一起玩
|
||||
||wechat|¥20.00|
|
||||
|程凤明|wechat|¥18.80|
|
||||
|Glowworm|wechat|¥8.88| 感谢大佬
|
||||
|徒行|wechat|¥10.00| 目前项目在转用这个框架,鼓励一下大佬吧👍👍👍
|
||||
|产品设计.软件开发|wechat|¥10.00| 祝愿走向更成功!
|
||||
|[charlieccGuo](https://github.com/charlieccGuo)|wechat|¥10.00|
|
||||
|yiran|wechat|¥20.00| 赏给大佬喝茶🍵
|
||||
|长夏朔酒|wechat|¥20.00| 请大佬喝茶
|
||||
|💥聪จุ๊บ 🇨🇳|wechat|¥66.66| 请大佬喝茶
|
||||
|Even_|wechat|¥10.00| 日照市民发来贺电!
|
||||
|智慧人生|wechat|¥50.00| 智慧人生
|
||||
|一滴水|wechat|¥10.00| goframe 越来越强大
|
||||
|[wenzi1](https://github.com/wenzi1)|alipay|¥100.00|
|
||||
|[hyuant](https://github.com/hyuant)|alipay|¥66.66|
|
||||
|*庆|alipay|¥9.99| 支持一下,gf越来越好
|
||||
|**君|alipay|¥10.00| 加油
|
||||
|
||||
|
||||
|
||||
|
||||
@ -120,11 +120,11 @@ The concurrency starts from `100` to `10000`.
|
||||
- [Tencent](https://www.tencent.com/)
|
||||
- [ZTE](https://www.zte.com.cn/china/)
|
||||
- [Ant Financial Services](https://www.antfin.com/)
|
||||
- [Medlinker](https://www.medlinker.com/)
|
||||
- [Yesk](http://www.yesky.com)
|
||||
- [MedLinker](https://www.medlinker.com/)
|
||||
- [KuCoin](https://www.kucoin.io/)
|
||||
- [Leyoujia Holding Group](https://www.leyoujia.com/)
|
||||
|
||||
- [LeYouJia](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [XiMaLaYa](https://www.ximalaya.com)
|
||||
|
||||
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).
|
||||
|
||||
|
||||
@ -141,11 +141,12 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
|
||||
- [中兴科技](https://www.zte.com.cn/china/)
|
||||
- [蚂蚁金服](https://www.antfin.com/)
|
||||
- [医联科技](https://www.medlinker.com/)
|
||||
- [天极数码](http://www.yesky.com)
|
||||
- [库币科技](https://www.kucoin.io/)
|
||||
- [乐有家](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [喜马拉雅](https://www.ximalaya.com)
|
||||
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里] (https://github.com/gogf/gf/issues/168)留言。
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。
|
||||
|
||||
# 贡献
|
||||
|
||||
|
||||
@ -11,9 +11,11 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/cmdenv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
|
||||
@ -188,6 +190,7 @@ type Sql struct {
|
||||
Error error // Execution result.
|
||||
Start int64 // Start execution timestamp in milliseconds.
|
||||
End int64 // End execution timestamp in milliseconds.
|
||||
Group string // Group is the group name of the configuration that the sql is executed from.
|
||||
}
|
||||
|
||||
// TableField is the struct for table field.
|
||||
@ -260,8 +263,17 @@ var (
|
||||
// regularFieldNameRegPattern is the regular expression pattern for a string
|
||||
// which is a regular field name of table.
|
||||
regularFieldNameRegPattern = `^[\w\.\-]+$`
|
||||
|
||||
// allDryRun sets dry-run feature for all database connections.
|
||||
// It is commonly used for command options for convenience.
|
||||
allDryRun = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
// allDryRun is initialized from environment or command options.
|
||||
allDryRun = cmdenv.Get("gf.gdb.dryrun", false).Bool()
|
||||
}
|
||||
|
||||
// Register registers custom database driver to gdb.
|
||||
func Register(name string, driver Driver) error {
|
||||
driverMap[name] = driver
|
||||
|
||||
@ -11,10 +11,11 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -59,6 +60,7 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro
|
||||
Error: err,
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
Group: c.DB.GetGroup(),
|
||||
}
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
@ -102,6 +104,7 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
|
||||
Error: err,
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
Group: c.DB.GetGroup(),
|
||||
}
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
@ -776,7 +779,7 @@ func (c *Core) MarshalJSON() ([]byte, error) {
|
||||
// writeSqlToLogger outputs the sql object to logger.
|
||||
// It is enabled when configuration "debug" is true.
|
||||
func (c *Core) writeSqlToLogger(v *Sql) {
|
||||
s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
|
||||
s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
c.logger.Error(s)
|
||||
|
||||
@ -186,6 +186,10 @@ func (c *Core) SetDryRun(dryrun bool) {
|
||||
|
||||
// GetDryRun returns the DryRun value.
|
||||
func (c *Core) GetDryRun() bool {
|
||||
if allDryRun {
|
||||
// Globally set.
|
||||
return true
|
||||
}
|
||||
return c.dryrun.Val()
|
||||
}
|
||||
|
||||
|
||||
@ -140,7 +140,7 @@ func (m *Model) Offset(offset int) *Model {
|
||||
|
||||
// Page sets the paging number for the model.
|
||||
// The parameter <page> is started from 1 for paging.
|
||||
// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
|
||||
// Note that, it differs that the Limit function starts from 0 for "LIMIT" statement.
|
||||
func (m *Model) Page(page, limit int) *Model {
|
||||
model := m.getModel()
|
||||
if page <= 0 {
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
@ -88,12 +89,18 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
case "date":
|
||||
if t, ok := fieldValue.(time.Time); ok {
|
||||
return gtime.NewFromTime(t).Format("Y-m-d")
|
||||
}
|
||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||
return t.Format("Y-m-d")
|
||||
|
||||
case
|
||||
"datetime",
|
||||
"timestamp":
|
||||
if t, ok := fieldValue.(time.Time); ok {
|
||||
return gtime.NewFromTime(t)
|
||||
}
|
||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||
return t.String()
|
||||
|
||||
|
||||
@ -32,11 +32,15 @@ import (
|
||||
// ScanList(&users, "User")
|
||||
// ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
//
|
||||
// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation>
|
||||
// parameter.
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given <relation> parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (r Result) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
|
||||
// Necessary checks for parameters.
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_Types(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS types (
|
||||
@ -32,8 +31,16 @@ func Test_Types(t *testing.T) {
|
||||
%s bool NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, "`blob`", "`binary`", "`date`", "`time`",
|
||||
"`decimal`", "`double`", "`bit`", "`tinyint`", "`bool`")); err != nil {
|
||||
`,
|
||||
"`blob`",
|
||||
"`binary`",
|
||||
"`date`",
|
||||
"`time`",
|
||||
"`decimal`",
|
||||
"`double`",
|
||||
"`bit`",
|
||||
"`tinyint`",
|
||||
"`bool`")); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable("types")
|
||||
@ -41,7 +48,7 @@ func Test_Types(t *testing.T) {
|
||||
"id": 1,
|
||||
"blob": "i love gf",
|
||||
"binary": []byte("abcdefgh"),
|
||||
"date": "2018-10-24",
|
||||
"date": "1880-10-24",
|
||||
"time": "10:00:01",
|
||||
"decimal": -123.456,
|
||||
"double": -123.456,
|
||||
|
||||
29
debug/gdebug/gdebug_grid.go
Normal file
29
debug/gdebug/gdebug_grid.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// gridRegex is the regular expression object for parsing goroutine id from stack information.
|
||||
gridRegex = regexp.MustCompile(`^\w+\s+(\d+)\s+`)
|
||||
)
|
||||
|
||||
// GoroutineId retrieves and returns the current goroutine id from stack information.
|
||||
// Be very aware that, it is with low performance as it uses runtime.Stack function.
|
||||
// It is commonly used for debugging purpose.
|
||||
func GoroutineId() int {
|
||||
buf := make([]byte, 26)
|
||||
runtime.Stack(buf, false)
|
||||
match := gridRegex.FindSubmatch(buf)
|
||||
id, _ := strconv.Atoi(string(match[1]))
|
||||
return id
|
||||
}
|
||||
@ -86,6 +86,7 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
// A string key.
|
||||
if !gstr.IsNumeric(array[i]) {
|
||||
if i == length-1 {
|
||||
*pointer = map[string]interface{}{array[i]: value}
|
||||
@ -97,23 +98,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
valn, err := strconv.Atoi(array[i])
|
||||
// Numeric index.
|
||||
valueNum, err := strconv.Atoi(array[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Leaf node.
|
||||
|
||||
if i == length-1 {
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
// Leaf node.
|
||||
if len((*pointer).([]interface{})) > valueNum {
|
||||
if removed && value == nil {
|
||||
// Deleting element.
|
||||
if pparent == nil {
|
||||
*pointer = append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...)
|
||||
*pointer = append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...)
|
||||
} else {
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...))
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...))
|
||||
}
|
||||
} else {
|
||||
(*pointer).([]interface{})[valn] = value
|
||||
(*pointer).([]interface{})[valueNum] = value
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
@ -124,19 +126,33 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
j.setPointerWithValue(pointer, array[i], value)
|
||||
} else {
|
||||
// It is not the root node.
|
||||
s := make([]interface{}, valn+1)
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[valn] = value
|
||||
s[valueNum] = value
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Branch node.
|
||||
if gstr.IsNumeric(array[i+1]) {
|
||||
n, _ := strconv.Atoi(array[i+1])
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
(*pointer).([]interface{})[valn] = make([]interface{}, n+1)
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
item := pSlice[valueNum]
|
||||
if s, ok := item.([]interface{}); ok {
|
||||
for i := 0; i < n-len(s); i++ {
|
||||
s = append(s, nil)
|
||||
}
|
||||
pparent = pointer
|
||||
pointer = &pSlice[valueNum]
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
var v interface{} = make([]interface{}, n+1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
@ -146,14 +162,26 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
v := (*pointer).([]interface{})
|
||||
if len(v) > valn {
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pointer = &(*pointer).([]interface{})[valueNum]
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, pSlice)
|
||||
s[valueNum] = make(map[string]interface{})
|
||||
if pparent != nil {
|
||||
// i > 0
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
} else {
|
||||
// i = 0
|
||||
var v interface{} = s
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,19 +205,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pparent = pointer
|
||||
}
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
var v1, v2 interface{}
|
||||
if i == length-1 {
|
||||
v = map[string]interface{}{
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: value,
|
||||
}
|
||||
} else {
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: nil,
|
||||
}
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v)
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v1)
|
||||
} else {
|
||||
*pointer = v
|
||||
*pointer = v1
|
||||
pparent = pointer
|
||||
}
|
||||
pointer = &v
|
||||
v2 = v1.(map[string]interface{})[array[i]]
|
||||
pointer = &v2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,9 +10,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/encoding/gini"
|
||||
"github.com/gogf/gf/encoding/gtoml"
|
||||
"github.com/gogf/gf/encoding/gxml"
|
||||
@ -188,22 +189,44 @@ func LoadContent(data interface{}, safe ...bool) (*Json, error) {
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
return doLoadContent(checkDataType(content), content, safe...)
|
||||
return LoadContentType(checkDataType(content), content, safe...)
|
||||
}
|
||||
|
||||
// LoadContentType creates a Json object from given type and content,
|
||||
// supporting data content type as follows:
|
||||
// JSON, XML, INI, YAML and TOML.
|
||||
func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) {
|
||||
content := gconv.Bytes(data)
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
//ignore UTF8-BOM
|
||||
if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
|
||||
content = content[3:]
|
||||
}
|
||||
return doLoadContent(dataType, content, safe...)
|
||||
}
|
||||
|
||||
// checkDataType automatically checks and returns the data type for <content>.
|
||||
// Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
|
||||
// functions to load the content for certain content type.
|
||||
func checkDataType(content []byte) string {
|
||||
if json.Valid(content) {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
return "xml"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*:\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*:\s*.+`, content) {
|
||||
} else if (gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content)) {
|
||||
return "yml"
|
||||
} else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false {
|
||||
return "ini"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-\."]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-\."]+\s*=\s*.+`, content) {
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
return "toml"
|
||||
} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
// Must contain "[xxx]" section.
|
||||
return "ini"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ func Test_Basic(t *testing.T) {
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(j.Get("0").([]interface{})), 3)
|
||||
t.Assert(j.Get("0"), 1)
|
||||
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.a")
|
||||
|
||||
115
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
115
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gjson
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_checkDataType(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
bb = """
|
||||
dig := dig; END;"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
dd = 11
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
"gf.gvalid.rule.required" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-if" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-unless" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.date" = "The :attribute value is not a valid date"
|
||||
"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format"
|
||||
"gf.gvalid.rule.email" = "The :attribute value must be a valid email address"
|
||||
"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number"
|
||||
"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number"
|
||||
"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number"
|
||||
"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number"
|
||||
"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number"
|
||||
"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address"
|
||||
"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address"
|
||||
"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address"
|
||||
"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address"
|
||||
"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address"
|
||||
"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format"
|
||||
"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max"
|
||||
"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max"
|
||||
"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string"
|
||||
"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string"
|
||||
"gf.gvalid.rule.array" = "The :attribute value must be an array"
|
||||
"gf.gvalid.rule.integer" = "The :attribute value must be an integer"
|
||||
"gf.gvalid.rule.float" = "The :attribute value must be a float"
|
||||
"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false"
|
||||
"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field"
|
||||
"gf.gvalid.rule.different" = "The :attribute value must be different from field :field"
|
||||
"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
`)
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\s\t\n\r]+[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.MatchString(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, string(data)))
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
[default]
|
||||
db.engine = mysql
|
||||
db.max.idle.conns = 5
|
||||
db.max.open.conns = 100
|
||||
allow_ips =
|
||||
api.key =
|
||||
api.secret =
|
||||
enable_tls = false
|
||||
concurrency.queue = 500
|
||||
auth_secret = 63358e6f3daf0e5775ec3fb4d2516b01d41530bf30960aa76972f6ce7e08552f
|
||||
ca_file =
|
||||
cert_file =
|
||||
key_file =
|
||||
host_port = 8088
|
||||
log_path = /Users/zhaosuji/go/src/git.medlinker.com/foundations/gocron/log
|
||||
#k8s-api地址(只提供内网访问)
|
||||
k8s-inner-api = http://127.0.0.1:8081/kube/add
|
||||
conf_dir = ./config
|
||||
app_conf = ./config/app.ini
|
||||
`)
|
||||
t.Assert(checkDataType(data), "ini")
|
||||
})
|
||||
}
|
||||
@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
|
||||
|
||||
;注释
|
||||
|
||||
[addr]
|
||||
#注释
|
||||
[addr]
|
||||
ip = 127.0.0.1
|
||||
port=9001
|
||||
enable=true
|
||||
|
||||
@ -8,6 +8,9 @@ package gjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
@ -31,17 +34,13 @@ func Test_Set1(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Set2(t *testing.T) {
|
||||
e := []byte(`[[null,1]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
e := `[[null,1]]`
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
s := p.MustToJsonString()
|
||||
t.Assert(s, e)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set3(t *testing.T) {
|
||||
@ -51,7 +50,6 @@ func Test_Set3(t *testing.T) {
|
||||
"k1": "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -227,3 +225,107 @@ func Test_Set14(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set15(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("root.0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("root.1.k2", "v2"), nil)
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"root":[{"k1":"v1"},{"k2":"v2"}`) ||
|
||||
gstr.Contains(s, `"root":[{"k2":"v2"},{"k1":"v1"}`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"k":"v"`) ||
|
||||
gstr.Contains(s, `"k":"v"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set16(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("processors.0.set.0value", "1"), nil)
|
||||
t.Assert(j.Set("processors.0.set.0field", "2"), nil)
|
||||
t.Assert(j.Set("description", "3"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"processors":[{"set":{"0field":"2","0value":"1"}}]`) ||
|
||||
gstr.Contains(s, `"processors":[{"set":{"0value":"1","0field":"2"}}]`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"description":"3"`) || gstr.Contains(s, `"description":"3"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set17(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("1.k2", "v2"), nil)
|
||||
// overwrite the previous slice.
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `{"k":"v"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set18(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,{"k1":"v1"},{"k2":"v2"}]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set19(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.1.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,[null,{"k1":"v1"}],[null,{"k2":"v2"}]]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set20(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("k1", "v1"), nil)
|
||||
t.Assert(j.Set("k2", g.Slice{1, 2, 3}), nil)
|
||||
t.Assert(j.Set("k2.1", 20), nil)
|
||||
t.Assert(j.Set("k2.2", g.Map{"k3": "v3"}), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.InArray(
|
||||
g.SliceStr{
|
||||
`{"k1":"v1","k2":[1,20,{"k3":"v3"}]}`,
|
||||
`{"k2":[1,20,{"k3":"v3"}],"k1":"v1"}`,
|
||||
},
|
||||
s,
|
||||
), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -31,14 +31,15 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
|
||||
db := instances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
// Configuration already exists.
|
||||
if gdb.GetConfig(group) != nil {
|
||||
// If configuration file does not exist but the gdb configurations are already set.
|
||||
if !Config().Available() && gdb.GetConfig(group) != nil {
|
||||
db, err := gdb.Instance(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
// Check the configuration file.
|
||||
var m map[string]interface{}
|
||||
// It firstly searches the configuration of the instance name.
|
||||
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
|
||||
@ -32,7 +32,31 @@ func T(content string, language ...string) string {
|
||||
return defaultManager.T(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func TF(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func TFL(format string, language string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func TranslateFormat(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language and returns the translated content.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func Translate(content string, language ...string) string {
|
||||
return defaultManager.Translate(content, language...)
|
||||
|
||||
@ -124,6 +124,30 @@ func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func (m *Manager) TF(format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) TFL(format string, language string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func (m *Manager) TranslateFormat(format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format), values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func (m *Manager) TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format, language), values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (m *Manager) Translate(content string, language ...string) string {
|
||||
|
||||
@ -73,6 +73,28 @@ func Test_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TranslateFormat(t *testing.T) {
|
||||
// TF
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
// TFL
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "ja", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "zh-CN", 2020), "你好世界 2020")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DefaultManager(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gi18n.SetPath(gdebug.TestDataPath("i18n"))
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package ghttp
|
||||
|
||||
import "github.com/gogf/gf/container/gvar"
|
||||
|
||||
// Get is a convenience method for sending GET request.
|
||||
// NOTE that remembers CLOSING the response object when it'll never be used.
|
||||
func Get(url string, data ...interface{}) (*ClientResponse, error) {
|
||||
@ -185,3 +187,69 @@ func TraceBytes(url string, data ...interface{}) []byte {
|
||||
func RequestBytes(method string, url string, data ...interface{}) []byte {
|
||||
return NewClient().RequestBytes(method, url, data...)
|
||||
}
|
||||
|
||||
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func GetVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("GET", url, data...)
|
||||
}
|
||||
|
||||
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PutVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("PUT", url, data...)
|
||||
}
|
||||
|
||||
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PostVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("POST", url, data...)
|
||||
}
|
||||
|
||||
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func DeleteVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("DELETE", url, data...)
|
||||
}
|
||||
|
||||
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func HeadVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("HEAD", url, data...)
|
||||
}
|
||||
|
||||
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PatchVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("PATCH", url, data...)
|
||||
}
|
||||
|
||||
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func ConnectVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func OptionsVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func TraceVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// RequestVar sends request using given HTTP method and data, retrieves converts the result
|
||||
// to specified pointer. It reads and closes the response object internally automatically.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func RequestVar(method string, url string, data ...interface{}) *gvar.Var {
|
||||
response, err := DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return gvar.New(nil)
|
||||
}
|
||||
defer response.Close()
|
||||
return gvar.New(response.ReadAll())
|
||||
}
|
||||
|
||||
77
net/ghttp/ghttp_client_var.go
Normal file
77
net/ghttp/ghttp_client_var.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
)
|
||||
|
||||
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) GetVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("GET", url, data...)
|
||||
}
|
||||
|
||||
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PutVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("PUT", url, data...)
|
||||
}
|
||||
|
||||
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PostVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("POST", url, data...)
|
||||
}
|
||||
|
||||
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) DeleteVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("DELETE", url, data...)
|
||||
}
|
||||
|
||||
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) HeadVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("HEAD", url, data...)
|
||||
}
|
||||
|
||||
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PatchVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("PATCH", url, data...)
|
||||
}
|
||||
|
||||
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) ConnectVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) OptionsVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) TraceVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// RequestVar sends request using given HTTP method and data, retrieves converts the result
|
||||
// to specified pointer. It reads and closes the response object internally automatically.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) RequestVar(method string, url string, data ...interface{}) *gvar.Var {
|
||||
response, err := c.DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return gvar.New(nil)
|
||||
}
|
||||
defer response.Close()
|
||||
return gvar.New(response.ReadAll())
|
||||
}
|
||||
@ -9,14 +9,15 @@ package ghttp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gres"
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -155,6 +156,7 @@ func (r *Request) IsAjaxRequest() bool {
|
||||
}
|
||||
|
||||
// GetClientIp returns the client ip of this request without port.
|
||||
// Note that this ip address might be modified by client header.
|
||||
func (r *Request) GetClientIp() string {
|
||||
if len(r.clientIp) == 0 {
|
||||
realIps := r.Header.Get("X-Forwarded-For")
|
||||
@ -178,17 +180,21 @@ func (r *Request) GetClientIp() string {
|
||||
r.clientIp = r.Header.Get("X-Real-IP")
|
||||
}
|
||||
if r.clientIp == "" || strings.EqualFold("unknown", realIps) {
|
||||
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
|
||||
if len(array) > 1 {
|
||||
r.clientIp = array[1]
|
||||
} else {
|
||||
r.clientIp = r.RemoteAddr
|
||||
}
|
||||
r.clientIp = r.GetRemoteIp()
|
||||
}
|
||||
}
|
||||
return r.clientIp
|
||||
}
|
||||
|
||||
// GetRemoteIp returns the ip from RemoteAddr.
|
||||
func (r *Request) GetRemoteIp() string {
|
||||
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
|
||||
if len(array) > 1 {
|
||||
return array[1]
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// GetUrl returns current URL of this request.
|
||||
func (r *Request) GetUrl() string {
|
||||
scheme := "http"
|
||||
@ -217,3 +223,13 @@ func (r *Request) GetReferer() string {
|
||||
func (r *Request) GetError() error {
|
||||
return r.error
|
||||
}
|
||||
|
||||
// ReloadParam is used for modifying request parameter.
|
||||
// Sometimes, we want to modify request parameters through middleware, but directly modifying Request.Body
|
||||
// is invalid, so it clears the parsed* marks to make the parameters re-parsed.
|
||||
func (r *Request) ReloadParam() {
|
||||
r.parsedBody = false
|
||||
r.parsedForm = false
|
||||
r.parsedQuery = false
|
||||
r.bodyContent = nil
|
||||
}
|
||||
|
||||
@ -189,7 +189,11 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseForm()
|
||||
return gconv.StructDeep(r.formMap, pointer, mapping...)
|
||||
m := r.formMap
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.
|
||||
|
||||
@ -193,7 +193,11 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
|
||||
// attribute mapping.
|
||||
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseQuery()
|
||||
return gconv.StructDeep(r.GetQueryMap(), pointer, mapping...)
|
||||
m := r.GetQueryMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.
|
||||
|
||||
@ -267,7 +267,11 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
|
||||
// the parameter <pointer> is a pointer to the struct object.
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(r.GetRequestMap(), pointer, mapping...)
|
||||
m := r.GetRequestMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.
|
||||
|
||||
@ -8,6 +8,18 @@ package ghttp
|
||||
|
||||
import "github.com/gogf/gf/container/gvar"
|
||||
|
||||
// GetRouterMap retrieves and returns a copy of router map.
|
||||
func (r *Request) GetRouterMap() map[string]string {
|
||||
if r.routerMap != nil {
|
||||
m := make(map[string]string, len(r.routerMap))
|
||||
for k, v := range r.routerMap {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRouterValue retrieves and returns the router value with given key name <key>.
|
||||
// It returns <def> if <key> does not exist.
|
||||
func (r *Request) GetRouterValue(key string, def ...interface{}) interface{} {
|
||||
|
||||
@ -55,7 +55,7 @@ func (r *Response) DefaultCORSOptions() CORSOptions {
|
||||
array := gstr.SplitAndTrim(headers, ",")
|
||||
for _, header := range array {
|
||||
if _, ok := defaultAllowHeadersMap[header]; !ok {
|
||||
options.AllowHeaders += header + ","
|
||||
options.AllowHeaders += "," + header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,28 +9,17 @@ package ghttp
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// Cookie for HTTP COOKIE management.
|
||||
type Cookie struct {
|
||||
data map[string]CookieItem // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxage time.Duration // The default cookie maxage.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// CookieItem is cookie item stored in Cookie management object.
|
||||
type CookieItem struct {
|
||||
value string // Cookie value.
|
||||
domain string // Cookie domain.
|
||||
path string // Cookie path.
|
||||
expireAt int64 // Cookie expiration timestamp.
|
||||
httpOnly bool
|
||||
data map[string]*http.Cookie // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxAge time.Duration // The default cookie max age.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// GetCookie creates or retrieves a cookie object with given request.
|
||||
@ -48,21 +37,20 @@ func GetCookie(r *Request) *Cookie {
|
||||
|
||||
// init does lazy initialization for cookie object.
|
||||
func (c *Cookie) init() {
|
||||
if c.data == nil {
|
||||
c.data = make(map[string]CookieItem)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxage = c.request.Server.GetCookieMaxAge()
|
||||
c.response = c.request.Response
|
||||
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
|
||||
//if c.domain == "" {
|
||||
// c.domain = c.request.GetHost()
|
||||
//}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem{
|
||||
v.Value, v.Domain, v.Path, int64(v.Expires.Second()), v.HttpOnly,
|
||||
}
|
||||
}
|
||||
if c.data != nil {
|
||||
return
|
||||
}
|
||||
c.data = make(map[string]*http.Cookie)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxAge = c.request.Server.GetCookieMaxAge()
|
||||
c.response = c.request.Response
|
||||
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
|
||||
//if c.domain == "" {
|
||||
// c.domain = c.request.GetHost()
|
||||
//}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +59,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
c.init()
|
||||
m := make(map[string]string)
|
||||
for k, v := range c.data {
|
||||
m[k] = v.value
|
||||
m[k] = v.Value
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -80,7 +68,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
func (c *Cookie) Contains(key string) bool {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expireAt >= 0 {
|
||||
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -89,7 +77,7 @@ func (c *Cookie) Contains(key string) bool {
|
||||
|
||||
// Set sets cookie item with default domain, path and expiration age.
|
||||
func (c *Cookie) Set(key, value string) {
|
||||
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
|
||||
c.SetCookie(key, value, c.domain, c.path, c.maxAge)
|
||||
}
|
||||
|
||||
// SetCookie sets cookie item given given domain, path and expiration age.
|
||||
@ -101,11 +89,25 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
}
|
||||
c.data[key] = CookieItem{
|
||||
value, domain, path, gtime.Timestamp() + int64(maxAge.Seconds()), isHttpOnly,
|
||||
c.data[key] = &http.Cookie{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
Expires: time.Now().Add(maxAge),
|
||||
HttpOnly: isHttpOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHttpCookie sets cookie with *http.Cookie.
|
||||
func (c *Cookie) SetHttpCookie(cookie *http.Cookie) {
|
||||
c.init()
|
||||
if cookie.Expires.IsZero() {
|
||||
cookie.Expires = time.Now().Add(c.maxAge)
|
||||
}
|
||||
c.data[cookie.Name] = cookie
|
||||
}
|
||||
|
||||
// GetSessionId retrieves and returns the session id from cookie.
|
||||
func (c *Cookie) GetSessionId() string {
|
||||
return c.Get(c.server.GetSessionIdName())
|
||||
@ -121,8 +123,8 @@ func (c *Cookie) SetSessionId(id string) {
|
||||
func (c *Cookie) Get(key string, def ...string) string {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expireAt >= 0 {
|
||||
return r.value
|
||||
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
|
||||
return r.Value
|
||||
}
|
||||
}
|
||||
if len(def) > 0 {
|
||||
@ -148,22 +150,12 @@ func (c *Cookie) Flush() {
|
||||
if len(c.data) == 0 {
|
||||
return
|
||||
}
|
||||
for k, v := range c.data {
|
||||
// Cookie item matches expire != 0 means it is set in this request,
|
||||
for _, v := range c.data {
|
||||
// If cookie item is v.Expires.IsZero() means it is set in this request,
|
||||
// which should be outputted to client.
|
||||
if v.expireAt == 0 {
|
||||
if v.Expires.IsZero() {
|
||||
continue
|
||||
}
|
||||
http.SetCookie(
|
||||
c.response.Writer,
|
||||
&http.Cookie{
|
||||
Name: k,
|
||||
Value: v.value,
|
||||
Domain: v.domain,
|
||||
Path: v.path,
|
||||
Expires: time.Unix(v.expireAt, 0),
|
||||
HttpOnly: v.httpOnly,
|
||||
},
|
||||
)
|
||||
http.SetCookie(c.response.Writer, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Close the session, which automatically update the TTL
|
||||
// of the session if it exists.
|
||||
request.Session.Close()
|
||||
|
||||
// Close the request and response body
|
||||
// to release the file descriptor in time.
|
||||
request.Request.Body.Close()
|
||||
if request.Request.Response != nil {
|
||||
request.Request.Response.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// ============================================================
|
||||
|
||||
@ -8,6 +8,7 @@ package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -33,6 +34,52 @@ func Test_Cookie(t *testing.T) {
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
r1, e1 := client.Get("/set?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
|
||||
t.Assert(e1, nil)
|
||||
t.Assert(r1.ReadAllString(), "")
|
||||
|
||||
t.Assert(client.GetContent("/set?k=key2&v=200"), "")
|
||||
|
||||
t.Assert(client.GetContent("/get?k=key1"), "100")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
t.Assert(client.GetContent("/get?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetHttpCookie(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Cookie.SetHttpCookie(&http.Cookie{
|
||||
Name: r.GetString("k"),
|
||||
Value: r.GetString("v"),
|
||||
})
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Cookie.Get(r.GetString("k")))
|
||||
})
|
||||
s.BindHandler("/remove", func(r *ghttp.Request) {
|
||||
r.Cookie.Remove(r.GetString("k"))
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
|
||||
@ -17,7 +17,7 @@ var (
|
||||
|
||||
func init() {
|
||||
genv.Set("UNDER_TEST", "1")
|
||||
for i := 8000; i <= 9000; i++ {
|
||||
for i := 7000; i <= 8000; i++ {
|
||||
ports.Append(i)
|
||||
}
|
||||
}
|
||||
|
||||
41
net/ghttp/ghttp_unit_ip_test.go
Normal file
41
net/ghttp/ghttp_unit_ip_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// static service testing.
|
||||
|
||||
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 TestRequest_GetRemoteIp(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRemoteIp())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.SetPort(p)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/"), "127.0.0.1")
|
||||
})
|
||||
|
||||
}
|
||||
@ -7,10 +7,14 @@
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -517,3 +521,39 @@ func Test_Params_GetRequestMap(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Modify(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/param/modify", func(r *ghttp.Request) {
|
||||
param := r.GetMap()
|
||||
param["id"] = 2
|
||||
paramBytes, err := gjson.Encode(param)
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Request.Body = ioutil.NopCloser(bytes.NewReader(paramBytes))
|
||||
r.ReloadParam()
|
||||
r.Response.Write(r.GetMap())
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(
|
||||
client.PostContent(
|
||||
"/param/modify",
|
||||
`{"id":1}`,
|
||||
),
|
||||
`{"id":2}`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,6 +73,33 @@ func Test_Router_Basic2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Value(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/{hash}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterString("hash"))
|
||||
})
|
||||
s.BindHandler("/{hash}.{type}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterString("type"))
|
||||
})
|
||||
s.BindHandler("/{hash}.{type}.map", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterMap()["type"])
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(client.GetContent("/data"), "data")
|
||||
t.Assert(client.GetContent("/data.json"), "json")
|
||||
t.Assert(client.GetContent("/data.json.map"), "json")
|
||||
})
|
||||
}
|
||||
|
||||
// HTTP method register.
|
||||
func Test_Router_Method(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
|
||||
@ -97,7 +97,7 @@ func (o *GroupObjRest) Head(r *ghttp.Request) {
|
||||
r.Response.Header().Set("head-ok", "1")
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest(t *testing.T) {
|
||||
func Test_Router_GroupRest1(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
g := s.Group("/api")
|
||||
@ -172,3 +172,80 @@ func Test_Router_GroupRest(t *testing.T) {
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest2(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Group("/api", func(group *ghttp.RouterGroup) {
|
||||
ctl := new(GroupCtlRest)
|
||||
obj := new(GroupObjRest)
|
||||
group.REST("/ctl", ctl)
|
||||
group.REST("/obj", obj)
|
||||
group.REST("/{.struct}/{.method}", ctl)
|
||||
group.REST("/{.struct}/{.method}", obj)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/api/ctl"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
|
||||
resp1, err := client.Head("/api/ctl")
|
||||
if err == nil {
|
||||
defer resp1.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp1.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/obj"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/obj"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/obj"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
|
||||
resp2, err := client.Head("/api/obj")
|
||||
if err == nil {
|
||||
defer resp2.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp2.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
|
||||
resp3, err := client.Head("/api/group-ctl-rest/head")
|
||||
if err == nil {
|
||||
defer resp3.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp3.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
|
||||
resp4, err := client.Head("/api/group-obj-rest/head")
|
||||
if err == nil {
|
||||
defer resp4.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,3 +157,38 @@ func Test_Router_Hook_Multi(t *testing.T) {
|
||||
t.Assert(client.GetContent("/multi-hook"), "12show")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Hook_ExitAll(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
s.Group("/hook", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
r.Response.Write("1")
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("2")
|
||||
})
|
||||
})
|
||||
|
||||
s.BindHookHandler("/hook/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
|
||||
r.Response.Write("hook")
|
||||
r.ExitAll()
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/test"), "test")
|
||||
t.Assert(client.GetContent("/hook/test"), "hook")
|
||||
})
|
||||
}
|
||||
|
||||
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Header() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
header = g.MapStrStr{
|
||||
"Span-Id": "0.1",
|
||||
"Trace-Id": "123456789",
|
||||
}
|
||||
)
|
||||
content := g.Client().Header(header).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_HeaderRaw() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
headerRaw = `
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3950.0 Safari/537.36
|
||||
Span-Id: 0.1
|
||||
Trace-Id: 123456789
|
||||
`
|
||||
)
|
||||
content := g.Client().HeaderRaw(headerRaw).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_Cookie() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/cookie"
|
||||
cookie = g.MapStrStr{
|
||||
"SessionId": "123",
|
||||
}
|
||||
)
|
||||
content := g.Client().Cookie(cookie).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// SessionId: 123
|
||||
}
|
||||
|
||||
func ExampleClient_ContentJson() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/json"
|
||||
jsonStr = `{"id":10000,"name":"john"}`
|
||||
jsonMap = g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}
|
||||
)
|
||||
// Post using JSON string.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonStr))
|
||||
// Post using JSON map.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonMap))
|
||||
|
||||
// Output:
|
||||
// Content-Type: application/json, id: 10000
|
||||
// Content-Type: application/json, id: 10000
|
||||
}
|
||||
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Get() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter along with URL.
|
||||
r1, err := g.Client().Get(url + "?id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with string parameter in request body.
|
||||
r2, err := g.Client().Get(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r3, err := g.Client().Get(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r3.Close()
|
||||
fmt.Println(r3.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().GetBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().GetContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
user *User
|
||||
url = "http://127.0.0.1:8999/var/json"
|
||||
)
|
||||
err := g.Client().GetVar(url).Scan(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(user)
|
||||
|
||||
// Output:
|
||||
// &{1 john}
|
||||
}
|
||||
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
p := 8999
|
||||
s := g.Server(p)
|
||||
// HTTP method handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"GET: query: %d, %s",
|
||||
r.GetQueryInt("id"),
|
||||
r.GetQueryString("name"),
|
||||
)
|
||||
})
|
||||
group.PUT("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"PUT: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.POST("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"POST: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.DELETE("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"DELETE: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.HEAD("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("head")
|
||||
})
|
||||
group.OPTIONS("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("options")
|
||||
})
|
||||
})
|
||||
// Client chaining operations handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/header", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Span-Id: %s, Trace-Id: %s",
|
||||
r.Header.Get("Span-Id"),
|
||||
r.Header.Get("Trace-Id"),
|
||||
)
|
||||
})
|
||||
group.ALL("/cookie", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"SessionId: %s",
|
||||
r.Cookie.Get("SessionId"),
|
||||
)
|
||||
})
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Content-Type: %s, id: %d",
|
||||
r.Header.Get("Content-Type"),
|
||||
r.GetInt("id"),
|
||||
)
|
||||
})
|
||||
})
|
||||
// Other testing handlers.
|
||||
s.Group("/var", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Write(`{"id":1,"name":"john"}`)
|
||||
})
|
||||
group.ALL("/jsons", func(r *ghttp.Request) {
|
||||
r.Response.Write(`[{"id":1,"name":"john"}, {"id":2,"name":"smith"}]`)
|
||||
})
|
||||
})
|
||||
s.SetAccessLogEnabled(false)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.SetPort(p)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Post() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter in request body.
|
||||
r1, err := g.Client().Post(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r2, err := g.Client().Post(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().PostBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
users []User
|
||||
url = "http://127.0.0.1:8999/var/jsons"
|
||||
)
|
||||
err := g.Client().PostVar(url).Scan(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(users)
|
||||
|
||||
// Output:
|
||||
// [{1 john} {2 smith}]
|
||||
}
|
||||
@ -10,10 +10,11 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleGetServer() {
|
||||
func ExampleHelloWorld() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("hello world")
|
||||
@ -22,6 +23,27 @@ func ExampleGetServer() {
|
||||
s.Run()
|
||||
}
|
||||
|
||||
// Custom saving file name.
|
||||
func ExampleUploadFile_Save() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/upload", func(r *ghttp.Request) {
|
||||
file := r.GetUploadFile("TestFile")
|
||||
if file == nil {
|
||||
r.Response.Write("empty file")
|
||||
return
|
||||
}
|
||||
file.Filename = "MyCustomFileName.txt"
|
||||
fileName, err := file.Save(gfile.TempDir())
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Response.Write(fileName)
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
func ExampleClientResponse_RawDump() {
|
||||
response, err := g.Client().Get("https://goframe.org")
|
||||
if err != nil {
|
||||
|
||||
@ -13,36 +13,42 @@ import (
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
var cache = New()
|
||||
var defaultCache = New()
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
// It does not expire if <duration> == 0.
|
||||
func Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
cache.Set(key, value, duration)
|
||||
defaultCache.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
return cache.SetIfNotExist(key, value, duration)
|
||||
return defaultCache.SetIfNotExist(key, value, duration)
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
cache.Sets(data, duration)
|
||||
defaultCache.Sets(data, duration)
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func Get(key interface{}) interface{} {
|
||||
return cache.Get(key)
|
||||
return defaultCache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
return cache.GetVar(key)
|
||||
return defaultCache.GetVar(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>,
|
||||
@ -51,14 +57,14 @@ func GetVar(key interface{}) *gvar.Var {
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSet(key, value, duration)
|
||||
return defaultCache.GetOrSet(key, value, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFunc(key, f, duration)
|
||||
return defaultCache.GetOrSetFunc(key, f, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
|
||||
@ -67,47 +73,59 @@ func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration)
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFuncLock(key, f, duration)
|
||||
return defaultCache.GetOrSetFuncLock(key, f, duration)
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func Contains(key interface{}) bool {
|
||||
return cache.Contains(key)
|
||||
return defaultCache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
return cache.Remove(keys...)
|
||||
return defaultCache.Remove(keys...)
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func Removes(keys []interface{}) {
|
||||
cache.Removes(keys)
|
||||
defaultCache.Removes(keys)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func Data() map[interface{}]interface{} {
|
||||
return cache.Data()
|
||||
return defaultCache.Data()
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func Keys() []interface{} {
|
||||
return cache.Keys()
|
||||
return defaultCache.Keys()
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func KeyStrings() []string {
|
||||
return cache.KeyStrings()
|
||||
return defaultCache.KeyStrings()
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func Values() []interface{} {
|
||||
return cache.Values()
|
||||
return defaultCache.Values()
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func Size() int {
|
||||
return cache.Size()
|
||||
return defaultCache.Size()
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func GetExpire(key interface{}) time.Duration {
|
||||
return defaultCache.GetExpire(key)
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
return defaultCache.UpdateExpire(key, duration)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ type memCache struct {
|
||||
// Internal cache item.
|
||||
type memCacheItem struct {
|
||||
v interface{} // Value.
|
||||
e int64 // Expire time in milliseconds.
|
||||
e int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// Internal event item.
|
||||
@ -97,7 +97,6 @@ func newMemCache(lruCap ...int) *memCache {
|
||||
}
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
@ -113,6 +112,52 @@ func (c *memCache) Set(key interface{}, value interface{}, duration time.Duratio
|
||||
})
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func (c *memCache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: value,
|
||||
e: item.e,
|
||||
}
|
||||
return item.v, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
newExpireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: item.v,
|
||||
e: newExpireTime,
|
||||
}
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: newExpireTime,
|
||||
})
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) GetExpire(key interface{}) time.Duration {
|
||||
c.dataMu.RLock()
|
||||
defer c.dataMu.RUnlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
|
||||
// cache, which is expired after <duration>.
|
||||
//
|
||||
@ -247,7 +292,11 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Du
|
||||
// It does nothing if function <f> returns nil.
|
||||
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, f(), duration)
|
||||
value := f()
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
|
||||
@ -15,15 +15,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
cache = gcache.New()
|
||||
cacheLru = gcache.New(10000)
|
||||
localCache = gcache.New()
|
||||
localCacheLru = gcache.New(10000)
|
||||
)
|
||||
|
||||
func Benchmark_CacheSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Set(i, i, 0)
|
||||
localCache.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -33,7 +33,7 @@ func Benchmark_CacheGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Get(i)
|
||||
localCache.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -43,7 +43,7 @@ func Benchmark_CacheRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Remove(i)
|
||||
localCache.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -53,7 +53,7 @@ func Benchmark_CacheLruSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Set(i, i, 0)
|
||||
localCacheLru.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -63,7 +63,7 @@ func Benchmark_CacheLruGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Get(i)
|
||||
localCacheLru.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -73,7 +73,7 @@ func Benchmark_CacheLruRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Remove(i)
|
||||
localCacheLru.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
package gcache_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +75,53 @@ func TestCache_Set_Expire(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Update_GetExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
expire1 := gcache.GetExpire(key)
|
||||
gcache.Update(key, 12)
|
||||
expire2 := gcache.GetExpire(key)
|
||||
t.Assert(gcache.GetVar(key), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
expire1 := cache.GetExpire(1)
|
||||
cache.Update(1, 12)
|
||||
expire2 := cache.GetExpire(1)
|
||||
t.Assert(cache.GetVar(1), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_UpdateExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
defer gcache.Remove(key)
|
||||
oldExpire := gcache.GetExpire(key)
|
||||
newExpire := 10 * time.Second
|
||||
gcache.UpdateExpire(key, newExpire)
|
||||
t.AssertNE(gcache.GetExpire(key), oldExpire)
|
||||
t.Assert(math.Ceil(gcache.GetExpire(key).Seconds()), 10)
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
oldExpire := cache.GetExpire(1)
|
||||
newExpire := 10 * time.Second
|
||||
cache.UpdateExpire(1, newExpire)
|
||||
t.AssertNE(cache.GetExpire(1), oldExpire)
|
||||
t.Assert(math.Ceil(cache.GetExpire(1).Seconds()), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Keys_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
@ -269,7 +318,7 @@ func TestCache_Basic(t *testing.T) {
|
||||
t.Assert(cache.Size(), 0)
|
||||
}
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Remove(g.Slice{1, 2, 3}...)
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
|
||||
@ -113,8 +113,10 @@ func (c *Config) filePath(file ...string) (path string) {
|
||||
// The parameter <path> can be absolute or relative path,
|
||||
// but absolute path is strongly recommended.
|
||||
func (c *Config) SetPath(path string) error {
|
||||
isDir := false
|
||||
realPath := ""
|
||||
var (
|
||||
isDir = false
|
||||
realPath = ""
|
||||
)
|
||||
if file := gres.Get(path); file != nil {
|
||||
realPath = path
|
||||
isDir = file.FileInfo().IsDir()
|
||||
|
||||
@ -9,11 +9,12 @@
|
||||
package gcfg_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gcfg"
|
||||
@ -475,3 +476,13 @@ func TestCfg_Config(t *testing.T) {
|
||||
t.Assert(gcfg.GetContent("name"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCfg_With_UTF8_BOM(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cfg := g.Cfg("test-cfg-with-utf8-bom")
|
||||
t.Assert(cfg.SetPath("testdata"), nil)
|
||||
cfg.SetFileName("cfg-with-utf8-bom.toml")
|
||||
t.Assert(cfg.GetInt("test.testInt"), 1)
|
||||
t.Assert(cfg.GetString("test.testStr"), "test")
|
||||
})
|
||||
}
|
||||
|
||||
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
[test]
|
||||
testInt=1
|
||||
testStr="test"
|
||||
@ -7,20 +7,30 @@
|
||||
|
||||
package gcmd
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Scan prints <info> to stdout, reads and returns user input, which stops by '\n'.
|
||||
func Scan(info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Print(info...)
|
||||
fmt.Scanln(&s)
|
||||
return s
|
||||
return readline()
|
||||
}
|
||||
|
||||
// Scanf prints <info> to stdout with <format>, reads and returns user input, which stops by '\n'.
|
||||
func Scanf(format string, info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Printf(format, info...)
|
||||
fmt.Scanln(&s)
|
||||
return readline()
|
||||
}
|
||||
|
||||
func readline() string {
|
||||
var s string
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
s, _ = reader.ReadString('\n')
|
||||
s = gstr.Trim(s)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -19,12 +19,12 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// Separator for file system.
|
||||
Separator = string(filepath.Separator)
|
||||
)
|
||||
|
||||
var (
|
||||
// Separator for file system.
|
||||
// It here defines the separator as variable
|
||||
// to allow it modified by developer if necessary.
|
||||
Separator = string(filepath.Separator)
|
||||
|
||||
// DefaultPerm is the default perm for file opening.
|
||||
DefaultPermOpen = os.FileMode(0666)
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfpool"
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
@ -32,6 +33,7 @@ import (
|
||||
type Logger struct {
|
||||
rmu sync.Mutex // Mutex for rotation feature.
|
||||
ctx context.Context // Context for logging.
|
||||
init *gtype.Bool // Initialized.
|
||||
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
|
||||
config Config // Logger configuration.
|
||||
}
|
||||
@ -58,12 +60,9 @@ const (
|
||||
// New creates and returns a custom logger.
|
||||
func New() *Logger {
|
||||
logger := &Logger{
|
||||
init: gtype.NewBool(),
|
||||
config: DefaultConfig(),
|
||||
}
|
||||
// Initialize the internal handler after some delay.
|
||||
gtimer.AddOnce(time.Second, func() {
|
||||
gtimer.AddOnce(logger.config.RotateCheckInterval, logger.rotateChecksTimely)
|
||||
})
|
||||
return logger
|
||||
}
|
||||
|
||||
@ -77,10 +76,11 @@ func NewWithWriter(writer io.Writer) *Logger {
|
||||
// Clone returns a new logger, which is the clone the current logger.
|
||||
// It's commonly used for chaining operations.
|
||||
func (l *Logger) Clone() *Logger {
|
||||
logger := Logger{}
|
||||
logger = *l
|
||||
logger := New()
|
||||
logger.ctx = l.ctx
|
||||
logger.config = l.config
|
||||
logger.parent = l
|
||||
return &logger
|
||||
return logger
|
||||
}
|
||||
|
||||
// getFilePath returns the logging file path.
|
||||
@ -99,6 +99,21 @@ func (l *Logger) getFilePath(now time.Time) string {
|
||||
|
||||
// print prints <s> to defined writer, logging file or passed <std>.
|
||||
func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
|
||||
// Lazy initialize for rotation feature.
|
||||
// It uses atomic reading operation to enhance the performance checking.
|
||||
// It here uses CAP for performance and concurrent safety.
|
||||
p := l
|
||||
if p.parent != nil {
|
||||
p = p.parent
|
||||
}
|
||||
if !p.init.Val() && p.init.Cas(false, true) {
|
||||
// It just initializes once for each logger.
|
||||
if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
|
||||
gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
|
||||
intlog.Printf("logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
now = time.Now()
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/gogf/gf/encoding/gcompress"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -81,8 +82,20 @@ func (l *Logger) rotateChecksTimely() {
|
||||
defer gtimer.AddOnce(l.config.RotateCheckInterval, l.rotateChecksTimely)
|
||||
// Checks whether file rotation not enabled.
|
||||
if l.config.RotateSize <= 0 && l.config.RotateExpire == 0 {
|
||||
intlog.Printf(
|
||||
"logging rotation ignore checks: RotateSize: %d, RotateExpire: %s",
|
||||
l.config.RotateSize, l.config.RotateExpire.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// It here uses memory lock to guarantee the concurrent safety.
|
||||
lockKey := "glog.rotateChecksTimely:" + l.config.Path
|
||||
if !gmlock.TryLock(lockKey) {
|
||||
return
|
||||
}
|
||||
defer gmlock.Unlock(lockKey)
|
||||
|
||||
var (
|
||||
now = time.Now()
|
||||
pattern = "*.log, *.gz"
|
||||
|
||||
@ -9,6 +9,7 @@ package grpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
@ -47,6 +48,14 @@ func Add(f func()) error {
|
||||
return pool.Add(f)
|
||||
}
|
||||
|
||||
// AddWithRecover pushes a new job to the pool with specified recover function.
|
||||
// The optional <recoverFunc> is called when any panic during executing of <userFunc>.
|
||||
// If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
|
||||
// The job will be executed asynchronously.
|
||||
func AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
|
||||
return pool.AddWithRecover(userFunc, recoverFunc...)
|
||||
}
|
||||
|
||||
// Size returns current goroutine count of default goroutine pool.
|
||||
func Size() int {
|
||||
return pool.Size()
|
||||
@ -81,6 +90,23 @@ func (p *Pool) Add(f func()) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWithRecover pushes a new job to the pool with specified recover function.
|
||||
// The optional <recoverFunc> is called when any panic during executing of <userFunc>.
|
||||
// If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
|
||||
// The job will be executed asynchronously.
|
||||
func (p *Pool) AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
|
||||
return p.Add(func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if len(recoverFunc) > 0 && recoverFunc[0] != nil {
|
||||
recoverFunc[0](errors.New(fmt.Sprintf(`%v`, err)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
userFunc()
|
||||
})
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the pool.
|
||||
// This capacity is defined when pool is created.
|
||||
// It returns -1 if there's no limit.
|
||||
|
||||
@ -97,6 +97,23 @@ func Test_Limit3(t *testing.T) {
|
||||
t.Assert(array.Len(), 100)
|
||||
t.Assert(pool.IsClosed(), true)
|
||||
t.AssertNE(pool.Add(func() {}), nil)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AddWithRecover(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewArray(true)
|
||||
grpool.AddWithRecover(func() {
|
||||
array.Append(1)
|
||||
panic(1)
|
||||
}, func(err error) {
|
||||
array.Append(1)
|
||||
})
|
||||
grpool.AddWithRecover(func() {
|
||||
panic(1)
|
||||
array.Append(1)
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ func parseDateStr(s string) (year, month, day int) {
|
||||
return
|
||||
}
|
||||
|
||||
// StrToTime converts string to *Time object.
|
||||
// StrToTime converts string to *Time object. It also supports timestamp string.
|
||||
// The parameter <format> is unnecessary, which specifies the format for converting like "Y-m-d H:i:s".
|
||||
// If <format> is given, it acts as same as function StrToTimeFormat.
|
||||
// If <format> is not given, it converts string as a "standard" datetime string.
|
||||
@ -236,6 +236,10 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
if len(format) > 0 {
|
||||
return StrToTimeFormat(str, format[0])
|
||||
}
|
||||
if isTimestampStr(str) {
|
||||
timestamp, _ := strconv.ParseInt(str, 10, 64)
|
||||
return NewFromTimeStamp(timestamp), nil
|
||||
}
|
||||
var (
|
||||
year, month, day int
|
||||
hour, min, sec, nsec int
|
||||
@ -290,8 +294,8 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
if h > 24 || m > 59 || s > 59 {
|
||||
return nil, gerror.Newf("invalid zone string: %s", match[6])
|
||||
}
|
||||
// Comparing the given time zone whether equals to current tine zone,
|
||||
// it converts it to UTC if they does not.
|
||||
// Comparing the given time zone whether equals to current time zone,
|
||||
// it converts it to UTC if they does not equal.
|
||||
_, localOffset := time.Now().Zone()
|
||||
// Comparing in seconds.
|
||||
if (h*3600 + m*60 + s) != localOffset {
|
||||
@ -326,7 +330,7 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if year <= 0 || month <= 0 || day <= 0 || hour < 0 || min < 0 || sec < 0 || nsec < 0 {
|
||||
if year <= 0 {
|
||||
return nil, errors.New("invalid time string:" + str)
|
||||
}
|
||||
// It finally converts all time to UTC time zone.
|
||||
@ -424,3 +428,17 @@ func FuncCost(f func()) int64 {
|
||||
f()
|
||||
return TimestampNano() - t
|
||||
}
|
||||
|
||||
// isTimestampStr checks and returns whether given string a timestamp string.
|
||||
func isTimestampStr(s string) bool {
|
||||
length := len(s)
|
||||
if length == 0 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -17,11 +17,22 @@ type Time struct {
|
||||
TimeWrapper
|
||||
}
|
||||
|
||||
// New creates and returns a Time object with given time.Time object.
|
||||
// The parameter <t> is optional.
|
||||
func New(t ...time.Time) *Time {
|
||||
if len(t) > 0 {
|
||||
return NewFromTime(t[0])
|
||||
// New creates and returns a Time object with given parameter.
|
||||
// The optional parameter can be type of: time.Time, string or integer.
|
||||
func New(param ...interface{}) *Time {
|
||||
if len(param) > 0 {
|
||||
switch r := param[0].(type) {
|
||||
case time.Time:
|
||||
return NewFromTime(r)
|
||||
case string:
|
||||
return NewFromStr(r)
|
||||
case []byte:
|
||||
return NewFromStr(string(r))
|
||||
case int:
|
||||
return NewFromTimeStamp(int64(r))
|
||||
case int64:
|
||||
return NewFromTimeStamp(r)
|
||||
}
|
||||
}
|
||||
return &Time{
|
||||
TimeWrapper{time.Time{}},
|
||||
|
||||
@ -86,9 +86,7 @@ func Test_RFC822(t *testing.T) {
|
||||
|
||||
func Test_StrToTime(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
//正常日期列表
|
||||
//正则的原因,日期"06.01.02","2006.01","2006..01"无法覆盖gtime.go的百分百
|
||||
var testDatetimes = []string{
|
||||
var testDateTimes = []string{
|
||||
"2006-01-02 15:04:05",
|
||||
"2006/01/02 15:04:05",
|
||||
"2006.01.02 15:04:05.000",
|
||||
@ -103,7 +101,7 @@ func Test_StrToTime(t *testing.T) {
|
||||
"02.jan.2006:15:04:05",
|
||||
}
|
||||
|
||||
for _, item := range testDatetimes {
|
||||
for _, item := range testDateTimes {
|
||||
timeTemp, err := gtime.StrToTime(item)
|
||||
if err != nil {
|
||||
t.Error("test fail")
|
||||
@ -155,7 +153,6 @@ func Test_StrToTime(t *testing.T) {
|
||||
var testDatesFail = []string{
|
||||
"2006.01",
|
||||
"06..02",
|
||||
"20060102",
|
||||
}
|
||||
|
||||
for _, item := range testDatesFail {
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
// time.Time
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := time.Now()
|
||||
timeTemp := gtime.New(timeNow)
|
||||
@ -24,6 +25,23 @@ func Test_New(t *testing.T) {
|
||||
timeTemp1 := gtime.New()
|
||||
t.Assert(timeTemp1.Time, time.Time{})
|
||||
})
|
||||
// string
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.String())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.TimestampMicroStr())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
// int64
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.TimestampMicro())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Nil(t *testing.T) {
|
||||
@ -42,7 +60,7 @@ func Test_NewFromStr(t *testing.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2006-01-02 15:04:05")
|
||||
|
||||
timeTemp1 := gtime.NewFromStr("20060102")
|
||||
timeTemp1 := gtime.NewFromStr("2006.0102")
|
||||
if timeTemp1 != nil {
|
||||
t.Error("test fail")
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ const (
|
||||
STATUS_READY = 0 // Job is ready for running.
|
||||
STATUS_RUNNING = 1 // Job is already running.
|
||||
STATUS_STOPPED = 2 // Job is stopped.
|
||||
STATUS_RESET = 3 // Job is reset.
|
||||
STATUS_CLOSED = -1 // Job is closed and waiting to be deleted.
|
||||
gPANIC_EXIT = "exit" // Internal usage for custom job exit function with panic.
|
||||
gDEFAULT_TIMES = math.MaxInt32 // Default limit running times, a big number.
|
||||
|
||||
@ -106,6 +106,11 @@ func (entry *Entry) Stop() {
|
||||
entry.status.Set(STATUS_STOPPED)
|
||||
}
|
||||
|
||||
//Reset reset the job.
|
||||
func (entry *Entry) Reset() {
|
||||
entry.status.Set(STATUS_RESET)
|
||||
}
|
||||
|
||||
// Close closes the job, and then it will be removed from the timer.
|
||||
func (entry *Entry) Close() {
|
||||
entry.status.Set(STATUS_CLOSED)
|
||||
@ -138,6 +143,8 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool)
|
||||
return false, true
|
||||
case STATUS_CLOSED:
|
||||
return false, false
|
||||
case STATUS_RESET:
|
||||
return false, true
|
||||
}
|
||||
// Firstly checks using the ticks, this may be low precision as one tick is a little bit long.
|
||||
if diff := nowTicks - entry.create; diff > 0 && diff%entry.interval == 0 {
|
||||
|
||||
@ -74,6 +74,10 @@ func (w *wheel) proceed() {
|
||||
}
|
||||
// If rolls on the job.
|
||||
if addable {
|
||||
//If STATUS_RESET , reset to runnable state.
|
||||
if entry.Status() == STATUS_RESET {
|
||||
entry.SetStatus(STATUS_READY)
|
||||
}
|
||||
entry.wheel.timer.doAddEntryByParent(entry.rawIntervalMs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
package gtimer_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +74,24 @@ func TestTimer_Start_Stop_Close(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_Reset(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
glog.Printf("start time:%d", time.Now().Unix())
|
||||
singleton := timer.AddSingleton(2*time.Second, func() {
|
||||
timestamp := time.Now().Unix()
|
||||
glog.Println(timestamp)
|
||||
array.Append(timestamp)
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
glog.Printf("reset time:%d", time.Now().Unix())
|
||||
singleton.Reset()
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Assert(array.Len(), 6)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_AddSingleton(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
|
||||
@ -24,9 +24,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
|
||||
firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
|
||||
)
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gstr_test
|
||||
|
||||
import (
|
||||
|
||||
@ -44,12 +44,12 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
|
||||
// Assert the common combination of types, and finally it uses reflection.
|
||||
m := make(map[string]interface{})
|
||||
dataMap := make(map[string]interface{})
|
||||
switch r := value.(type) {
|
||||
case string:
|
||||
// If it is a JSON string, automatically unmarshal it!
|
||||
if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' {
|
||||
if err := json.Unmarshal([]byte(r), &m); err != nil {
|
||||
if err := json.Unmarshal([]byte(r), &dataMap); err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -58,7 +58,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
case []byte:
|
||||
// If it is a JSON string, automatically unmarshal it!
|
||||
if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' {
|
||||
if err := json.Unmarshal(r, &m); err != nil {
|
||||
if err := json.Unmarshal(r, &dataMap); err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -66,61 +66,61 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]int:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]uint:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]float32:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]float64:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[string]bool:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]int:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]uint:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]float32:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]float64:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]interface{}:
|
||||
return r
|
||||
case map[int]interface{}:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[int]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[uint]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
|
||||
default:
|
||||
@ -146,15 +146,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
length := rv.Len()
|
||||
for i := 0; i < length; i += 2 {
|
||||
if i+1 < length {
|
||||
m[String(rv.Index(i).Interface())] = rv.Index(i + 1).Interface()
|
||||
dataMap[String(rv.Index(i).Interface())] = rv.Index(i + 1).Interface()
|
||||
} else {
|
||||
m[String(rv.Index(i).Interface())] = nil
|
||||
dataMap[String(rv.Index(i).Interface())] = nil
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
ks := rv.MapKeys()
|
||||
for _, k := range ks {
|
||||
m[String(k.Interface())] = rv.MapIndex(k).Interface()
|
||||
dataMap[String(k.Interface())] = rv.MapIndex(k).Interface()
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Map converting interface check.
|
||||
@ -165,7 +165,6 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
var (
|
||||
rtField reflect.StructField
|
||||
rvField reflect.Value
|
||||
rvKind reflect.Kind
|
||||
rt = rv.Type()
|
||||
name = ""
|
||||
tagArray = StructTagPriority
|
||||
@ -216,36 +215,45 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
rvKind = rvField.Kind()
|
||||
if rvKind == reflect.Ptr {
|
||||
rvField = rvField.Elem()
|
||||
rvKind = rvField.Kind()
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
rvAttrKind = rvField.Kind()
|
||||
)
|
||||
if rvAttrKind == reflect.Ptr {
|
||||
rvAttrField = rvField.Elem()
|
||||
rvAttrKind = rvAttrField.Kind()
|
||||
}
|
||||
if rvKind == reflect.Struct {
|
||||
hasNoTag := name == fieldName
|
||||
if rvAttrKind == reflect.Struct {
|
||||
var (
|
||||
hasNoTag = name == fieldName
|
||||
rvAttrInterface = rvAttrField.Interface()
|
||||
)
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
for k, v := range doMapConvert(rvField.Interface(), recursive, tags...) {
|
||||
m[k] = v
|
||||
for k, v := range doMapConvert(rvAttrInterface, recursive, tags...) {
|
||||
dataMap[k] = v
|
||||
}
|
||||
} else {
|
||||
// It means this attribute field has desired tag.
|
||||
m[name] = doMapConvert(rvField.Interface(), recursive, tags...)
|
||||
if m := doMapConvert(rvAttrInterface, recursive, tags...); len(m) > 0 {
|
||||
dataMap[name] = m
|
||||
} else {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if rvField.IsValid() {
|
||||
m[name] = rvField.Interface()
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
m[name] = nil
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rvField.IsValid() {
|
||||
m[name] = rvField.Interface()
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
m[name] = nil
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,7 +261,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return m
|
||||
return dataMap
|
||||
}
|
||||
|
||||
// MapStrStr converts <value> to map[string]string.
|
||||
|
||||
@ -86,3 +86,12 @@ func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MapOmitEmpty deletes all empty values from guven map.
|
||||
func MapOmitEmpty(data map[string]interface{}) {
|
||||
for k, v := range data {
|
||||
if IsEmpty(v) {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,3 +118,19 @@ func Test_MapContainsPossibleKey(t *testing.T) {
|
||||
t.Assert(gutil.MapContainsPossibleKey(m, "none"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapOmitEmpty(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := g.Map{
|
||||
"k1": "john",
|
||||
"e1": "",
|
||||
"e2": 0,
|
||||
"e3": nil,
|
||||
"k2": "smith",
|
||||
}
|
||||
gutil.MapOmitEmpty(m)
|
||||
t.Assert(len(m), 2)
|
||||
t.AssertNE(m["k1"], nil)
|
||||
t.AssertNE(m["k2"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -38,11 +38,11 @@ var (
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
"same": {},
|
||||
"different": {},
|
||||
"in": {},
|
||||
"not-in": {},
|
||||
"regex": {},
|
||||
//"same": {},
|
||||
//"different": {},
|
||||
//"in": {},
|
||||
//"not-in": {},
|
||||
//"regex": {},
|
||||
}
|
||||
// allSupportedRules defines all supported rules that is used for quick checks.
|
||||
allSupportedRules = map[string]struct{}{
|
||||
|
||||
@ -89,8 +89,10 @@ func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Err
|
||||
}
|
||||
if e := doCheck(key, value, rule, customMsgs[key], data); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// If value is nil or empty string and has no required* rules,
|
||||
// clear the error message.
|
||||
// ===========================================================
|
||||
if gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
|
||||
@ -151,8 +151,10 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *
|
||||
}
|
||||
if e := doCheck(key, value, rule, customMessage[key], params); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// If value is nil or empty string and has no required* rules,
|
||||
// clear the error message.
|
||||
// ===========================================================
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
|
||||
@ -25,7 +25,7 @@ var (
|
||||
// It returns error if there's already the same rule registered previously.
|
||||
func RegisterRule(rule string, f RuleFunc) error {
|
||||
if _, ok := allSupportedRules[rule]; ok {
|
||||
return fmt.Errorf(`validation rule "%s" is already registed`, rule)
|
||||
return fmt.Errorf(`validation rule "%s" is already registered`, rule)
|
||||
}
|
||||
allSupportedRules[rule] = struct{}{}
|
||||
customRuleFuncMap[rule] = f
|
||||
|
||||
@ -165,18 +165,21 @@ func Test_Date(t *testing.T) {
|
||||
val4 := "2010-11-01"
|
||||
val5 := "2010.11.01"
|
||||
val6 := "2010/11/01"
|
||||
val7 := "2010=11=01"
|
||||
err1 := gvalid.Check(val1, rule, nil)
|
||||
err2 := gvalid.Check(val2, rule, nil)
|
||||
err3 := gvalid.Check(val3, rule, nil)
|
||||
err4 := gvalid.Check(val4, rule, nil)
|
||||
err5 := gvalid.Check(val5, rule, nil)
|
||||
err6 := gvalid.Check(val6, rule, nil)
|
||||
t.AssertNE(err1, nil)
|
||||
t.AssertNE(err2, nil)
|
||||
err7 := gvalid.Check(val7, rule, nil)
|
||||
t.Assert(err1, nil)
|
||||
t.Assert(err2, nil)
|
||||
t.Assert(err3, nil)
|
||||
t.Assert(err4, nil)
|
||||
t.Assert(err5, nil)
|
||||
t.Assert(err6, nil)
|
||||
t.AssertNE(err7, nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gvalid_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
@ -250,3 +251,45 @@ func Test_CheckStruct_With_Inherit(t *testing.T) {
|
||||
t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Optional(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId string `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId *gvar.Var `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId int `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
t.Assert(err.String(), "project id must between 1, 10000")
|
||||
})
|
||||
}
|
||||
|
||||
108
util/gvalid/gvalid_z_example_test.go
Normal file
108
util/gvalid/gvalid_z_example_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gvalid_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
func ExampleCheckMap() {
|
||||
params := map[string]interface{}{
|
||||
"passport": "",
|
||||
"password": "123456",
|
||||
"password2": "1234567",
|
||||
}
|
||||
rules := []string{
|
||||
"passport@required|length:6,16#账号不能为空|账号长度应当在:min到:max之间",
|
||||
"password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等",
|
||||
"password2@required|length:6,16#",
|
||||
}
|
||||
if e := gvalid.CheckMap(params, rules); e != nil {
|
||||
fmt.Println(e.Map())
|
||||
fmt.Println(e.FirstItem())
|
||||
fmt.Println(e.FirstString())
|
||||
}
|
||||
// May Output:
|
||||
// map[required:账号不能为空 length:账号长度应当在6到16之间]
|
||||
// passport map[required:账号不能为空 length:账号长度应当在6到16之间]
|
||||
// 账号不能为空
|
||||
}
|
||||
|
||||
func ExampleCheckMap2() {
|
||||
params := map[string]interface{}{
|
||||
"passport": "",
|
||||
"password": "123456",
|
||||
"password2": "1234567",
|
||||
}
|
||||
rules := []string{
|
||||
"passport@length:6,16#账号不能为空|账号长度应当在:min到:max之间",
|
||||
"password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等",
|
||||
"password2@required|length:6,16#",
|
||||
}
|
||||
if e := gvalid.CheckMap(params, rules); e != nil {
|
||||
fmt.Println(e.Map())
|
||||
fmt.Println(e.FirstItem())
|
||||
fmt.Println(e.FirstString())
|
||||
}
|
||||
// Output:
|
||||
// map[same:两次密码输入不相等]
|
||||
// password map[same:两次密码输入不相等]
|
||||
// 两次密码输入不相等
|
||||
}
|
||||
|
||||
// Empty string attribute.
|
||||
func ExampleCheckStruct() {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId string `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
fmt.Println(err)
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
|
||||
// Empty pointer attribute.
|
||||
func ExampleCheckStruct2() {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId *gvar.Var `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
fmt.Println(err)
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
|
||||
// Empty integer attribute.
|
||||
func ExampleCheckStruct3() {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId int `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
fmt.Println(err)
|
||||
// Output:
|
||||
// project id must between 1, 10000
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.13.3"
|
||||
const VERSION = "v1.13.4"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user