mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 262f27748c | |||
| a29aef7e1c | |||
| 1671391195 | |||
| 28b0d59c61 | |||
| 86433cef25 | |||
| e96ccd5f71 | |||
| 50a087bb3d | |||
| 1ec049c52f | |||
| ec38805542 | |||
| 4c8517d075 | |||
| edf06da6ea | |||
| eb43a2040e | |||
| 9d0ecc7d3e | |||
| ad943c5e02 | |||
| bdb4fd0d25 | |||
| 2440e05457 | |||
| 1337c6c0d1 | |||
| 957689e07c | |||
| 3952d74f87 | |||
| 6dc4b81693 | |||
| 9cd953b7be | |||
| 631810dda2 | |||
| 8c12bc5506 | |||
| d4091a4826 | |||
| a7c269886b | |||
| f54593037b | |||
| 0415cf6a08 | |||
| 87c22d32b0 | |||
| 27dd15b403 | |||
| dd452c19ce | |||
| acc0846cf3 | |||
| 5a6738841f | |||
| bea451c9d6 | |||
| 676e904ec6 | |||
| 49aa5c61bc | |||
| a2272b852c | |||
| 1fa77630f9 | |||
| a841c4cc05 | |||
| 1874808e3b | |||
| 149b67916b | |||
| 7fb6f58162 | |||
| a309114a18 | |||
| 35cbde9530 | |||
| b9211b182a | |||
| 81ec499ae9 | |||
| f2e276eabd | |||
| f6dbaba1f8 | |||
| 65dcff052a | |||
| e3861567c7 | |||
| e89a20c725 | |||
| a8acc6bd28 | |||
| eb5efc735e | |||
| 737af527cd | |||
| 875d2b7e63 | |||
| bf1cb0e1bd | |||
| 2bfeb1b06c | |||
| 820e4302b7 | |||
| 84d761b418 | |||
| e5e27f2ac4 | |||
| efca9b18a8 | |||
| 607821ecbc | |||
| 7cc1b239d4 | |||
| efa8de34da | |||
| bcf45e3c5a | |||
| 0a3cd1d2ab | |||
| 3e621856c8 | |||
| 32e4d64ddb | |||
| 46e45ca84b | |||
| fcb13bd8ee | |||
| eacad9b453 | |||
| ed479e2a13 | |||
| 19937cb75d | |||
| a95093222c | |||
| 7b599d1882 | |||
| 4d501fd2f4 | |||
| c3d3053ded | |||
| 14d5fd3e11 | |||
| 9f79453334 | |||
| c7f1c881c0 | |||
| c39c032b4e | |||
| fa96c18308 | |||
| d90145a47f | |||
| 4871f86346 | |||
| 919eaf1e9a | |||
| 8a84ca16d1 | |||
| 12b4fdd692 | |||
| 3d8451d5d0 | |||
| cf88f28519 | |||
| 15d99eee46 | |||
| 6d68277db8 | |||
| 3e3b5557f7 | |||
| ba1a9d9f8e | |||
| 3e1a7953ec | |||
| 073fb2d717 | |||
| 7f33021184 | |||
| b396096721 | |||
| 0a5c6d832f | |||
| d279566114 | |||
| 2693cbb136 | |||
| f7a9be4292 | |||
| a926033b66 | |||
| dbcdd06b19 | |||
| ff5dab5c70 | |||
| 84b576418f | |||
| 253b124903 | |||
| 33dc5ddf79 | |||
| a456fa537c | |||
| 9e1fb93e08 | |||
| 5d24f702be | |||
| 6cc4747965 | |||
| 437fc04620 | |||
| 937f8e6919 | |||
| 6e08eebcbe | |||
| d9422d00ac |
@ -8,9 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
db := g.DB()
|
||||
db.SetMaxIdleConnCount(10)
|
||||
db.SetMaxOpenConnCount(10)
|
||||
db.SetMaxConnLifetime(time.Minute)
|
||||
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
@ -19,7 +16,7 @@ func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
go db.Table("user").All()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ go:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "1.15.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
24
DONATOR.MD
24
DONATOR.MD
@ -11,7 +11,7 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
| Name | Channel | Amount | Comment
|
||||
|---|---|--- | ---
|
||||
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥301.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥501.00 |
|
||||
|[mg91](https://gitee.com/mg91)|gitee|¥10.00 |
|
||||
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 |
|
||||
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 |
|
||||
@ -97,6 +97,28 @@ 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| 加油
|
||||
|向回走的闹钟|wechat|¥20.00| 越来越好
|
||||
|金毛|wechat|¥100.00|
|
||||
|莫失莫忘|wechat|¥100.00|
|
||||
|**航|alipay|¥20.00|
|
||||
|
||||
|
||||
|
||||
|
||||
@ -120,11 +120,11 @@ The concurrency starts from `100` to `10000`.
|
||||
- [Tencent](https://www.tencent.com/)
|
||||
- [ZTE](https://www.zte.com.cn/china/)
|
||||
- [Ant Financial Services](https://www.antfin.com/)
|
||||
- [Medlinker](https://www.medlinker.com/)
|
||||
- [Yesk](http://www.yesky.com)
|
||||
- [MedLinker](https://www.medlinker.com/)
|
||||
- [KuCoin](https://www.kucoin.io/)
|
||||
- [Leyoujia Holding Group](https://www.leyoujia.com/)
|
||||
|
||||
- [LeYouJia](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [XiMaLaYa](https://www.ximalaya.com)
|
||||
|
||||
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).
|
||||
|
||||
|
||||
@ -141,11 +141,12 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
|
||||
- [中兴科技](https://www.zte.com.cn/china/)
|
||||
- [蚂蚁金服](https://www.antfin.com/)
|
||||
- [医联科技](https://www.medlinker.com/)
|
||||
- [天极数码](http://www.yesky.com)
|
||||
- [库币科技](https://www.kucoin.io/)
|
||||
- [乐有家](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [喜马拉雅](https://www.ximalaya.com)
|
||||
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里] (https://github.com/gogf/gf/issues/168)留言。
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。
|
||||
|
||||
# 贡献
|
||||
|
||||
|
||||
@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *AnyAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) {
|
||||
})
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *ListMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -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
|
||||
@ -271,10 +283,10 @@ func Register(name string, driver Driver) error {
|
||||
// New creates and returns an ORM object with global configurations.
|
||||
// The parameter <name> specifies the configuration group name,
|
||||
// which is DEFAULT_GROUP_NAME in default.
|
||||
func New(name ...string) (db DB, err error) {
|
||||
group := configs.group
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
group = name[0]
|
||||
func New(group ...string) (db DB, err error) {
|
||||
groupName := configs.group
|
||||
if len(group) > 0 && group[0] != "" {
|
||||
groupName = group[0]
|
||||
}
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
@ -282,10 +294,10 @@ func New(name ...string) (db DB, err error) {
|
||||
if len(configs.config) < 1 {
|
||||
return nil, errors.New("empty database configuration")
|
||||
}
|
||||
if _, ok := configs.config[group]; ok {
|
||||
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
||||
if _, ok := configs.config[groupName]; ok {
|
||||
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
|
||||
c := &Core{
|
||||
group: group,
|
||||
group: groupName,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
schema: gtype.NewString(),
|
||||
@ -308,7 +320,7 @@ func New(name ...string) (db DB, err error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
@ -331,7 +334,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -344,7 +350,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -360,7 +369,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -375,11 +387,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// doInsert inserts or updates data for given table.
|
||||
//
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
@ -407,7 +422,13 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map, reflect.Struct:
|
||||
case reflect.Struct:
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = DataToMapDeep(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = DataToMapDeep(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
@ -462,28 +483,41 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// BatchInsertIgnore batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
@ -633,15 +667,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
|
||||
return c.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// doUpdate does "UPDATE ... " statement for the table.
|
||||
// Also see Update.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
@ -701,15 +731,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
|
||||
return c.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
// Also see Delete.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
if link == nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
@ -776,7 +802,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)
|
||||
|
||||
@ -115,6 +115,14 @@ func GetDefaultGroup() string {
|
||||
return configs.group
|
||||
}
|
||||
|
||||
// IsConfigured checks and returns whether the database configured.
|
||||
// It returns true if any configuration exists.
|
||||
func IsConfigured() bool {
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
return len(configs.config) > 0
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for orm.
|
||||
func (c *Core) SetLogger(logger *glog.Logger) {
|
||||
c.logger = logger
|
||||
@ -186,6 +194,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()
|
||||
}
|
||||
|
||||
|
||||
@ -223,7 +223,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
isnull(e.text,'') as [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
left join systypes b on a.xtype=b.xusertype
|
||||
left join systypes b on a.xtype=b.xtype and a.xusertype=b.xusertype
|
||||
inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
|
||||
left join syscomments e on a.cdefault=e.id
|
||||
left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
|
||||
|
||||
@ -78,44 +78,45 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of oracle server.
|
||||
func (d *DriverOracle) parseSql(sql string) string {
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
var (
|
||||
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
|
||||
allMatch, _ = gregex.MatchAllString(patten, sql)
|
||||
)
|
||||
if len(allMatch) == 0 {
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
|
||||
keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
|
||||
)
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false &&
|
||||
strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
for i := 1; i < len(allMatch[index]); i++ {
|
||||
if len(strings.TrimSpace(allMatch[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
if strings.HasPrefix(allMatch[index][i], "LIMIT") {
|
||||
if allMatch[index][i+2] != "" {
|
||||
first, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+2])
|
||||
} else {
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +104,6 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvField reflect.Value
|
||||
@ -182,7 +181,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
fieldValue := rvField.Interface()
|
||||
switch fieldValue.(type) {
|
||||
case time.Time, *time.Time:
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
data[name] = fieldValue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
@ -317,8 +316,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
return where
|
||||
}
|
||||
if len(where) == 1 {
|
||||
rv := reflect.ValueOf(where[0])
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(where[0])
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -33,40 +33,20 @@ func (m *Model) Fields(fields string) *Model {
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
// Note that this function supports only single table operations.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
}
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
fieldsArray := make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
model.fields = ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(model.fields) > 0 {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += k
|
||||
}
|
||||
model.fields = model.db.QuoteString(model.fields)
|
||||
return model
|
||||
}
|
||||
|
||||
// Deprecated, use GetFieldsStr instead.
|
||||
// This function name confuses the user that it was a chaining function.
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
return m.GetFieldsStr(prefix...)
|
||||
}
|
||||
|
||||
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
func (m *Model) GetFieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
@ -93,11 +73,17 @@ func (m *Model) FieldsStr(prefix ...string) string {
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Deprecated, use GetFieldsExStr instead.
|
||||
// This function name confuses the user that it was a chaining function.
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
return m.GetFieldsExStr(fields, prefix...)
|
||||
}
|
||||
|
||||
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
|
||||
// joined with char ','.
|
||||
// The parameter <fields> specifies the fields that are excluded.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u.").
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
|
||||
@ -6,7 +6,21 @@
|
||||
|
||||
package gdb
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// isSubQuery checks and returns whether given string a sub-query sql string.
|
||||
func isSubQuery(s string) bool {
|
||||
s = gstr.TrimLeft(s)
|
||||
if p := gstr.Pos(s, " "); p != -1 {
|
||||
if gstr.Equal(s[:p], "select") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
// The parameter <table> can be joined table and its joined condition,
|
||||
@ -14,19 +28,32 @@ import "fmt"
|
||||
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) LeftJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -37,19 +64,32 @@ func (m *Model) LeftJoin(table ...string) *Model {
|
||||
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) RightJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -60,19 +100,32 @@ func (m *Model) RightJoin(table ...string) *Model {
|
||||
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) InnerJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@ -8,7 +8,9 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
@ -53,12 +55,13 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
}
|
||||
conditionWhere += softDeletingCondition
|
||||
}
|
||||
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
return m.doGetAllBySql(
|
||||
fmt.Sprintf(
|
||||
"SELECT %s FROM %s%s",
|
||||
m.fields,
|
||||
m.getFieldsFiltered(),
|
||||
m.tables,
|
||||
conditionWhere+conditionExtra,
|
||||
),
|
||||
@ -66,6 +69,53 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
|
||||
// really be committed to underlying database driver.
|
||||
func (m *Model) getFieldsFiltered() string {
|
||||
if m.fieldsEx == "" {
|
||||
// No filtering.
|
||||
return m.fields
|
||||
}
|
||||
var (
|
||||
fieldsArray []string
|
||||
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
|
||||
)
|
||||
if m.fields != "*" {
|
||||
// Filter custom fields with fieldEx.
|
||||
fieldsArray = make([]string, 0, 8)
|
||||
for _, v := range gstr.SplitAndTrim(m.fields, ",") {
|
||||
fieldsArray = append(fieldsArray, v[gstr.PosR(v, "-")+1:])
|
||||
}
|
||||
} else {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
// Filter table fields with fieldEx.
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
}
|
||||
fieldsArray = make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
}
|
||||
newFields := ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(newFields) > 0 {
|
||||
newFields += ","
|
||||
}
|
||||
newFields += k
|
||||
}
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
|
||||
@ -86,8 +86,8 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
// Base table.
|
||||
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
// Multiple joined tables.
|
||||
matches, _ := gregex.MatchAllString(`JOIN (.+?) ON`, m.tables)
|
||||
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
|
||||
matches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
|
||||
for _, match := range matches {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -115,7 +115,6 @@ func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) e
|
||||
default:
|
||||
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue queries and returns the field value from database.
|
||||
@ -154,7 +153,10 @@ func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -167,7 +169,10 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -183,7 +188,10 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -198,31 +206,46 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result,
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
@ -240,11 +263,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoUpdate(tx.tx, table, data, newWhere, newArgs...)
|
||||
return tx.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -259,9 +278,5 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoDelete(tx.tx, table, newWhere, newArgs...)
|
||||
return tx.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -303,3 +303,12 @@ func Test_Func_DataToMapDeep(t *testing.T) {
|
||||
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isSubQuery(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(isSubQuery("user"), false)
|
||||
t.Assert(isSubQuery("user.uid"), false)
|
||||
t.Assert(isSubQuery("u, user.uid"), false)
|
||||
t.Assert(isSubQuery("select 1"), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -52,20 +52,20 @@ func Test_Model_Insert(t *testing.T) {
|
||||
t.Assert(n, 1)
|
||||
|
||||
type User struct {
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime string `json:"create_time"`
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime *gtime.Time `json:"create_time"`
|
||||
}
|
||||
// Model inserting.
|
||||
result, err = db.Table(table).Filter().Data(User{
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
CreateTime: gtime.Now().String(),
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -80,7 +80,7 @@ func Test_Model_Insert(t *testing.T) {
|
||||
Passport: "t4",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "T4",
|
||||
CreateTime: gtime.Now().String(),
|
||||
CreateTime: gtime.Now(),
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -94,7 +94,30 @@ func Test_Model_Insert(t *testing.T) {
|
||||
n, _ = result.RowsAffected()
|
||||
t.Assert(n, 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := db.Table(table)
|
||||
array := garray.New()
|
||||
for i := 1; i <= SIZE; i++ {
|
||||
array.Append(g.Map{
|
||||
"id": i,
|
||||
"uid": i,
|
||||
"passport": fmt.Sprintf("t%d", i),
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": fmt.Sprintf("name_%d", i),
|
||||
"create_time": gtime.Now().String(),
|
||||
})
|
||||
}
|
||||
|
||||
result, err := user.Filter().Data(array).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertIgnore(t *testing.T) {
|
||||
@ -2243,6 +2266,19 @@ func Test_Model_DryRun(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Join_SubQuery(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
subQuery := fmt.Sprintf("select * from `%s`", table)
|
||||
r, err := db.Table(table, "t1").Fields("t2.id").LeftJoin(subQuery, "t2", "t2.id=t1.id").Array()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(r), SIZE)
|
||||
t.Assert(r[0], "1")
|
||||
t.Assert(r[SIZE-1], SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Cache(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -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,
|
||||
|
||||
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Oracle_parseSql(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `UPDATE user SET name='john'`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 0, 10`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 10) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT ENAME FROM USER_INFO WHERE ID=2 LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT ENAME FROM USER_INFO WHERE ID=2 ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
}
|
||||
29
debug/gdebug/gdebug_grid.go
Normal file
29
debug/gdebug/gdebug_grid.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// gridRegex is the regular expression object for parsing goroutine id from stack information.
|
||||
gridRegex = regexp.MustCompile(`^\w+\s+(\d+)\s+`)
|
||||
)
|
||||
|
||||
// GoroutineId retrieves and returns the current goroutine id from stack information.
|
||||
// Be very aware that, it is with low performance as it uses runtime.Stack function.
|
||||
// It is commonly used for debugging purpose.
|
||||
func GoroutineId() int {
|
||||
buf := make([]byte, 26)
|
||||
runtime.Stack(buf, false)
|
||||
match := gridRegex.FindSubmatch(buf)
|
||||
id, _ := strconv.Atoi(string(match[1]))
|
||||
return id
|
||||
}
|
||||
@ -86,6 +86,7 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
// A string key.
|
||||
if !gstr.IsNumeric(array[i]) {
|
||||
if i == length-1 {
|
||||
*pointer = map[string]interface{}{array[i]: value}
|
||||
@ -97,23 +98,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
valn, err := strconv.Atoi(array[i])
|
||||
// Numeric index.
|
||||
valueNum, err := strconv.Atoi(array[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Leaf node.
|
||||
|
||||
if i == length-1 {
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
// Leaf node.
|
||||
if len((*pointer).([]interface{})) > valueNum {
|
||||
if removed && value == nil {
|
||||
// Deleting element.
|
||||
if pparent == nil {
|
||||
*pointer = append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...)
|
||||
*pointer = append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...)
|
||||
} else {
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...))
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...))
|
||||
}
|
||||
} else {
|
||||
(*pointer).([]interface{})[valn] = value
|
||||
(*pointer).([]interface{})[valueNum] = value
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
@ -124,19 +126,33 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
j.setPointerWithValue(pointer, array[i], value)
|
||||
} else {
|
||||
// It is not the root node.
|
||||
s := make([]interface{}, valn+1)
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[valn] = value
|
||||
s[valueNum] = value
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Branch node.
|
||||
if gstr.IsNumeric(array[i+1]) {
|
||||
n, _ := strconv.Atoi(array[i+1])
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
(*pointer).([]interface{})[valn] = make([]interface{}, n+1)
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
item := pSlice[valueNum]
|
||||
if s, ok := item.([]interface{}); ok {
|
||||
for i := 0; i < n-len(s); i++ {
|
||||
s = append(s, nil)
|
||||
}
|
||||
pparent = pointer
|
||||
pointer = &pSlice[valueNum]
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
var v interface{} = make([]interface{}, n+1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
@ -146,14 +162,26 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
v := (*pointer).([]interface{})
|
||||
if len(v) > valn {
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pointer = &(*pointer).([]interface{})[valueNum]
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, pSlice)
|
||||
s[valueNum] = make(map[string]interface{})
|
||||
if pparent != nil {
|
||||
// i > 0
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
} else {
|
||||
// i = 0
|
||||
var v interface{} = s
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,19 +205,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pparent = pointer
|
||||
}
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
var v1, v2 interface{}
|
||||
if i == length-1 {
|
||||
v = map[string]interface{}{
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: value,
|
||||
}
|
||||
} else {
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: nil,
|
||||
}
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v)
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v1)
|
||||
} else {
|
||||
*pointer = v
|
||||
*pointer = v1
|
||||
pparent = pointer
|
||||
}
|
||||
pointer = &v
|
||||
v2 = v1.(map[string]interface{})[array[i]]
|
||||
pointer = &v2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,9 +10,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/encoding/gini"
|
||||
"github.com/gogf/gf/encoding/gtoml"
|
||||
"github.com/gogf/gf/encoding/gxml"
|
||||
@ -54,8 +55,10 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
}
|
||||
default:
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -71,9 +74,7 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
case reflect.Map, reflect.Struct:
|
||||
i := interface{}(nil)
|
||||
// Note that it uses Map function implementing the converting.
|
||||
// Note that it here should not use MapDeep function if you really know what it means.
|
||||
i = gconv.Map(data, tags)
|
||||
i = gconv.MapDeep(data, tags)
|
||||
j = &Json{
|
||||
p: &i,
|
||||
c: byte(gDEFAULT_SPLIT_CHAR),
|
||||
@ -188,22 +189,60 @@ 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...)
|
||||
}
|
||||
|
||||
// IsValidDataType checks and returns whether given <dataType> a valid data type for loading.
|
||||
func IsValidDataType(dataType string) bool {
|
||||
if dataType == "" {
|
||||
return false
|
||||
}
|
||||
if dataType[0] == '.' {
|
||||
dataType = dataType[1:]
|
||||
}
|
||||
switch dataType {
|
||||
case "json", "js", "xml", "yaml", "yml", "toml", "ini":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
|
||||
((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
|
||||
return "yml"
|
||||
} else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false {
|
||||
return "ini"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-\."]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-\."]+\s*=\s*.+`, content) {
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
return "toml"
|
||||
} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
// Must contain "[xxx]" section.
|
||||
return "ini"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ func Test_Basic(t *testing.T) {
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(j.Get("0").([]interface{})), 3)
|
||||
t.Assert(j.Get("0"), 1)
|
||||
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.a")
|
||||
|
||||
133
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
133
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
// 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")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# API Server
|
||||
[server]
|
||||
address = ":8199"
|
||||
|
||||
# Jenkins
|
||||
[jenkins]
|
||||
url = "https://jenkins-swimlane.com"
|
||||
nodeJsStaticBuildCmdTpl = """
|
||||
npm i --registry=https://registry.npm.taobao.org
|
||||
wget http://consul.infra:8500/v1/kv/app_{{.SwimlaneName}}/{{.RepoName}}/.env.qa?raw=true -O ./env.qa
|
||||
npm run build:qa
|
||||
"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
}
|
||||
@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
|
||||
|
||||
;注释
|
||||
|
||||
[addr]
|
||||
#注释
|
||||
[addr]
|
||||
ip = 127.0.0.1
|
||||
port=9001
|
||||
enable=true
|
||||
|
||||
@ -47,3 +47,25 @@ func Test_Load_NewWithTag(t *testing.T) {
|
||||
t.Assert(j.Get("addr-json"), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_New_CustomStruct(t *testing.T) {
|
||||
type Base struct {
|
||||
Id int
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Name string
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 1
|
||||
user.Name = "john"
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(user)
|
||||
t.AssertNE(j, nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s == `{"Id":1,"Name":"john"}` || s == `{"Name":"john","Id":1}`, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ package gjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
@ -31,17 +34,13 @@ func Test_Set1(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Set2(t *testing.T) {
|
||||
e := []byte(`[[null,1]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
e := `[[null,1]]`
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
s := p.MustToJsonString()
|
||||
t.Assert(s, e)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set3(t *testing.T) {
|
||||
@ -51,7 +50,6 @@ func Test_Set3(t *testing.T) {
|
||||
"k1": "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -227,3 +225,107 @@ func Test_Set14(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set15(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("root.0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("root.1.k2", "v2"), nil)
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"root":[{"k1":"v1"},{"k2":"v2"}`) ||
|
||||
gstr.Contains(s, `"root":[{"k2":"v2"},{"k1":"v1"}`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"k":"v"`) ||
|
||||
gstr.Contains(s, `"k":"v"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set16(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("processors.0.set.0value", "1"), nil)
|
||||
t.Assert(j.Set("processors.0.set.0field", "2"), nil)
|
||||
t.Assert(j.Set("description", "3"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"processors":[{"set":{"0field":"2","0value":"1"}}]`) ||
|
||||
gstr.Contains(s, `"processors":[{"set":{"0value":"1","0field":"2"}}]`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"description":"3"`) || gstr.Contains(s, `"description":"3"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set17(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("1.k2", "v2"), nil)
|
||||
// overwrite the previous slice.
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `{"k":"v"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set18(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,{"k1":"v1"},{"k2":"v2"}]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set19(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.1.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,[null,{"k1":"v1"}],[null,{"k2":"v2"}]]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set20(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("k1", "v1"), nil)
|
||||
t.Assert(j.Set("k2", g.Slice{1, 2, 3}), nil)
|
||||
t.Assert(j.Set("k2.1", 20), nil)
|
||||
t.Assert(j.Set("k2.2", g.Map{"k3": "v3"}), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.InArray(
|
||||
g.SliceStr{
|
||||
`{"k1":"v1","k2":[1,20,{"k3":"v3"}]}`,
|
||||
`{"k2":[1,20,{"k3":"v3"}],"k1":"v1"}`,
|
||||
},
|
||||
s,
|
||||
), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
)
|
||||
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map or
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map, struct or
|
||||
// slice for data access reason, or it will make no sense.
|
||||
//
|
||||
// The parameter <safe> specifies whether using this Json object in concurrent-safe context, which
|
||||
|
||||
@ -31,25 +31,26 @@ 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 {
|
||||
db, err := gdb.Instance(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
var m map[string]interface{}
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
configNodeKey string
|
||||
)
|
||||
// It firstly searches the configuration of the instance name.
|
||||
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if nodeKey == "" {
|
||||
nodeKey = gDATABASE_NODE_NAME
|
||||
if Config().Available() {
|
||||
configNodeKey, _ = gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if configNodeKey == "" {
|
||||
configNodeKey = gDATABASE_NODE_NAME
|
||||
}
|
||||
configMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if m = Config().GetMap(nodeKey); len(m) == 0 {
|
||||
if len(configMap) == 0 && !gdb.IsConfigured() {
|
||||
panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, gDATABASE_NODE_NAME))
|
||||
}
|
||||
if len(configMap) == 0 {
|
||||
configMap = make(map[string]interface{})
|
||||
}
|
||||
// Parse <m> as map-slice and adds it to gdb's global configurations.
|
||||
for group, groupConfig := range m {
|
||||
for g, groupConfig := range configMap {
|
||||
cg := gdb.ConfigGroup{}
|
||||
switch value := groupConfig.(type) {
|
||||
case []interface{}:
|
||||
@ -64,37 +65,51 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
}
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", group, cg)
|
||||
gdb.SetConfigGroup(group, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", g, cg)
|
||||
gdb.SetConfigGroup(g, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", g, cg)
|
||||
intlog.Printf("%s, %#v", g, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse <m> as a single node configuration,
|
||||
// which is the default group configuration.
|
||||
if node := parseDBConfigNode(m); node != nil {
|
||||
if node := parseDBConfigNode(configMap); node != nil {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if node.LinkInfo != "" || node.Host != "" {
|
||||
cg = append(cg, *node)
|
||||
}
|
||||
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new ORM object with given configurations.
|
||||
if db, err := gdb.New(name...); err == nil {
|
||||
// Initialize logger for ORM.
|
||||
var m map[string]interface{}
|
||||
m = Config().GetMap(fmt.Sprintf("%s.%s", nodeKey, gLOGGER_NODE_NAME))
|
||||
if len(m) == 0 {
|
||||
m = Config().GetMap(nodeKey)
|
||||
}
|
||||
if len(m) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(m); err != nil {
|
||||
panic(err)
|
||||
if Config().Available() {
|
||||
// Initialize logger for ORM.
|
||||
var loggerConfigMap map[string]interface{}
|
||||
loggerConfigMap = Config().GetMap(fmt.Sprintf("%s.%s", configNodeKey, gLOGGER_NODE_NAME))
|
||||
if len(loggerConfigMap) == 0 {
|
||||
loggerConfigMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if len(loggerConfigMap) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(loggerConfigMap); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return db
|
||||
} else {
|
||||
// It panics often because it dose not find its configuration for given group.
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -32,7 +32,31 @@ func T(content string, language ...string) string {
|
||||
return defaultManager.T(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func TF(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func TFL(format string, language string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func TranslateFormat(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language and returns the translated content.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func Translate(content string, language ...string) string {
|
||||
return defaultManager.Translate(content, language...)
|
||||
|
||||
@ -124,6 +124,30 @@ func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func (m *Manager) TF(format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) TFL(format string, language string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func (m *Manager) TranslateFormat(format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format), values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func (m *Manager) TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format, language), values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (m *Manager) Translate(content string, language ...string) string {
|
||||
|
||||
@ -73,6 +73,28 @@ func Test_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TranslateFormat(t *testing.T) {
|
||||
// TF
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
// TFL
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "ja", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "zh-CN", 2020), "你好世界 2020")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DefaultManager(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gi18n.SetPath(gdebug.TestDataPath("i18n"))
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package ghttp
|
||||
|
||||
import "github.com/gogf/gf/container/gvar"
|
||||
|
||||
// Get is a convenience method for sending GET request.
|
||||
// NOTE that remembers CLOSING the response object when it'll never be used.
|
||||
func Get(url string, data ...interface{}) (*ClientResponse, error) {
|
||||
@ -185,3 +187,69 @@ func TraceBytes(url string, data ...interface{}) []byte {
|
||||
func RequestBytes(method string, url string, data ...interface{}) []byte {
|
||||
return NewClient().RequestBytes(method, url, data...)
|
||||
}
|
||||
|
||||
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func GetVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("GET", url, data...)
|
||||
}
|
||||
|
||||
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PutVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("PUT", url, data...)
|
||||
}
|
||||
|
||||
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PostVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("POST", url, data...)
|
||||
}
|
||||
|
||||
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func DeleteVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("DELETE", url, data...)
|
||||
}
|
||||
|
||||
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func HeadVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("HEAD", url, data...)
|
||||
}
|
||||
|
||||
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func PatchVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("PATCH", url, data...)
|
||||
}
|
||||
|
||||
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func ConnectVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func OptionsVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func TraceVar(url string, data ...interface{}) *gvar.Var {
|
||||
return RequestVar("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// RequestVar sends request using given HTTP method and data, retrieves converts the result
|
||||
// to specified pointer. It reads and closes the response object internally automatically.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
|
||||
func RequestVar(method string, url string, data ...interface{}) *gvar.Var {
|
||||
response, err := DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return gvar.New(nil)
|
||||
}
|
||||
defer response.Close()
|
||||
return gvar.New(response.ReadAll())
|
||||
}
|
||||
|
||||
@ -149,3 +149,14 @@ func (c *Client) Proxy(proxyURL string) *Client {
|
||||
newClient.SetProxy(proxyURL)
|
||||
return c
|
||||
}
|
||||
|
||||
// RedirectLimit is a chaining function,
|
||||
// which sets the redirect limit the number of jumps for the request.
|
||||
func (c *Client) RedirectLimit(redirectLimit int) *Client {
|
||||
newClient := c
|
||||
if c.parent == nil {
|
||||
newClient = c.Clone()
|
||||
}
|
||||
newClient.SetRedirectLimit(redirectLimit)
|
||||
return c
|
||||
}
|
||||
|
||||
77
net/ghttp/ghttp_client_var.go
Normal file
77
net/ghttp/ghttp_client_var.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
)
|
||||
|
||||
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) GetVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("GET", url, data...)
|
||||
}
|
||||
|
||||
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PutVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("PUT", url, data...)
|
||||
}
|
||||
|
||||
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PostVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("POST", url, data...)
|
||||
}
|
||||
|
||||
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) DeleteVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("DELETE", url, data...)
|
||||
}
|
||||
|
||||
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) HeadVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("HEAD", url, data...)
|
||||
}
|
||||
|
||||
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) PatchVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("PATCH", url, data...)
|
||||
}
|
||||
|
||||
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) ConnectVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) OptionsVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) TraceVar(url string, data ...interface{}) *gvar.Var {
|
||||
return c.RequestVar("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// RequestVar sends request using given HTTP method and data, retrieves converts the result
|
||||
// to specified pointer. It reads and closes the response object internally automatically.
|
||||
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
|
||||
func (c *Client) RequestVar(method string, url string, data ...interface{}) *gvar.Var {
|
||||
response, err := c.DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return gvar.New(nil)
|
||||
}
|
||||
defer response.Close()
|
||||
return gvar.New(response.ReadAll())
|
||||
}
|
||||
@ -9,14 +9,15 @@ package ghttp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gres"
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -155,6 +156,7 @@ func (r *Request) IsAjaxRequest() bool {
|
||||
}
|
||||
|
||||
// GetClientIp returns the client ip of this request without port.
|
||||
// Note that this ip address might be modified by client header.
|
||||
func (r *Request) GetClientIp() string {
|
||||
if len(r.clientIp) == 0 {
|
||||
realIps := r.Header.Get("X-Forwarded-For")
|
||||
@ -178,17 +180,21 @@ func (r *Request) GetClientIp() string {
|
||||
r.clientIp = r.Header.Get("X-Real-IP")
|
||||
}
|
||||
if r.clientIp == "" || strings.EqualFold("unknown", realIps) {
|
||||
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
|
||||
if len(array) > 1 {
|
||||
r.clientIp = array[1]
|
||||
} else {
|
||||
r.clientIp = r.RemoteAddr
|
||||
}
|
||||
r.clientIp = r.GetRemoteIp()
|
||||
}
|
||||
}
|
||||
return r.clientIp
|
||||
}
|
||||
|
||||
// GetRemoteIp returns the ip from RemoteAddr.
|
||||
func (r *Request) GetRemoteIp() string {
|
||||
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
|
||||
if len(array) > 1 {
|
||||
return array[1]
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// GetUrl returns current URL of this request.
|
||||
func (r *Request) GetUrl() string {
|
||||
scheme := "http"
|
||||
@ -217,3 +223,13 @@ func (r *Request) GetReferer() string {
|
||||
func (r *Request) GetError() error {
|
||||
return r.error
|
||||
}
|
||||
|
||||
// ReloadParam is used for modifying request parameter.
|
||||
// Sometimes, we want to modify request parameters through middleware, but directly modifying Request.Body
|
||||
// is invalid, so it clears the parsed* marks to make the parameters re-parsed.
|
||||
func (r *Request) ReloadParam() {
|
||||
r.parsedBody = false
|
||||
r.parsedForm = false
|
||||
r.parsedQuery = false
|
||||
r.bodyContent = nil
|
||||
}
|
||||
|
||||
@ -189,7 +189,11 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseForm()
|
||||
return gconv.StructDeep(r.formMap, pointer, mapping...)
|
||||
m := r.formMap
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.
|
||||
|
||||
@ -193,7 +193,11 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
|
||||
// attribute mapping.
|
||||
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseQuery()
|
||||
return gconv.StructDeep(r.GetQueryMap(), pointer, mapping...)
|
||||
m := r.GetQueryMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.
|
||||
|
||||
@ -267,7 +267,11 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
|
||||
// the parameter <pointer> is a pointer to the struct object.
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(r.GetRequestMap(), pointer, mapping...)
|
||||
m := r.GetRequestMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.
|
||||
|
||||
@ -8,6 +8,18 @@ package ghttp
|
||||
|
||||
import "github.com/gogf/gf/container/gvar"
|
||||
|
||||
// GetRouterMap retrieves and returns a copy of router map.
|
||||
func (r *Request) GetRouterMap() map[string]string {
|
||||
if r.routerMap != nil {
|
||||
m := make(map[string]string, len(r.routerMap))
|
||||
for k, v := range r.routerMap {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRouterValue retrieves and returns the router value with given key name <key>.
|
||||
// It returns <def> if <key> does not exist.
|
||||
func (r *Request) GetRouterValue(key string, def ...interface{}) interface{} {
|
||||
|
||||
@ -55,7 +55,7 @@ func (r *Response) DefaultCORSOptions() CORSOptions {
|
||||
array := gstr.SplitAndTrim(headers, ",")
|
||||
for _, header := range array {
|
||||
if _, ok := defaultAllowHeadersMap[header]; !ok {
|
||||
options.AllowHeaders += header + ","
|
||||
options.AllowHeaders += "," + header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,11 +267,6 @@ func (s *Server) Start() error {
|
||||
return errors.New("[ghttp] server is already running")
|
||||
}
|
||||
|
||||
// If there's no route registered and no static service enabled,
|
||||
// it then returns an error of invalid usage of server.
|
||||
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
|
||||
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
|
||||
}
|
||||
// Logging path setting check.
|
||||
if s.config.LogPath != "" {
|
||||
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
|
||||
@ -316,6 +311,12 @@ func (s *Server) Start() error {
|
||||
// Check the group routes again.
|
||||
s.handlePreBindItems()
|
||||
|
||||
// If there's no route registered and no static service enabled,
|
||||
// it then returns an error of invalid usage of server.
|
||||
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
|
||||
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
|
||||
}
|
||||
|
||||
// Start the HTTP server.
|
||||
reloaded := false
|
||||
fdMapStr := genv.Get(gADMIN_ACTION_RELOAD_ENVKEY)
|
||||
|
||||
@ -9,28 +9,17 @@ package ghttp
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// Cookie for HTTP COOKIE management.
|
||||
type Cookie struct {
|
||||
data map[string]CookieItem // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxage time.Duration // The default cookie maxage.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// CookieItem is cookie item stored in Cookie management object.
|
||||
type CookieItem struct {
|
||||
value string // Cookie value.
|
||||
domain string // Cookie domain.
|
||||
path string // Cookie path.
|
||||
expireAt int64 // Cookie expiration timestamp.
|
||||
httpOnly bool
|
||||
data map[string]*http.Cookie // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxAge time.Duration // The default cookie max age.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// GetCookie creates or retrieves a cookie object with given request.
|
||||
@ -48,21 +37,20 @@ func GetCookie(r *Request) *Cookie {
|
||||
|
||||
// init does lazy initialization for cookie object.
|
||||
func (c *Cookie) init() {
|
||||
if c.data == nil {
|
||||
c.data = make(map[string]CookieItem)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxage = c.request.Server.GetCookieMaxAge()
|
||||
c.response = c.request.Response
|
||||
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
|
||||
//if c.domain == "" {
|
||||
// c.domain = c.request.GetHost()
|
||||
//}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem{
|
||||
v.Value, v.Domain, v.Path, int64(v.Expires.Second()), v.HttpOnly,
|
||||
}
|
||||
}
|
||||
if c.data != nil {
|
||||
return
|
||||
}
|
||||
c.data = make(map[string]*http.Cookie)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxAge = c.request.Server.GetCookieMaxAge()
|
||||
c.response = c.request.Response
|
||||
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
|
||||
//if c.domain == "" {
|
||||
// c.domain = c.request.GetHost()
|
||||
//}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +59,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
c.init()
|
||||
m := make(map[string]string)
|
||||
for k, v := range c.data {
|
||||
m[k] = v.value
|
||||
m[k] = v.Value
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -80,7 +68,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
func (c *Cookie) Contains(key string) bool {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expireAt >= 0 {
|
||||
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -89,7 +77,7 @@ func (c *Cookie) Contains(key string) bool {
|
||||
|
||||
// Set sets cookie item with default domain, path and expiration age.
|
||||
func (c *Cookie) Set(key, value string) {
|
||||
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
|
||||
c.SetCookie(key, value, c.domain, c.path, c.maxAge)
|
||||
}
|
||||
|
||||
// SetCookie sets cookie item given given domain, path and expiration age.
|
||||
@ -101,11 +89,25 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
}
|
||||
c.data[key] = CookieItem{
|
||||
value, domain, path, gtime.Timestamp() + int64(maxAge.Seconds()), isHttpOnly,
|
||||
c.data[key] = &http.Cookie{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
Expires: time.Now().Add(maxAge),
|
||||
HttpOnly: isHttpOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHttpCookie sets cookie with *http.Cookie.
|
||||
func (c *Cookie) SetHttpCookie(cookie *http.Cookie) {
|
||||
c.init()
|
||||
if cookie.Expires.IsZero() {
|
||||
cookie.Expires = time.Now().Add(c.maxAge)
|
||||
}
|
||||
c.data[cookie.Name] = cookie
|
||||
}
|
||||
|
||||
// GetSessionId retrieves and returns the session id from cookie.
|
||||
func (c *Cookie) GetSessionId() string {
|
||||
return c.Get(c.server.GetSessionIdName())
|
||||
@ -121,8 +123,8 @@ func (c *Cookie) SetSessionId(id string) {
|
||||
func (c *Cookie) Get(key string, def ...string) string {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expireAt >= 0 {
|
||||
return r.value
|
||||
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
|
||||
return r.Value
|
||||
}
|
||||
}
|
||||
if len(def) > 0 {
|
||||
@ -148,22 +150,12 @@ func (c *Cookie) Flush() {
|
||||
if len(c.data) == 0 {
|
||||
return
|
||||
}
|
||||
for k, v := range c.data {
|
||||
// Cookie item matches expire != 0 means it is set in this request,
|
||||
for _, v := range c.data {
|
||||
// If cookie item is v.Expires.IsZero() means it is set in this request,
|
||||
// which should be outputted to client.
|
||||
if v.expireAt == 0 {
|
||||
if v.Expires.IsZero() {
|
||||
continue
|
||||
}
|
||||
http.SetCookie(
|
||||
c.response.Writer,
|
||||
&http.Cookie{
|
||||
Name: k,
|
||||
Value: v.value,
|
||||
Domain: v.domain,
|
||||
Path: v.path,
|
||||
Expires: time.Unix(v.expireAt, 0),
|
||||
HttpOnly: v.httpOnly,
|
||||
},
|
||||
)
|
||||
http.SetCookie(c.response.Writer, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Close the session, which automatically update the TTL
|
||||
// of the session if it exists.
|
||||
request.Session.Close()
|
||||
|
||||
// Close the request and response body
|
||||
// to release the file descriptor in time.
|
||||
request.Request.Body.Close()
|
||||
if request.Request.Response != nil {
|
||||
request.Request.Response.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// ============================================================
|
||||
|
||||
@ -8,6 +8,7 @@ package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -33,6 +34,52 @@ func Test_Cookie(t *testing.T) {
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
r1, e1 := client.Get("/set?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
|
||||
t.Assert(e1, nil)
|
||||
t.Assert(r1.ReadAllString(), "")
|
||||
|
||||
t.Assert(client.GetContent("/set?k=key2&v=200"), "")
|
||||
|
||||
t.Assert(client.GetContent("/get?k=key1"), "100")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
t.Assert(client.GetContent("/get?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetHttpCookie(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/set", func(r *ghttp.Request) {
|
||||
r.Cookie.SetHttpCookie(&http.Cookie{
|
||||
Name: r.GetString("k"),
|
||||
Value: r.GetString("v"),
|
||||
})
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Cookie.Get(r.GetString("k")))
|
||||
})
|
||||
s.BindHandler("/remove", func(r *ghttp.Request) {
|
||||
r.Cookie.Remove(r.GetString("k"))
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
|
||||
@ -17,7 +17,7 @@ var (
|
||||
|
||||
func init() {
|
||||
genv.Set("UNDER_TEST", "1")
|
||||
for i := 8000; i <= 9000; i++ {
|
||||
for i := 7000; i <= 8000; i++ {
|
||||
ports.Append(i)
|
||||
}
|
||||
}
|
||||
|
||||
41
net/ghttp/ghttp_unit_ip_test.go
Normal file
41
net/ghttp/ghttp_unit_ip_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// static service testing.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestRequest_GetRemoteIp(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRemoteIp())
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.SetPort(p)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/"), "127.0.0.1")
|
||||
})
|
||||
|
||||
}
|
||||
@ -7,10 +7,14 @@
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -517,3 +521,39 @@ func Test_Params_GetRequestMap(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Modify(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/param/modify", func(r *ghttp.Request) {
|
||||
param := r.GetMap()
|
||||
param["id"] = 2
|
||||
paramBytes, err := gjson.Encode(param)
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Request.Body = ioutil.NopCloser(bytes.NewReader(paramBytes))
|
||||
r.ReloadParam()
|
||||
r.Response.Write(r.GetMap())
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(
|
||||
client.PostContent(
|
||||
"/param/modify",
|
||||
`{"id":1}`,
|
||||
),
|
||||
`{"id":2}`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,6 +73,33 @@ func Test_Router_Basic2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Value(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/{hash}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterString("hash"))
|
||||
})
|
||||
s.BindHandler("/{hash}.{type}", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterString("type"))
|
||||
})
|
||||
s.BindHandler("/{hash}.{type}.map", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetRouterMap()["type"])
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(client.GetContent("/data"), "data")
|
||||
t.Assert(client.GetContent("/data.json"), "json")
|
||||
t.Assert(client.GetContent("/data.json.map"), "json")
|
||||
})
|
||||
}
|
||||
|
||||
// HTTP method register.
|
||||
func Test_Router_Method(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
|
||||
@ -97,7 +97,7 @@ func (o *GroupObjRest) Head(r *ghttp.Request) {
|
||||
r.Response.Header().Set("head-ok", "1")
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest(t *testing.T) {
|
||||
func Test_Router_GroupRest1(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
g := s.Group("/api")
|
||||
@ -172,3 +172,80 @@ func Test_Router_GroupRest(t *testing.T) {
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest2(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Group("/api", func(group *ghttp.RouterGroup) {
|
||||
ctl := new(GroupCtlRest)
|
||||
obj := new(GroupObjRest)
|
||||
group.REST("/ctl", ctl)
|
||||
group.REST("/obj", obj)
|
||||
group.REST("/{.struct}/{.method}", ctl)
|
||||
group.REST("/{.struct}/{.method}", obj)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/api/ctl"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
|
||||
resp1, err := client.Head("/api/ctl")
|
||||
if err == nil {
|
||||
defer resp1.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp1.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/obj"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/obj"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/obj"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
|
||||
resp2, err := client.Head("/api/obj")
|
||||
if err == nil {
|
||||
defer resp2.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp2.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
|
||||
resp3, err := client.Head("/api/group-ctl-rest/head")
|
||||
if err == nil {
|
||||
defer resp3.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp3.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
|
||||
resp4, err := client.Head("/api/group-obj-rest/head")
|
||||
if err == nil {
|
||||
defer resp4.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,3 +157,38 @@ func Test_Router_Hook_Multi(t *testing.T) {
|
||||
t.Assert(client.GetContent("/multi-hook"), "12show")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Hook_ExitAll(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
s.Group("/hook", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
r.Response.Write("1")
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("2")
|
||||
})
|
||||
})
|
||||
|
||||
s.BindHookHandler("/hook/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
|
||||
r.Response.Write("hook")
|
||||
r.ExitAll()
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/test"), "test")
|
||||
t.Assert(client.GetContent("/hook/test"), "hook")
|
||||
})
|
||||
}
|
||||
|
||||
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Header() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
header = g.MapStrStr{
|
||||
"Span-Id": "0.1",
|
||||
"Trace-Id": "123456789",
|
||||
}
|
||||
)
|
||||
content := g.Client().Header(header).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_HeaderRaw() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
headerRaw = `
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3950.0 Safari/537.36
|
||||
Span-Id: 0.1
|
||||
Trace-Id: 123456789
|
||||
`
|
||||
)
|
||||
content := g.Client().HeaderRaw(headerRaw).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_Cookie() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/cookie"
|
||||
cookie = g.MapStrStr{
|
||||
"SessionId": "123",
|
||||
}
|
||||
)
|
||||
content := g.Client().Cookie(cookie).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// SessionId: 123
|
||||
}
|
||||
|
||||
func ExampleClient_ContentJson() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/json"
|
||||
jsonStr = `{"id":10000,"name":"john"}`
|
||||
jsonMap = g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}
|
||||
)
|
||||
// Post using JSON string.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonStr))
|
||||
// Post using JSON map.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonMap))
|
||||
|
||||
// Output:
|
||||
// Content-Type: application/json, id: 10000
|
||||
// Content-Type: application/json, id: 10000
|
||||
}
|
||||
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Get() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter along with URL.
|
||||
r1, err := g.Client().Get(url + "?id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with string parameter in request body.
|
||||
r2, err := g.Client().Get(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r3, err := g.Client().Get(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r3.Close()
|
||||
fmt.Println(r3.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().GetBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().GetContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
user *User
|
||||
url = "http://127.0.0.1:8999/var/json"
|
||||
)
|
||||
err := g.Client().GetVar(url).Scan(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(user)
|
||||
|
||||
// Output:
|
||||
// &{1 john}
|
||||
}
|
||||
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
p := 8999
|
||||
s := g.Server(p)
|
||||
// HTTP method handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"GET: query: %d, %s",
|
||||
r.GetQueryInt("id"),
|
||||
r.GetQueryString("name"),
|
||||
)
|
||||
})
|
||||
group.PUT("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"PUT: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.POST("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"POST: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.DELETE("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"DELETE: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.HEAD("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("head")
|
||||
})
|
||||
group.OPTIONS("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("options")
|
||||
})
|
||||
})
|
||||
// Client chaining operations handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/header", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Span-Id: %s, Trace-Id: %s",
|
||||
r.Header.Get("Span-Id"),
|
||||
r.Header.Get("Trace-Id"),
|
||||
)
|
||||
})
|
||||
group.ALL("/cookie", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"SessionId: %s",
|
||||
r.Cookie.Get("SessionId"),
|
||||
)
|
||||
})
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Content-Type: %s, id: %d",
|
||||
r.Header.Get("Content-Type"),
|
||||
r.GetInt("id"),
|
||||
)
|
||||
})
|
||||
})
|
||||
// Other testing handlers.
|
||||
s.Group("/var", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Write(`{"id":1,"name":"john"}`)
|
||||
})
|
||||
group.ALL("/jsons", func(r *ghttp.Request) {
|
||||
r.Response.Write(`[{"id":1,"name":"john"}, {"id":2,"name":"smith"}]`)
|
||||
})
|
||||
})
|
||||
s.SetAccessLogEnabled(false)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.SetPort(p)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Post() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter in request body.
|
||||
r1, err := g.Client().Post(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r2, err := g.Client().Post(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().PostBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
users []User
|
||||
url = "http://127.0.0.1:8999/var/jsons"
|
||||
)
|
||||
err := g.Client().PostVar(url).Scan(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(users)
|
||||
|
||||
// Output:
|
||||
// [{1 john} {2 smith}]
|
||||
}
|
||||
@ -10,10 +10,11 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleGetServer() {
|
||||
func ExampleHelloWorld() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("hello world")
|
||||
@ -22,6 +23,27 @@ func ExampleGetServer() {
|
||||
s.Run()
|
||||
}
|
||||
|
||||
// Custom saving file name.
|
||||
func ExampleUploadFile_Save() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/upload", func(r *ghttp.Request) {
|
||||
file := r.GetUploadFile("TestFile")
|
||||
if file == nil {
|
||||
r.Response.Write("empty file")
|
||||
return
|
||||
}
|
||||
file.Filename = "MyCustomFileName.txt"
|
||||
fileName, err := file.Save(gfile.TempDir())
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Response.Write(fileName)
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
func ExampleClientResponse_RawDump() {
|
||||
response, err := g.Client().Get("https://goframe.org")
|
||||
if err != nil {
|
||||
|
||||
@ -13,36 +13,42 @@ import (
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
var cache = New()
|
||||
var defaultCache = New()
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
// It does not expire if <duration> == 0.
|
||||
func Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
cache.Set(key, value, duration)
|
||||
defaultCache.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
return cache.SetIfNotExist(key, value, duration)
|
||||
return defaultCache.SetIfNotExist(key, value, duration)
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
cache.Sets(data, duration)
|
||||
defaultCache.Sets(data, duration)
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func Get(key interface{}) interface{} {
|
||||
return cache.Get(key)
|
||||
return defaultCache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
return cache.GetVar(key)
|
||||
return defaultCache.GetVar(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>,
|
||||
@ -51,14 +57,14 @@ func GetVar(key interface{}) *gvar.Var {
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSet(key, value, duration)
|
||||
return defaultCache.GetOrSet(key, value, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFunc(key, f, duration)
|
||||
return defaultCache.GetOrSetFunc(key, f, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
|
||||
@ -67,47 +73,59 @@ func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration)
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFuncLock(key, f, duration)
|
||||
return defaultCache.GetOrSetFuncLock(key, f, duration)
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func Contains(key interface{}) bool {
|
||||
return cache.Contains(key)
|
||||
return defaultCache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
return cache.Remove(keys...)
|
||||
return defaultCache.Remove(keys...)
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func Removes(keys []interface{}) {
|
||||
cache.Removes(keys)
|
||||
defaultCache.Removes(keys)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func Data() map[interface{}]interface{} {
|
||||
return cache.Data()
|
||||
return defaultCache.Data()
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func Keys() []interface{} {
|
||||
return cache.Keys()
|
||||
return defaultCache.Keys()
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func KeyStrings() []string {
|
||||
return cache.KeyStrings()
|
||||
return defaultCache.KeyStrings()
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func Values() []interface{} {
|
||||
return cache.Values()
|
||||
return defaultCache.Values()
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func Size() int {
|
||||
return cache.Size()
|
||||
return defaultCache.Size()
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func GetExpire(key interface{}) time.Duration {
|
||||
return defaultCache.GetExpire(key)
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
return defaultCache.UpdateExpire(key, duration)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ type memCache struct {
|
||||
// Internal cache item.
|
||||
type memCacheItem struct {
|
||||
v interface{} // Value.
|
||||
e int64 // Expire time in milliseconds.
|
||||
e int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// Internal event item.
|
||||
@ -97,7 +97,6 @@ func newMemCache(lruCap ...int) *memCache {
|
||||
}
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
@ -113,6 +112,52 @@ func (c *memCache) Set(key interface{}, value interface{}, duration time.Duratio
|
||||
})
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func (c *memCache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: value,
|
||||
e: item.e,
|
||||
}
|
||||
return item.v, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
newExpireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: item.v,
|
||||
e: newExpireTime,
|
||||
}
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: newExpireTime,
|
||||
})
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) GetExpire(key interface{}) time.Duration {
|
||||
c.dataMu.RLock()
|
||||
defer c.dataMu.RUnlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
|
||||
// cache, which is expired after <duration>.
|
||||
//
|
||||
@ -247,7 +292,11 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Du
|
||||
// It does nothing if function <f> returns nil.
|
||||
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, f(), duration)
|
||||
value := f()
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
|
||||
@ -15,15 +15,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
cache = gcache.New()
|
||||
cacheLru = gcache.New(10000)
|
||||
localCache = gcache.New()
|
||||
localCacheLru = gcache.New(10000)
|
||||
)
|
||||
|
||||
func Benchmark_CacheSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Set(i, i, 0)
|
||||
localCache.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -33,7 +33,7 @@ func Benchmark_CacheGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Get(i)
|
||||
localCache.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -43,7 +43,7 @@ func Benchmark_CacheRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Remove(i)
|
||||
localCache.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -53,7 +53,7 @@ func Benchmark_CacheLruSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Set(i, i, 0)
|
||||
localCacheLru.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -63,7 +63,7 @@ func Benchmark_CacheLruGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Get(i)
|
||||
localCacheLru.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -73,7 +73,7 @@ func Benchmark_CacheLruRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Remove(i)
|
||||
localCacheLru.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
package gcache_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +75,53 @@ func TestCache_Set_Expire(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Update_GetExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
expire1 := gcache.GetExpire(key)
|
||||
gcache.Update(key, 12)
|
||||
expire2 := gcache.GetExpire(key)
|
||||
t.Assert(gcache.GetVar(key), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
expire1 := cache.GetExpire(1)
|
||||
cache.Update(1, 12)
|
||||
expire2 := cache.GetExpire(1)
|
||||
t.Assert(cache.GetVar(1), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_UpdateExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
defer gcache.Remove(key)
|
||||
oldExpire := gcache.GetExpire(key)
|
||||
newExpire := 10 * time.Second
|
||||
gcache.UpdateExpire(key, newExpire)
|
||||
t.AssertNE(gcache.GetExpire(key), oldExpire)
|
||||
t.Assert(math.Ceil(gcache.GetExpire(key).Seconds()), 10)
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
oldExpire := cache.GetExpire(1)
|
||||
newExpire := 10 * time.Second
|
||||
cache.UpdateExpire(1, newExpire)
|
||||
t.AssertNE(cache.GetExpire(1), oldExpire)
|
||||
t.Assert(math.Ceil(cache.GetExpire(1).Seconds()), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Keys_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
@ -269,7 +318,7 @@ func TestCache_Basic(t *testing.T) {
|
||||
t.Assert(cache.Size(), 0)
|
||||
}
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Remove(g.Slice{1, 2, 3}...)
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
|
||||
@ -113,8 +113,10 @@ func (c *Config) filePath(file ...string) (path string) {
|
||||
// The parameter <path> can be absolute or relative path,
|
||||
// but absolute path is strongly recommended.
|
||||
func (c *Config) SetPath(path string) error {
|
||||
isDir := false
|
||||
realPath := ""
|
||||
var (
|
||||
isDir = false
|
||||
realPath = ""
|
||||
)
|
||||
if file := gres.Get(path); file != nil {
|
||||
realPath = path
|
||||
isDir = file.FileInfo().IsDir()
|
||||
@ -332,7 +334,10 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
content = ""
|
||||
filePath = ""
|
||||
)
|
||||
// The configured content can be any kind of data type different from its file type.
|
||||
isFromConfigContent := true
|
||||
if content = GetContent(name); content == "" {
|
||||
isFromConfigContent = false
|
||||
filePath = c.filePath(name)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
@ -344,7 +349,17 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
}
|
||||
}
|
||||
// Note that the underlying configuration json object operations are concurrent safe.
|
||||
if j, err := gjson.LoadContent(content, true); err == nil {
|
||||
var (
|
||||
j *gjson.Json
|
||||
err error
|
||||
)
|
||||
dataType := gfile.ExtName(name)
|
||||
if gjson.IsValidDataType(dataType) && !isFromConfigContent {
|
||||
j, err = gjson.LoadContentType(dataType, content, true)
|
||||
} else {
|
||||
j, err = gjson.LoadContent(content, true)
|
||||
}
|
||||
if err == nil {
|
||||
j.SetViolenceCheck(c.vc)
|
||||
// Add monitor for this configuration file,
|
||||
// any changes of this file will refresh its cache in Config object.
|
||||
|
||||
@ -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"
|
||||
@ -230,21 +231,16 @@ func Test_SetFileName(t *testing.T) {
|
||||
|
||||
func Test_Instance(t *testing.T) {
|
||||
config := `
|
||||
{
|
||||
"array": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"redis": {
|
||||
"cache": "127.0.0.1:6379,1",
|
||||
"disk": "127.0.0.1:6379,0"
|
||||
},
|
||||
"v1": 1,
|
||||
"v2": "true",
|
||||
"v3": "off",
|
||||
"v4": "1.234"
|
||||
}
|
||||
array = [1.0, 2.0, 3.0]
|
||||
v1 = 1.0
|
||||
v2 = "true"
|
||||
v3 = "off"
|
||||
v4 = "1.234"
|
||||
|
||||
[redis]
|
||||
cache = "127.0.0.1:6379,1"
|
||||
disk = "127.0.0.1:6379,0"
|
||||
|
||||
`
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := gcfg.DEFAULT_CONFIG_FILE
|
||||
@ -475,3 +471,13 @@ func TestCfg_Config(t *testing.T) {
|
||||
t.Assert(gcfg.GetContent("name"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCfg_With_UTF8_BOM(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cfg := g.Cfg("test-cfg-with-utf8-bom")
|
||||
t.Assert(cfg.SetPath("testdata"), nil)
|
||||
cfg.SetFileName("cfg-with-utf8-bom.toml")
|
||||
t.Assert(cfg.GetInt("test.testInt"), 1)
|
||||
t.Assert(cfg.GetString("test.testStr"), "test")
|
||||
})
|
||||
}
|
||||
|
||||
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
[test]
|
||||
testInt=1
|
||||
testStr="test"
|
||||
@ -7,20 +7,30 @@
|
||||
|
||||
package gcmd
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Scan prints <info> to stdout, reads and returns user input, which stops by '\n'.
|
||||
func Scan(info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Print(info...)
|
||||
fmt.Scanln(&s)
|
||||
return s
|
||||
return readline()
|
||||
}
|
||||
|
||||
// Scanf prints <info> to stdout with <format>, reads and returns user input, which stops by '\n'.
|
||||
func Scanf(format string, info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Printf(format, info...)
|
||||
fmt.Scanln(&s)
|
||||
return readline()
|
||||
}
|
||||
|
||||
func readline() string {
|
||||
var s string
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
s, _ = reader.ReadString('\n')
|
||||
s = gstr.Trim(s)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -19,12 +19,12 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// Separator for file system.
|
||||
Separator = string(filepath.Separator)
|
||||
)
|
||||
|
||||
var (
|
||||
// Separator for file system.
|
||||
// It here defines the separator as variable
|
||||
// to allow it modified by developer if necessary.
|
||||
Separator = string(filepath.Separator)
|
||||
|
||||
// DefaultPerm is the default perm for file opening.
|
||||
DefaultPermOpen = os.FileMode(0666)
|
||||
|
||||
|
||||
@ -7,12 +7,18 @@
|
||||
package gfile
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
// Max recursive depth for directory scanning.
|
||||
gMAX_SCAN_DEPTH = 100000
|
||||
)
|
||||
|
||||
// ScanDir returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
@ -23,7 +29,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, nil)
|
||||
list, err := doScanDir(0, path, pattern, isRecursive, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -47,7 +53,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list, err := doScanDir(path, pattern, recursive, handler)
|
||||
list, err := doScanDir(0, path, pattern, recursive, handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -69,7 +75,7 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, func(path string) string {
|
||||
list, err := doScanDir(0, path, pattern, isRecursive, func(path string) string {
|
||||
if IsDir(path) {
|
||||
return ""
|
||||
}
|
||||
@ -84,6 +90,38 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ScanDirFileFunc returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
|
||||
// symbol to separate multiple patterns.
|
||||
//
|
||||
// The parameter <recursive> specifies whether scanning the <path> recursively, which
|
||||
// means it scans its sub-files and appends the files path to result array if the sub-file
|
||||
// is also a folder. It is false in default.
|
||||
//
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
//
|
||||
// Note that the parameter <path> for <handler> is not a directory but a file.
|
||||
// It returns only files, exclusive of directories.
|
||||
func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list, err := doScanDir(0, path, pattern, recursive, func(path string) string {
|
||||
if IsDir(path) {
|
||||
return ""
|
||||
}
|
||||
return handler(path)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Strings(list)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory and returns the absolute path
|
||||
// list of files that are not sorted.
|
||||
//
|
||||
@ -97,7 +135,10 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func doScanDir(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
func doScanDir(depth int, path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
if depth >= gMAX_SCAN_DEPTH {
|
||||
return nil, gerror.Newf("directory scanning exceeds max recursive depth: %d", gMAX_SCAN_DEPTH)
|
||||
}
|
||||
list := ([]string)(nil)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@ -115,7 +156,7 @@ func doScanDir(path string, pattern string, recursive bool, handler func(path st
|
||||
for _, name := range names {
|
||||
filePath = path + Separator + name
|
||||
if IsDir(filePath) && recursive {
|
||||
array, _ := doScanDir(filePath, pattern, true, handler)
|
||||
array, _ := doScanDir(depth+1, filePath, pattern, true, handler)
|
||||
if len(array) > 0 {
|
||||
list = append(list, array...)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gfile_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"testing"
|
||||
|
||||
@ -65,3 +66,30 @@ func Test_ScanDirFile(t *testing.T) {
|
||||
t.AssertIN(teatPath+gfile.Separator+"dir2"+gfile.Separator+"file2", files)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanDirFileFunc(t *testing.T) {
|
||||
teatPath := gdebug.TestDataPath()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New()
|
||||
files, err := gfile.ScanDirFileFunc(teatPath, "*", false, func(path string) string {
|
||||
array.Append(1)
|
||||
return path
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(files), 0)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New()
|
||||
files, err := gfile.ScanDirFileFunc(teatPath, "*", true, func(path string) string {
|
||||
array.Append(1)
|
||||
if gfile.Basename(path) == "file1" {
|
||||
return path
|
||||
}
|
||||
return ""
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(files), 1)
|
||||
t.Assert(array.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -215,7 +230,8 @@ func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffe
|
||||
}
|
||||
} else {
|
||||
if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,7 +250,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
return
|
||||
}
|
||||
if stat.Size() > l.config.RotateSize {
|
||||
l.rotateFileBySize(now)
|
||||
@ -243,7 +261,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
}
|
||||
if _, err := file.Write(buffer.Bytes()); err != nil {
|
||||
file.Close()
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
return
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
@ -257,7 +277,8 @@ func (l *Logger) getFilePointer(path string) *gfpool.File {
|
||||
gDEFAULT_FILE_EXPIRE,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package glog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"io"
|
||||
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
@ -55,7 +56,8 @@ func (l *Logger) Path(path string) *Logger {
|
||||
}
|
||||
if path != "" {
|
||||
if err := logger.SetPath(path); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
return logger
|
||||
@ -73,7 +75,8 @@ func (l *Logger) Cat(category string) *Logger {
|
||||
}
|
||||
if logger.config.Path != "" {
|
||||
if err := logger.SetPath(gfile.Join(logger.config.Path, category)); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
return logger
|
||||
@ -115,7 +118,8 @@ func (l *Logger) LevelStr(levelStr string) *Logger {
|
||||
logger = l
|
||||
}
|
||||
if err := logger.SetLevelStr(levelStr); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
@ -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"
|
||||
@ -27,7 +28,8 @@ func (l *Logger) rotateFileBySize(now time.Time) {
|
||||
l.rmu.Lock()
|
||||
defer l.rmu.Unlock()
|
||||
if err := l.doRotateFile(l.getFilePath(now)); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +83,20 @@ func (l *Logger) rotateChecksTimely() {
|
||||
defer gtimer.AddOnce(l.config.RotateCheckInterval, l.rotateChecksTimely)
|
||||
// Checks whether file rotation not enabled.
|
||||
if l.config.RotateSize <= 0 && l.config.RotateExpire == 0 {
|
||||
intlog.Printf(
|
||||
"logging rotation ignore checks: RotateSize: %d, RotateExpire: %s",
|
||||
l.config.RotateSize, l.config.RotateExpire.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// It here uses memory lock to guarantee the concurrent safety.
|
||||
lockKey := "glog.rotateChecksTimely:" + l.config.Path
|
||||
if !gmlock.TryLock(lockKey) {
|
||||
return
|
||||
}
|
||||
defer gmlock.Unlock(lockKey)
|
||||
|
||||
var (
|
||||
now = time.Now()
|
||||
pattern = "*.log, *.gz"
|
||||
|
||||
@ -9,6 +9,7 @@ package grpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
@ -47,6 +48,14 @@ func Add(f func()) error {
|
||||
return pool.Add(f)
|
||||
}
|
||||
|
||||
// AddWithRecover pushes a new job to the pool with specified recover function.
|
||||
// The optional <recoverFunc> is called when any panic during executing of <userFunc>.
|
||||
// If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
|
||||
// The job will be executed asynchronously.
|
||||
func AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
|
||||
return pool.AddWithRecover(userFunc, recoverFunc...)
|
||||
}
|
||||
|
||||
// Size returns current goroutine count of default goroutine pool.
|
||||
func Size() int {
|
||||
return pool.Size()
|
||||
@ -81,6 +90,23 @@ func (p *Pool) Add(f func()) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWithRecover pushes a new job to the pool with specified recover function.
|
||||
// The optional <recoverFunc> is called when any panic during executing of <userFunc>.
|
||||
// If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
|
||||
// The job will be executed asynchronously.
|
||||
func (p *Pool) AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
|
||||
return p.Add(func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if len(recoverFunc) > 0 && recoverFunc[0] != nil {
|
||||
recoverFunc[0](errors.New(fmt.Sprintf(`%v`, err)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
userFunc()
|
||||
})
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the pool.
|
||||
// This capacity is defined when pool is created.
|
||||
// It returns -1 if there's no limit.
|
||||
|
||||
@ -97,6 +97,23 @@ func Test_Limit3(t *testing.T) {
|
||||
t.Assert(array.Len(), 100)
|
||||
t.Assert(pool.IsClosed(), true)
|
||||
t.AssertNE(pool.Add(func() {}), nil)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AddWithRecover(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.NewArray(true)
|
||||
grpool.AddWithRecover(func() {
|
||||
array.Append(1)
|
||||
panic(1)
|
||||
}, func(err error) {
|
||||
array.Append(1)
|
||||
})
|
||||
grpool.AddWithRecover(func() {
|
||||
panic(1)
|
||||
array.Append(1)
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ func parseDateStr(s string) (year, month, day int) {
|
||||
return
|
||||
}
|
||||
|
||||
// StrToTime converts string to *Time object.
|
||||
// StrToTime converts string to *Time object. It also supports timestamp string.
|
||||
// The parameter <format> is unnecessary, which specifies the format for converting like "Y-m-d H:i:s".
|
||||
// If <format> is given, it acts as same as function StrToTimeFormat.
|
||||
// If <format> is not given, it converts string as a "standard" datetime string.
|
||||
@ -236,6 +236,10 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
if len(format) > 0 {
|
||||
return StrToTimeFormat(str, format[0])
|
||||
}
|
||||
if isTimestampStr(str) {
|
||||
timestamp, _ := strconv.ParseInt(str, 10, 64)
|
||||
return NewFromTimeStamp(timestamp), nil
|
||||
}
|
||||
var (
|
||||
year, month, day int
|
||||
hour, min, sec, nsec int
|
||||
@ -290,8 +294,8 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
if h > 24 || m > 59 || s > 59 {
|
||||
return nil, gerror.Newf("invalid zone string: %s", match[6])
|
||||
}
|
||||
// Comparing the given time zone whether equals to current tine zone,
|
||||
// it converts it to UTC if they does not.
|
||||
// Comparing the given time zone whether equals to current time zone,
|
||||
// it converts it to UTC if they does not equal.
|
||||
_, localOffset := time.Now().Zone()
|
||||
// Comparing in seconds.
|
||||
if (h*3600 + m*60 + s) != localOffset {
|
||||
@ -326,7 +330,7 @@ func StrToTime(str string, format ...string) (*Time, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if year <= 0 || month <= 0 || day <= 0 || hour < 0 || min < 0 || sec < 0 || nsec < 0 {
|
||||
if year <= 0 {
|
||||
return nil, errors.New("invalid time string:" + str)
|
||||
}
|
||||
// It finally converts all time to UTC time zone.
|
||||
@ -424,3 +428,17 @@ func FuncCost(f func()) int64 {
|
||||
f()
|
||||
return TimestampNano() - t
|
||||
}
|
||||
|
||||
// isTimestampStr checks and returns whether given string a timestamp string.
|
||||
func isTimestampStr(s string) bool {
|
||||
length := len(s)
|
||||
if length == 0 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -17,11 +17,34 @@ 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])
|
||||
// apiUnixNano is an interface definition commonly for custom time.Time wrapper.
|
||||
type apiUnixNano interface {
|
||||
UnixNano() int64
|
||||
}
|
||||
|
||||
// New creates and returns a Time object with given parameter.
|
||||
// The optional parameter can be type of: time.Time/*time.Time, string or integer.
|
||||
func New(param ...interface{}) *Time {
|
||||
if len(param) > 0 {
|
||||
switch r := param[0].(type) {
|
||||
case time.Time:
|
||||
r.Nanosecond()
|
||||
return NewFromTime(r)
|
||||
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)
|
||||
default:
|
||||
if v, ok := r.(apiUnixNano); ok {
|
||||
return NewFromTimeStamp(v.UnixNano())
|
||||
}
|
||||
}
|
||||
}
|
||||
return &Time{
|
||||
TimeWrapper{time.Time{}},
|
||||
@ -73,6 +96,7 @@ func NewFromStrLayout(str string, layout string) *Time {
|
||||
|
||||
// NewFromTimeStamp creates and returns a Time object with given timestamp,
|
||||
// which can be in seconds to nanoseconds.
|
||||
// Eg: 1600443866 and 1600443866199266000 are both considered as valid timestamp number.
|
||||
func NewFromTimeStamp(timestamp int64) *Time {
|
||||
if timestamp == 0 {
|
||||
return &Time{}
|
||||
|
||||
@ -86,9 +86,7 @@ func Test_RFC822(t *testing.T) {
|
||||
|
||||
func Test_StrToTime(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
//正常日期列表
|
||||
//正则的原因,日期"06.01.02","2006.01","2006..01"无法覆盖gtime.go的百分百
|
||||
var testDatetimes = []string{
|
||||
var testDateTimes = []string{
|
||||
"2006-01-02 15:04:05",
|
||||
"2006/01/02 15:04:05",
|
||||
"2006.01.02 15:04:05.000",
|
||||
@ -103,7 +101,7 @@ func Test_StrToTime(t *testing.T) {
|
||||
"02.jan.2006:15:04:05",
|
||||
}
|
||||
|
||||
for _, item := range testDatetimes {
|
||||
for _, item := range testDateTimes {
|
||||
timeTemp, err := gtime.StrToTime(item)
|
||||
if err != nil {
|
||||
t.Error("test fail")
|
||||
@ -155,7 +153,6 @@ func Test_StrToTime(t *testing.T) {
|
||||
var testDatesFail = []string{
|
||||
"2006.01",
|
||||
"06..02",
|
||||
"20060102",
|
||||
}
|
||||
|
||||
for _, item := range testDatesFail {
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
// time.Time
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := time.Now()
|
||||
timeTemp := gtime.New(timeNow)
|
||||
@ -24,6 +25,23 @@ func Test_New(t *testing.T) {
|
||||
timeTemp1 := gtime.New()
|
||||
t.Assert(timeTemp1.Time, time.Time{})
|
||||
})
|
||||
// string
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.String())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.TimestampMicroStr())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
// int64
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeNow := gtime.Now()
|
||||
timeTemp := gtime.New(timeNow.TimestampMicro())
|
||||
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Nil(t *testing.T) {
|
||||
@ -42,7 +60,7 @@ func Test_NewFromStr(t *testing.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2006-01-02 15:04:05")
|
||||
|
||||
timeTemp1 := gtime.NewFromStr("20060102")
|
||||
timeTemp1 := gtime.NewFromStr("2006.0102")
|
||||
if timeTemp1 != nil {
|
||||
t.Error("test fail")
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ const (
|
||||
STATUS_READY = 0 // Job is ready for running.
|
||||
STATUS_RUNNING = 1 // Job is already running.
|
||||
STATUS_STOPPED = 2 // Job is stopped.
|
||||
STATUS_RESET = 3 // Job is reset.
|
||||
STATUS_CLOSED = -1 // Job is closed and waiting to be deleted.
|
||||
gPANIC_EXIT = "exit" // Internal usage for custom job exit function with panic.
|
||||
gDEFAULT_TIMES = math.MaxInt32 // Default limit running times, a big number.
|
||||
|
||||
@ -106,6 +106,11 @@ func (entry *Entry) Stop() {
|
||||
entry.status.Set(STATUS_STOPPED)
|
||||
}
|
||||
|
||||
//Reset reset the job.
|
||||
func (entry *Entry) Reset() {
|
||||
entry.status.Set(STATUS_RESET)
|
||||
}
|
||||
|
||||
// Close closes the job, and then it will be removed from the timer.
|
||||
func (entry *Entry) Close() {
|
||||
entry.status.Set(STATUS_CLOSED)
|
||||
@ -138,6 +143,8 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool)
|
||||
return false, true
|
||||
case STATUS_CLOSED:
|
||||
return false, false
|
||||
case STATUS_RESET:
|
||||
return false, true
|
||||
}
|
||||
// Firstly checks using the ticks, this may be low precision as one tick is a little bit long.
|
||||
if diff := nowTicks - entry.create; diff > 0 && diff%entry.interval == 0 {
|
||||
|
||||
@ -74,6 +74,10 @@ func (w *wheel) proceed() {
|
||||
}
|
||||
// If rolls on the job.
|
||||
if addable {
|
||||
//If STATUS_RESET , reset to runnable state.
|
||||
if entry.Status() == STATUS_RESET {
|
||||
entry.SetStatus(STATUS_READY)
|
||||
}
|
||||
entry.wheel.timer.doAddEntryByParent(entry.rawIntervalMs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
package gtimer_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +74,24 @@ func TestTimer_Start_Stop_Close(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_Reset(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
glog.Printf("start time:%d", time.Now().Unix())
|
||||
singleton := timer.AddSingleton(2*time.Second, func() {
|
||||
timestamp := time.Now().Unix()
|
||||
glog.Println(timestamp)
|
||||
array.Append(timestamp)
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
glog.Printf("reset time:%d", time.Now().Unix())
|
||||
singleton.Reset()
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Assert(array.Len(), 6)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_AddSingleton(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
|
||||
@ -24,9 +24,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
|
||||
firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
|
||||
)
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gstr_test
|
||||
|
||||
import (
|
||||
|
||||
@ -381,8 +381,10 @@ func String(i interface{}) string {
|
||||
return f.Error()
|
||||
}
|
||||
// Reflect checks.
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(value)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
switch kind {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
@ -394,6 +396,8 @@ func String(i interface{}) string {
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
case reflect.String:
|
||||
return rv.String()
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
|
||||
@ -37,19 +37,19 @@ func MapDeep(value interface{}, tags ...string) map[string]interface{} {
|
||||
// doMapConvert implements the map converting.
|
||||
// It automatically checks and converts json string to map if <value> is string/[]byte.
|
||||
//
|
||||
// TODO completely implement the recursive converting for all types.
|
||||
// TODO completely implement the recursive converting for all types, especially the map.
|
||||
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assert the common combination of types, and finally it uses reflection.
|
||||
m := make(map[string]interface{})
|
||||
dataMap := make(map[string]interface{})
|
||||
switch r := value.(type) {
|
||||
case string:
|
||||
// If it is a JSON string, automatically unmarshal it!
|
||||
if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' {
|
||||
if err := json.Unmarshal([]byte(r), &m); err != nil {
|
||||
if err := json.Unmarshal([]byte(r), &dataMap); err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -58,7 +58,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
case []byte:
|
||||
// If it is a JSON string, automatically unmarshal it!
|
||||
if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' {
|
||||
if err := json.Unmarshal(r, &m); err != nil {
|
||||
if err := json.Unmarshal(r, &dataMap); err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -66,61 +66,61 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]int:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]uint:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]float32:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[interface{}]float64:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[string]bool:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]int:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]uint:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]float32:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]float64:
|
||||
for k, v := range r {
|
||||
m[k] = v
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]interface{}:
|
||||
return r
|
||||
case map[int]interface{}:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[int]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
case map[uint]string:
|
||||
for k, v := range r {
|
||||
m[String(k)] = v
|
||||
dataMap[String(k)] = v
|
||||
}
|
||||
|
||||
default:
|
||||
@ -146,15 +146,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
length := rv.Len()
|
||||
for i := 0; i < length; i += 2 {
|
||||
if i+1 < length {
|
||||
m[String(rv.Index(i).Interface())] = rv.Index(i + 1).Interface()
|
||||
dataMap[String(rv.Index(i).Interface())] = rv.Index(i + 1).Interface()
|
||||
} else {
|
||||
m[String(rv.Index(i).Interface())] = nil
|
||||
dataMap[String(rv.Index(i).Interface())] = nil
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
ks := rv.MapKeys()
|
||||
for _, k := range ks {
|
||||
m[String(k.Interface())] = rv.MapIndex(k).Interface()
|
||||
dataMap[String(k.Interface())] = rv.MapIndex(k).Interface()
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Map converting interface check.
|
||||
@ -165,7 +165,6 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
var (
|
||||
rtField reflect.StructField
|
||||
rvField reflect.Value
|
||||
rvKind reflect.Kind
|
||||
rt = rv.Type()
|
||||
name = ""
|
||||
tagArray = StructTagPriority
|
||||
@ -216,36 +215,45 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
rvKind = rvField.Kind()
|
||||
if rvKind == reflect.Ptr {
|
||||
rvField = rvField.Elem()
|
||||
rvKind = rvField.Kind()
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
rvAttrKind = rvField.Kind()
|
||||
)
|
||||
if rvAttrKind == reflect.Ptr {
|
||||
rvAttrField = rvField.Elem()
|
||||
rvAttrKind = rvAttrField.Kind()
|
||||
}
|
||||
if rvKind == reflect.Struct {
|
||||
hasNoTag := name == fieldName
|
||||
if rvAttrKind == reflect.Struct {
|
||||
var (
|
||||
hasNoTag = name == fieldName
|
||||
rvAttrInterface = rvAttrField.Interface()
|
||||
)
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
for k, v := range doMapConvert(rvField.Interface(), recursive, tags...) {
|
||||
m[k] = v
|
||||
for k, v := range doMapConvert(rvAttrInterface, recursive, tags...) {
|
||||
dataMap[k] = v
|
||||
}
|
||||
} else {
|
||||
// It means this attribute field has desired tag.
|
||||
m[name] = doMapConvert(rvField.Interface(), recursive, tags...)
|
||||
if m := doMapConvert(rvAttrInterface, recursive, tags...); len(m) > 0 {
|
||||
dataMap[name] = m
|
||||
} else {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if rvField.IsValid() {
|
||||
m[name] = rvField.Interface()
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
m[name] = nil
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rvField.IsValid() {
|
||||
m[name] = rvField.Interface()
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
m[name] = nil
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,7 +261,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return m
|
||||
return dataMap
|
||||
}
|
||||
|
||||
// MapStrStr converts <value> to map[string]string.
|
||||
|
||||
@ -86,3 +86,12 @@ func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MapOmitEmpty deletes all empty values from guven map.
|
||||
func MapOmitEmpty(data map[string]interface{}) {
|
||||
for k, v := range data {
|
||||
if IsEmpty(v) {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,3 +118,19 @@ func Test_MapContainsPossibleKey(t *testing.T) {
|
||||
t.Assert(gutil.MapContainsPossibleKey(m, "none"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapOmitEmpty(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := g.Map{
|
||||
"k1": "john",
|
||||
"e1": "",
|
||||
"e2": 0,
|
||||
"e3": nil,
|
||||
"k2": "smith",
|
||||
}
|
||||
gutil.MapOmitEmpty(m)
|
||||
t.Assert(len(m), 2)
|
||||
t.AssertNE(m["k1"], nil)
|
||||
t.AssertNE(m["k2"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/net/gipv6"
|
||||
@ -38,11 +39,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{}{
|
||||
@ -124,9 +125,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
// It converts value to string and then does the validation.
|
||||
var (
|
||||
// Do not trim it as the space is also part of the value.
|
||||
val = gconv.String(value)
|
||||
data = make(map[string]string)
|
||||
errorMsgs = make(map[string]string)
|
||||
data = make(map[string]string)
|
||||
errorMsgArray = make(map[string]string)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
for k, v := range gconv.Map(params[0]) {
|
||||
@ -151,7 +151,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
ruleItems := strings.Split(strings.TrimSpace(rules), "|")
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
if _, ok := allSupportedRules[array[0]]; !ok {
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
if !ok && customRuleFuncMap[array[0]] == nil {
|
||||
if i > 0 && ruleItems[i-1][:5] == "regex" {
|
||||
ruleItems[i-1] += "|" + ruleItems[i]
|
||||
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
||||
@ -170,303 +171,37 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
}
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
item = ruleItems[index]
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
ruleVal = strings.TrimSpace(results[2])
|
||||
err error
|
||||
item = ruleItems[index]
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
rulePattern = strings.TrimSpace(results[2])
|
||||
)
|
||||
if len(msgArray) > index {
|
||||
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
||||
}
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(val, ruleKey, ruleVal, data)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
// It checks custom validation rules with most priority.
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
ruleVal += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(ruleVal, val)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(val, ruleVal); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, val)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, val)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, val)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, val)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(val)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(val)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, val)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, val)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(val)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(val); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(val, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(val)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, val)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, val)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, val)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(val) || gipv6.Validate(val)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(val)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(val)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, val)
|
||||
|
||||
default:
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgs[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
} else {
|
||||
errorMsgs[ruleKey] = "Invalid rule name: " + ruleKey
|
||||
} else {
|
||||
// It checks build-in validation rules if there's no custom rule.
|
||||
match, err = doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap)
|
||||
if !match && err != nil {
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,16 +209,303 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
if !match {
|
||||
// It does nothing if the error message for this rule
|
||||
// is already set in previous validation.
|
||||
if _, ok := errorMsgs[ruleKey]; !ok {
|
||||
errorMsgs[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
if _, ok := errorMsgArray[ruleKey]; !ok {
|
||||
errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
if len(errorMsgArray) > 0 {
|
||||
return newError([]string{rules}, ErrorMap{
|
||||
key: errorMsgs,
|
||||
key: errorMsgArray,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCheckBuildInRules(
|
||||
index int,
|
||||
value interface{},
|
||||
ruleKey string,
|
||||
rulePattern string,
|
||||
ruleItems []string,
|
||||
dataMap map[string]string,
|
||||
customMsgMap map[string]string,
|
||||
) (match bool, err error) {
|
||||
valueStr := gconv.String(value)
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(valueStr, ruleKey, rulePattern, dataMap)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
rulePattern += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(rulePattern, valueStr)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(valueStr, rulePattern); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, valueStr)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, valueStr)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, valueStr)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(valueStr)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(valueStr)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, valueStr)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(valueStr)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(valueStr); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(valueStr, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(valueStr)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, valueStr)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, valueStr)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, valueStr)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(valueStr) || gipv6.Validate(valueStr)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(valueStr)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(valueStr)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, errors.New("Invalid rule name: " + ruleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user