mirror of
https://gitee.com/johng/gf
synced 2026-06-09 02:57:43 +08:00
Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| ca8e2c2986 | |||
| 2f0e5a45c5 | |||
| c4b28b0bc4 | |||
| 6cc4747965 | |||
| 91cd34b26a | |||
| e35a2f028c | |||
| 9cff2bd10f | |||
| 6d406498db | |||
| 939ae37baf | |||
| 4d6c8744e5 | |||
| e252d8b740 | |||
| 9b8d63e21b | |||
| 437fc04620 | |||
| 04dee090a3 | |||
| 245c6d24a1 | |||
| e92fd05f9f | |||
| 937f8e6919 | |||
| f489e6273e | |||
| 4f99bdbc87 | |||
| 1250b33220 | |||
| b57aee4595 | |||
| d9da51933d | |||
| 9fca93e7d8 | |||
| 854b2ed185 | |||
| cfac03bc40 | |||
| a3cb4a6ae8 | |||
| 2798fa4444 | |||
| 8bac0614f5 | |||
| 646280a6a9 | |||
| 208bdffdf7 | |||
| 9e7291903f | |||
| d56835fc00 | |||
| 1d5e717a80 | |||
| 2f44d9ae18 | |||
| 0627ab81d6 | |||
| 8167a398fc | |||
| 6291751014 | |||
| ee5ddaab52 | |||
| 207476be1f | |||
| b9b470c2ae | |||
| 52b6e8ef9d | |||
| 48c84bf74a | |||
| 5be30b3684 | |||
| 54a2b13825 | |||
| d44ddae3dc | |||
| 7bbc2459ba | |||
| 0cb77caa2a | |||
| 3f5f76458d | |||
| 835e07e8de | |||
| 3fc5e43abe | |||
| 9c8cb26bd6 | |||
| 540f4d2d0c | |||
| 51be255821 | |||
| 7d278fea25 | |||
| ca72d3b23a | |||
| 534cd3be1c | |||
| 78536de1b5 | |||
| edc67d9ec3 | |||
| 4dd12434b7 | |||
| f654bb2eda | |||
| 205f98cfeb | |||
| 6e08eebcbe | |||
| d9422d00ac |
@ -512,16 +512,6 @@ func mapToStruct() {
|
||||
}
|
||||
}
|
||||
|
||||
// getQueriedSqls
|
||||
func getQueriedSqls() {
|
||||
for k, v := range db.GetQueriedSqls() {
|
||||
fmt.Println(k, ":")
|
||||
fmt.Println("Sql :", v.Sql)
|
||||
fmt.Println("Args :", v.Args)
|
||||
fmt.Println("Error:", v.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
db.PingMaster()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -513,16 +513,6 @@ func mapToStruct() {
|
||||
}
|
||||
}
|
||||
|
||||
// getQueriedSqls
|
||||
func getQueriedSqls() {
|
||||
for k, v := range db.GetQueriedSqls() {
|
||||
fmt.Println(k, ":")
|
||||
fmt.Println("Sql :", v.Sql)
|
||||
fmt.Println("Args :", v.Args)
|
||||
fmt.Println("Error:", v.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
db.PingMaster()
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.SetConfigWithMap(g.Map{"Graceful": true})
|
||||
s.EnableAdmin()
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gfcache"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := 0
|
||||
r := ""
|
||||
path := gfile.TempDir() + gfile.Separator + "temp"
|
||||
gfile.PutContents(path, "hello")
|
||||
|
||||
s = gfcache.GetSize()
|
||||
r = gfcache.GetContents(path)
|
||||
fmt.Println(s, r)
|
||||
|
||||
gfile.PutContentsAppend(path, " john")
|
||||
|
||||
// 等待1秒以便gfsnotify回调能处理完成
|
||||
time.Sleep(time.Second)
|
||||
|
||||
s = gfcache.GetSize()
|
||||
r = gfcache.GetContents(path)
|
||||
fmt.Println(s, r)
|
||||
}
|
||||
@ -5,6 +5,7 @@ go:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "1.15.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
35
DONATOR.MD
35
DONATOR.MD
@ -1,12 +1,17 @@
|
||||
# Donators
|
||||
|
||||
We currently accept donation by [Alipay](https://goframe.org/images/donate.png) / [Gitee](https://gitee.com/johng/gf),
|
||||
please note your github/gitee account in your payment bill.
|
||||
We currently accept donation by [Wechat](https://goframe.org/images/donate.png) / [Alipay](https://goframe.org/images/donate.png) / [Gitee](https://gitee.com/johng/gf),
|
||||
please note your github/gitee account in your payment bill. All the donations will be used only for `GoFrame` project development and its community construction.
|
||||
|
||||
> If you cannot see the donation image, please click [here](https://goframe.org/images/donate.png).
|
||||
|
||||
<img src="https://goframe.org/images/donate.png?20200718"/>
|
||||
|
||||
|
||||
| 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 |
|
||||
@ -92,8 +97,30 @@ please note your github/gitee account in your payment bill.
|
||||
|六七 ·|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|
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.jpeg"/>
|
||||
|
||||
|
||||
13
README.MD
13
README.MD
@ -115,9 +115,18 @@ The concurrency starts from `100` to `10000`.
|
||||
|
||||
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
|
||||
|
||||
# Known Users
|
||||
# Part Of Users
|
||||
|
||||
Logos are not authorized to be shown due to trademark copyrights.
|
||||
- [Tencent](https://www.tencent.com/)
|
||||
- [ZTE](https://www.zte.com.cn/china/)
|
||||
- [Ant Financial Services](https://www.antfin.com/)
|
||||
- [MedLinker](https://www.medlinker.com/)
|
||||
- [KuCoin](https://www.kucoin.io/)
|
||||
- [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).
|
||||
|
||||
|
||||
# Contributors
|
||||
|
||||
11
README_ZH.MD
11
README_ZH.MD
@ -137,7 +137,16 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
|
||||
|
||||
# 用户
|
||||
|
||||
由于商标版权缘故,未经厂商商务部授权允许无法用于宣传展示。
|
||||
- [腾讯科技](https://www.tencent.com/)
|
||||
- [中兴科技](https://www.zte.com.cn/china/)
|
||||
- [蚂蚁金服](https://www.antfin.com/)
|
||||
- [医联科技](https://www.medlinker.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) 留言。
|
||||
|
||||
# 贡献
|
||||
|
||||
|
||||
@ -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()))
|
||||
|
||||
@ -5,4 +5,7 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gtree provides concurrent-safe/unsafe tree containers.
|
||||
//
|
||||
// Some implements are from: https://github.com/emirpasic/gods
|
||||
// Thanks!
|
||||
package gtree
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -142,6 +144,7 @@ type DB interface {
|
||||
QuotePrefixTableName(table string) string
|
||||
Tables(schema ...string) (tables []string, err error)
|
||||
TableFields(table string, schema ...string) (map[string]*TableField, error)
|
||||
HasTable(name string) (bool, error)
|
||||
|
||||
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
|
||||
// it's committed to underlying driver. The parameter <link> specifies the current
|
||||
@ -154,7 +157,7 @@ type DB interface {
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
convertValue(fieldValue []byte, fieldType string) interface{}
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
}
|
||||
|
||||
@ -187,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.
|
||||
@ -259,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
|
||||
@ -270,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()
|
||||
@ -281,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(),
|
||||
@ -307,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 (
|
||||
@ -492,15 +526,15 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
params []interface{}
|
||||
listMap List
|
||||
)
|
||||
switch v := list.(type) {
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
listMap = v.List()
|
||||
listMap = value.List()
|
||||
case Record:
|
||||
listMap = List{v.Map()}
|
||||
listMap = List{value.Map()}
|
||||
case List:
|
||||
listMap = v
|
||||
listMap = value
|
||||
case Map:
|
||||
listMap = List{v}
|
||||
listMap = List{value}
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
@ -517,8 +551,21 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map, reflect.Struct:
|
||||
listMap = List{DataToMapDeep(v)}
|
||||
case reflect.Map:
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
@ -620,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 (
|
||||
@ -688,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 {
|
||||
@ -724,7 +763,7 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
columnNames[k] = v.Name()
|
||||
}
|
||||
var (
|
||||
values = make([]sql.RawBytes, len(columnNames))
|
||||
values = make([]interface{}, len(columnNames))
|
||||
records = make(Result, 0)
|
||||
scanArgs = make([]interface{}, len(values))
|
||||
)
|
||||
@ -735,19 +774,12 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
if err := rows.Scan(scanArgs...); err != nil {
|
||||
return records, err
|
||||
}
|
||||
// Creates a new row object.
|
||||
row := make(Record)
|
||||
// Note that the internal looping variable <value> is type of []byte,
|
||||
// which points to the same memory address. So it should do a copy.
|
||||
for i, value := range values {
|
||||
if value == nil {
|
||||
row[columnNames[i]] = gvar.New(nil)
|
||||
} else {
|
||||
// As sql.RawBytes is type of slice,
|
||||
// it should do a copy of it.
|
||||
v := make([]byte, len(value))
|
||||
copy(v, value)
|
||||
row[columnNames[i]] = gvar.New(c.DB.convertValue(v, columnTypes[i]))
|
||||
row[columnNames[i]] = gvar.New(c.DB.convertValue(value, columnTypes[i]))
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
@ -770,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)
|
||||
@ -778,3 +810,17 @@ func (c *Core) writeSqlToLogger(v *Sql) {
|
||||
c.logger.Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
// HasTable determine whether the table name exists in the database.
|
||||
func (c *Core) HasTable(name string) (bool, error) {
|
||||
tableList, err := c.DB.Tables()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, table := range tableList {
|
||||
if table == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -64,10 +64,10 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string "@vx".
|
||||
// Convert place holder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@v%d", index)
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return d.parseSql(str), args
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,10 +62,10 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string "$vx".
|
||||
// Convert place holder char '?' to string "$x".
|
||||
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("$v%d", index)
|
||||
return fmt.Sprintf("$%d", index)
|
||||
})
|
||||
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return sql, args
|
||||
|
||||
@ -55,14 +55,34 @@ const (
|
||||
var (
|
||||
// quoteWordReg is the regular expression object for a word check.
|
||||
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
|
||||
|
||||
// Priority tags for struct converting for orm field mapping.
|
||||
structTagPriority = append([]string{ORM_TAG_FOR_STRUCT}, gconv.StructTagPriority...)
|
||||
)
|
||||
|
||||
// ListItemValues is alias for gutil.ListItemValues.
|
||||
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
//
|
||||
// The parameter <list> supports types like:
|
||||
// []map[string]interface{}
|
||||
// []map[string]sub-map
|
||||
// []struct
|
||||
// []struct:sub-struct
|
||||
// Note that the sub-map/sub-struct makes sense only if the optional parameter <subKey> is given.
|
||||
// See gutil.ListItemValues.
|
||||
func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) {
|
||||
return gutil.ListItemValues(list, key, subKey...)
|
||||
}
|
||||
|
||||
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
// See gutil.ListItemValuesUnique.
|
||||
func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} {
|
||||
return gutil.ListItemValuesUnique(list, key, subKey...)
|
||||
}
|
||||
|
||||
// GetInsertOperationByOption returns proper insert option with given parameter <option>.
|
||||
func GetInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
@ -78,33 +98,98 @@ func GetInsertOperationByOption(option int) string {
|
||||
}
|
||||
|
||||
// DataToMapDeep converts struct object to map type recursively.
|
||||
func DataToMapDeep(obj interface{}) map[string]interface{} {
|
||||
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
// The parameter <obj> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvField reflect.Value
|
||||
rvKind reflect.Kind
|
||||
rtField reflect.StructField
|
||||
)
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
rvValue = v
|
||||
} else {
|
||||
rvValue = reflect.ValueOf(value)
|
||||
}
|
||||
rvKind = rvValue.Kind()
|
||||
if rvKind == reflect.Ptr {
|
||||
rvValue = rvValue.Elem()
|
||||
rvKind = rvValue.Kind()
|
||||
}
|
||||
// If given <value> is not a struct, it uses gconv.Map for converting.
|
||||
if rvKind != reflect.Struct {
|
||||
return gconv.Map(value, structTagPriority...)
|
||||
}
|
||||
// Struct handling.
|
||||
var (
|
||||
fieldTag reflect.StructTag
|
||||
rvType = rvValue.Type()
|
||||
name = ""
|
||||
data = make(map[string]interface{})
|
||||
)
|
||||
for i := 0; i < rvValue.NumField(); i++ {
|
||||
rtField = rvType.Field(i)
|
||||
rvField = rvValue.Field(i)
|
||||
fieldName := rtField.Name
|
||||
if !utils.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
if _, ok := value.(time.Time); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := value.(*time.Time); ok {
|
||||
continue
|
||||
}
|
||||
// Use string conversion in default.
|
||||
if s, ok := value.(apiString); ok {
|
||||
data[key] = s.String()
|
||||
continue
|
||||
}
|
||||
delete(data, key)
|
||||
for k, v := range DataToMapDeep(value) {
|
||||
// Struct attribute inherit
|
||||
if rtField.Anonymous {
|
||||
for k, v := range DataToMapDeep(rvField) {
|
||||
data[k] = v
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Other attributes.
|
||||
name = ""
|
||||
fieldTag = rtField.Tag
|
||||
for _, tag := range structTagPriority {
|
||||
if s := fieldTag.Get(tag); s != "" {
|
||||
name = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = fieldName
|
||||
} else {
|
||||
// The "orm" tag supports json tag feature: -, omitempty
|
||||
// The "orm" tag would be like: "id,priority", so it should use splitting handling.
|
||||
name = gstr.Trim(name)
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
array := gstr.SplitAndTrim(name, ",")
|
||||
if len(array) > 1 {
|
||||
switch array[1] {
|
||||
case "omitempty":
|
||||
if empty.IsEmpty(rvField.Interface()) {
|
||||
continue
|
||||
} else {
|
||||
name = array[0]
|
||||
}
|
||||
default:
|
||||
name = array[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
fieldValue := rvField.Interface()
|
||||
switch fieldValue.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
data[name] = fieldValue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := fieldValue.(apiString); ok {
|
||||
data[name] = s.String()
|
||||
} else {
|
||||
data[name] = fieldValue
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
@ -231,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()
|
||||
@ -553,7 +640,7 @@ func formatError(err error, sql string, args ...interface{}) error {
|
||||
func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
index := -1
|
||||
newQuery, _ := gregex.ReplaceStringFunc(
|
||||
`(\?|:v\d+|\$v\d+|@v\d+)`, sql, func(s string) string {
|
||||
`(\?|:v\d+|\$\d+|@p\d+)`, sql, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
|
||||
@ -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]
|
||||
@ -127,3 +113,24 @@ func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
newFields = m.db.QuoteString(newFields)
|
||||
return newFields
|
||||
}
|
||||
|
||||
// HasField determine whether the field exists in the table.
|
||||
func (m *Model) HasField(field string) (bool, error) {
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
return false, fmt.Errorf(`empty table fields for table "%s"`, m.tables)
|
||||
}
|
||||
fieldsArray := make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
for _, f := range fieldsArray {
|
||||
if f == field {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -72,11 +72,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case reflect.Map:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
case reflect.Struct:
|
||||
if v, ok := data[0].(apiMapStrAny); ok {
|
||||
model.data = v.MapStrAny()
|
||||
} else if v, ok := data[0].(apiInterfaces); ok {
|
||||
array := v.Interfaces()
|
||||
list := make(List, len(array))
|
||||
if v, ok := data[0].(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -21,7 +22,12 @@ import (
|
||||
|
||||
// convertValue automatically checks and converts field value from database type
|
||||
// to golang variable type.
|
||||
func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{} {
|
||||
// If there's no type retrieved, it returns the <fieldValue> directly
|
||||
// to use its original data type, as <fieldValue> is type of interface{}.
|
||||
if fieldType == "" {
|
||||
return fieldValue
|
||||
}
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
t = strings.ToLower(t)
|
||||
switch t {
|
||||
@ -32,7 +38,7 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
"tinyblob",
|
||||
"mediumblob",
|
||||
"longblob":
|
||||
return fieldValue
|
||||
return gconv.Bytes(fieldValue)
|
||||
|
||||
case
|
||||
"int",
|
||||
@ -43,21 +49,21 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
"mediumint",
|
||||
"serial":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
gconv.Uint(string(fieldValue))
|
||||
gconv.Uint(gconv.String(fieldValue))
|
||||
}
|
||||
return gconv.Int(string(fieldValue))
|
||||
return gconv.Int(gconv.String(fieldValue))
|
||||
|
||||
case
|
||||
"big_int",
|
||||
"bigint",
|
||||
"bigserial":
|
||||
if gstr.ContainsI(fieldType, "unsigned") {
|
||||
gconv.Uint64(string(fieldValue))
|
||||
gconv.Uint64(gconv.String(fieldValue))
|
||||
}
|
||||
return gconv.Int64(string(fieldValue))
|
||||
return gconv.Int64(gconv.String(fieldValue))
|
||||
|
||||
case "real":
|
||||
return gconv.Float32(string(fieldValue))
|
||||
return gconv.Float32(gconv.String(fieldValue))
|
||||
|
||||
case
|
||||
"float",
|
||||
@ -66,10 +72,10 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
"money",
|
||||
"numeric",
|
||||
"smallmoney":
|
||||
return gconv.Float64(string(fieldValue))
|
||||
return gconv.Float64(gconv.String(fieldValue))
|
||||
|
||||
case "bit":
|
||||
s := string(fieldValue)
|
||||
s := gconv.String(fieldValue)
|
||||
// mssql is true|false string.
|
||||
if strings.EqualFold(s, "true") {
|
||||
return 1
|
||||
@ -77,41 +83,47 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
if strings.EqualFold(s, "false") {
|
||||
return 0
|
||||
}
|
||||
return gbinary.BeDecodeToInt64(fieldValue)
|
||||
return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue))
|
||||
|
||||
case "bool":
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
case "date":
|
||||
t, _ := gtime.StrToTime(string(fieldValue))
|
||||
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":
|
||||
t, _ := gtime.StrToTime(string(fieldValue))
|
||||
if t, ok := fieldValue.(time.Time); ok {
|
||||
return gtime.NewFromTime(t)
|
||||
}
|
||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||
return t.String()
|
||||
|
||||
default:
|
||||
// Auto detect field type, using key match.
|
||||
switch {
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char") || strings.Contains(t, "character"):
|
||||
return string(fieldValue)
|
||||
return gconv.String(fieldValue)
|
||||
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double") || strings.Contains(t, "numeric"):
|
||||
return gconv.Float64(string(fieldValue))
|
||||
return gconv.Float64(gconv.String(fieldValue))
|
||||
|
||||
case strings.Contains(t, "bool"):
|
||||
return gconv.Bool(string(fieldValue))
|
||||
return gconv.Bool(gconv.String(fieldValue))
|
||||
|
||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
||||
return fieldValue
|
||||
|
||||
case strings.Contains(t, "int"):
|
||||
return gconv.Int(string(fieldValue))
|
||||
return gconv.Int(gconv.String(fieldValue))
|
||||
|
||||
case strings.Contains(t, "time"):
|
||||
s := string(fieldValue)
|
||||
s := gconv.String(fieldValue)
|
||||
t, err := gtime.StrToTime(s)
|
||||
if err != nil {
|
||||
return s
|
||||
@ -119,7 +131,7 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
return t.String()
|
||||
|
||||
case strings.Contains(t, "date"):
|
||||
s := string(fieldValue)
|
||||
s := gconv.String(fieldValue)
|
||||
t, err := gtime.StrToTime(s)
|
||||
if err != nil {
|
||||
return s
|
||||
@ -127,7 +139,7 @@ func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
return t.Format("Y-m-d")
|
||||
|
||||
default:
|
||||
return string(fieldValue)
|
||||
return gconv.String(fieldValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/os/gcmd"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -75,13 +76,13 @@ func Test_Func_FormatSqlWithArgs(t *testing.T) {
|
||||
// mssql
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s string
|
||||
s = FormatSqlWithArgs("select * from table where id>=@v1 and sex=@v2", []interface{}{100, 1})
|
||||
s = FormatSqlWithArgs("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
|
||||
t.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// pgsql
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s string
|
||||
s = FormatSqlWithArgs("select * from table where id>=$v1 and sex=$v2", []interface{}{100, 1})
|
||||
s = FormatSqlWithArgs("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
|
||||
t.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// oracle
|
||||
@ -289,3 +290,25 @@ CREATE TABLE %s (
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Func_DataToMapDeep(t *testing.T) {
|
||||
type Test struct {
|
||||
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := DataToMapDeep(new(Test))
|
||||
t.Assert(len(m), 1)
|
||||
t.AssertNE(m["reset_password_token_at"], nil)
|
||||
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)
|
||||
@ -2437,3 +2473,37 @@ func Test_Model_Empty_Slice_Argument(t *testing.T) {
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_HasTable(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.HasTable(table)
|
||||
t.Assert(result, true)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.HasTable("table12321")
|
||||
t.Assert(result, false)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_HasField(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).HasField("id")
|
||||
t.Assert(result, true)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).HasField("id123")
|
||||
t.Assert(result, false)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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`)
|
||||
})
|
||||
}
|
||||
@ -15,7 +15,6 @@ package gredis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
@ -46,6 +45,8 @@ type Config struct {
|
||||
IdleTimeout time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0)
|
||||
MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0)
|
||||
ConnectTimeout time.Duration // Dial connection timeout.
|
||||
TLS bool // Specifies the config to use when a TLS connection is dialed.
|
||||
TLSSkipVerify bool // Disables server name verification when connecting over TLS
|
||||
}
|
||||
|
||||
// Pool statistics.
|
||||
@ -102,6 +103,8 @@ func New(config Config) *Redis {
|
||||
"tcp",
|
||||
fmt.Sprintf("%s:%d", config.Host, config.Port),
|
||||
redis.DialConnectTimeout(config.ConnectTimeout),
|
||||
redis.DialUseTLS(config.TLS),
|
||||
redis.DialTLSSkipVerify(config.TLSSkipVerify),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -163,6 +166,7 @@ func (r *Redis) Conn() *Conn {
|
||||
}
|
||||
|
||||
// Alias of Conn, see Conn.
|
||||
// Deprecated.
|
||||
func (r *Redis) GetConn() *Conn {
|
||||
return r.Conn()
|
||||
}
|
||||
@ -204,21 +208,27 @@ func (r *Redis) Stats() *PoolStats {
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// Do automatically get a connection from pool, and close it when the reply received.
|
||||
// It does not really "close" the connection, but drops it back to the connection pool.
|
||||
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
|
||||
func (r *Redis) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
conn := &Conn{r.pool.Get()}
|
||||
defer conn.Close()
|
||||
return conn.Do(command, args...)
|
||||
return conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
// DoWithTimeout sends a command to the server and returns the received reply.
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (r *Redis) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
|
||||
conn := &Conn{r.pool.Get()}
|
||||
defer conn.Close()
|
||||
return conn.DoWithTimeout(timeout, commandName, args...)
|
||||
}
|
||||
|
||||
// DoVar returns value from Do as gvar.Var.
|
||||
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
|
||||
v, err := r.Do(command, args...)
|
||||
if result, ok := v.([]byte); ok {
|
||||
return gvar.New(gconv.UnsafeBytesToStr(result)), err
|
||||
}
|
||||
// It treats all returned slice as string slice.
|
||||
if result, ok := v.([]interface{}); ok {
|
||||
return gvar.New(gconv.Strings(result)), err
|
||||
}
|
||||
return gvar.New(v), err
|
||||
func (r *Redis) DoVar(commandName string, args ...interface{}) (*gvar.Var, error) {
|
||||
return resultToVar(r.Do(commandName, args...))
|
||||
}
|
||||
|
||||
// DoVarWithTimeout returns value from Do as gvar.Var.
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (r *Redis) DoVarWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (*gvar.Var, error) {
|
||||
return resultToVar(r.DoWithTimeout(timeout, commandName, args...))
|
||||
}
|
||||
|
||||
@ -110,6 +110,12 @@ func ConfigFromStr(str string) (config Config, err error) {
|
||||
if v, ok := parse["maxConnLifetime"]; ok {
|
||||
config.MaxConnLifetime = gconv.Duration(v) * time.Second
|
||||
}
|
||||
if v, ok := parse["tls"]; ok {
|
||||
config.TLS = gconv.Bool(v)
|
||||
}
|
||||
if v, ok := parse["skipVerify"]; ok {
|
||||
config.TLSSkipVerify = gconv.Bool(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
array, _ = gregex.MatchString(`([^:]+):*(\d*),{0,1}(\d*),{0,1}(.*)`, str)
|
||||
|
||||
@ -7,14 +7,19 @@
|
||||
package gredis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// It uses json.Marshal for struct/slice/map type values before committing them to redis.
|
||||
func (c *Conn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (c *Conn) do(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
@ -40,17 +45,64 @@ func (c *Conn) Do(commandName string, args ...interface{}) (reply interface{}, e
|
||||
}
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
conn, ok := c.Conn.(redis.ConnWithTimeout)
|
||||
if !ok {
|
||||
return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`)
|
||||
}
|
||||
return conn.DoWithTimeout(timeout, commandName, args...)
|
||||
}
|
||||
return c.Conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// It uses json.Marshal for struct/slice/map type values before committing them to redis.
|
||||
func (c *Conn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
return c.do(0, commandName, args...)
|
||||
}
|
||||
|
||||
// DoWithTimeout sends a command to the server and returns the received reply.
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (c *Conn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
return c.do(timeout, commandName, args...)
|
||||
}
|
||||
|
||||
// DoVar retrieves and returns the result from command as gvar.Var.
|
||||
func (c *Conn) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
|
||||
v, err := c.Do(command, args...)
|
||||
return gvar.New(v), err
|
||||
func (c *Conn) DoVar(commandName string, args ...interface{}) (*gvar.Var, error) {
|
||||
return resultToVar(c.Do(commandName, args...))
|
||||
}
|
||||
|
||||
// DoVarWithTimeout retrieves and returns the result from command as gvar.Var.
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (c *Conn) DoVarWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (*gvar.Var, error) {
|
||||
return resultToVar(c.DoWithTimeout(timeout, commandName, args...))
|
||||
}
|
||||
|
||||
// ReceiveVar receives a single reply as gvar.Var from the Redis server.
|
||||
func (c *Conn) ReceiveVar() (*gvar.Var, error) {
|
||||
v, err := c.Receive()
|
||||
return gvar.New(v), err
|
||||
return resultToVar(c.Receive())
|
||||
}
|
||||
|
||||
// ReceiveVarWithTimeout receives a single reply as gvar.Var from the Redis server.
|
||||
// The timeout overrides the read timeout set when dialing the connection.
|
||||
func (c *Conn) ReceiveVarWithTimeout(timeout time.Duration) (*gvar.Var, error) {
|
||||
conn, ok := c.Conn.(redis.ConnWithTimeout)
|
||||
if !ok {
|
||||
return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`)
|
||||
}
|
||||
return resultToVar(conn.ReceiveWithTimeout(timeout))
|
||||
}
|
||||
|
||||
// resultToVar converts redis operation result to gvar.Var.
|
||||
func resultToVar(result interface{}, err error) (*gvar.Var, error) {
|
||||
if err == nil {
|
||||
if result, ok := result.([]byte); ok {
|
||||
return gvar.New(gconv.UnsafeBytesToStr(result)), err
|
||||
}
|
||||
// It treats all returned slice as string slice.
|
||||
if result, ok := result.([]interface{}); ok {
|
||||
return gvar.New(gconv.Strings(result)), err
|
||||
}
|
||||
}
|
||||
return gvar.New(result), err
|
||||
}
|
||||
|
||||
51
database/gredis/gredis_z_unit_conn_test.go
Normal file
51
database/gredis/gredis_z_unit_conn_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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 gredis_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/database/gredis"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConn_DoWithTimeout(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
redis := gredis.New(config)
|
||||
t.AssertNE(redis, nil)
|
||||
conn := redis.Conn()
|
||||
defer conn.Close()
|
||||
|
||||
_, err := conn.DoWithTimeout(time.Second, "set", "test", "123")
|
||||
t.Assert(err, nil)
|
||||
defer conn.DoWithTimeout(time.Second, "del", "test")
|
||||
|
||||
r, err := conn.DoWithTimeout(time.Second, "get", "test")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, "123")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConn_ReceiveVarWithTimeout(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
redis := gredis.New(config)
|
||||
t.AssertNE(redis, nil)
|
||||
conn := redis.Conn()
|
||||
defer conn.Close()
|
||||
|
||||
_, err := conn.DoVarWithTimeout(time.Second, "Subscribe", "gf")
|
||||
t.Assert(err, nil)
|
||||
|
||||
v, err := redis.DoVarWithTimeout(time.Second, "PUBLISH", "gf", "test")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.String(), "1")
|
||||
|
||||
v, _ = conn.ReceiveVar()
|
||||
t.Assert(len(v.Strings()), 3)
|
||||
t.Assert(v.Strings()[2], "test")
|
||||
})
|
||||
}
|
||||
@ -18,7 +18,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/database/gredis"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
redis2 "github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -181,28 +180,20 @@ func Test_Error(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.String(), "v")
|
||||
|
||||
conn := redis.GetConn()
|
||||
conn := redis.Conn()
|
||||
defer conn.Close()
|
||||
_, err = conn.DoVar("SET", "k", "v")
|
||||
t.Assert(err, nil)
|
||||
|
||||
//v, err = conn.ReceiveVar()
|
||||
//t.Assert(err, nil)
|
||||
//t.Assert(v.String(), "v")
|
||||
_, err = conn.DoVar("Subscribe", "gf")
|
||||
t.Assert(err, nil)
|
||||
|
||||
psc := redis2.PubSubConn{Conn: conn}
|
||||
psc.Subscribe("gf")
|
||||
redis.DoVar("PUBLISH", "gf", "gf test")
|
||||
go func() {
|
||||
for {
|
||||
v, _ := conn.ReceiveVar()
|
||||
switch obj := v.Val().(type) {
|
||||
case redis2.Message:
|
||||
t.Assert(string(obj.Data), "gf test")
|
||||
case redis2.Subscription:
|
||||
_, err = redis.DoVar("PUBLISH", "gf", "test")
|
||||
t.Assert(err, nil)
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
v, _ = conn.ReceiveVar()
|
||||
t.Assert(len(v.Strings()), 3)
|
||||
t.Assert(v.Strings()[2], "test")
|
||||
|
||||
time.Sleep(time.Second)
|
||||
})
|
||||
|
||||
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,15 +10,15 @@ 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"
|
||||
"github.com/gogf/gf/encoding/gyaml"
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
"github.com/gogf/gf/os/gfcache"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -55,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()
|
||||
@ -72,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),
|
||||
@ -99,7 +99,7 @@ func Load(path string, safe ...bool) (*Json, error) {
|
||||
} else {
|
||||
path = p
|
||||
}
|
||||
return doLoadContent(gfile.Ext(path), gfcache.GetBinContents(path), safe...)
|
||||
return doLoadContent(gfile.Ext(path), gfile.GetBytesWithCache(path), safe...)
|
||||
}
|
||||
|
||||
// LoadJson creates a Json object from given JSON format content.
|
||||
@ -189,22 +189,44 @@ func LoadContent(data interface{}, safe ...bool) (*Json, error) {
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
return doLoadContent(checkDataType(content), content, safe...)
|
||||
return LoadContentType(checkDataType(content), content, safe...)
|
||||
}
|
||||
|
||||
// LoadContentType creates a Json object from given type and content,
|
||||
// supporting data content type as follows:
|
||||
// JSON, XML, INI, YAML and TOML.
|
||||
func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) {
|
||||
content := gconv.Bytes(data)
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
//ignore UTF8-BOM
|
||||
if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
|
||||
content = content[3:]
|
||||
}
|
||||
return doLoadContent(dataType, content, safe...)
|
||||
}
|
||||
|
||||
// checkDataType automatically checks and returns the data type for <content>.
|
||||
// Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
|
||||
// functions to load the content for certain content type.
|
||||
func checkDataType(content []byte) string {
|
||||
if json.Valid(content) {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
return "xml"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*:\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*:\s*.+`, content) {
|
||||
} else if (gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content)) {
|
||||
return "yml"
|
||||
} else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false {
|
||||
return "ini"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-\."]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-\."]+\s*=\s*.+`, content) {
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
return "toml"
|
||||
} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
// Must contain "[xxx]" section.
|
||||
return "ini"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ func Test_Basic(t *testing.T) {
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(j.Get("0").([]interface{})), 3)
|
||||
t.Assert(j.Get("0"), 1)
|
||||
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.a")
|
||||
|
||||
115
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
115
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gjson
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_checkDataType(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
bb = """
|
||||
dig := dig; END;"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
dd = 11
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
"gf.gvalid.rule.required" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-if" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-unless" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.date" = "The :attribute value is not a valid date"
|
||||
"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format"
|
||||
"gf.gvalid.rule.email" = "The :attribute value must be a valid email address"
|
||||
"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number"
|
||||
"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number"
|
||||
"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number"
|
||||
"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number"
|
||||
"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number"
|
||||
"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address"
|
||||
"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address"
|
||||
"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address"
|
||||
"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address"
|
||||
"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address"
|
||||
"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format"
|
||||
"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max"
|
||||
"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max"
|
||||
"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string"
|
||||
"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string"
|
||||
"gf.gvalid.rule.array" = "The :attribute value must be an array"
|
||||
"gf.gvalid.rule.integer" = "The :attribute value must be an integer"
|
||||
"gf.gvalid.rule.float" = "The :attribute value must be a float"
|
||||
"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false"
|
||||
"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field"
|
||||
"gf.gvalid.rule.different" = "The :attribute value must be different from field :field"
|
||||
"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
`)
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\s\t\n\r]+[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.MatchString(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, string(data)))
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
[default]
|
||||
db.engine = mysql
|
||||
db.max.idle.conns = 5
|
||||
db.max.open.conns = 100
|
||||
allow_ips =
|
||||
api.key =
|
||||
api.secret =
|
||||
enable_tls = false
|
||||
concurrency.queue = 500
|
||||
auth_secret = 63358e6f3daf0e5775ec3fb4d2516b01d41530bf30960aa76972f6ce7e08552f
|
||||
ca_file =
|
||||
cert_file =
|
||||
key_file =
|
||||
host_port = 8088
|
||||
log_path = /Users/zhaosuji/go/src/git.medlinker.com/foundations/gocron/log
|
||||
#k8s-api地址(只提供内网访问)
|
||||
k8s-inner-api = http://127.0.0.1:8081/kube/add
|
||||
conf_dir = ./config
|
||||
app_conf = ./config/app.ini
|
||||
`)
|
||||
t.Assert(checkDataType(data), "ini")
|
||||
})
|
||||
}
|
||||
@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
|
||||
|
||||
;注释
|
||||
|
||||
[addr]
|
||||
#注释
|
||||
[addr]
|
||||
ip = 127.0.0.1
|
||||
port=9001
|
||||
enable=true
|
||||
|
||||
@ -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
|
||||
|
||||
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ go 1.11
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.4
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
|
||||
@ -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 {
|
||||
@ -222,10 +246,10 @@ func (m *Manager) init() {
|
||||
} else if m.options.Path != "" {
|
||||
files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true)
|
||||
if len(files) == 0 {
|
||||
intlog.Printf(
|
||||
"no i18n files found in configured directory: %s",
|
||||
m.options.Path,
|
||||
)
|
||||
//intlog.Printf(
|
||||
// "no i18n files found in configured directory: %s",
|
||||
// m.options.Path,
|
||||
//)
|
||||
return
|
||||
}
|
||||
var (
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -25,6 +25,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Debugging configured.
|
||||
if !cmdenv.Get("GF_DEBUG").IsEmpty() {
|
||||
isGFDebug = true
|
||||
return
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
|
||||
if !gfile.Exists(path) {
|
||||
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
|
||||
}
|
||||
if file, err := writer.CreateFormFile(array[0], path); err == nil {
|
||||
if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil {
|
||||
if f, err := os.Open(path); err == nil {
|
||||
if _, err = io.Copy(file, f); err != nil {
|
||||
f.Close()
|
||||
@ -227,6 +227,10 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
|
||||
req.Body = utils.NewReadCloser(reqBodyContent, false)
|
||||
for {
|
||||
if resp.Response, err = c.Do(req); err != nil {
|
||||
// The response might not be nil when err != nil.
|
||||
if resp.Response != nil {
|
||||
resp.Response.Body.Close()
|
||||
}
|
||||
if c.retryCount > 0 {
|
||||
c.retryCount--
|
||||
time.Sleep(c.retryInterval)
|
||||
|
||||
@ -63,6 +63,9 @@ func (r *ClientResponse) ReadAllString() string {
|
||||
|
||||
// Close closes the response when it will never be used.
|
||||
func (r *ClientResponse) Close() error {
|
||||
if r == nil || r.Response == nil || r.Response.Close {
|
||||
return nil
|
||||
}
|
||||
r.Response.Close = true
|
||||
return r.Response.Body.Close()
|
||||
}
|
||||
|
||||
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"
|
||||
@ -126,6 +127,11 @@ func (r *Request) IsExited() bool {
|
||||
return r.exit
|
||||
}
|
||||
|
||||
// GetHeader retrieves and returns the header value with given <key>.
|
||||
func (r *Request) GetHeader(key string) string {
|
||||
return r.Header.Get(key)
|
||||
}
|
||||
|
||||
// GetHost returns current request host name, which might be a domain or an IP without port.
|
||||
func (r *Request) GetHost() string {
|
||||
if len(r.parsedHost) == 0 {
|
||||
@ -150,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")
|
||||
@ -173,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"
|
||||
@ -212,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +210,10 @@ func serverProcessInit() {
|
||||
// Process message handler.
|
||||
// It's enabled only graceful feature is enabled.
|
||||
if gracefulEnabled {
|
||||
intlog.Printf("%d: graceful reload feature is enabled", gproc.Pid())
|
||||
go handleProcessMessage()
|
||||
} else {
|
||||
intlog.Printf("%d: graceful reload feature is disabled", gproc.Pid())
|
||||
}
|
||||
|
||||
// It's an ugly calling for better initializing the main package path
|
||||
@ -264,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 {
|
||||
@ -313,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)
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -22,8 +22,9 @@ type utilAdmin struct{}
|
||||
// Index shows the administration page.
|
||||
func (p *utilAdmin) Index(r *Request) {
|
||||
data := map[string]interface{}{
|
||||
"pid": gproc.Pid(),
|
||||
"uri": strings.TrimRight(r.URL.Path, "/"),
|
||||
"pid": gproc.Pid(),
|
||||
"path": gfile.SelfPath(),
|
||||
"uri": strings.TrimRight(r.URL.Path, "/"),
|
||||
}
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
@ -31,7 +32,8 @@ func (p *utilAdmin) Index(r *Request) {
|
||||
<title>GoFrame Web Server Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>PID: {{.pid}}</p>
|
||||
<p>Pid: {{.pid}}</p>
|
||||
<p>File Path: {{.path}}</p>
|
||||
<p><a href="{{$.uri}}/restart">Restart</a></p>
|
||||
<p><a href="{{$.uri}}/shutdown">Shutdown</a></p>
|
||||
</body>
|
||||
@ -46,7 +48,7 @@ func (p *utilAdmin) Restart(r *Request) {
|
||||
// Custom start binary path when this process exits.
|
||||
path := r.GetQueryString("newExeFilePath")
|
||||
if path == "" {
|
||||
path = os.Args[0]
|
||||
path = gfile.SelfPath()
|
||||
}
|
||||
if len(path) > 0 {
|
||||
err = RestartAllServer(path)
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -49,6 +50,9 @@ var serverProcessStatus = gtype.NewInt()
|
||||
// RestartAllServer restarts all the servers of the process.
|
||||
// The optional parameter <newExeFilePath> specifies the new binary file for creating process.
|
||||
func RestartAllServer(newExeFilePath ...string) error {
|
||||
if !gracefulEnabled {
|
||||
return errors.New("graceful reload feature is disabled")
|
||||
}
|
||||
serverActionLocker.Lock()
|
||||
defer serverActionLocker.Unlock()
|
||||
if err := checkProcessStatus(); err != nil {
|
||||
@ -147,7 +151,7 @@ func forkRestartProcess(newExeFilePath ...string) error {
|
||||
env = append(env, gADMIN_ACTION_RESTART_ENVKEY+"=1")
|
||||
p := gproc.NewProcess(path, os.Args, env)
|
||||
if _, err := p.Start(); err != nil {
|
||||
glog.Errorf("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
|
||||
glog.Errorf(`%d: fork process failed, error:%s, are you running using "go run"?`, gproc.Pid(), err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -257,8 +261,10 @@ func handleProcessMessage() {
|
||||
for {
|
||||
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {
|
||||
if bytes.EqualFold(msg.Data, []byte("exit")) {
|
||||
intlog.Printf("%d: process message: exit", gproc.Pid())
|
||||
gracefulShutdownWebServers()
|
||||
allDoneChan <- struct{}{}
|
||||
intlog.Printf("%d: process message: exit done", gproc.Pid())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +267,7 @@ func Config() ServerConfig {
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: true,
|
||||
Graceful: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}()
|
||||
|
||||
// ============================================================
|
||||
|
||||
@ -64,6 +64,22 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
if len(path) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
// In case of double '/' URI, for example:
|
||||
// /user//index, //user/index, //user//index//
|
||||
var previousIsSep = false
|
||||
for i := 0; i < len(path); {
|
||||
if path[i] == '/' {
|
||||
if previousIsSep {
|
||||
path = path[:i] + path[i+1:]
|
||||
continue
|
||||
} else {
|
||||
previousIsSep = true
|
||||
}
|
||||
} else {
|
||||
previousIsSep = false
|
||||
}
|
||||
i++
|
||||
}
|
||||
// Split the URL.path to separate parts.
|
||||
var array []string
|
||||
if strings.EqualFold("/", path) {
|
||||
@ -71,9 +87,12 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
} else {
|
||||
array = strings.Split(path[1:], "/")
|
||||
}
|
||||
parsedItemList := glist.New()
|
||||
lastMiddlewareElem := (*glist.Element)(nil)
|
||||
repeatHandlerCheckMap := make(map[int]struct{}, 16)
|
||||
var (
|
||||
lastMiddlewareElem *glist.Element
|
||||
parsedItemList = glist.New()
|
||||
repeatHandlerCheckMap = make(map[int]struct{}, 16)
|
||||
)
|
||||
|
||||
// Default domain has the most priority when iteration.
|
||||
for _, domain := range []string{gDEFAULT_DOMAIN, domain} {
|
||||
p, ok := s.serveTree[domain]
|
||||
@ -83,10 +102,6 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
// Make a list array with capacity of 16.
|
||||
lists := make([]*glist.List, 0, 16)
|
||||
for i, part := range array {
|
||||
// In case of double '/' URI, eg: /user//index
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
// Add all list of each node to the list array.
|
||||
if v, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
|
||||
@ -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"
|
||||
@ -404,6 +408,27 @@ func Test_Params_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Header(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/header", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetHeader("test"))
|
||||
})
|
||||
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.Header(g.MapStrStr{"test": "123456"}).GetContent("/header"), "123456")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_SupportChars(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
@ -496,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()
|
||||
@ -137,6 +164,9 @@ func Test_Router_ExtraChar(t *testing.T) {
|
||||
t.Assert(client.GetContent("/api/test"), "test")
|
||||
t.Assert(client.GetContent("/api/test/"), "test")
|
||||
t.Assert(client.GetContent("/api/test//"), "test")
|
||||
t.Assert(client.GetContent("//api/test//"), "test")
|
||||
t.Assert(client.GetContent("//api//test//"), "test")
|
||||
t.Assert(client.GetContent("///api///test///"), "test")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ func (o *GroupObjRest) Head(r *ghttp.Request) {
|
||||
r.Response.Header().Set("head-ok", "1")
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest(t *testing.T) {
|
||||
func Test_Router_GroupRest1(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
g := s.Group("/api")
|
||||
@ -172,3 +172,80 @@ func Test_Router_GroupRest(t *testing.T) {
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_GroupRest2(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.Group("/api", func(group *ghttp.RouterGroup) {
|
||||
ctl := new(GroupCtlRest)
|
||||
obj := new(GroupObjRest)
|
||||
group.REST("/ctl", ctl)
|
||||
group.REST("/obj", obj)
|
||||
group.REST("/{.struct}/{.method}", ctl)
|
||||
group.REST("/{.struct}/{.method}", obj)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/api/ctl"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
|
||||
resp1, err := client.Head("/api/ctl")
|
||||
if err == nil {
|
||||
defer resp1.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp1.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/obj"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/obj"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/obj"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
|
||||
resp2, err := client.Head("/api/obj")
|
||||
if err == nil {
|
||||
defer resp2.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp2.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
|
||||
t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
|
||||
t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
|
||||
resp3, err := client.Head("/api/group-ctl-rest/head")
|
||||
if err == nil {
|
||||
defer resp3.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp3.Header.Get("head-ok"), "1")
|
||||
|
||||
t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
|
||||
t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
|
||||
t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
|
||||
t.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
|
||||
t.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
|
||||
t.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
|
||||
t.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
|
||||
resp4, err := client.Head("/api/group-obj-rest/head")
|
||||
if err == nil {
|
||||
defer resp4.Close()
|
||||
}
|
||||
t.Assert(err, nil)
|
||||
t.Assert(resp4.Header.Get("head-ok"), "1")
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,3 +157,38 @@ func Test_Router_Hook_Multi(t *testing.T) {
|
||||
t.Assert(client.GetContent("/multi-hook"), "12show")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Hook_ExitAll(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("test")
|
||||
})
|
||||
s.Group("/hook", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
r.Response.Write("1")
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/test", func(r *ghttp.Request) {
|
||||
r.Response.Write("2")
|
||||
})
|
||||
})
|
||||
|
||||
s.BindHookHandler("/hook/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
|
||||
r.Response.Write("hook")
|
||||
r.ExitAll()
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/test"), "test")
|
||||
t.Assert(client.GetContent("/hook/test"), "hook")
|
||||
})
|
||||
}
|
||||
|
||||
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
86
net/ghttp/ghttp_z_example_client_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Header() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
header = g.MapStrStr{
|
||||
"Span-Id": "0.1",
|
||||
"Trace-Id": "123456789",
|
||||
}
|
||||
)
|
||||
content := g.Client().Header(header).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_HeaderRaw() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/header"
|
||||
headerRaw = `
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3950.0 Safari/537.36
|
||||
Span-Id: 0.1
|
||||
Trace-Id: 123456789
|
||||
`
|
||||
)
|
||||
content := g.Client().HeaderRaw(headerRaw).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// Span-Id: 0.1, Trace-Id: 123456789
|
||||
}
|
||||
|
||||
func ExampleClient_Cookie() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/cookie"
|
||||
cookie = g.MapStrStr{
|
||||
"SessionId": "123",
|
||||
}
|
||||
)
|
||||
content := g.Client().Cookie(cookie).PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// SessionId: 123
|
||||
}
|
||||
|
||||
func ExampleClient_ContentJson() {
|
||||
var (
|
||||
url = "http://127.0.0.1:8999/json"
|
||||
jsonStr = `{"id":10000,"name":"john"}`
|
||||
jsonMap = g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}
|
||||
)
|
||||
// Post using JSON string.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonStr))
|
||||
// Post using JSON map.
|
||||
fmt.Println(g.Client().ContentJson().PostContent(url, jsonMap))
|
||||
|
||||
// Output:
|
||||
// Content-Type: application/json, id: 10000
|
||||
// Content-Type: application/json, id: 10000
|
||||
}
|
||||
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
89
net/ghttp/ghttp_z_example_get_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Get() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter along with URL.
|
||||
r1, err := g.Client().Get(url + "?id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with string parameter in request body.
|
||||
r2, err := g.Client().Get(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r3, err := g.Client().Get(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r3.Close()
|
||||
fmt.Println(r3.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().GetBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().GetContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// GET: query: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_GetVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
user *User
|
||||
url = "http://127.0.0.1:8999/var/json"
|
||||
)
|
||||
err := g.Client().GetVar(url).Scan(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(user)
|
||||
|
||||
// Output:
|
||||
// &{1 john}
|
||||
}
|
||||
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
95
net/ghttp/ghttp_z_example_init_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
p := 8999
|
||||
s := g.Server(p)
|
||||
// HTTP method handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"GET: query: %d, %s",
|
||||
r.GetQueryInt("id"),
|
||||
r.GetQueryString("name"),
|
||||
)
|
||||
})
|
||||
group.PUT("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"PUT: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.POST("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"POST: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.DELETE("/", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"DELETE: form: %d, %s",
|
||||
r.GetFormInt("id"),
|
||||
r.GetFormString("name"),
|
||||
)
|
||||
})
|
||||
group.HEAD("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("head")
|
||||
})
|
||||
group.OPTIONS("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("options")
|
||||
})
|
||||
})
|
||||
// Client chaining operations handlers.
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/header", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Span-Id: %s, Trace-Id: %s",
|
||||
r.Header.Get("Span-Id"),
|
||||
r.Header.Get("Trace-Id"),
|
||||
)
|
||||
})
|
||||
group.ALL("/cookie", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"SessionId: %s",
|
||||
r.Cookie.Get("SessionId"),
|
||||
)
|
||||
})
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Writef(
|
||||
"Content-Type: %s, id: %d",
|
||||
r.Header.Get("Content-Type"),
|
||||
r.GetInt("id"),
|
||||
)
|
||||
})
|
||||
})
|
||||
// Other testing handlers.
|
||||
s.Group("/var", func(group *ghttp.RouterGroup) {
|
||||
group.ALL("/json", func(r *ghttp.Request) {
|
||||
r.Response.Write(`{"id":1,"name":"john"}`)
|
||||
})
|
||||
group.ALL("/jsons", func(r *ghttp.Request) {
|
||||
r.Response.Write(`[{"id":1,"name":"john"}, {"id":2,"name":"smith"}]`)
|
||||
})
|
||||
})
|
||||
s.SetAccessLogEnabled(false)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.SetPort(p)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
79
net/ghttp/ghttp_z_example_post_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleClient_Post() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
// Send with string parameter in request body.
|
||||
r1, err := g.Client().Post(url, "id=10000&name=john")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r1.Close()
|
||||
fmt.Println(r1.ReadAllString())
|
||||
|
||||
// Send with map parameter.
|
||||
r2, err := g.Client().Post(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r2.Close()
|
||||
fmt.Println(r2.ReadAllString())
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostBytes() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(string(g.Client().PostBytes(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostContent() {
|
||||
url := "http://127.0.0.1:8999"
|
||||
fmt.Println(g.Client().PostContent(url, g.Map{
|
||||
"id": 10000,
|
||||
"name": "john",
|
||||
}))
|
||||
|
||||
// Output:
|
||||
// POST: form: 10000, john
|
||||
}
|
||||
|
||||
func ExampleClient_PostVar() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
var (
|
||||
users []User
|
||||
url = "http://127.0.0.1:8999/var/jsons"
|
||||
)
|
||||
err := g.Client().PostVar(url).Scan(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(users)
|
||||
|
||||
// Output:
|
||||
// [{1 john} {2 smith}]
|
||||
}
|
||||
@ -10,10 +10,11 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleGetServer() {
|
||||
func ExampleHelloWorld() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("hello world")
|
||||
@ -22,6 +23,27 @@ func ExampleGetServer() {
|
||||
s.Run()
|
||||
}
|
||||
|
||||
// Custom saving file name.
|
||||
func ExampleUploadFile_Save() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/upload", func(r *ghttp.Request) {
|
||||
file := r.GetUploadFile("TestFile")
|
||||
if file == nil {
|
||||
r.Response.Write("empty file")
|
||||
return
|
||||
}
|
||||
file.Filename = "MyCustomFileName.txt"
|
||||
fileName, err := file.Save(gfile.TempDir())
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Response.Write(fileName)
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
func ExampleClientResponse_RawDump() {
|
||||
response, err := g.Client().Get("https://goframe.org")
|
||||
if err != nil {
|
||||
|
||||
@ -13,36 +13,42 @@ import (
|
||||
)
|
||||
|
||||
// Default cache object.
|
||||
var cache = New()
|
||||
var defaultCache = New()
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
// It does not expire if <duration> == 0.
|
||||
func Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
cache.Set(key, value, duration)
|
||||
defaultCache.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
return cache.SetIfNotExist(key, value, duration)
|
||||
return defaultCache.SetIfNotExist(key, value, duration)
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
cache.Sets(data, duration)
|
||||
defaultCache.Sets(data, duration)
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func Get(key interface{}) interface{} {
|
||||
return cache.Get(key)
|
||||
return defaultCache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
return cache.GetVar(key)
|
||||
return defaultCache.GetVar(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>,
|
||||
@ -51,14 +57,14 @@ func GetVar(key interface{}) *gvar.Var {
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSet(key, value, duration)
|
||||
return defaultCache.GetOrSet(key, value, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFunc(key, f, duration)
|
||||
return defaultCache.GetOrSetFunc(key, f, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
|
||||
@ -67,47 +73,59 @@ func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration)
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
return cache.GetOrSetFuncLock(key, f, duration)
|
||||
return defaultCache.GetOrSetFuncLock(key, f, duration)
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func Contains(key interface{}) bool {
|
||||
return cache.Contains(key)
|
||||
return defaultCache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
return cache.Remove(keys...)
|
||||
return defaultCache.Remove(keys...)
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func Removes(keys []interface{}) {
|
||||
cache.Removes(keys)
|
||||
defaultCache.Removes(keys)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func Data() map[interface{}]interface{} {
|
||||
return cache.Data()
|
||||
return defaultCache.Data()
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func Keys() []interface{} {
|
||||
return cache.Keys()
|
||||
return defaultCache.Keys()
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func KeyStrings() []string {
|
||||
return cache.KeyStrings()
|
||||
return defaultCache.KeyStrings()
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func Values() []interface{} {
|
||||
return cache.Values()
|
||||
return defaultCache.Values()
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func Size() int {
|
||||
return cache.Size()
|
||||
return defaultCache.Size()
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func GetExpire(key interface{}) time.Duration {
|
||||
return defaultCache.GetExpire(key)
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
return defaultCache.UpdateExpire(key, duration)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ type memCache struct {
|
||||
// Internal cache item.
|
||||
type memCacheItem struct {
|
||||
v interface{} // Value.
|
||||
e int64 // Expire time in milliseconds.
|
||||
e int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// Internal event item.
|
||||
@ -97,7 +97,6 @@ func newMemCache(lruCap ...int) *memCache {
|
||||
}
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
@ -113,6 +112,52 @@ func (c *memCache) Set(key interface{}, value interface{}, duration time.Duratio
|
||||
})
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func (c *memCache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: value,
|
||||
e: item.e,
|
||||
}
|
||||
return item.v, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
newExpireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
v: item.v,
|
||||
e: newExpireTime,
|
||||
}
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: newExpireTime,
|
||||
})
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) GetExpire(key interface{}) time.Duration {
|
||||
c.dataMu.RLock()
|
||||
defer c.dataMu.RUnlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
|
||||
// cache, which is expired after <duration>.
|
||||
//
|
||||
@ -247,7 +292,11 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Du
|
||||
// It does nothing if function <f> returns nil.
|
||||
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, f(), duration)
|
||||
value := f()
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
|
||||
@ -15,15 +15,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
cache = gcache.New()
|
||||
cacheLru = gcache.New(10000)
|
||||
localCache = gcache.New()
|
||||
localCacheLru = gcache.New(10000)
|
||||
)
|
||||
|
||||
func Benchmark_CacheSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Set(i, i, 0)
|
||||
localCache.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -33,7 +33,7 @@ func Benchmark_CacheGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Get(i)
|
||||
localCache.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -43,7 +43,7 @@ func Benchmark_CacheRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cache.Remove(i)
|
||||
localCache.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -53,7 +53,7 @@ func Benchmark_CacheLruSet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Set(i, i, 0)
|
||||
localCacheLru.Set(i, i, 0)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -63,7 +63,7 @@ func Benchmark_CacheLruGet(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Get(i)
|
||||
localCacheLru.Get(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
@ -73,7 +73,7 @@ func Benchmark_CacheLruRemove(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
cacheLru.Remove(i)
|
||||
localCacheLru.Remove(i)
|
||||
i++
|
||||
}
|
||||
})
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
package gcache_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/guid"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +75,53 @@ func TestCache_Set_Expire(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Update_GetExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
expire1 := gcache.GetExpire(key)
|
||||
gcache.Update(key, 12)
|
||||
expire2 := gcache.GetExpire(key)
|
||||
t.Assert(gcache.GetVar(key), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
expire1 := cache.GetExpire(1)
|
||||
cache.Update(1, 12)
|
||||
expire2 := cache.GetExpire(1)
|
||||
t.Assert(cache.GetVar(1), 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_UpdateExpire(t *testing.T) {
|
||||
// gcache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
defer gcache.Remove(key)
|
||||
oldExpire := gcache.GetExpire(key)
|
||||
newExpire := 10 * time.Second
|
||||
gcache.UpdateExpire(key, newExpire)
|
||||
t.AssertNE(gcache.GetExpire(key), oldExpire)
|
||||
t.Assert(math.Ceil(gcache.GetExpire(key).Seconds()), 10)
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
oldExpire := cache.GetExpire(1)
|
||||
newExpire := 10 * time.Second
|
||||
cache.UpdateExpire(1, newExpire)
|
||||
t.AssertNE(cache.GetExpire(1), oldExpire)
|
||||
t.Assert(math.Ceil(cache.GetExpire(1).Seconds()), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Keys_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
@ -269,7 +318,7 @@ func TestCache_Basic(t *testing.T) {
|
||||
t.Assert(cache.Size(), 0)
|
||||
}
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Remove(g.Slice{1, 2, 3}...)
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
|
||||
@ -113,8 +113,10 @@ func (c *Config) filePath(file ...string) (path string) {
|
||||
// The parameter <path> can be absolute or relative path,
|
||||
// but absolute path is strongly recommended.
|
||||
func (c *Config) SetPath(path string) error {
|
||||
isDir := false
|
||||
realPath := ""
|
||||
var (
|
||||
isDir = false
|
||||
realPath = ""
|
||||
)
|
||||
if file := gres.Get(path); file != nil {
|
||||
realPath = path
|
||||
isDir = file.FileInfo().IsDir()
|
||||
|
||||
@ -9,11 +9,12 @@
|
||||
package gcfg_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gcfg"
|
||||
@ -475,3 +476,13 @@ func TestCfg_Config(t *testing.T) {
|
||||
t.Assert(gcfg.GetContent("name"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCfg_With_UTF8_BOM(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cfg := g.Cfg("test-cfg-with-utf8-bom")
|
||||
t.Assert(cfg.SetPath("testdata"), nil)
|
||||
cfg.SetFileName("cfg-with-utf8-bom.toml")
|
||||
t.Assert(cfg.GetInt("test.testInt"), 1)
|
||||
t.Assert(cfg.GetString("test.testStr"), "test")
|
||||
})
|
||||
}
|
||||
|
||||
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
4
os/gcfg/testdata/cfg-with-utf8-bom.toml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
[test]
|
||||
testInt=1
|
||||
testStr="test"
|
||||
@ -7,20 +7,30 @@
|
||||
|
||||
package gcmd
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Scan prints <info> to stdout, reads and returns user input, which stops by '\n'.
|
||||
func Scan(info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Print(info...)
|
||||
fmt.Scanln(&s)
|
||||
return s
|
||||
return readline()
|
||||
}
|
||||
|
||||
// Scanf prints <info> to stdout with <format>, reads and returns user input, which stops by '\n'.
|
||||
func Scanf(format string, info ...interface{}) string {
|
||||
var s string
|
||||
fmt.Printf(format, info...)
|
||||
fmt.Scanln(&s)
|
||||
return readline()
|
||||
}
|
||||
|
||||
func readline() string {
|
||||
var s string
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
s, _ = reader.ReadString('\n')
|
||||
s = gstr.Trim(s)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ const (
|
||||
STATUS_RUNNING = gtimer.STATUS_RUNNING
|
||||
STATUS_STOPPED = gtimer.STATUS_STOPPED
|
||||
STATUS_CLOSED = gtimer.STATUS_CLOSED
|
||||
|
||||
gDEFAULT_TIMES = math.MaxInt32
|
||||
)
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ package gcron
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -16,25 +17,26 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
// 运行时间管理对象
|
||||
// cronSchedule is the schedule for cron job.
|
||||
type cronSchedule struct {
|
||||
create int64 // 创建时间戳(秒)
|
||||
every int64 // 运行时间间隔(秒)
|
||||
pattern string // 原始注册字符串
|
||||
second map[int]struct{}
|
||||
minute map[int]struct{}
|
||||
hour map[int]struct{}
|
||||
day map[int]struct{}
|
||||
week map[int]struct{}
|
||||
month map[int]struct{}
|
||||
create int64 // Created timestamp.
|
||||
every int64 // Running interval in seconds.
|
||||
pattern string // The raw cron pattern string.
|
||||
second map[int]struct{} // Job can run in these second numbers.
|
||||
minute map[int]struct{} // Job can run in these minute numbers.
|
||||
hour map[int]struct{} // Job can run in these hour numbers.
|
||||
day map[int]struct{} // Job can run in these day numbers.
|
||||
week map[int]struct{} // Job can run in these week numbers.
|
||||
month map[int]struct{} // Job can run in these moth numbers.
|
||||
}
|
||||
|
||||
const (
|
||||
gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)$`
|
||||
// regular expression for cron pattern, which contains 6 parts of time units.
|
||||
gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$`
|
||||
)
|
||||
|
||||
var (
|
||||
// 预定义的定时格式
|
||||
// Predefined pattern map.
|
||||
predefinedPatternMap = map[string]string{
|
||||
"@yearly": "0 0 0 1 1 *",
|
||||
"@annually": "0 0 0 1 1 *",
|
||||
@ -44,7 +46,7 @@ var (
|
||||
"@midnight": "0 0 0 * * *",
|
||||
"@hourly": "0 0 * * * *",
|
||||
}
|
||||
// 月份与数字对应表
|
||||
// Short month name to its number.
|
||||
monthMap = map[string]int{
|
||||
"jan": 1,
|
||||
"feb": 2,
|
||||
@ -59,7 +61,7 @@ var (
|
||||
"nov": 11,
|
||||
"dec": 12,
|
||||
}
|
||||
// 星期与数字对应表
|
||||
// Short week name to its number.
|
||||
weekMap = map[string]int{
|
||||
"sun": 0,
|
||||
"mon": 1,
|
||||
@ -71,15 +73,15 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// 解析定时格式为cronSchedule对象
|
||||
// newSchedule creates and returns a schedule object for given cron pattern.
|
||||
func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
// 处理预定义的定时格式
|
||||
// Check if the predefined patterns.
|
||||
if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
|
||||
key := strings.ToLower(match[1])
|
||||
if v, ok := predefinedPatternMap[key]; ok {
|
||||
pattern = v
|
||||
} else if strings.Compare(key, "@every") == 0 {
|
||||
if d, err := time.ParseDuration(match[2]); err != nil {
|
||||
if d, err := gtime.ParseDuration(match[2]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &cronSchedule{
|
||||
@ -92,44 +94,45 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
|
||||
}
|
||||
}
|
||||
// 处理通用的定时格式定义
|
||||
// Handle the common cron pattern, like:
|
||||
// 0 0 0 1 1 2
|
||||
if match, _ := gregex.MatchString(gREGEX_FOR_CRON, pattern); len(match) == 7 {
|
||||
schedule := &cronSchedule{
|
||||
create: time.Now().Unix(),
|
||||
every: 0,
|
||||
pattern: pattern,
|
||||
}
|
||||
// 秒
|
||||
// Second.
|
||||
if m, err := parseItem(match[1], 0, 59, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.second = m
|
||||
}
|
||||
// 分
|
||||
// Minute.
|
||||
if m, err := parseItem(match[2], 0, 59, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.minute = m
|
||||
}
|
||||
// 时
|
||||
// Hour.
|
||||
if m, err := parseItem(match[3], 0, 23, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.hour = m
|
||||
}
|
||||
// 天
|
||||
// Day.
|
||||
if m, err := parseItem(match[4], 1, 31, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.day = m
|
||||
}
|
||||
// 月
|
||||
// Month.
|
||||
if m, err := parseItem(match[5], 1, 12, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.month = m
|
||||
}
|
||||
// 周
|
||||
// Week.
|
||||
if m, err := parseItem(match[6], 0, 6, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@ -141,7 +144,7 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 解析定时格式中的每一项定时配置
|
||||
// parseItem parses every item in the pattern and returns the result as map.
|
||||
func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
|
||||
m := make(map[int]struct{}, max-min+1)
|
||||
if item == "*" || (allowQuestionMark && item == "?") {
|
||||
@ -159,19 +162,23 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s
|
||||
interval = i
|
||||
}
|
||||
}
|
||||
rangeMin := min
|
||||
rangeMax := max
|
||||
rangeArray := strings.Split(intervalArray[0], "-")
|
||||
valueType := byte(0)
|
||||
var (
|
||||
rangeMin = min
|
||||
rangeMax = max
|
||||
fieldType = byte(0)
|
||||
rangeArray = strings.Split(intervalArray[0], "-") // Like: 1-30, JAN-DEC
|
||||
)
|
||||
switch max {
|
||||
case 6:
|
||||
valueType = 'w'
|
||||
case 11:
|
||||
valueType = 'm'
|
||||
// It's checking week field.
|
||||
fieldType = 'w'
|
||||
case 12:
|
||||
// It's checking month field.
|
||||
fieldType = 'm'
|
||||
}
|
||||
// 例如: */5
|
||||
// Eg: */5
|
||||
if rangeArray[0] != "*" {
|
||||
if i, err := parseItemValue(rangeArray[0], valueType); err != nil {
|
||||
if i, err := parseItemValue(rangeArray[0], fieldType); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
|
||||
} else {
|
||||
rangeMin = i
|
||||
@ -179,7 +186,7 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s
|
||||
}
|
||||
}
|
||||
if len(rangeArray) == 2 {
|
||||
if i, err := parseItemValue(rangeArray[1], valueType); err != nil {
|
||||
if i, err := parseItemValue(rangeArray[1], fieldType); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
|
||||
} else {
|
||||
rangeMax = i
|
||||
@ -193,38 +200,41 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// 将配置项值转换为数字
|
||||
func parseItemValue(value string, valueType byte) (int, error) {
|
||||
// parseItemValue parses the field value to a number according to its field type.
|
||||
func parseItemValue(value string, fieldType byte) (int, error) {
|
||||
if gregex.IsMatchString(`^\d+$`, value) {
|
||||
// 纯数字
|
||||
// Pure number.
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
return i, nil
|
||||
}
|
||||
} else {
|
||||
// 英文字母
|
||||
switch valueType {
|
||||
// Check if contains letter,
|
||||
// it converts the value to number according to predefined map.
|
||||
switch fieldType {
|
||||
case 'm':
|
||||
if i, ok := monthMap[strings.ToLower(value)]; ok {
|
||||
return int(i), nil
|
||||
return i, nil
|
||||
}
|
||||
case 'w':
|
||||
if i, ok := weekMap[strings.ToLower(value)]; ok {
|
||||
return int(i), nil
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value))
|
||||
}
|
||||
|
||||
// 判断给定的时间是否满足schedule
|
||||
// meet checks if the given time <t> meets the runnable point for the job.
|
||||
func (s *cronSchedule) meet(t time.Time) bool {
|
||||
if s.every != 0 {
|
||||
// It checks using interval.
|
||||
diff := t.Unix() - s.create
|
||||
if diff > 0 {
|
||||
return diff%s.every == 0
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
// It checks using normal cron pattern.
|
||||
if _, ok := s.second[t.Second()]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gfcache provides reading and caching for file contents.
|
||||
package gfcache
|
||||
package gfile
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/internal/cmdenv"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -23,27 +20,27 @@ const (
|
||||
|
||||
var (
|
||||
// Default expire time for file content caching.
|
||||
cacheExpire = cmdenv.Get("gf.gfcache.expire", gDEFAULT_CACHE_EXPIRE).Duration()
|
||||
cacheExpire = cmdenv.Get("gf.gfile.cache", gDEFAULT_CACHE_EXPIRE).Duration()
|
||||
)
|
||||
|
||||
// GetContents returns string content of given file by <path> from cache.
|
||||
// If there's no content in the cache, it will read it from disk file specified by <path>.
|
||||
// The parameter <expire> specifies the caching time for this file content in seconds.
|
||||
func GetContents(path string, duration ...time.Duration) string {
|
||||
return string(GetBinContents(path, duration...))
|
||||
func GetContentsWithCache(path string, duration ...time.Duration) string {
|
||||
return string(GetBytesWithCache(path, duration...))
|
||||
}
|
||||
|
||||
// GetBinContents returns []byte content of given file by <path> from cache.
|
||||
// If there's no content in the cache, it will read it from disk file specified by <path>.
|
||||
// The parameter <expire> specifies the caching time for this file content in seconds.
|
||||
func GetBinContents(path string, duration ...time.Duration) []byte {
|
||||
func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
key := cacheKey(path)
|
||||
expire := cacheExpire
|
||||
if len(duration) > 0 {
|
||||
expire = duration[0]
|
||||
}
|
||||
r := gcache.GetOrSetFuncLock(key, func() interface{} {
|
||||
b := gfile.GetBytes(path)
|
||||
b := GetBytes(path)
|
||||
if b != nil {
|
||||
// Adding this <path> to gfsnotify,
|
||||
// it will clear its cache if there's any changes of the file.
|
||||
@ -62,5 +59,5 @@ func GetBinContents(path string, duration ...time.Duration) []byte {
|
||||
|
||||
// cacheKey produces the cache key for gcache.
|
||||
func cacheKey(path string) string {
|
||||
return "gf.gfcache:" + path
|
||||
return "gf.gfile.cache:" + path
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user