Compare commits

..

87 Commits

Author SHA1 Message Date
87c22d32b0 version updates 2020-08-26 14:49:53 +08:00
27dd15b403 improve initialization for logger of package glog 2020-08-26 14:37:02 +08:00
dd452c19ce add example for custom uploading file name for ghttp.Server 2020-08-24 23:05:30 +08:00
acc0846cf3 fix issue in unit testing case of package gvalid 2020-08-24 22:26:12 +08:00
5a6738841f travis ci update 2020-08-22 00:21:24 +08:00
bea451c9d6 improve date type support for package gdb; improve datetime string for gtime.StrToTime; improve function gtime.New for more types 2020-08-21 23:41:12 +08:00
676e904ec6 improve package gvalid 2020-08-21 00:00:00 +08:00
49aa5c61bc improve package gvalid 2020-08-20 23:37:02 +08:00
a2272b852c improve package gvalid 2020-08-20 23:28:58 +08:00
1fa77630f9 improve package gvalid 2020-08-20 23:28:10 +08:00
a841c4cc05 improve package gvalid 2020-08-20 23:25:36 +08:00
1874808e3b improve unit testing case for package gcache 2020-08-19 21:37:26 +08:00
149b67916b README updates 2020-08-19 21:10:40 +08:00
7fb6f58162 README updates 2020-08-19 21:09:25 +08:00
a309114a18 donator updates 2020-08-15 21:58:03 +08:00
35cbde9530 comment update for package gstr 2020-08-15 21:28:24 +08:00
b9211b182a comment update for function ghttp.Request. 2020-08-15 12:35:56 +08:00
81ec499ae9 Merge pull request #867 from pingyeaa/master
Solve the problem of invalid modification of request parameters
2020-08-15 12:33:24 +08:00
f2e276eabd add function Test_Params_Modify for ghttp_unit_param_test.go 2020-08-14 20:02:52 +08:00
f6dbaba1f8 add function ReloadParam for ghttp_request.go 2020-08-14 18:34:43 +08:00
65dcff052a add function ReloadParam for ghttp_request.go 2020-08-14 18:19:48 +08:00
e3861567c7 add function ReloadParam for ghttp_request.go 2020-08-14 16:22:37 +08:00
e89a20c725 add ReloadParam func to ghttp_request.go 2020-08-14 15:19:56 +08:00
a8acc6bd28 update unit testing case for package gdb 2020-08-14 00:51:22 +08:00
eb5efc735e improve unit testing case of ScanList for package gdb 2020-08-13 23:45:22 +08:00
737af527cd improve *Struct functions for ghttp.Request 2020-08-13 23:29:00 +08:00
875d2b7e63 donator updates 2020-08-13 19:25:51 +08:00
bf1cb0e1bd donator updates 2020-08-13 19:23:31 +08:00
2bfeb1b06c README updates 2020-08-13 18:59:13 +08:00
820e4302b7 improve function *Struct for ghttp.Request 2020-08-13 18:51:59 +08:00
84d761b418 add function AddWithRecover for package grpool 2020-08-12 23:53:05 +08:00
e5e27f2ac4 add function GetRouterMap for ghtt.Request 2020-08-12 21:01:17 +08:00
efca9b18a8 improve content type checks for package gjson 2020-08-12 20:53:47 +08:00
607821ecbc add function LoadContentType for package gjson 2020-08-12 20:38:48 +08:00
7cc1b239d4 add functions Update/UpdateExpire/GetExpire for package gcache 2020-08-12 20:13:13 +08:00
efa8de34da Merge pull request #860 from XWR940711/master
add func gcache.SetVar/gcache.SetExpire/gcacge.GetExpire.
2020-08-12 19:19:19 +08:00
bcf45e3c5a CI-glog失败重测 2020-08-12 10:32:40 +08:00
0a3cd1d2ab 修正方法代码.使用Benchmark测试. 2020-08-12 09:57:47 +08:00
3e621856c8 change the port range for unit testing cases of ghttp.Server 2020-08-12 09:10:50 +08:00
32e4d64ddb improve package gjson for automatic content type checking 2020-08-12 00:11:25 +08:00
46e45ca84b improve package gjson for automatic content type checking 2020-08-11 23:46:21 +08:00
fcb13bd8ee improve package gjson for automatic content type checking 2020-08-11 23:36:40 +08:00
eacad9b453 improve package gjson for automatic content type checking 2020-08-11 23:22:09 +08:00
ed479e2a13 improve package gjson for automatic content type checking 2020-08-11 23:20:22 +08:00
19937cb75d improve MapDeep for package gconv 2020-08-11 20:13:47 +08:00
a95093222c add GetRemoteIp for ghttp.Request; add *Var feature for ghttp.Client 2020-08-11 15:23:42 +08:00
7b599d1882 Merge branch 'master' of https://github.com/gogf/gf 2020-08-10 23:04:28 +08:00
4d501fd2f4 improve function Set for package gjson 2020-08-10 23:03:35 +08:00
c3d3053ded improve GetOrSetFunc for package gcache 2020-08-09 21:28:31 +08:00
14d5fd3e11 add translation format feature for package gi18n 2020-08-08 16:46:52 +08:00
9f79453334 improve ghttp.Server by closing the request and response body to release the file descriptor in time 2020-08-08 11:09:58 +08:00
c7f1c881c0 Merge pull request #850 from coderzhuang/master
fix: Access-Control-Request-Headers
2020-08-08 09:59:45 +08:00
c39c032b4e Merge pull request #844 from qinyuguang/scan
gcmd.Scan supports read line that contains whitespace
2020-08-08 09:58:32 +08:00
fa96c18308 add function MapOmitEmpty for package gutil 2020-08-08 09:53:32 +08:00
d90145a47f add goroutine id retrieving feature for package gdebug 2020-08-08 09:42:24 +08:00
4871f86346 adjust TestCache_Expire_SetVar Test Code. 2020-08-07 16:26:26 +08:00
919eaf1e9a adjust TestCache_Expire_SetVar Test Code. 2020-08-07 14:05:21 +08:00
8a84ca16d1 add func gcache.SetVar/gcache.SetExpire/gcacge.GetExpire.
add test TestCache_Expire_SetVar.
2020-08-07 13:24:41 +08:00
12b4fdd692 fix: Access-Control-Request-Headers 2020-08-04 11:57:42 +08:00
3d8451d5d0 comment update for gdb.Model.Page 2020-08-04 09:22:21 +08:00
cf88f28519 gcmd.Scanf supports read line that contains whitespace 2020-08-03 21:00:02 +08:00
15d99eee46 add more unit testing case for ghttp.Server 2020-08-03 20:49:19 +08:00
6d68277db8 improve cookie feature for ghttp.Server 2020-08-03 20:00:00 +08:00
3e3b5557f7 readme updates 2020-08-03 18:58:43 +08:00
ba1a9d9f8e gcmd.Scan supports read line that contains whitespace 2020-08-01 02:13:42 +08:00
3e1a7953ec Merge branch 'master' of https://github.com/gogf/gf 2020-07-31 09:57:45 +08:00
073fb2d717 improve sql logging structure for package gdb 2020-07-31 09:57:26 +08:00
7f33021184 Merge pull request #825 from chenall/gjson-support-utf8-with-bom
fix configfile with UTF8-BOM issue
2020-07-31 00:08:05 +08:00
b396096721 improve dry-run feature by adding global dry-run variable reading from environment or command options 2020-07-30 23:08:27 +08:00
0a5c6d832f add configration group name for logging content for package gdb 2020-07-30 23:00:20 +08:00
d279566114 Merge pull request #769 from fxk2006/master 2020-07-30 22:55:39 +08:00
2693cbb136 add more unit testing case for ghttp.Server 2020-07-30 22:45:50 +08:00
f7a9be4292 improve package glog for rotation feature 2020-07-30 21:09:45 +08:00
a926033b66 move Separator from const to variable for package gfile 2020-07-30 20:57:08 +08:00
dbcdd06b19 Merge branch 'master' of https://github.com/gogf/gf 2020-07-30 20:49:32 +08:00
ff5dab5c70 improve package glog for rotation feature 2020-07-30 20:49:11 +08:00
84b576418f Merge pull request #835 from XWR940711/master
add func gtimer.Entry.Reset()
2020-07-30 20:20:50 +08:00
253b124903 fix issue in database configuration for package gind 2020-07-30 20:18:18 +08:00
33dc5ddf79 fix issue in database configuration for package gind 2020-07-30 18:52:27 +08:00
a456fa537c fix logger configure issue for package gdb 2020-07-30 17:16:29 +08:00
9e1fb93e08 adjust gtimer.Entry.Reset() Test Code. 2020-07-30 11:00:19 +08:00
5d24f702be adjust gtimer.Entry.Reset() Test Code. 2020-07-30 10:37:48 +08:00
6cc4747965 add func gtimer.Entry.Reset() 2020-07-29 11:22:46 +08:00
437fc04620 improve testCfg_With_UTF8_BOM unit_test 2020-07-25 14:07:33 +08:00
937f8e6919 fix configfile with UTF8-BOM issue 2020-07-25 10:57:40 +08:00
6e08eebcbe 修改sql打印debug信息,增加数据库配置的group名称,用于区分sql来源,尤其是多数据库配置的时候 2020-06-28 16:58:43 +08:00
d9422d00ac 修改sql打印debug信息,增加数据库配置的group名称,用于区分sql来源,尤其是多数据库配置的时候 2020-06-28 16:50:13 +08:00
79 changed files with 1980 additions and 252 deletions

View File

@ -5,6 +5,7 @@ go:
- "1.12.x"
- "1.13.x"
- "1.14.x"
- "1.15.x"
branches:
only:

View File

@ -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| 加油

View File

@ -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).

View File

@ -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) 留言。
# 贡献

View File

@ -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

View File

@ -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)

View File

@ -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()
}

View File

@ -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 {

View File

@ -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()

View File

@ -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.

View File

@ -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,

View 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
}

View File

@ -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
}
}
}

View File

@ -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 ""
}

View File

@ -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")

View 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")
})
}

View File

@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
;注释
[addr]
#注释
[addr]
ip = 127.0.0.1
port=9001
enable=true

View File

@ -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)
})
}

View File

@ -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)

View File

@ -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...)

View File

@ -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 {

View File

@ -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"))

View File

@ -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())
}

View 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())
}

View File

@ -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
}

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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{} {

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}()
// ============================================================

View File

@ -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()

View File

@ -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)
}
}

View 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")
})
}

View File

@ -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}`,
)
})
}

View File

@ -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()

View File

@ -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")
})
}

View File

@ -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")
})
}

View 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
}

View File

@ -0,0 +1,89 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package 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}
}

View 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)
}

View 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}]
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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++
}
})

View File

@ -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)

View File

@ -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()

View File

@ -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")
})
}

View File

@ -0,0 +1,4 @@

[test]
testInt=1
testStr="test"

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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.

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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{}},

View File

@ -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 {

View File

@ -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")
}

View File

@ -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.

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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]+)$`)
)

View File

@ -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 (

View File

@ -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.

View File

@ -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)
}
}
}

View File

@ -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)
})
}

View File

@ -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{}{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
})
}

View File

@ -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")
})
}

View 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
}

View File

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