Merge pull request #3 from gogf/master

update
This commit is contained in:
wenzi
2020-03-17 22:11:51 +08:00
committed by GitHub
119 changed files with 3698 additions and 2211 deletions

View File

@ -9,7 +9,7 @@ import (
func main() {
// 创建一个对象池过期时间为1000毫秒
p := gpool.New(1000, nil)
p := gpool.New(1000*time.Millisecond, nil)
// 从池中取一个对象返回nil及错误信息
fmt.Println(p.Get())

View File

@ -11,7 +11,7 @@ import (
func main() {
// 创建对象复用池对象过期时间为3000毫秒并给定创建及销毁方法
p := gpool.New(3000, func() (interface{}, error) {
p := gpool.New(3000*time.Millisecond, func() (interface{}, error) {
return gtcp.NewConn("www.baidu.com:80")
}, func(i interface{}) {
glog.Println("expired")

View File

@ -0,0 +1,17 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.WriteTplContent(`${.name}`, g.Map{
"name": "john",
})
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,2 @@
[viewer]
delimiters = ["${", "}"]

View File

@ -11,7 +11,7 @@ import (
func main() {
for {
time.Sleep(time.Second)
if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, 60000000*1000); err == nil {
if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, time.Hour); err == nil {
fmt.Println(f.Name())
f.Close()
} else {

View File

@ -1,16 +1,35 @@
package main
import (
"fmt"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/os/gtimer"
"time"
)
func main() {
data := "@var(.prefix)您收到的验证码为:@var(.code),请在@var(.expire)内完成验证"
result, err := gregex.ReplaceStringFuncMatch(`(@var\(\.\w+\))`, data, func(match []string) string {
fmt.Println(match)
return "#"
})
fmt.Println(err)
fmt.Println(result)
func GetList() {
START:
for {
res, err := g.Redis().DoVar("RPOP", "mill")
if err != nil {
glog.Debug("Rpop:", err)
break
}
glog.Debug(res)
if res.IsEmpty() {
glog.Debug("nil")
continue START
}
interval := 50 * time.Second
gtimer.AddOnce(interval, func() {
glog.Debug("end------:", res, gtime.Now().Format("Y-m-d H:i:s"))
})
}
}
func main() {
g.Redis().SetMaxActive(2)
//g.Redis().SetMaxIdle(100)
GetList()
}

View File

@ -40,7 +40,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|R*s|wechat|¥18.88| 谢谢GF辛苦了
|粟*e|wechat|¥50.00|
|[李超](https://github.com/effortlee)|wechat|¥124.00|
|张炳贤|wechat+qq|¥600.00|
|soidea666|wechat+qq|¥800.00|
|[王哈哈](https://gitee.com/develop1024)|wechat|¥6.66| 希望gf越来越好
|夕景|alipay+qq|¥9.96+3.57|
|struggler|alipay|¥18.80|
@ -51,6 +51,21 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|[Zeroing-ZY](https://gitee.com/yunjieg)|gitee|¥20.00| 感谢您的开源项目!
|[katydid酱](https://gitee.com/katydid2005)|gitee|¥50.00| 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬!
|[李海峰](https://gitee.com/dlhf)|gitee|¥10.00| 希望GF越来越好框架很牛逼
|陆昱天|alipay|¥100.00|
|[Dockercore](https://github.com/dockercore)|wechat|¥200.00| 非常喜欢!简洁好用!文档超级全!
|🚶|wechat|¥6.88| 喝杯冰阔落
|a*l|wechat|¥10.00| gf
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00|
|*包|wechat|¥9.99|
|重庆宝尔威科技|wechat|¥6.66|
|琦玉-QPT|wechat|¥6.66|
|sailsea|wechat|¥11.00|
|[seny0929](https://gitee.com/seny0929)|wechat|¥99.90|
|*华|wechat|¥6.66| 感谢郭强的热心
|[Playhi](https://github.com/Playhi)|alipay|¥10.00|
|北京京纬互动科技有限公司|alipay|¥200.00|
|米司特包|wechat|¥99.99|
|金毛|alipay|¥100.00|
<img src="https://goframe.org/images/donate.png"/>

View File

@ -8,11 +8,12 @@
[English](README.MD) | 简体中文
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件,
如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、
定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等
支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。
实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,
如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、
配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
# 特点

View File

@ -33,9 +33,9 @@ type SortedArray struct {
// NewSortedArray creates and returns an empty sorted array.
// The parameter <safe> is used to specify whether using array in concurrent-safety, which is false in default.
// The parameter <comparator> used to compare values to sort in array,
// if it returns value < 0, means v1 < v2;
// if it returns value = 0, means v1 = v2;
// if it returns value > 0, means v1 > v2;
// if it returns value < 0, means v1 < v2; the v1 will be inserted before v2;
// if it returns value = 0, means v1 = v2; the v1 will be replaced by v2;
// if it returns value > 0, means v1 > v2; the v1 will be inserted after v2;
func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
return NewSortedArraySize(0, comparator, safe...)
}

View File

@ -17,22 +17,31 @@ import (
"github.com/gogf/gf/os/gtimer"
)
// Object-Reusable Pool.
// Pool is an Object-Reusable Pool.
type Pool struct {
list *glist.List // Available/idle list.
closed *gtype.Bool // Whether the pool is closed.
Expire int64 // Max idle time(ms), after which it is recycled.
NewFunc func() (interface{}, error) // Callback function to create item.
ExpireFunc func(interface{}) // Expired destruction function for objects.
// This function needs to be defined when the pool object
// needs to perform additional destruction operations.
// Available/idle items list.
list *glist.List
// Whether the pool is closed.
closed *gtype.Bool
// Time To Live for pool items.
TTL time.Duration
// Callback function to create pool item.
NewFunc func() (interface{}, error)
// ExpireFunc is the for expired items destruction.
// This function needs to be defined when the pool items
// need to perform additional destruction operations.
// Eg: net.Conn, os.File, etc.
ExpireFunc func(interface{})
}
// Pool item.
type poolItem struct {
expire int64 // Expire time(millisecond).
value interface{} // Value.
expire int64 // Expire timestamp in milliseconds.
value interface{} // Item value.
}
// Creation function for object.
@ -41,47 +50,64 @@ type NewFunc func() (interface{}, error)
// Destruction function for object.
type ExpireFunc func(interface{})
// New returns a new object pool.
// New creates and returns a new object pool.
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
//
// Expiration logic:
// expire = 0 : not expired;
// expire < 0 : immediate expired after use;
// expire > 0 : timeout expired;
// Note that the expiration time unit is ** milliseconds **.
func New(expire int, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
// Note the expiration logic:
// ttl = 0 : not expired;
// ttl < 0 : immediate expired after use;
// ttl > 0 : timeout expired;
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
r := &Pool{
list: glist.New(true),
closed: gtype.NewBool(),
Expire: int64(expire),
TTL: ttl,
NewFunc: newFunc,
}
if len(expireFunc) > 0 {
r.ExpireFunc = expireFunc[0]
}
gtimer.AddSingleton(time.Second, r.checkExpire)
gtimer.AddSingleton(time.Second, r.checkExpireItems)
return r
}
// Put puts an item to pool.
func (p *Pool) Put(value interface{}) {
func (p *Pool) Put(value interface{}) error {
if p.closed.Val() {
return errors.New("pool is closed")
}
item := &poolItem{
value: value,
}
if p.Expire == 0 {
if p.TTL == 0 {
item.expire = 0
} else {
item.expire = gtime.TimestampMilli() + p.Expire
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
// So we need calculate the milliseconds using its nanoseconds value.
item.expire = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
}
p.list.PushBack(item)
return nil
}
// Clear clears pool, which means it will remove all items from pool.
func (p *Pool) Clear() {
p.list.RemoveAll()
if p.ExpireFunc != nil {
for {
if r := p.list.PopFront(); r != nil {
p.ExpireFunc(r.(*poolItem).value)
} else {
break
}
}
} else {
p.list.RemoveAll()
}
}
// Get picks an item from pool.
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
// it creates and returns one from NewFunc.
func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
@ -106,12 +132,13 @@ func (p *Pool) Size() int {
// Close closes the pool. If <p> has ExpireFunc,
// then it automatically closes all items using this function before it's closed.
// Commonly you do not need call this function manually.
func (p *Pool) Close() {
p.closed.Set(true)
}
// checkExpire removes expired items from pool every second.
func (p *Pool) checkExpire() {
// checkExpire removes expired items from pool in every second.
func (p *Pool) checkExpireItems() {
if p.closed.Val() {
// If p has ExpireFunc,
// then it must close all items using this function.
@ -126,11 +153,24 @@ func (p *Pool) checkExpire() {
}
gtimer.Exit()
}
// All items do not expire.
if p.TTL == 0 {
return
}
// The latest item expire timestamp in milliseconds.
var latestExpire int64 = -1
// Retrieve the current timestamp in milliseconds, it expires the items
// by comparing with this timestamp. It is not accurate comparison for
// every items expired, but high performance.
var timestampMilli = gtime.TimestampMilli()
for {
// TODO Do not use Pop and Push mechanism, which is not graceful.
if latestExpire > timestampMilli {
break
}
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.TimestampMilli() {
latestExpire = item.expire
if item.expire > timestampMilli {
p.list.PushFront(item)
break
}

View File

@ -11,11 +11,12 @@ package gpool_test
import (
"sync"
"testing"
"time"
"github.com/gogf/gf/container/gpool"
)
var pool = gpool.New(99999999, nil)
var pool = gpool.New(time.Hour, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {

View File

@ -62,7 +62,7 @@ func Test_Gpool(t *testing.T) {
gtest.Case(t, func() {
//
//expire > 0
p2 := gpool.New(2000, nil, ef)
p2 := gpool.New(2*time.Second, nil, ef)
for index := 0; index < 10; index++ {
p2.Put(index)
}

View File

@ -157,6 +157,12 @@ func BenchmarkBool_Val(b *testing.B) {
}
}
func BenchmarkBool_Cas(b *testing.B) {
for i := 0; i < b.N; i++ {
bl.Cas(false, true)
}
}
func BenchmarkString_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
str.Set(strconv.Itoa(i))

View File

@ -334,6 +334,15 @@ func Test_Struct(t *testing.T) {
gtest.Assert(testObj.Test, Kv["Test"])
})
gtest.Case(t, func() {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
gtest.Assert(o.Test, -25)
})
}
func Test_Json(t *testing.T) {

View File

@ -47,6 +47,7 @@ type DB interface {
GetAll(query string, args ...interface{}) (Result, error)
GetOne(query string, args ...interface{}) (Record, error)
GetValue(query string, args ...interface{}) (Value, error)
GetArray(query string, args ...interface{}) ([]Value, error)
GetCount(query string, args ...interface{}) (int, error)
GetStruct(objPointer interface{}, query string, args ...interface{}) error
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
@ -181,12 +182,13 @@ type Map = map[string]interface{}
type List = []Map
const (
gINSERT_OPTION_DEFAULT = 0
gINSERT_OPTION_REPLACE = 1
gINSERT_OPTION_SAVE = 2
gINSERT_OPTION_IGNORE = 3
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
gINSERT_OPTION_DEFAULT = 0
gINSERT_OPTION_REPLACE = 1
gINSERT_OPTION_SAVE = 2
gINSERT_OPTION_IGNORE = 3
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool.
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
)
var (
@ -225,13 +227,14 @@ func New(name ...string) (db DB, err error) {
if _, ok := configs.config[group]; ok {
if node, err := getConfigNodeByGroup(group, true); err == nil {
c := &Core{
group: group,
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
logger: glog.New(),
prefix: node.Prefix,
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
group: group,
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
logger: glog.New(),
prefix: node.Prefix,
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
}
if v, ok := driverMap[node.Type]; ok {
c.DB, err = v.New(c, node)
@ -363,7 +366,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
n.Name = nodeSchema
node = &n
}
// Cache the underlying connection object by node.
// Cache the underlying connection pool object by node.
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
sqlDb, err = c.DB.Open(node)
if err != nil {

View File

@ -1,25 +0,0 @@
// 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 "database/sql"
// batchSqlResult is execution result for batch operations.
type batchSqlResult struct {
rowsAffected int64
lastResult sql.Result
}
// see sql.Result.RowsAffected
func (r *batchSqlResult) RowsAffected() (int64, error) {
return r.rowsAffected, nil
}
// see sql.Result.LastInsertId
func (r *batchSqlResult) LastInsertId() (int64, error) {
return r.lastResult.LastInsertId()
}

View File

@ -177,6 +177,16 @@ func (c *Core) GetOne(query string, args ...interface{}) (Record, error) {
return nil, nil
}
// GetArray queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
func (c *Core) GetArray(query string, args ...interface{}) ([]Value, error) {
all, err := c.DB.DoGetAll(nil, query, args...)
if err != nil {
return nil, err
}
return all.Array(), nil
}
// GetStruct queries one record from database and converts it to given struct.
// The parameter <pointer> should be a pointer to struct.
func (c *Core) GetStruct(pointer interface{}, query string, args ...interface{}) error {
@ -494,7 +504,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
holders = append(holders, "?")
}
// Prepare the batch result pointer.
batchResult := new(batchSqlResult)
batchResult := new(SqlResult)
charL, charR := c.DB.GetChars()
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
@ -545,8 +555,8 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
batchResult.result = r
batchResult.affected += n
}
params = params[:0]
values = values[:0]
@ -711,7 +721,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("[%d ms] %s", v.End-v.Start, v.Format)
s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
c.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)

View File

@ -377,7 +377,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
keys = append(keys, k)
holders = append(holders, "?")
}
batchResult := new(batchSqlResult)
batchResult := new(SqlResult)
charL, charR := d.DB.GetChars()
keyStr := charL + strings.Join(keys, charL+","+charR) + charR
valueHolderStr := strings.Join(holders, ",")
@ -393,8 +393,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
batchResult.result = r
batchResult.affected += n
}
}
return batchResult, nil
@ -421,8 +421,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
batchResult.result = r
batchResult.affected += n
}
params = params[:0]
intoStr = intoStr[:0]
@ -437,8 +437,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
batchResult.result = r
batchResult.affected += n
}
}
return batchResult, nil

View File

@ -66,7 +66,7 @@ func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interf
index++
return fmt.Sprintf("$%d", index)
})
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $1 OFFSET $2`, sql)
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
return sql, args
}

View File

@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/os/gtime"
"reflect"
"regexp"
@ -282,12 +283,25 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
newArgs = append(newArgs, args...)
newWhere = buffer.String()
if len(newArgs) > 0 {
// It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1)
if gstr.Pos(newWhere, "?") == -1 {
if lastOperatorReg.MatchString(newWhere) {
// Eg: Where/And/Or("uid>=", 1)
newWhere += "?"
} else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) {
newWhere += "=?"
newWhere = db.QuoteString(newWhere)
if len(newArgs) > 0 {
if utils.IsArray(newArgs[0]) {
// Eg: Where("id", []int{1,2,3})
newWhere += " IN (?)"
} else if empty.IsNil(newArgs[0]) {
// Eg: Where("id", nil)
newWhere += " IS NULL"
newArgs = nil
} else {
// Eg: Where/And/Or("uid", 1)
newWhere += "=?"
}
}
}
}
}
@ -316,7 +330,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new
// formatWhereKeyValue handles each key-value pair of the parameter map.
func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} {
key = db.QuoteWord(key)
quotedKey := db.QuoteWord(key)
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
@ -324,36 +338,43 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
// the key string, it automatically adds '?' holder chars according to its arguments count
// and converts it to "IN" statement.
rv := reflect.ValueOf(value)
switch rv.Kind() {
kind := rv.Kind()
switch kind {
case reflect.Slice, reflect.Array:
count := gstr.Count(key, "?")
count := gstr.Count(quotedKey, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
buffer.WriteString(quotedKey + " IN(?)")
newArgs = append(newArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
buffer.WriteString(quotedKey)
newArgs = append(newArgs, value)
} else {
buffer.WriteString(key)
buffer.WriteString(quotedKey)
newArgs = append(newArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
if value == nil || empty.IsNil(rv) {
if gregex.IsMatchString(`^[\w\.\-]+$`, key) {
// The key is a single field name.
buffer.WriteString(quotedKey + " IS NULL")
} else {
// The key may have operation chars.
buffer.WriteString(quotedKey)
}
} else {
// It also supports "LIKE" statement, which we considers it an operator.
key = gstr.Trim(key)
if gstr.Pos(key, "?") == -1 {
quotedKey = gstr.Trim(quotedKey)
if gstr.Pos(quotedKey, "?") == -1 {
like := " like"
if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) {
buffer.WriteString(key + " ?")
} else if lastOperatorReg.MatchString(key) {
buffer.WriteString(key + " ?")
if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) {
buffer.WriteString(quotedKey + " ?")
} else if lastOperatorReg.MatchString(quotedKey) {
buffer.WriteString(quotedKey + " ?")
} else {
buffer.WriteString(key + "=?")
buffer.WriteString(quotedKey + "=?")
}
} else {
buffer.WriteString(key)
buffer.WriteString(quotedKey)
}
newArgs = append(newArgs, value)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
// 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 gdb
import (
"time"
)
// Cache sets the cache feature for the model. It caches the result of the sql, which means
// if there's another same sql request, it just reads and returns the result from cache, it
// but not committed and executed into the database.
//
// If the parameter <duration> < 0, which means it clear the cache with given <name>.
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
// It does not support cache on transaction.
if model.tx == nil {
model.cacheEnabled = true
}
return model
}

View File

@ -0,0 +1,170 @@
// 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 gdb
import "github.com/gogf/gf/util/gconv"
// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
// multiple conditions will be joined into where statement using "AND".
// Eg:
// Where("uid=10000")
// Where("uid", 10000)
// Where("money>? AND name like ?", 99999, "vip_%")
// Where("uid", 1).Where("name", "john")
// Where("status IN (?)", g.Slice{1,2,3})
// Where("age IN(?,?)", 18, 50)
// Where(User{ Id : 1, UserName : "john"})
func (m *Model) Where(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_WHERE,
where: where,
args: args,
})
return model
}
// WherePri does the same logic as Model.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
if len(args) > 0 {
return m.Where(where, args...)
}
newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where)
return m.Where(newWhere[0], newWhere[1:]...)
}
// And adds "AND" condition to the where statement.
func (m *Model) And(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_AND,
where: where,
args: args,
})
return model
}
// Or adds "OR" condition to the where statement.
func (m *Model) Or(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_OR,
where: where,
args: args,
})
return model
}
// Group sets the "GROUP BY" statement for the model.
func (m *Model) Group(groupBy string) *Model {
model := m.getModel()
model.groupBy = m.db.QuoteString(groupBy)
return model
}
// GroupBy is alias of Model.Group.
// See Model.Group.
// Deprecated.
func (m *Model) GroupBy(groupBy string) *Model {
return m.Group(groupBy)
}
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy string) *Model {
model := m.getModel()
model.orderBy = m.db.QuoteString(orderBy)
return model
}
// OrderBy is alias of Model.Order.
// See Model.Order.
// Deprecated.
func (m *Model) OrderBy(orderBy string) *Model {
return m.Order(orderBy)
}
// Limit sets the "LIMIT" statement for the model.
// The parameter <limit> can be either one or two number, if passed two number is passed,
// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
// statement.
func (m *Model) Limit(limit ...int) *Model {
model := m.getModel()
switch len(limit) {
case 1:
model.limit = limit[0]
case 2:
model.start = limit[0]
model.limit = limit[1]
}
return model
}
// Offset sets the "OFFSET" statement for the model.
// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
func (m *Model) Offset(offset int) *Model {
model := m.getModel()
model.offset = offset
return 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.
func (m *Model) Page(page, limit int) *Model {
model := m.getModel()
if page <= 0 {
page = 1
}
model.start = (page - 1) * limit
model.limit = limit
return model
}
// ForPage is alias of Model.Page.
// See Model.Page.
// Deprecated.
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}
// getAll does the query from database.
func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = query + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), query, m.mergeArguments(args)...)
// Cache the result.
if len(cacheKey) > 0 && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -0,0 +1,27 @@
// 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 gdb
import (
"database/sql"
)
// Delete does "DELETE FROM ... " statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).Delete()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
condition, conditionArgs := m.formatCondition(false)
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
}

View File

@ -0,0 +1,94 @@
// 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 gdb
import (
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
}
model := m.getModel()
model.filter = true
return model
}
// Fields sets the operation fields of the model, multiple fields joined using char ','.
func (m *Model) Fields(fields string) *Model {
model := m.getModel()
model.fields = fields
return model
}
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
model := m.getModel()
model.fieldsEx = fields
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
if m, err := m.db.TableFields(m.tables); err == nil {
model.fields = ""
for k, _ := range m {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
}
return model
}
// 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 {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
for _, field := range m {
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
return fieldsArray.Join(",")
}
return ""
}
// 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 {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
for _, field := range m {
if fieldsExSet.Contains(field.Name) {
continue
}
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
fieldsArray.FilterEmpty()
return fieldsArray.Join(",")
}
return ""
}

View File

@ -0,0 +1,213 @@
// 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 gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"reflect"
)
// Batch sets the batch operation number for the model.
func (m *Model) Batch(batch int) *Model {
model := m.getModel()
model.batch = batch
return model
}
// Data sets the operation data for the model.
// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
// Eg:
// Data("uid=10000")
// Data("uid", 10000)
// Data("uid=? AND name=?", 10000, "john")
// Data(g.Map{"uid": 10000, "name":"john"})
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
func (m *Model) Data(data ...interface{}) *Model {
model := m.getModel()
if len(data) > 1 {
s := gconv.String(data[0])
if gstr.Contains(s, "?") {
model.data = s
model.extraArgs = data[1:]
} else {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
m[gconv.String(data[i])] = data[i+1]
}
model.data = m
}
} else {
switch params := data[0].(type) {
case Result:
model.data = params.List()
case Record:
model.data = params.Map()
case List:
model.data = params
case Map:
model.data = params
default:
rv := reflect.ValueOf(params)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice, reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = DataToMapDeep(rv.Index(i).Interface())
}
model.data = list
case reflect.Map, reflect.Struct:
model.data = DataToMapDeep(data[0])
default:
model.data = data[0]
}
}
}
return model
}
// Insert does "INSERT INTO ..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("inserting into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch insert.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
option,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
option,
)
}
return nil, errors.New("inserting into table with invalid data type")
}
// Replace does "REPLACE INTO ..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Replace()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("replacing into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch replace.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_REPLACE,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_REPLACE,
)
}
return nil, errors.New("replacing into table with invalid data type")
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
//
// It updates the record if there's primary or unique index in the saving data,
// or else it inserts a new record into the table.
func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Save()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("saving into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch save.
batch := gDEFAULT_BATCH_NUM
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_SAVE,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single save.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_SAVE,
)
}
return nil, errors.New("saving into table with invalid data type")
}

View File

@ -0,0 +1,30 @@
// 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 gdb
import "fmt"
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
func (m *Model) LeftJoin(table string, on string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
return model
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
func (m *Model) RightJoin(table string, on string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
return model
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
func (m *Model) InnerJoin(table string, on string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
return model
}

View File

@ -0,0 +1,21 @@
// 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
// LockUpdate sets the lock for update for current operation.
func (m *Model) LockUpdate() *Model {
model := m.getModel()
model.lockInfo = "FOR UPDATE"
return model
}
// LockShared sets the lock in share mode for current operation.
func (m *Model) LockShared() *Model {
model := m.getModel()
model.lockInfo = "LOCK IN SHARE MODE"
return model
}

View File

@ -0,0 +1,27 @@
// 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 gdb
// Option adds extra operation option for the model.
func (m *Model) Option(option int) *Model {
model := m.getModel()
model.option = model.option | option
return model
}
// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// the data and where attributes for empty values.
// Deprecated, use OmitEmpty instead.
func (m *Model) OptionOmitEmpty() *Model {
return m.Option(OPTION_OMITEMPTY)
}
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// the data and where attributes for empty values.
func (m *Model) OmitEmpty() *Model {
return m.Option(OPTION_OMITEMPTY)
}

View File

@ -0,0 +1,315 @@
// 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 gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/util/gconv"
"reflect"
)
// Select is alias of Model.All.
// See Model.All.
// Deprecated.
func (m *Model) Select(where ...interface{}) (Result, error) {
return m.All(where...)
}
// All does "SELECT FROM ..." statement for the model.
// It retrieves the records from table and returns the result as slice type.
// It returns nil if there's no record retrieved with the given conditions from table.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) All(where ...interface{}) (Result, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).All()
}
condition, conditionArgs := m.formatCondition(false)
return m.getAll(
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition),
conditionArgs...,
)
}
// 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
if page == 0 {
page = 1
}
model := m
for {
model = model.Page(page, limit)
data, err := model.All()
if err != nil {
callback(nil, err)
break
}
if len(data) == 0 {
break
}
if callback(data, err) == false {
break
}
if len(data) < limit {
break
}
page++
}
}
// One retrieves one record from table and returns the result as map type.
// It returns nil if there's no record retrieved with the given conditions from table.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) One(where ...interface{}) (Record, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).One()
}
condition, conditionArgs := m.formatCondition(true)
all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...)
if err != nil {
return nil, err
}
if len(all) > 0 {
return all[0], nil
}
return nil, nil
}
// Value retrieves a specified record value from table and returns the result as interface type.
// It returns nil if there's no record found with the given conditions from table.
//
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
// Also see Model.Fields and Model.Where functions.
func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
if len(fieldsAndWhere) > 0 {
if len(fieldsAndWhere) > 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value()
} else if len(fieldsAndWhere) == 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Value()
} else {
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
}
}
one, err := m.One()
if err != nil {
return nil, err
}
for _, v := range one {
return v, nil
}
return nil, nil
}
// Array queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
//
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
// Also see Model.Fields and Model.Where functions.
func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
if len(fieldsAndWhere) > 0 {
if len(fieldsAndWhere) > 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array()
} else if len(fieldsAndWhere) == 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Array()
} else {
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
}
}
all, err := m.All()
if err != nil {
return nil, err
}
return all.Array(), nil
}
// Struct retrieves one record from table and converts it into given struct.
// The parameter <pointer> should be type of *struct/**struct. If type **struct is given,
// it can create the struct internally during converting.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table.
//
// Eg:
// user := new(User)
// err := db.Table("user").Where("id", 1).Struct(user)
//
// user := (*User)(nil)
// err := db.Table("user").Where("id", 1).Struct(&user)
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
one, err := m.One(where...)
if err != nil {
return err
}
if len(one) == 0 {
return sql.ErrNoRows
}
return one.Struct(pointer)
}
// Structs retrieves records from table and converts them into given struct slice.
// The parameter <pointer> should be type of *[]struct/*[]*struct. It can create and fill the struct
// slice internally during converting.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table.
//
// Eg:
// users := ([]User)(nil)
// err := db.Table("user").Structs(&users)
//
// users := ([]*User)(nil)
// err := db.Table("user").Structs(&users)
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
all, err := m.All(where...)
if err != nil {
return err
}
if len(all) == 0 {
return sql.ErrNoRows
}
return all.Structs(pointer)
}
// Scan automatically calls Struct or Structs function according to the type of parameter <pointer>.
// It calls function Struct if <pointer> is type of *struct/**struct.
// It calls function Structs if <pointer> is type of *[]struct/*[]*struct.
//
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
//
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
// from table.
//
// Eg:
// user := new(User)
// err := db.Table("user").Where("id", 1).Struct(user)
//
// user := (*User)(nil)
// err := db.Table("user").Where("id", 1).Struct(&user)
//
// users := ([]User)(nil)
// err := db.Table("user").Structs(&users)
//
// users := ([]*User)(nil)
// err := db.Table("user").Structs(&users)
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
t := reflect.TypeOf(pointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
switch t.Elem().Kind() {
case reflect.Array:
case reflect.Slice:
return m.Structs(pointer, where...)
default:
return m.Struct(pointer, where...)
}
return nil
}
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
func (m *Model) Count(where ...interface{}) (int, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).Count()
}
countFields := "COUNT(1)"
if m.fields != "" && m.fields != "*" {
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
}
condition, conditionArgs := m.formatCondition(false)
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition)
if len(m.groupBy) > 0 {
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
}
list, err := m.getAll(s, conditionArgs...)
if err != nil {
return 0, err
}
if len(list) > 0 {
for _, v := range list[0] {
return v.Int(), nil
}
}
return 0, nil
}
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
// Also see Model.WherePri and Model.One.
func (m *Model) FindOne(where ...interface{}) (Record, error) {
if len(where) > 0 {
return m.WherePri(where[0], where[1:]...).One()
}
return m.One()
}
// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
// Also see Model.WherePri and Model.All.
func (m *Model) FindAll(where ...interface{}) (Result, error) {
if len(where) > 0 {
return m.WherePri(where[0], where[1:]...).All()
}
return m.All()
}
// FindValue retrieves and returns single field value by Model.WherePri and Model.Value.
// Also see Model.WherePri and Model.Value.
func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) {
if len(fieldsAndWhere) >= 2 {
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Value()
}
if len(fieldsAndWhere) == 1 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
}
return m.Value()
}
// FindArray queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
// Also see Model.WherePri and Model.Value.
func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) {
if len(fieldsAndWhere) >= 2 {
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Array()
}
if len(fieldsAndWhere) == 1 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
}
return m.Array()
}
// FindCount retrieves and returns the record number by Model.WherePri and Model.Count.
// Also see Model.WherePri and Model.Count.
func (m *Model) FindCount(where ...interface{}) (int, error) {
if len(where) > 0 {
return m.WherePri(where[0], where[1:]...).Count()
}
return m.Count()
}
// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan.
// Also see Model.WherePri and Model.Scan.
func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
if len(where) > 0 {
return m.WherePri(where[0], where[1:]...).Scan(pointer)
}
return m.Scan(pointer)
}

View File

@ -0,0 +1,45 @@
// 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 gdb
import (
"database/sql"
"errors"
)
// Update does "UPDATE ... " statement for the model.
//
// If the optional parameter <dataAndWhere> is given, the dataAndWhere[0] is the updated data field,
// and dataAndWhere[1:] is treated as where condition fields.
// Also see Model.Data and Model.Where functions.
func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
if len(dataAndWhere) > 0 {
if len(dataAndWhere) > 2 {
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update()
} else if len(dataAndWhere) == 2 {
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1]).Update()
} else {
return m.Data(dataAndWhere[0]).Update()
}
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("updating table with empty data")
}
condition, conditionArgs := m.formatCondition(false)
return m.db.DoUpdate(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(m.data),
condition,
m.mergeArguments(conditionArgs)...,
)
}

View File

@ -0,0 +1,205 @@
// 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 gdb
import (
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
)
// getModel creates and returns a cloned model of current model if <safe> is true, or else it returns
// the current model.
func (m *Model) getModel() *Model {
if !m.safe {
return m
} else {
return m.Clone()
}
}
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
if list, ok := m.data.(List); ok {
for k, item := range list {
list[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
}
return list
} else if item, ok := m.data.(Map); ok {
return m.doFilterDataMapForInsertOrUpdate(item, true)
}
return data
}
// doFilterDataMapForInsertOrUpdate does the filter features for map.
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
if m.filter {
data = m.db.filterFields(m.schema, m.tables, data)
}
// Remove key-value pairs of which the value is empty.
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
m := gmap.NewStrAnyMapFrom(data)
m.FilterEmpty()
data = m.Map()
}
if len(m.fields) > 0 && m.fields != "*" {
// Keep specified fields.
set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
for k := range data {
if !set.Contains(k) {
delete(data, k)
}
}
} else if len(m.fieldsEx) > 0 {
// Filter specified fields.
for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") {
delete(data, v)
}
}
return data
}
// getLink returns the underlying database link object with configured <linkType> attribute.
// The parameter <master> specifies whether using the master node if master-slave configured.
func (m *Model) getLink(master bool) Link {
if m.tx != nil {
return m.tx.tx
}
linkType := m.linkType
if linkType == 0 {
if master {
linkType = gLINK_TYPE_MASTER
} else {
linkType = gLINK_TYPE_SLAVE
}
}
switch linkType {
case gLINK_TYPE_MASTER:
link, err := m.db.GetMaster(m.schema)
if err != nil {
panic(err)
}
return link
case gLINK_TYPE_SLAVE:
link, err := m.db.GetSlave(m.schema)
if err != nil {
panic(err)
}
return link
}
return nil
}
// getPrimaryKey retrieves and returns the primary key name of the model table.
// It parses m.tables to retrieve the primary table name, supporting m.tables like:
// "user", "user u", "user as u, user_detail as ud".
func (m *Model) getPrimaryKey() string {
table := gstr.SplitAndTrim(m.tables, " ")[0]
tableFields, err := m.db.TableFields(table)
if err != nil {
return ""
}
for name, field := range tableFields {
if gstr.ContainsI(field.Key, "pri") {
return name
}
}
return ""
}
// checkAndRemoveCache checks and remove the cache if necessary.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
// Note that this function does not change any attribute value of the <m>.
//
// The parameter <limit> specifies whether limits querying only one record if m.limit is not set.
func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) {
var where string
if len(m.whereHolder) > 0 {
for _, v := range m.whereHolder {
switch v.operator {
case gWHERE_HOLDER_WHERE:
if where == "" {
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
where = newWhere
conditionArgs = newArgs
}
continue
}
fallthrough
case gWHERE_HOLDER_AND:
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if where[0] == '(' {
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
} else {
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
}
conditionArgs = append(conditionArgs, newArgs...)
}
case gWHERE_HOLDER_OR:
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
if len(newWhere) > 0 {
if where[0] == '(' {
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
} else {
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
}
conditionArgs = append(conditionArgs, newArgs...)
}
}
}
}
if where != "" {
condition += " WHERE " + where
}
if m.groupBy != "" {
condition += " GROUP BY " + m.groupBy
}
if m.orderBy != "" {
condition += " ORDER BY " + m.orderBy
}
if m.limit != 0 {
if m.start >= 0 {
condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
} else {
condition += fmt.Sprintf(" LIMIT %d", m.limit)
}
} else if limit {
condition += " LIMIT 1"
}
if m.offset >= 0 {
condition += fmt.Sprintf(" OFFSET %d", m.offset)
}
if m.lockInfo != "" {
condition += " " + m.lockInfo
}
return
}
// mergeArguments creates and returns new arguments by merging <m.extraArgs> and given <args>.
func (m *Model) mergeArguments(args []interface{}) []interface{} {
if len(m.extraArgs) > 0 {
newArgs := make([]interface{}, len(m.extraArgs)+len(args))
copy(newArgs, m.extraArgs)
copy(newArgs[len(m.extraArgs):], args)
return newArgs
}
return args
}

View File

@ -0,0 +1,47 @@
// 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 "database/sql"
// SqlResult is execution result for sql operations.
// It also supports batch operation result for rowsAffected.
type SqlResult struct {
result sql.Result
affected int64
}
// MustGetAffected returns the affected rows count, if any error occurs, it panics.
func (r *SqlResult) MustGetAffected() int64 {
rows, err := r.RowsAffected()
if err != nil {
panic(err)
}
return rows
}
// MustGetInsertId returns the last insert id, if any error occurs, it panics.
func (r *SqlResult) MustGetInsertId() int64 {
id, err := r.LastInsertId()
if err != nil {
panic(err)
}
return id
}
// see sql.Result.RowsAffected
func (r *SqlResult) RowsAffected() (int64, error) {
if r.affected > 0 {
return r.affected, nil
}
return r.result.RowsAffected()
}
// see sql.Result.LastInsertId
func (r *SqlResult) LastInsertId() (int64, error) {
return r.result.LastInsertId()
}

View File

@ -28,11 +28,33 @@ func (r Result) Xml(rootTag ...string) string {
// List converts <r> to a List.
func (r Result) List() List {
l := make(List, len(r))
list := make(List, len(r))
for k, v := range r {
l[k] = v.Map()
list[k] = v.Map()
}
return l
return list
}
// Array retrieves and returns specified column values as slice.
// The parameter <field> is optional is the column field is only one.
func (r Result) Array(field ...string) []Value {
array := make([]Value, len(r))
if len(r) == 0 {
return array
}
key := ""
if len(field) > 0 && field[0] != "" {
key = field[0]
} else {
for k, _ := range r[0] {
key = k
break
}
}
for k, v := range r {
array[k] = v[key]
}
return array
}
// MapKeyStr converts <r> to a map[string]Map of which key is specified by <key>.

View File

@ -534,6 +534,38 @@ func Test_Model_Value(t *testing.T) {
})
}
func Test_Model_Array(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
all, err := db.Table(table).Where("id", g.Slice{1, 2, 3}).All()
gtest.Assert(err, nil)
gtest.Assert(all.Array("id"), g.Slice{1, 2, 3})
gtest.Assert(all.Array("nickname"), g.Slice{"name_1", "name_2", "name_3"})
})
gtest.Case(t, func() {
array, err := db.Table(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array()
gtest.Assert(err, nil)
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
})
gtest.Case(t, func() {
array, err := db.Table(table).Array("nickname", "id", g.Slice{1, 2, 3})
gtest.Assert(err, nil)
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
})
gtest.Case(t, func() {
array, err := db.Table(table).FindArray("nickname", "id", g.Slice{1, 2, 3})
gtest.Assert(err, nil)
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
})
gtest.Case(t, func() {
array, err := db.Table(table).FindArray("nickname", g.Slice{1, 2, 3})
gtest.Assert(err, nil)
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
})
}
func Test_Model_FindValue(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -904,6 +936,18 @@ func Test_Model_GroupBy(t *testing.T) {
})
}
func Test_Model_Data(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
result, err := db.Table(table).Data("nickname=?", "test").Where("id=?", 3).Update()
gtest.Assert(err, nil)
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
}
func Test_Model_Where(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -1147,6 +1191,44 @@ func Test_Model_Where(t *testing.T) {
})
}
func Test_Model_Where_ISNULL_1(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.Case(t, func() {
//db.SetDebug(true)
result, err := db.Table(table).Data("nickname", nil).Where("id", 2).Update()
gtest.Assert(err, nil)
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
one, err := db.Table(table).Where("nickname", nil).One()
gtest.Assert(err, nil)
gtest.Assert(one.IsEmpty(), false)
gtest.Assert(one["id"], 2)
})
}
func Test_Model_Where_ISNULL_2(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// complicated one.
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?": "%name%",
"id between ? and ?": g.Slice{1, 3},
"id > 0": nil,
"create_time > 0": nil,
"id": g.Slice{1, 2, 3},
}
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
}
func Test_Model_WherePri(t *testing.T) {
table := createInitTable()
defer dropTable(table)

View File

@ -41,10 +41,10 @@ type Config struct {
Port int
Db int
Pass string // Password for AUTH.
MaxIdle int // Maximum number of connections allowed to be idle (default is 0 means no idle connection)
MaxActive int // Maximum number of connections limit (default is 0 means no limit)
IdleTimeout time.Duration // Maximum idle time for connection (default is 60 seconds, not allowed to be set to 0)
MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 60 seconds, not allowed to be set to 0)
MaxIdle int // Maximum number of connections allowed to be idle (default is 10)
MaxActive int // Maximum number of connections limit (default is 0 means no limit).
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.
}
@ -54,8 +54,9 @@ type PoolStats struct {
}
const (
gDEFAULT_POOL_IDLE_TIMEOUT = 30 * time.Second
gDEFAULT_POOL_IDLE_TIMEOUT = 10 * time.Second
gDEFAULT_POOL_CONN_TIMEOUT = 10 * time.Second
gDEFAULT_POOL_MAX_IDLE = 10
gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second
)
@ -67,6 +68,12 @@ var (
// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
func New(config Config) *Redis {
// The MaxIdle is the most important attribute of the connection pool.
// Only if this attribute is set, the created connections from client
// can not exceed the limit of the server.
if config.MaxIdle == 0 {
config.MaxIdle = gDEFAULT_POOL_MAX_IDLE
}
if config.IdleTimeout == 0 {
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
}
@ -132,7 +139,8 @@ func NewFromStr(str string) (*Redis, error) {
// It is not necessary to call Close manually.
func (r *Redis) Close() error {
if r.group != "" {
// If it is an instance object, it needs to remove it from the instance Map.
// If it is an instance object,
// it needs to remove it from the instance Map.
instances.Remove(r.group)
}
pools.Remove(fmt.Sprintf("%v", r.config))
@ -151,22 +159,31 @@ func (r *Redis) GetConn() *Conn {
return r.Conn()
}
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
// SetMaxIdle sets the maximum number of idle connections in the pool.
func (r *Redis) SetMaxIdle(value int) {
r.pool.MaxIdle = value
}
// SetMaxActive sets the MaxActive attribute of the connection pool.
// SetMaxActive sets the maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
//
// Note that if the pool is at the MaxActive limit, then all the operations will wait for
// a connection to be returned to the pool before returning.
func (r *Redis) SetMaxActive(value int) {
r.pool.MaxActive = value
}
// SetIdleTimeout sets the IdleTimeout attribute of the connection pool.
// It closes connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
func (r *Redis) SetIdleTimeout(value time.Duration) {
r.pool.IdleTimeout = value
}
// SetMaxConnLifetime sets the MaxConnLifetime attribute of the connection pool.
// It closes connections older than this duration. If the value is zero, then
// the pool does not close connections based on age.
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
r.pool.MaxConnLifetime = value
}

View File

@ -6,272 +6,3 @@
// Package gdebug contains facilities for programs to debug themselves while they are running.
package gdebug
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"github.com/gogf/gf/encoding/ghash"
"github.com/gogf/gf/crypto/gmd5"
)
const (
gMAX_DEPTH = 1000
gFILTER_KEY = "/debug/gdebug/gdebug"
)
var (
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
binaryVersion = "" // The version of current running binary(uint64 hex).
binaryVersionMd5 = "" // The version of current running binary(MD5).
selfPath = "" // Current running binary absolute path.
)
func init() {
if goRootForFilter != "" {
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
}
// Initialize internal package variable: selfPath.
selfPath, _ := exec.LookPath(os.Args[0])
if selfPath != "" {
selfPath, _ = filepath.Abs(selfPath)
}
if selfPath == "" {
selfPath, _ = filepath.Abs(os.Args[0])
}
}
// BinVersion returns the version of current running binary.
// It uses ghash.BKDRHash+BASE36 algorithm to calculate the unique version of the binary.
func BinVersion() string {
if binaryVersion == "" {
binaryContent, _ := ioutil.ReadFile(selfPath)
binaryVersion = strconv.FormatInt(
int64(ghash.BKDRHash(binaryContent)),
36,
)
}
return binaryVersion
}
// BinVersionMd5 returns the version of current running binary.
// It uses MD5 algorithm to calculate the unique version of the binary.
func BinVersionMd5() string {
if binaryVersionMd5 == "" {
binaryVersionMd5, _ = gmd5.EncryptFile(selfPath)
}
return binaryVersionMd5
}
// PrintStack prints to standard error the stack trace returned by runtime.Stack.
func PrintStack(skip ...int) {
fmt.Print(Stack(skip...))
}
// Stack returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func Stack(skip ...int) string {
return StackWithFilter("", skip...)
}
// StackWithFilter returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
//
// The parameter <filter> is used to filter the path of the caller.
func StackWithFilter(filter string, skip ...int) string {
return StackWithFilters([]string{filter}, skip...)
}
// StackWithFilters returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
//
// The parameter <filters> is a slice of strings, which are used to filter the path of the caller.
func StackWithFilters(filters []string, skip ...int) string {
number := 0
if len(skip) > 0 {
number = skip[0]
}
name := ""
space := " "
index := 1
buffer := bytes.NewBuffer(nil)
filtered := false
ok := true
pc, file, line, start := callerFromIndex(filters)
for i := start + number; i < gMAX_DEPTH; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
filtered = false
for _, filter := range filters {
if filter != "" && strings.Contains(file, filter) {
filtered = true
break
}
}
if filtered {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
if fn := runtime.FuncForPC(pc); fn == nil {
name = "unknown"
} else {
name = fn.Name()
}
if index > 9 {
space = " "
}
buffer.WriteString(fmt.Sprintf("%d.%s%s\n %s:%d\n", index, space, name, file, line))
index++
} else {
break
}
}
return buffer.String()
}
// CallerPath returns the function name and the absolute file path along with its line number of the caller.
func Caller(skip ...int) (function string, path string, line int) {
return CallerWithFilter("", skip...)
}
// CallerPathWithFilter returns the function name and the absolute file path along with its line number of the caller.
//
// The parameter <filter> is used to filter the path of the caller.
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
number := 0
if len(skip) > 0 {
number = skip[0]
}
ok := true
pc, file, line, start := callerFromIndex([]string{filter})
if start != -1 {
for i := start + number; i < gMAX_DEPTH; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if filter != "" && strings.Contains(file, filter) {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
function := ""
if fn := runtime.FuncForPC(pc); fn == nil {
function = "unknown"
} else {
function = fn.Name()
}
return function, file, line
} else {
break
}
}
}
return "", "", -1
}
// callerFromIndex returns the caller position and according information exclusive of the debug package.
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
var filtered, ok bool
for index = 0; index < gMAX_DEPTH; index++ {
if pc, file, line, ok = runtime.Caller(index); ok {
filtered = false
for _, filter := range filters {
if filter != "" && strings.Contains(file, filter) {
filtered = true
break
}
}
if filtered {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
return
}
}
return 0, "", -1, -1
}
// CallerPackage returns the package name of the caller.
func CallerPackage() string {
function, _, _ := Caller()
indexSplit := strings.LastIndexByte(function, '/')
if indexSplit == -1 {
return function[:strings.IndexByte(function, '.')]
} else {
leftPart := function[:indexSplit+1]
rightPart := function[indexSplit+1:]
indexDot := strings.IndexByte(function, '.')
rightPart = rightPart[:indexDot-1]
return leftPart + rightPart
}
}
// CallerFunction returns the function name of the caller.
func CallerFunction() string {
function, _, _ := Caller()
function = function[strings.LastIndexByte(function, '/')+1:]
function = function[strings.IndexByte(function, '.')+1:]
return function
}
// CallerFilePath returns the file path of the caller.
func CallerFilePath() string {
_, path, _ := Caller()
return path
}
// CallerDirectory returns the directory of the caller.
func CallerDirectory() string {
_, path, _ := Caller()
return filepath.Dir(path)
}
// CallerFileLine returns the file path along with the line number of the caller.
func CallerFileLine() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, path, line)
}
// CallerFileLineShort returns the file name along with the line number of the caller.
func CallerFileLineShort() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
}
// FuncPath returns the complete function path of given <f>.
func FuncPath(f interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
// FuncName returns the function name of given <f>.
func FuncName(f interface{}) string {
path := FuncPath(f)
if path == "" {
return ""
}
index := strings.LastIndexByte(path, '/')
if index < 0 {
index = strings.LastIndexByte(path, '\\')
}
return path[index+1:]
}

View File

@ -0,0 +1,174 @@
// 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 (
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
)
const (
gMAX_DEPTH = 1000
gFILTER_KEY = "/debug/gdebug/gdebug"
)
var (
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
binaryVersion = "" // The version of current running binary(uint64 hex).
binaryVersionMd5 = "" // The version of current running binary(MD5).
selfPath = "" // Current running binary absolute path.
)
func init() {
if goRootForFilter != "" {
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
}
// Initialize internal package variable: selfPath.
selfPath, _ := exec.LookPath(os.Args[0])
if selfPath != "" {
selfPath, _ = filepath.Abs(selfPath)
}
if selfPath == "" {
selfPath, _ = filepath.Abs(os.Args[0])
}
}
// CallerPath returns the function name and the absolute file path along with its line number of the caller.
func Caller(skip ...int) (function string, path string, line int) {
return CallerWithFilter("", skip...)
}
// CallerPathWithFilter returns the function name and the absolute file path along with its line number of the caller.
//
// The parameter <filter> is used to filter the path of the caller.
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
number := 0
if len(skip) > 0 {
number = skip[0]
}
ok := true
pc, file, line, start := callerFromIndex([]string{filter})
if start != -1 {
for i := start + number; i < gMAX_DEPTH; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if filter != "" && strings.Contains(file, filter) {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
function := ""
if fn := runtime.FuncForPC(pc); fn == nil {
function = "unknown"
} else {
function = fn.Name()
}
return function, file, line
} else {
break
}
}
}
return "", "", -1
}
// callerFromIndex returns the caller position and according information exclusive of the debug package.
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
var filtered, ok bool
for index = 0; index < gMAX_DEPTH; index++ {
if pc, file, line, ok = runtime.Caller(index); ok {
filtered = false
for _, filter := range filters {
if filter != "" && strings.Contains(file, filter) {
filtered = true
break
}
}
if filtered {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
return
}
}
return 0, "", -1, -1
}
// CallerPackage returns the package name of the caller.
func CallerPackage() string {
function, _, _ := Caller()
indexSplit := strings.LastIndexByte(function, '/')
if indexSplit == -1 {
return function[:strings.IndexByte(function, '.')]
} else {
leftPart := function[:indexSplit+1]
rightPart := function[indexSplit+1:]
indexDot := strings.IndexByte(function, '.')
rightPart = rightPart[:indexDot-1]
return leftPart + rightPart
}
}
// CallerFunction returns the function name of the caller.
func CallerFunction() string {
function, _, _ := Caller()
function = function[strings.LastIndexByte(function, '/')+1:]
function = function[strings.IndexByte(function, '.')+1:]
return function
}
// CallerFilePath returns the file path of the caller.
func CallerFilePath() string {
_, path, _ := Caller()
return path
}
// CallerDirectory returns the directory of the caller.
func CallerDirectory() string {
_, path, _ := Caller()
return filepath.Dir(path)
}
// CallerFileLine returns the file path along with the line number of the caller.
func CallerFileLine() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, path, line)
}
// CallerFileLineShort returns the file name along with the line number of the caller.
func CallerFileLineShort() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
}
// FuncPath returns the complete function path of given <f>.
func FuncPath(f interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
// FuncName returns the function name of given <f>.
func FuncName(f interface{}) string {
path := FuncPath(f)
if path == "" {
return ""
}
index := strings.LastIndexByte(path, '/')
if index < 0 {
index = strings.LastIndexByte(path, '\\')
}
return path[index+1:]
}

View File

@ -0,0 +1,87 @@
// 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 (
"bytes"
"fmt"
"runtime"
"strings"
)
// PrintStack prints to standard error the stack trace returned by runtime.Stack.
func PrintStack(skip ...int) {
fmt.Print(Stack(skip...))
}
// Stack returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func Stack(skip ...int) string {
return StackWithFilter("", skip...)
}
// StackWithFilter returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
//
// The parameter <filter> is used to filter the path of the caller.
func StackWithFilter(filter string, skip ...int) string {
return StackWithFilters([]string{filter}, skip...)
}
// StackWithFilters returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
//
// The parameter <filters> is a slice of strings, which are used to filter the path of the caller.
func StackWithFilters(filters []string, skip ...int) string {
number := 0
if len(skip) > 0 {
number = skip[0]
}
name := ""
space := " "
index := 1
buffer := bytes.NewBuffer(nil)
filtered := false
ok := true
pc, file, line, start := callerFromIndex(filters)
for i := start + number; i < gMAX_DEPTH; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
filtered = false
for _, filter := range filters {
if filter != "" && strings.Contains(file, filter) {
filtered = true
break
}
}
if filtered {
continue
}
if strings.Contains(file, gFILTER_KEY) {
continue
}
if fn := runtime.FuncForPC(pc); fn == nil {
name = "unknown"
} else {
name = fn.Name()
}
if index > 9 {
space = " "
}
buffer.WriteString(fmt.Sprintf("%d.%s%s\n %s:%d\n", index, space, name, file, line))
index++
} else {
break
}
}
return buffer.String()
}

View File

@ -0,0 +1,17 @@
// 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 (
"path/filepath"
)
// TestDataPath retrieves and returns the testdata path of current package.
// It is used for unit testing cases only.
func TestDataPath() string {
return CallerDirectory() + string(filepath.Separator) + "testdata"
}

View File

@ -0,0 +1,36 @@
// 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 (
"github.com/gogf/gf/crypto/gmd5"
"github.com/gogf/gf/encoding/ghash"
"io/ioutil"
"strconv"
)
// BinVersion returns the version of current running binary.
// It uses ghash.BKDRHash+BASE36 algorithm to calculate the unique version of the binary.
func BinVersion() string {
if binaryVersion == "" {
binaryContent, _ := ioutil.ReadFile(selfPath)
binaryVersion = strconv.FormatInt(
int64(ghash.BKDRHash(binaryContent)),
36,
)
}
return binaryVersion
}
// BinVersionMd5 returns the version of current running binary.
// It uses MD5 algorithm to calculate the unique version of the binary.
func BinVersionMd5() string {
if binaryVersionMd5 == "" {
binaryVersionMd5, _ = gmd5.EncryptFile(selfPath)
}
return binaryVersionMd5
}

View File

@ -67,7 +67,7 @@ func Test_Basic(t *testing.T) {
}
func Test_File(t *testing.T) {
path := gfile.Join(gdebug.CallerDirectory(), "testdata", "test")
path := gfile.Join(gdebug.TestDataPath(), "test")
expect := "dGVzdA=="
gtest.Case(t, func() {
b, err := gbase64.EncodeFile(path)

View File

@ -9,10 +9,11 @@ package gcompress
import (
"bytes"
"compress/gzip"
"github.com/gogf/gf/os/gfile"
"io"
)
// Gzip compresses <data> with gzip algorithm.
// Gzip compresses <data> using gzip algorithm.
// The optional parameter <level> specifies the compression level from
// 1 to 9 which means from none to the best compression.
//
@ -38,6 +39,38 @@ func Gzip(data []byte, level ...int) ([]byte, error) {
return buf.Bytes(), nil
}
// GzipFile compresses the file <src> to <dst> using gzip algorithm.
func GzipFile(src, dst string, level ...int) error {
var writer *gzip.Writer
var err error
srcFile, err := gfile.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := gfile.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
if len(level) > 0 {
writer, err = gzip.NewWriterLevel(dstFile, level[0])
if err != nil {
return err
}
} else {
writer = gzip.NewWriter(dstFile)
}
defer writer.Close()
_, err = io.Copy(writer, srcFile)
if err != nil {
return err
}
return nil
}
// UnGzip decompresses <data> with gzip algorithm.
func UnGzip(data []byte) ([]byte, error) {
var buf bytes.Buffer
@ -53,3 +86,28 @@ func UnGzip(data []byte) ([]byte, error) {
}
return buf.Bytes(), nil
}
// UnGzip decompresses file <src> to <dst> using gzip algorithm.
func UnGzipFile(src, dst string) error {
srcFile, err := gfile.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := gfile.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
reader, err := gzip.NewReader(srcFile)
if err != nil {
return err
}
defer reader.Close()
if _, err = io.Copy(dstFile, reader); err != nil {
return err
}
return nil
}

View File

@ -7,6 +7,9 @@
package gcompress_test
import (
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gtime"
"testing"
"github.com/gogf/gf/encoding/gcompress"
@ -26,14 +29,37 @@ func Test_Gzip_UnGzip(t *testing.T) {
0x24, 0xa8, 0xd1, 0x0d, 0x00,
0x00, 0x00,
}
gtest.Case(t, func() {
arr := []byte(src)
data, _ := gcompress.Gzip(arr)
gtest.Assert(data, gzip)
arr := []byte(src)
data, _ := gcompress.Gzip(arr)
gtest.Assert(data, gzip)
data, _ = gcompress.UnGzip(gzip)
gtest.Assert(data, arr)
data, _ = gcompress.UnGzip(gzip)
gtest.Assert(data, arr)
data, _ = gcompress.UnGzip(gzip[1:])
gtest.Assert(data, nil)
data, _ = gcompress.UnGzip(gzip[1:])
gtest.Assert(data, nil)
})
}
func Test_Gzip_UnGzip_File(t *testing.T) {
srcPath := gfile.Join(gdebug.TestDataPath(), "gzip", "file.txt")
dstPath1 := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), "gzip.zip")
dstPath2 := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), "file.txt")
// Compress.
gtest.Case(t, func() {
err := gcompress.GzipFile(srcPath, dstPath1, 9)
gtest.Assert(err, nil)
defer gfile.Remove(dstPath1)
gtest.Assert(gfile.Exists(dstPath1), true)
// Decompress.
err = gcompress.UnGzipFile(dstPath1, dstPath2)
gtest.Assert(err, nil)
defer gfile.Remove(dstPath2)
gtest.Assert(gfile.Exists(dstPath2), true)
gtest.Assert(gfile.GetContents(srcPath), gfile.GetContents(dstPath2))
})
}

View File

@ -20,8 +20,8 @@ import (
func Test_ZipPath(t *testing.T) {
// file
gtest.Case(t, func() {
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "path1", "1.txt")
dstPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "zip.zip")
srcPath := gfile.Join(gdebug.TestDataPath(), "zip", "path1", "1.txt")
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
gtest.Assert(gfile.Exists(dstPath), false)
err := gcompress.ZipPath(srcPath, dstPath)
@ -44,8 +44,8 @@ func Test_ZipPath(t *testing.T) {
})
// directory
gtest.Case(t, func() {
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip")
dstPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "zip.zip")
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@ -77,10 +77,10 @@ func Test_ZipPath(t *testing.T) {
})
// multiple paths joined using char ','
gtest.Case(t, func() {
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip")
srcPath1 := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "path1")
srcPath2 := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "path2")
dstPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "zip.zip")
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
srcPath1 := gfile.Join(gdebug.TestDataPath(), "zip", "path1")
srcPath2 := gfile.Join(gdebug.TestDataPath(), "zip", "path2")
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@ -116,9 +116,9 @@ func Test_ZipPath(t *testing.T) {
func Test_ZipPathWriter(t *testing.T) {
gtest.Case(t, func() {
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip")
srcPath1 := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "path1")
srcPath2 := gfile.Join(gdebug.CallerDirectory(), "testdata", "zip", "path2")
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
srcPath1 := gfile.Join(gdebug.TestDataPath(), "zip", "path1")
srcPath2 := gfile.Join(gdebug.TestDataPath(), "zip", "path2")
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)

View File

@ -0,0 +1 @@
This is a test file for gzip compression.

View File

@ -462,39 +462,3 @@ func Test_IsNil(t *testing.T) {
gtest.Assert(j.IsNil(), true)
})
}
func Test_ToStructDeep(t *testing.T) {
gtest.Case(t, func() {
type Item struct {
Title string `json:"title"`
Key string `json:"key"`
}
type M struct {
Id string `json:"id"`
Me map[string]interface{} `json:"me"`
Txt string `json:"txt"`
Items []*Item `json:"items"`
}
txt := `{
"id":"88888",
"me":{"name":"mikey","day":"20009"},
"txt":"hello",
"items":null
}`
j, err := gjson.LoadContent(txt)
gtest.Assert(err, nil)
gtest.Assert(j.GetString("me.name"), "mikey")
gtest.Assert(j.GetString("items"), "")
gtest.Assert(j.GetBool("items"), false)
gtest.Assert(j.GetArray("items"), nil)
m := new(M)
err = j.ToStructDeep(m)
gtest.Assert(err, nil)
gtest.AssertNE(m.Me, nil)
gtest.Assert(m.Me["day"], "20009")
gtest.Assert(m.Items, nil)
})
}

View File

@ -0,0 +1,132 @@
// 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_test
import (
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_ToStruct1(t *testing.T) {
gtest.Case(t, func() {
type BaseInfoItem struct {
IdCardNumber string `db:"id_card_number" json:"idCardNumber" field:"id_card_number"`
IsHouseholder bool `db:"is_householder" json:"isHouseholder" field:"is_householder"`
HouseholderRelation string `db:"householder_relation" json:"householderRelation" field:"householder_relation"`
UserName string `db:"user_name" json:"userName" field:"user_name"`
UserSex string `db:"user_sex" json:"userSex" field:"user_sex"`
UserAge int `db:"user_age" json:"userAge" field:"user_age"`
UserNation string `db:"user_nation" json:"userNation" field:"user_nation"`
}
type UserCollectionAddReq struct {
BaseInfo []BaseInfoItem `db:"_" json:"baseInfo" field:"_"`
}
jsonContent := `{
"baseInfo": [{
"idCardNumber": "520101199412141111",
"isHouseholder": true,
"householderRelation": "户主",
"userName": "李四",
"userSex": "男",
"userAge": 32,
"userNation": "苗族",
"userPhone": "13084183323",
"liveAddress": {},
"occupationInfo": [{
"occupationType": "经商",
"occupationBusinessInfo": [{
"occupationClass": "制造业",
"businessLicenseNumber": "32020000012300",
"businessName": "土灶柴火鸡",
"spouseName": "",
"spouseIdCardNumber": "",
"businessLicensePhotoId": 125,
"businessPlace": "租赁房产",
"hasGoodsInsurance": true,
"businessScopeStr": "柴火鸡;烧烤",
"businessAddress": {},
"businessPerformAbility": {
"businessType": "服务业",
"businessLife": 5,
"salesRevenue": 8000,
"familyEquity": 6000
}
}],
"occupationWorkInfo": {
"occupationClass": "",
"companyName": "",
"companyType": "",
"workYearNum": 0,
"spouseName": "",
"spouseIdCardNumber": "",
"spousePhone": "",
"spouseEducation": "",
"spouseCompanyName": "",
"workLevel": "",
"workAddress": {},
"workPerformAbility": {
"familyAnnualIncome": 0,
"familyEquity": 0,
"workCooperationState": "",
"workMoneyCooperationState": ""
}
},
"occupationAgricultureInfo": []
}],
"assetsInfo": [],
"expenditureInfo": [],
"incomeInfo": [],
"liabilityInfo": []
}]
}`
data := new(UserCollectionAddReq)
j, err := gjson.LoadJson(jsonContent)
gtest.Assert(err, nil)
err = j.ToStruct(data)
gtest.Assert(err, nil)
g.Dump(data)
})
}
func Test_ToStructDeep(t *testing.T) {
gtest.Case(t, func() {
type Item struct {
Title string `json:"title"`
Key string `json:"key"`
}
type M struct {
Id string `json:"id"`
Me map[string]interface{} `json:"me"`
Txt string `json:"txt"`
Items []*Item `json:"items"`
}
txt := `{
"id":"88888",
"me":{"name":"mikey","day":"20009"},
"txt":"hello",
"items":null
}`
j, err := gjson.LoadContent(txt)
gtest.Assert(err, nil)
gtest.Assert(j.GetString("me.name"), "mikey")
gtest.Assert(j.GetString("items"), "")
gtest.Assert(j.GetBool("items"), false)
gtest.Assert(j.GetArray("items"), nil)
m := new(M)
err = j.ToStructDeep(m)
gtest.Assert(err, nil)
gtest.AssertNE(m.Me, nil)
gtest.Assert(m.Me["day"], "20009")
gtest.Assert(m.Items, nil)
})
}

View File

@ -8,12 +8,7 @@
package gins
import (
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/os/gcfg"
"github.com/gogf/gf/os/gfsnotify"
)
var (
@ -59,15 +54,3 @@ func GetOrSetFuncLock(name string, f func() interface{}) interface{} {
func SetIfNotExist(name string, instance interface{}) bool {
return instances.SetIfNotExist(name, instance)
}
// addConfigMonitor adds fsnotify monitor for configuration file if it exists.
func addConfigMonitor(key string, config *gcfg.Config) {
if path := config.FilePath(); path != "" && gfile.Exists(path) {
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
instances.Remove(key)
})
if err != nil {
intlog.Error(err)
}
}
}

View File

@ -72,7 +72,6 @@ func Database(name ...string) gdb.DB {
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
}
}
addConfigMonitor(instanceKey, config)
if db, err := gdb.New(name...); err == nil {
// Initialize logger for ORM.

View File

@ -36,7 +36,6 @@ func Redis(name ...string) *gredis.Redis {
if err != nil {
panic(err)
}
addConfigMonitor(instanceKey, config)
return gredis.New(redisConfig)
} else {
panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group))

View File

@ -34,6 +34,9 @@ func Server(name ...interface{}) *ghttp.Server {
panic(err)
}
}
// As it might use template feature,
// it initialize the view instance as well.
_ = getViewInstance()
}
return s
}).(*ghttp.Server)

View File

@ -25,22 +25,30 @@ func View(name ...string) *gview.View {
}
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEWER, instanceName)
return instances.GetOrSetFuncLock(instanceKey, func() interface{} {
view := gview.Instance(instanceName)
// To avoid file no found error while it's not necessary.
if Config().Available() {
var m map[string]interface{}
// It firstly searches the configuration of the instance name.
if m = Config().GetMap(fmt.Sprintf(`%s.%s`, gVIEWER_NODE_NAME, instanceName)); m == nil {
// If the configuration for the instance does not exist,
// it uses the default view configuration.
m = Config().GetMap(gVIEWER_NODE_NAME)
}
if m != nil {
if err := view.SetConfigWithMap(m); err != nil {
panic(err)
}
}
}
return view
return getViewInstance(instanceName)
}).(*gview.View)
}
func getViewInstance(name ...string) *gview.View {
instanceName := gview.DEFAULT_NAME
if len(name) > 0 && name[0] != "" {
instanceName = name[0]
}
view := gview.Instance(instanceName)
// To avoid file no found error while it's not necessary.
if Config().Available() {
var m map[string]interface{}
// It firstly searches the configuration of the instance name.
if m = Config().GetMap(fmt.Sprintf(`%s.%s`, gVIEWER_NODE_NAME, instanceName)); m == nil {
// If the configuration for the instance does not exist,
// it uses the default view configuration.
m = Config().GetMap(gVIEWER_NODE_NAME)
}
if m != nil {
if err := view.SetConfigWithMap(m); err != nil {
panic(err)
}
}
}
return view
}

View File

@ -4,9 +4,10 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins
package gins_test
import (
"github.com/gogf/gf/frame/gins"
"testing"
"github.com/gogf/gf/test/gtest"
@ -14,30 +15,30 @@ import (
func Test_SetGet(t *testing.T) {
gtest.Case(t, func() {
Set("test-user", 1)
gtest.Assert(Get("test-user"), 1)
gtest.Assert(Get("none-exists"), nil)
gins.Set("test-user", 1)
gtest.Assert(gins.Get("test-user"), 1)
gtest.Assert(gins.Get("none-exists"), nil)
})
gtest.Case(t, func() {
gtest.Assert(GetOrSet("test-1", 1), 1)
gtest.Assert(Get("test-1"), 1)
gtest.Assert(gins.GetOrSet("test-1", 1), 1)
gtest.Assert(gins.Get("test-1"), 1)
})
gtest.Case(t, func() {
gtest.Assert(GetOrSetFunc("test-2", func() interface{} {
gtest.Assert(gins.GetOrSetFunc("test-2", func() interface{} {
return 2
}), 2)
gtest.Assert(Get("test-2"), 2)
gtest.Assert(gins.Get("test-2"), 2)
})
gtest.Case(t, func() {
gtest.Assert(GetOrSetFuncLock("test-3", func() interface{} {
gtest.Assert(gins.GetOrSetFuncLock("test-3", func() interface{} {
return 3
}), 3)
gtest.Assert(Get("test-3"), 3)
gtest.Assert(gins.Get("test-3"), 3)
})
gtest.Case(t, func() {
gtest.Assert(SetIfNotExist("test-4", 4), true)
gtest.Assert(Get("test-4"), 4)
gtest.Assert(SetIfNotExist("test-4", 5), false)
gtest.Assert(Get("test-4"), 4)
gtest.Assert(gins.SetIfNotExist("test-4", 4), true)
gtest.Assert(gins.Get("test-4"), 4)
gtest.Assert(gins.SetIfNotExist("test-4", 5), false)
gtest.Assert(gins.Get("test-4"), 4)
})
}

View File

@ -4,10 +4,12 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins
package gins_test
import (
"fmt"
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/frame/gins"
"testing"
"time"
@ -18,157 +20,186 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Config(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=1"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "8692651"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
gtest.Case(t, func() {
gtest.AssertNE(Config(), nil)
})
var (
configContent = gfile.GetContents(
gfile.Join(gdebug.TestDataPath(), "config", "config.toml"),
)
)
func Test_Config1(t *testing.T) {
gtest.Case(t, func() {
gtest.AssertNE(configContent, "")
})
gtest.Case(t, func() {
gtest.AssertNE(gins.Config(), nil)
})
}
func Test_Config2(t *testing.T) {
// relative path
gtest.Case(t, func() {
path := "config.toml"
err := gfile.PutContents(path, config)
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config().Clear()
gtest.Assert(Config().Get("test"), "v=1")
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
defer gfile.Remove(dirPath)
name := "config.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
gtest.Assert(err, nil)
err = gins.Config().AddPath(dirPath)
gtest.Assert(err, nil)
defer gins.Config().Clear()
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
// relative path, config folder
gtest.Case(t, func() {
path := "config/config.toml"
err := gfile.PutContents(path, config)
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config().Clear()
gtest.Assert(Config().Get("test"), "v=1")
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
defer gfile.Remove(dirPath)
gtest.Case(t, func() {
path := "test.toml"
err := gfile.PutContents(path, config)
name := "config/config.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config("test").Clear()
Config("test").SetFileName("test.toml")
gtest.Assert(Config("test").Get("test"), "v=1")
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
path := "config/test.toml"
err := gfile.PutContents(path, config)
err = gins.Config().AddPath(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config("test").Clear()
Config("test").SetFileName("test.toml")
gtest.Assert(Config("test").Get("test"), "v=1")
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
// absolute path
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer Config().Clear()
gtest.Assert(Config().AddPath(path), nil)
gtest.Assert(Config().Get("test"), "v=1")
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
defer gins.Config().Clear()
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer Config().Clear()
gtest.Assert(Config().AddPath(path), nil)
gtest.Assert(Config().Get("test"), "v=1")
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer Config("test").Clear()
Config("test").SetFileName("test.toml")
gtest.Assert(Config("test").AddPath(path), nil)
gtest.Assert(Config("test").Get("test"), "v=1")
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer Config().Clear()
Config("test").SetFileName("test.toml")
gtest.Assert(Config("test").AddPath(path), nil)
gtest.Assert(Config("test").Get("test"), "v=1")
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
})
}
func Test_Config3(t *testing.T) {
gtest.Case(t, func() {
gtest.Case(t, func() {
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(dirPath)
name := "test.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
gtest.Assert(err, nil)
err = gins.Config("test").AddPath(dirPath)
gtest.Assert(err, nil)
defer gins.Config("test").Clear()
gins.Config("test").SetFileName("test.toml")
gtest.Assert(gins.Config("test").Get("test"), "v=1")
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(dirPath)
name := "config/test.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
gtest.Assert(err, nil)
err = gins.Config("test").AddPath(dirPath)
gtest.Assert(err, nil)
defer gins.Config("test").Clear()
gins.Config("test").SetFileName("test.toml")
gtest.Assert(gins.Config("test").Get("test"), "v=1")
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file for next unit testing case.
time.Sleep(500 * time.Millisecond)
})
}
func Test_Config4(t *testing.T) {
gtest.Case(t, func() {
// absolute path
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, configContent)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Clear()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, configContent)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Clear()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, configContent)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config("test").Clear()
gins.Config("test").SetFileName("test.toml")
gtest.Assert(gins.Config("test").AddPath(path), nil)
gtest.Assert(gins.Config("test").Get("test"), "v=1")
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, configContent)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Clear()
gins.Config("test").SetFileName("test.toml")
gtest.Assert(gins.Config("test").AddPath(path), nil)
gtest.Assert(gins.Config("test").Get("test"), "v=1")
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
})
})
}
func Test_Basic2(t *testing.T) {
config := `log-path = "logs"`
gtest.Case(t, func() {
@ -179,6 +210,6 @@ func Test_Basic2(t *testing.T) {
_ = gfile.Remove(path)
}()
gtest.Assert(Config().Get("log-path"), "logs")
gtest.Assert(gins.Config().Get("log-path"), "logs")
})
}

View File

@ -4,9 +4,12 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins
package gins_test
import (
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/frame/gins"
"github.com/gogf/gf/os/gtime"
"testing"
"time"
@ -15,42 +18,22 @@ import (
)
func Test_Database(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=2"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "12345678"
name = "test"
type = "mysql"
role = "master"
weight = "1"
charset = "utf8"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "12345678"
name = "test"
type = "mysql"
role = "master"
weight = "1"
charset = "utf8"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
path := "config.toml"
err := gfile.PutContents(path, config)
databaseContent := gfile.GetContents(gfile.Join(gdebug.TestDataPath(), "database", "config.toml"))
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config().Clear()
defer gfile.Remove(dirPath)
name := "config.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), databaseContent)
gtest.Assert(err, nil)
err = gins.Config().AddPath(dirPath)
gtest.Assert(err, nil)
defer gins.Config().Clear()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
@ -58,8 +41,8 @@ test = "v=2"
gtest.Case(t, func() {
//fmt.Println("gins Test_Database", Config().Get("test"))
dbDefault := Database()
dbTest := Database("test")
dbDefault := gins.Database()
dbTest := gins.Database("test")
gtest.AssertNE(dbDefault, nil)
gtest.AssertNE(dbTest, nil)

View File

@ -4,9 +4,12 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins
package gins_test
import (
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/frame/gins"
"github.com/gogf/gf/os/gtime"
"testing"
"time"
@ -15,45 +18,22 @@ import (
)
func Test_Redis(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=3"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,7"
cache = "127.0.0.1:6379,8"
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
`
path := "config.toml"
err := gfile.PutContents(path, config)
redisContent := gfile.GetContents(gfile.Join(gdebug.TestDataPath(), "redis", "config.toml"))
var err error
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer Config().Clear()
defer gfile.Remove(dirPath)
name := "config.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), redisContent)
gtest.Assert(err, nil)
err = gins.Config().AddPath(dirPath)
gtest.Assert(err, nil)
defer gins.Config().Clear()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500 * time.Millisecond)
@ -61,9 +41,9 @@ test = "v=3"
gtest.Case(t, func() {
//fmt.Println("gins Test_Redis", Config().Get("test"))
redisDefault := Redis()
redisCache := Redis("cache")
redisDisk := Redis("disk")
redisDefault := gins.Redis()
redisCache := gins.Redis("cache")
redisDisk := gins.Redis("disk")
gtest.AssertNE(redisDefault, nil)
gtest.AssertNE(redisCache, nil)
gtest.AssertNE(redisDisk, nil)

View File

@ -52,7 +52,7 @@ func Test_View(t *testing.T) {
func Test_View_Config(t *testing.T) {
// view1 test1
gtest.Case(t, func() {
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view1")
dirPath := gfile.Join(gdebug.TestDataPath(), "view1")
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer gcfg.ClearContent()
defer instances.Clear()
@ -74,7 +74,7 @@ func Test_View_Config(t *testing.T) {
})
// view1 test2
gtest.Case(t, func() {
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view1")
dirPath := gfile.Join(gdebug.TestDataPath(), "view1")
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer gcfg.ClearContent()
defer instances.Clear()
@ -96,7 +96,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.Case(t, func() {
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view2")
dirPath := gfile.Join(gdebug.TestDataPath(), "view2")
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer gcfg.ClearContent()
defer instances.Clear()
@ -118,7 +118,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.Case(t, func() {
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view2")
dirPath := gfile.Join(gdebug.TestDataPath(), "view2")
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer gcfg.ClearContent()
defer instances.Clear()

29
frame/gins/testdata/config/config.toml vendored Normal file
View File

@ -0,0 +1,29 @@
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=1"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "8692651"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"

View File

@ -0,0 +1,29 @@
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=2"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "12345678"
name = "test"
type = "mysql"
role = "master"
weight = "1"
charset = "utf8"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "12345678"
name = "test"
type = "mysql"
role = "master"
weight = "1"
charset = "utf8"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"

32
frame/gins/testdata/redis/config.toml vendored Normal file
View File

@ -0,0 +1,32 @@
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=3"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,7"
cache = "127.0.0.1:6379,8"
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"

View File

@ -6,5 +6,9 @@
package gmvc
// Model is the base struct for model.
type Model struct{}
import "github.com/gogf/gf/database/gdb"
type (
M = Model // M is alias for Model, just for short write purpose.
Model = *gdb.Model // Model is alias for *gdb.Model.
)

View File

@ -51,7 +51,12 @@ func IsEmpty(value interface{}) bool {
return len(value) == 0
default:
// Finally using reflect.
rv := reflect.ValueOf(value)
var rv reflect.Value
if v, ok := value.(reflect.Value); ok {
rv = v
} else {
rv = reflect.ValueOf(value)
}
switch rv.Kind() {
case reflect.Chan,
reflect.Map,
@ -77,7 +82,12 @@ func IsNil(value interface{}) bool {
if value == nil {
return true
}
rv := reflect.ValueOf(value)
var rv reflect.Value
if v, ok := value.(reflect.Value); ok {
rv = v
} else {
rv = reflect.ValueOf(value)
}
switch rv.Kind() {
case reflect.Chan,
reflect.Map,

8
internal/utils/utils.go Normal file
View File

@ -0,0 +1,8 @@
// 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 utils provides some utility functions for internal usage.
package utils

View File

@ -0,0 +1,26 @@
// 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 utils
import "reflect"
// IsArray checks whether given value is array/slice.
// Note that it uses reflect internally implementing this feature.
func IsArray(value interface{}) bool {
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Array, reflect.Slice:
return true
default:
return false
}
}

View File

@ -4,8 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package utilstr provides some string functions for internal usage.
package utilstr
package utils
import "strings"

View File

@ -7,8 +7,8 @@
package ghttp
import (
"context"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/os/gres"
"github.com/gogf/gf/os/gview"
"net/http"
@ -23,23 +23,23 @@ import (
// Request is the context object for a request.
type Request struct {
*http.Request
Server *Server // Parent server.
Server *Server // Server.
Cookie *Cookie // Cookie.
Session *gsession.Session // Session.
Response *Response // Corresponding Response of this request.
Router *Router // Matched Router for this request. Note that it's only available in HTTP handler, not in HOOK or MiddleWare.
Router *Router // Matched Router for this request. Note that it's not available in HOOK handler.
EnterTime int64 // Request starting time in microseconds.
LeaveTime int64 // Request ending time in microseconds.
Middleware *Middleware // The middleware manager.
StaticFile *StaticFile // Static file object when static file serving.
Context *gmap.StrAnyMap // Custom context map for internal usage purpose.
handlers []*handlerParsedItem // All matched handlers containing handler, hook and middleware for this request .
Middleware *Middleware // Middleware manager.
StaticFile *StaticFile // Static file object for static file serving.
Context context.Context // Custom context for internal usage purpose.
handlers []*handlerParsedItem // All matched handlers containing handler, hook and middleware for this request.
hasHookHandler bool // A bool marking whether there's hook handler in the handlers for performance purpose.
hasServeHandler bool // A bool marking whether there's serving handler in the handlers for performance purpose.
parsedQuery bool // A bool marking whether the GET parameters parsed.
parsedBody bool // A bool marking whether the request body parsed.
parsedForm bool // A bool marking whether request Form parsed for HTTP method PUT, POST, PATCH.
paramsMap map[string]interface{} // Custom parameters.
paramsMap map[string]interface{} // Custom parameters map.
routerMap map[string]string // Router parameters map, which might be nil if there're no router parameters.
queryMap map[string]interface{} // Query parameters map, which is nil if there's no query string.
formMap map[string]interface{} // Form parameters map, which is nil if there's no form data from client.
@ -68,7 +68,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
Request: r,
Response: newResponse(s, w),
EnterTime: gtime.TimestampMilli(),
Context: gmap.NewStrAnyMap(),
Context: r.Context(),
}
request.Cookie = GetCookie(request)
request.Session = s.sessionManager.New(request.GetSessionId())

View File

@ -36,7 +36,7 @@ type UploadFiles []*UploadFile
// Note that it will overwrite the target file if there's already a same name file exist.
func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename string, err error) {
if f == nil {
return
return "", errors.New("file is empty, maybe you retrieve it from invalid field name or form enctype")
}
if !gfile.Exists(dirPath) {
if err = gfile.Mkdir(dirPath); err != nil {
@ -77,7 +77,7 @@ func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename stri
// The parameter <randomlyRename> specifies whether randomly renames all the file names.
func (fs UploadFiles) Save(dirPath string, randomlyRename ...bool) (filenames []string, err error) {
if len(fs) == 0 {
return nil, nil
return nil, errors.New("file array is empty, maybe you retrieve it from invalid field name or form enctype")
}
for _, f := range fs {
if filename, err := f.Save(dirPath, randomlyRename...); err != nil {

View File

@ -94,28 +94,13 @@ func (r *Response) CORS(options CORSOptions) {
r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
}
// No continue service handling if it's OPTIONS request.
// Note that there's special checks in previous router searching,
// so if it goes to here it means there's already serving handler exist.
if gstr.Equal(r.Request.Method, "OPTIONS") {
// Request method handler searching.
// It here simply uses Server.routesMap attribute enhancing the searching performance.
if method := r.Request.Header.Get("Access-Control-Request-Method"); method != "" {
routerKey := ""
for _, domain := range []string{gDEFAULT_DOMAIN, r.Request.GetHost()} {
for _, v := range []string{gDEFAULT_METHOD, method} {
routerKey = r.Server.routerMapKey("", v, r.Request.URL.Path, domain)
if r.Server.routesMap[routerKey] != nil {
if r.Status == 0 {
r.Status = http.StatusOK
}
// No continue serving.
r.Request.ExitAll()
}
}
}
}
// Cannot find the request serving handler, it then responses 404.
if r.Status == 0 {
r.Status = http.StatusNotFound
r.Status = http.StatusOK
}
// No continue serving.
r.Request.ExitAll()
}
}

View File

@ -89,6 +89,7 @@ type (
ctrlInfo *handlerController // Controller information for reflect usage.
hookName string // Hook type name.
router *Router // Router object.
source string // Source file path:line when registering.
}
// handlerParsedItem is the item parsed from URL.Path.
@ -105,7 +106,7 @@ type (
// registeredRouteItem stores the information of the router and is used for route map.
registeredRouteItem struct {
file string // Source file path and its line number.
source string // Source file path and its line number.
handler *handlerItem // Handler object.
}

View File

@ -12,121 +12,148 @@ import (
// 域名管理器对象
type Domain struct {
s *Server // 所属Server
m map[string]bool // 多域名
server *Server // 所属Server
domains map[string]struct{} // 多域名
}
// 生成一个域名对象, 参数 domains 支持给定多个域名。
func (s *Server) Domain(domains string) *Domain {
d := &Domain{
s: s,
m: make(map[string]bool),
server: s,
domains: make(map[string]struct{}),
}
for _, v := range strings.Split(domains, ",") {
d.m[strings.TrimSpace(v)] = true
d.domains[strings.TrimSpace(v)] = struct{}{}
}
return d
}
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHandler(pattern+"@"+domain, handler)
for domain, _ := range d.domains {
d.server.BindHandler(pattern+"@"+domain, handler)
}
}
func (d *Domain) doBindHandler(pattern string, handler HandlerFunc, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindHandler(pattern+"@"+domain, handler, middleware)
func (d *Domain) doBindHandler(
pattern string, handler HandlerFunc,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindHandler(pattern+"@"+domain, handler, middleware, source)
}
}
func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string) {
for domain, _ := range d.m {
d.s.BindObject(pattern+"@"+domain, obj, methods...)
for domain, _ := range d.domains {
d.server.BindObject(pattern+"@"+domain, obj, methods...)
}
}
func (d *Domain) doBindObject(pattern string, obj interface{}, methods string, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindObject(pattern+"@"+domain, obj, methods, middleware)
func (d *Domain) doBindObject(
pattern string, obj interface{}, methods string,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindObject(pattern+"@"+domain, obj, methods, middleware, source)
}
}
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) {
for domain, _ := range d.m {
d.s.BindObjectMethod(pattern+"@"+domain, obj, method)
for domain, _ := range d.domains {
d.server.BindObjectMethod(pattern+"@"+domain, obj, method)
}
}
func (d *Domain) doBindObjectMethod(pattern string, obj interface{}, method string, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindObjectMethod(pattern+"@"+domain, obj, method, middleware)
func (d *Domain) doBindObjectMethod(
pattern string, obj interface{}, method string,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindObjectMethod(pattern+"@"+domain, obj, method, middleware, source)
}
}
func (d *Domain) BindObjectRest(pattern string, obj interface{}) {
for domain, _ := range d.m {
d.s.BindObjectRest(pattern+"@"+domain, obj)
for domain, _ := range d.domains {
d.server.BindObjectRest(pattern+"@"+domain, obj)
}
}
func (d *Domain) doBindObjectRest(pattern string, obj interface{}, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindObjectRest(pattern+"@"+domain, obj, middleware)
func (d *Domain) doBindObjectRest(
pattern string, obj interface{},
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindObjectRest(pattern+"@"+domain, obj, middleware, source)
}
}
func (d *Domain) BindController(pattern string, c Controller, methods ...string) {
for domain, _ := range d.m {
d.s.BindController(pattern+"@"+domain, c, methods...)
for domain, _ := range d.domains {
d.server.BindController(pattern+"@"+domain, c, methods...)
}
}
func (d *Domain) doBindController(pattern string, c Controller, methods string, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindController(pattern+"@"+domain, c, methods, middleware)
func (d *Domain) doBindController(
pattern string, c Controller, methods string,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindController(pattern+"@"+domain, c, methods, middleware, source)
}
}
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) {
for domain, _ := range d.m {
d.s.BindControllerMethod(pattern+"@"+domain, c, method)
for domain, _ := range d.domains {
d.server.BindControllerMethod(pattern+"@"+domain, c, method)
}
}
func (d *Domain) doBindControllerMethod(pattern string, c Controller, method string, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindControllerMethod(pattern+"@"+domain, c, method, middleware)
func (d *Domain) doBindControllerMethod(
pattern string, c Controller, method string,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindControllerMethod(pattern+"@"+domain, c, method, middleware, source)
}
}
func (d *Domain) BindControllerRest(pattern string, c Controller) {
for domain, _ := range d.m {
d.s.BindControllerRest(pattern+"@"+domain, c)
for domain, _ := range d.domains {
d.server.BindControllerRest(pattern+"@"+domain, c)
}
}
func (d *Domain) doBindControllerRest(pattern string, c Controller, middleware []HandlerFunc) {
for domain, _ := range d.m {
d.s.doBindControllerRest(pattern+"@"+domain, c, middleware)
func (d *Domain) doBindControllerRest(
pattern string, c Controller,
middleware []HandlerFunc, source string,
) {
for domain, _ := range d.domains {
d.server.doBindControllerRest(pattern+"@"+domain, c, middleware, source)
}
}
func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHookHandler(pattern+"@"+domain, hook, handler)
for domain, _ := range d.domains {
d.server.BindHookHandler(pattern+"@"+domain, hook, handler)
}
}
func (d *Domain) doBindHookHandler(pattern string, hook string, handler HandlerFunc, source string) {
for domain, _ := range d.domains {
d.server.doBindHookHandler(pattern+"@"+domain, hook, handler, source)
}
}
func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for domain, _ := range d.m {
d.s.BindHookHandlerByMap(pattern+"@"+domain, hookmap)
for domain, _ := range d.domains {
d.server.BindHookHandlerByMap(pattern+"@"+domain, hookmap)
}
}
func (d *Domain) BindStatusHandler(status int, handler HandlerFunc) {
for domain, _ := range d.m {
d.s.setStatusHandler(d.s.statusHandlerKey(status, domain), handler)
for domain, _ := range d.domains {
d.server.setStatusHandler(d.server.statusHandlerKey(status, domain), handler)
}
}
@ -137,14 +164,14 @@ func (d *Domain) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) {
}
func (d *Domain) BindMiddleware(pattern string, handlers ...HandlerFunc) {
for domain, _ := range d.m {
d.s.BindMiddleware(pattern+"@"+domain, handlers...)
for domain, _ := range d.domains {
d.server.BindMiddleware(pattern+"@"+domain, handlers...)
}
}
func (d *Domain) BindMiddlewareDefault(handlers ...HandlerFunc) {
for domain, _ := range d.m {
d.s.BindMiddleware(gDEFAULT_MIDDLEWARE_PATTERN+"@"+domain, handlers...)
for domain, _ := range d.domains {
d.server.BindMiddleware(gDEFAULT_MIDDLEWARE_PATTERN+"@"+domain, handlers...)
}
}

View File

@ -67,6 +67,10 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err
// is the well designed router storage structure for router searching when the request is under serving.
func (s *Server) setHandler(pattern string, handler *handlerItem) {
handler.itemId = handlerIdGenerator.Add(1)
if handler.source == "" {
_, file, line := gdebug.CallerWithFilter(gFILTER_KEY)
handler.source = fmt.Sprintf(`%s:%d`, file, line)
}
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
s.Logger().Fatal("invalid pattern:", pattern, err)
@ -83,7 +87,10 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
switch handler.itemType {
case gHANDLER_TYPE_HANDLER, gHANDLER_TYPE_OBJECT, gHANDLER_TYPE_CONTROLLER:
if item, ok := s.routesMap[routerKey]; ok {
s.Logger().Fatalf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
s.Logger().Fatalf(
`duplicated route registry "%s" at %s , already registered at %s`,
pattern, handler.source, item[0].source,
)
return
}
}
@ -184,9 +191,9 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
if _, ok := s.routesMap[routerKey]; !ok {
s.routesMap[routerKey] = make([]registeredRouteItem, 0)
}
_, file, line := gdebug.CallerWithFilter(gFILTER_KEY)
routeItem := registeredRouteItem{
file: fmt.Sprintf(`%s:%d`, file, line),
source: handler.source,
handler: handler,
}
switch handler.itemType {

View File

@ -7,6 +7,8 @@
package ghttp
import (
"fmt"
"github.com/gogf/gf/debug/gdebug"
"reflect"
"strings"
@ -36,6 +38,7 @@ type (
pattern string
object interface{} // Can be handler, controller or object.
params []interface{} // Extra parameters for route registering depending on the type.
source string // // Handler is register at certain source file path:line.
}
)
@ -53,10 +56,10 @@ func (s *Server) handlePreBindItems() {
if item.group.server != nil && item.group.server != s {
continue
}
if item.group.domain != nil && item.group.domain.s != s {
if item.group.domain != nil && item.group.domain.server != s {
continue
}
item.group.doBind(item.bindType, item.pattern, item.object, item.params...)
item.group.doBindRoutersToServer(item)
}
preBindItems = preBindItems[:0]
}
@ -147,9 +150,9 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup {
bindType := gstr.ToUpper(gconv.String(item[0]))
switch bindType {
case "REST":
group.preBind("REST", gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
group.preBindToLocalArray("REST", gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
case "MIDDLEWARE":
group.preBind("MIDDLEWARE", gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
group.preBindToLocalArray("MIDDLEWARE", gconv.String(item[0])+":"+gconv.String(item[1]), item[2])
default:
if strings.EqualFold(bindType, "ALL") {
bindType = ""
@ -157,9 +160,9 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup {
bindType += ":"
}
if len(item) > 3 {
group.preBind("HANDLER", bindType+gconv.String(item[1]), item[2], item[3])
group.preBindToLocalArray("HANDLER", bindType+gconv.String(item[1]), item[2], item[3])
} else {
group.preBind("HANDLER", bindType+gconv.String(item[1]), item[2])
group.preBindToLocalArray("HANDLER", bindType+gconv.String(item[1]), item[2])
}
}
}
@ -168,62 +171,62 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup {
// ALL registers a http handler to given route pattern and all http methods.
func (g *RouterGroup) ALL(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", gDEFAULT_METHOD+":"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", gDEFAULT_METHOD+":"+pattern, object, params...)
}
// GET registers a http handler to given route pattern and http method: GET.
func (g *RouterGroup) GET(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "GET:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "GET:"+pattern, object, params...)
}
// PUT registers a http handler to given route pattern and http method: PUT.
func (g *RouterGroup) PUT(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "PUT:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "PUT:"+pattern, object, params...)
}
// POST registers a http handler to given route pattern and http method: POST.
func (g *RouterGroup) POST(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "POST:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "POST:"+pattern, object, params...)
}
// DELETE registers a http handler to given route pattern and http method: DELETE.
func (g *RouterGroup) DELETE(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "DELETE:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "DELETE:"+pattern, object, params...)
}
// PATCH registers a http handler to given route pattern and http method: PATCH.
func (g *RouterGroup) PATCH(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "PATCH:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "PATCH:"+pattern, object, params...)
}
// HEAD registers a http handler to given route pattern and http method: HEAD.
func (g *RouterGroup) HEAD(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "HEAD:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "HEAD:"+pattern, object, params...)
}
// CONNECT registers a http handler to given route pattern and http method: CONNECT.
func (g *RouterGroup) CONNECT(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "CONNECT:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "CONNECT:"+pattern, object, params...)
}
// OPTIONS registers a http handler to given route pattern and http method: OPTIONS.
func (g *RouterGroup) OPTIONS(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "OPTIONS:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "OPTIONS:"+pattern, object, params...)
}
// TRACE registers a http handler to given route pattern and http method: TRACE.
func (g *RouterGroup) TRACE(pattern string, object interface{}, params ...interface{}) *RouterGroup {
return g.Clone().preBind("HANDLER", "TRACE:"+pattern, object, params...)
return g.Clone().preBindToLocalArray("HANDLER", "TRACE:"+pattern, object, params...)
}
// REST registers a http handler to given route pattern according to REST rule.
func (g *RouterGroup) REST(pattern string, object interface{}) *RouterGroup {
return g.Clone().preBind("REST", pattern, object)
return g.Clone().preBindToLocalArray("REST", pattern, object)
}
// Hook registers a hook to given route pattern.
func (g *RouterGroup) Hook(pattern string, hook string, handler HandlerFunc) *RouterGroup {
return g.Clone().preBind("HANDLER", pattern, handler, hook)
return g.Clone().preBindToLocalArray("HANDLER", pattern, handler, hook)
}
// Middleware binds one or more middleware to the router group.
@ -232,14 +235,16 @@ func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup {
return g
}
// preBind adds the route registering parameters to internal variable array for lazily registering feature.
func (g *RouterGroup) preBind(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup {
// preBindToLocalArray adds the route registering parameters to internal variable array for lazily registering feature.
func (g *RouterGroup) preBindToLocalArray(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup {
_, file, line := gdebug.CallerWithFilter(gFILTER_KEY)
preBindItems = append(preBindItems, preBindItem{
group: g,
bindType: bindType,
pattern: pattern,
object: object,
params: params,
source: fmt.Sprintf(`%s:%d`, file, line),
})
return g
}
@ -255,8 +260,15 @@ func (g *RouterGroup) getPrefix() string {
return prefix
}
// doBind does really registering for the group.
func (g *RouterGroup) doBind(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup {
// doBindRoutersToServer does really registering for the group.
func (g *RouterGroup) doBindRoutersToServer(item preBindItem) *RouterGroup {
var (
bindType = item.bindType
pattern = item.pattern
object = item.object
params = item.params
source = item.source
)
prefix := g.getPrefix()
// Route check.
if len(prefix) > 0 {
@ -271,7 +283,9 @@ func (g *RouterGroup) doBind(bindType string, pattern string, object interface{}
if bindType == "REST" {
pattern = prefix + "/" + strings.TrimLeft(path, "/")
} else {
pattern = g.server.serveHandlerKey(method, prefix+"/"+strings.TrimLeft(path, "/"), domain)
pattern = g.server.serveHandlerKey(
method, prefix+"/"+strings.TrimLeft(path, "/"), domain,
)
}
}
// Filter repeated char '/'.
@ -286,62 +300,102 @@ func (g *RouterGroup) doBind(bindType string, pattern string, object interface{}
case "HANDLER":
if h, ok := object.(HandlerFunc); ok {
if g.server != nil {
g.server.doBindHandler(pattern, h, g.middleware)
g.server.doBindHandler(pattern, h, g.middleware, source)
} else {
g.domain.doBindHandler(pattern, h, g.middleware)
g.domain.doBindHandler(pattern, h, g.middleware, source)
}
} else if g.isController(object) {
if len(extras) > 0 {
if g.server != nil {
g.server.doBindControllerMethod(pattern, object.(Controller), extras[0], g.middleware)
if gstr.Contains(extras[0], ",") {
g.server.doBindController(
pattern, object.(Controller), extras[0], g.middleware, source,
)
} else {
g.server.doBindControllerMethod(
pattern, object.(Controller), extras[0], g.middleware, source,
)
}
} else {
g.domain.doBindControllerMethod(pattern, object.(Controller), extras[0], g.middleware)
if gstr.Contains(extras[0], ",") {
g.domain.doBindController(
pattern, object.(Controller), extras[0], g.middleware, source,
)
} else {
g.domain.doBindControllerMethod(
pattern, object.(Controller), extras[0], g.middleware, source,
)
}
}
} else {
if g.server != nil {
g.server.doBindController(pattern, object.(Controller), "", g.middleware)
g.server.doBindController(
pattern, object.(Controller), "", g.middleware, source,
)
} else {
g.domain.doBindController(pattern, object.(Controller), "", g.middleware)
g.domain.doBindController(
pattern, object.(Controller), "", g.middleware, source,
)
}
}
} else {
if len(extras) > 0 {
if g.server != nil {
g.server.doBindObjectMethod(pattern, object, extras[0], g.middleware)
if gstr.Contains(extras[0], ",") {
g.server.doBindObject(
pattern, object, extras[0], g.middleware, source,
)
} else {
g.server.doBindObjectMethod(
pattern, object, extras[0], g.middleware, source,
)
}
} else {
g.domain.doBindObjectMethod(pattern, object, extras[0], g.middleware)
if gstr.Contains(extras[0], ",") {
g.domain.doBindObject(
pattern, object, extras[0], g.middleware, source,
)
} else {
g.domain.doBindObjectMethod(
pattern, object, extras[0], g.middleware, source,
)
}
}
} else {
if g.server != nil {
g.server.doBindObject(pattern, object, "", g.middleware)
g.server.doBindObject(pattern, object, "", g.middleware, source)
} else {
g.domain.doBindObject(pattern, object, "", g.middleware)
g.domain.doBindObject(pattern, object, "", g.middleware, source)
}
}
}
case "REST":
if g.isController(object) {
if g.server != nil {
g.server.doBindControllerRest(pattern, object.(Controller), g.middleware)
g.server.doBindControllerRest(
pattern, object.(Controller), g.middleware, source,
)
} else {
g.domain.doBindControllerRest(pattern, object.(Controller), g.middleware)
g.domain.doBindControllerRest(
pattern, object.(Controller), g.middleware, source,
)
}
} else {
if g.server != nil {
g.server.doBindObjectRest(pattern, object, g.middleware)
g.server.doBindObjectRest(pattern, object, g.middleware, source)
} else {
g.domain.doBindObjectRest(pattern, object, g.middleware)
g.domain.doBindObjectRest(pattern, object, g.middleware, source)
}
}
case "HOOK":
if h, ok := object.(HandlerFunc); ok {
if g.server != nil {
g.server.BindHookHandler(pattern, extras[0], h)
g.server.doBindHookHandler(pattern, extras[0], h, source)
} else {
g.domain.BindHookHandler(pattern, extras[0], h)
g.domain.doBindHookHandler(pattern, extras[0], h, source)
}
} else {
g.server.Logger().Fatalf("invalid hook handler for pattern:%s", pattern)
g.server.Logger().Fatalf("invalid hook handler for pattern: %s", pattern)
}
}
return g

View File

@ -13,11 +13,16 @@ import (
// 绑定指定的hook回调函数, pattern参数同BindHandler支持命名路由hook参数的值由ghttp server设定参数不区分大小写
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) {
s.doBindHookHandler(pattern, hook, handler, "")
}
func (s *Server) doBindHookHandler(pattern string, hook string, handler HandlerFunc, source string) {
s.setHandler(pattern, &handlerItem{
itemType: gHANDLER_TYPE_HOOK,
itemName: gdebug.FuncPath(handler),
itemFunc: handler,
hookName: hook,
source: source,
})
}

View File

@ -24,12 +24,12 @@ func (s *Server) BindController(pattern string, controller Controller, method ..
if len(method) > 0 {
bindMethod = method[0]
}
s.doBindController(pattern, controller, bindMethod, nil)
s.doBindController(pattern, controller, bindMethod, nil, "")
}
// 绑定路由到指定的方法执行, 第三个参数method仅支持一个方法注册不支持多个并且区分大小写。
func (s *Server) BindControllerMethod(pattern string, controller Controller, method string) {
s.doBindControllerMethod(pattern, controller, method, nil)
s.doBindControllerMethod(pattern, controller, method, nil, "")
}
// 绑定控制器(RESTFul)控制器需要实现gmvc.Controller接口
@ -37,10 +37,13 @@ func (s *Server) BindControllerMethod(pattern string, controller Controller, met
// 因此只会绑定HTTP Method对应的方法其他方法不会自动注册绑定
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
func (s *Server) BindControllerRest(pattern string, controller Controller) {
s.doBindControllerRest(pattern, controller, nil)
s.doBindControllerRest(pattern, controller, nil, "")
}
func (s *Server) doBindController(pattern string, controller Controller, method string, middleware []HandlerFunc) {
func (s *Server) doBindController(
pattern string, controller Controller, method string,
middleware []HandlerFunc, source string,
) {
// Convert input method to map for convenience and high performance searching.
var methodMap map[string]bool
if len(method) > 0 {
@ -98,6 +101,7 @@ func (s *Server) doBindController(pattern string, controller Controller, method
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index
@ -117,13 +121,20 @@ func (s *Server) doBindController(pattern string, controller Controller, method
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
}
}
s.bindHandlerByMap(m)
}
func (s *Server) doBindControllerMethod(pattern string, controller Controller, method string, middleware []HandlerFunc) {
func (s *Server) doBindControllerMethod(
pattern string,
controller Controller,
method string,
middleware []HandlerFunc,
source string,
) {
m := make(map[string]*handlerItem)
v := reflect.ValueOf(controller)
t := v.Type()
@ -141,8 +152,10 @@ func (s *Server) doBindControllerMethod(pattern string, controller Controller, m
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := methodValue.Interface().(func()); !ok {
s.Logger().Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, methodValue.Type().String())
s.Logger().Errorf(
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, methodValue.Type().String(),
)
return
}
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
@ -154,11 +167,15 @@ func (s *Server) doBindControllerMethod(pattern string, controller Controller, m
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
s.bindHandlerByMap(m)
}
func (s *Server) doBindControllerRest(pattern string, controller Controller, middleware []HandlerFunc) {
func (s *Server) doBindControllerRest(
pattern string, controller Controller,
middleware []HandlerFunc, source string,
) {
// 遍历控制器获取方法列表并构造成uri
m := make(map[string]*handlerItem)
v := reflect.ValueOf(controller)
@ -177,8 +194,10 @@ func (s *Server) doBindControllerRest(pattern string, controller Controller, mid
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s.Logger().Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, v.Method(i).Type().String())
s.Logger().Errorf(
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
)
return
}
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
@ -190,6 +209,7 @@ func (s *Server) doBindControllerRest(pattern string, controller Controller, mid
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
}
s.bindHandlerByMap(m)

View File

@ -16,18 +16,22 @@ import (
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (s *Server) BindHandler(pattern string, handler HandlerFunc) {
s.doBindHandler(pattern, handler, nil)
s.doBindHandler(pattern, handler, nil, "")
}
// 绑定URI到操作函数/方法
// pattern的格式形如/user/list, put:/user, delete:/user, post:/user@johng.cn
// 支持RESTful的请求格式具体业务逻辑由绑定的处理方法来执行
func (s *Server) doBindHandler(pattern string, handler HandlerFunc, middleware []HandlerFunc) {
func (s *Server) doBindHandler(
pattern string, handler HandlerFunc,
middleware []HandlerFunc, source string,
) {
s.setHandler(pattern, &handlerItem{
itemName: gdebug.FuncPath(handler),
itemType: gHANDLER_TYPE_HANDLER,
itemFunc: handler,
middleware: middleware,
source: source,
})
}

View File

@ -23,22 +23,25 @@ func (s *Server) BindObject(pattern string, object interface{}, method ...string
if len(method) > 0 {
bindMethod = method[0]
}
s.doBindObject(pattern, object, bindMethod, nil)
s.doBindObject(pattern, object, bindMethod, nil, "")
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数method仅支持一个方法注册不支持多个并且区分大小写。
func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) {
s.doBindObjectMethod(pattern, object, method, nil)
s.doBindObjectMethod(pattern, object, method, nil, "")
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面,
// 需要注意对象方法的定义必须按照 ghttp.HandlerFunc 来定义
func (s *Server) BindObjectRest(pattern string, object interface{}) {
s.doBindObjectRest(pattern, object, nil)
s.doBindObjectRest(pattern, object, nil, "")
}
func (s *Server) doBindObject(pattern string, object interface{}, method string, middleware []HandlerFunc) {
func (s *Server) doBindObject(
pattern string, object interface{}, method string,
middleware []HandlerFunc, source string,
) {
// Convert input method to map for convenience and high performance searching purpose.
var methodMap map[string]bool
if len(method) > 0 {
@ -107,6 +110,7 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
initFunc: initFunc,
shutFunc: shutFunc,
middleware: middleware,
source: source,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI。
// 注意当pattern带有内置变量时不会自动加该路由。
@ -123,6 +127,7 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
initFunc: initFunc,
shutFunc: shutFunc,
middleware: middleware,
source: source,
}
}
}
@ -131,7 +136,10 @@ func (s *Server) doBindObject(pattern string, object interface{}, method string,
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数method仅支持一个方法注册不支持多个并且区分大小写。
func (s *Server) doBindObjectMethod(pattern string, object interface{}, method string, middleware []HandlerFunc) {
func (s *Server) doBindObjectMethod(
pattern string, object interface{}, method string,
middleware []HandlerFunc, source string,
) {
m := make(map[string]*handlerItem)
v := reflect.ValueOf(object)
t := v.Type()
@ -170,12 +178,16 @@ func (s *Server) doBindObjectMethod(pattern string, object interface{}, method s
initFunc: initFunc,
shutFunc: shutFunc,
middleware: middleware,
source: source,
}
s.bindHandlerByMap(m)
}
func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware []HandlerFunc) {
func (s *Server) doBindObjectRest(
pattern string, object interface{},
middleware []HandlerFunc, source string,
) {
m := make(map[string]*handlerItem)
v := reflect.ValueOf(object)
t := v.Type()
@ -213,6 +225,7 @@ func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware
initFunc: initFunc,
shutFunc: shutFunc,
middleware: middleware,
source: source,
}
}
s.bindHandlerByMap(m)

View File

@ -7,6 +7,7 @@
package ghttp_test
import (
"context"
"fmt"
"testing"
"time"
@ -21,15 +22,15 @@ func Test_Context(t *testing.T) {
s := g.Server(p)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(func(r *ghttp.Request) {
r.Context.Set("traceid", 123)
r.Context = context.WithValue(r.Context, "traceid", 123)
r.Middleware.Next()
})
group.GET("/", func(r *ghttp.Request) {
r.Response.Write(r.Context.Get("traceid"))
r.Response.Write(r.Context.Value("traceid"))
})
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()

View File

@ -178,7 +178,7 @@ func Test_Middleware_With_Static(t *testing.T) {
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.SetServerRoot(gfile.Join(gdebug.CallerDirectory(), "testdata", "static1"))
s.SetServerRoot(gfile.Join(gdebug.TestDataPath(), "static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
@ -250,7 +250,7 @@ func Test_Middleware_Hook_With_Static(t *testing.T) {
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetServerRoot(gfile.Join(gdebug.CallerDirectory(), "testdata", "static1"))
s.SetServerRoot(gfile.Join(gdebug.TestDataPath(), "static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)

View File

@ -15,7 +15,7 @@ import (
"time"
)
func Test_Middleware_CORS(t *testing.T) {
func Test_Middleware_CORS1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
@ -40,6 +40,7 @@ func Test_Middleware_CORS(t *testing.T) {
resp, err := client.Get("/api.v2/user/list")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 0)
gtest.Assert(resp.StatusCode, 404)
resp.Close()
// POST request matches the route and CORS middleware.
@ -61,6 +62,7 @@ func Test_Middleware_CORS(t *testing.T) {
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 0)
gtest.Assert(resp.ReadAllString(), "Not Found")
gtest.Assert(resp.StatusCode, 404)
resp.Close()
})
// OPTIONS POST
@ -71,6 +73,73 @@ func Test_Middleware_CORS(t *testing.T) {
resp, err := client.Options("/api.v2/user/list")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 1)
gtest.Assert(resp.StatusCode, 200)
resp.Close()
})
}
func Test_Middleware_CORS2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.GET("/user/list/{type}", func(r *ghttp.Request) {
r.Response.Write(r.Get("type"))
})
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
// Common Checks.
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/api.v2"), "Not Found")
// Get request.
resp, err := client.Get("/api.v2/user/list/1")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 1)
gtest.Assert(resp.Header["Access-Control-Allow-Headers"][0], "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With")
gtest.Assert(resp.Header["Access-Control-Allow-Methods"][0], "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE")
gtest.Assert(resp.Header["Access-Control-Allow-Origin"][0], "*")
gtest.Assert(resp.Header["Access-Control-Max-Age"][0], "3628800")
gtest.Assert(resp.ReadAllString(), "1")
resp.Close()
})
// OPTIONS GET None.
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
client.SetHeader("Access-Control-Request-Method", "GET")
resp, err := client.Options("/api.v2/user")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 0)
gtest.Assert(resp.StatusCode, 404)
resp.Close()
})
// OPTIONS GET
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
client.SetHeader("Access-Control-Request-Method", "GET")
resp, err := client.Options("/api.v2/user/list/1")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 1)
gtest.Assert(resp.StatusCode, 200)
resp.Close()
})
// OPTIONS POST
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
client.SetHeader("Access-Control-Request-Method", "POST")
resp, err := client.Options("/api.v2/user/list/1")
gtest.Assert(err, nil)
gtest.Assert(len(resp.Header["Access-Control-Allow-Headers"]), 0)
gtest.Assert(resp.StatusCode, 404)
resp.Close()
})
}

View File

@ -45,7 +45,7 @@ func Test_Params_File_Single(t *testing.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file1.txt")
srcPath := gfile.Join(gdebug.TestDataPath(), "upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent("/upload/single", g.Map{
"file": "@file:" + srcPath,
@ -61,7 +61,7 @@ func Test_Params_File_Single(t *testing.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file2.txt")
srcPath := gfile.Join(gdebug.TestDataPath(), "upload", "file2.txt")
content := client.PostContent("/upload/single", g.Map{
"file": "@file:" + srcPath,
"randomlyRename": true,
@ -98,7 +98,7 @@ func Test_Params_File_CustomName(t *testing.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
srcPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file1.txt")
srcPath := gfile.Join(gdebug.TestDataPath(), "upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "my.txt")
content := client.PostContent("/upload/single", g.Map{
"file": "@file:" + srcPath,
@ -135,8 +135,8 @@ func Test_Params_File_Batch(t *testing.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
srcPath1 := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file1.txt")
srcPath2 := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file2.txt")
srcPath1 := gfile.Join(gdebug.TestDataPath(), "upload", "file1.txt")
srcPath2 := gfile.Join(gdebug.TestDataPath(), "upload", "file2.txt")
dstPath1 := gfile.Join(dstDirPath, "file1.txt")
dstPath2 := gfile.Join(dstDirPath, "file2.txt")
content := client.PostContent("/upload/batch", g.Map{
@ -155,8 +155,8 @@ func Test_Params_File_Batch(t *testing.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
srcPath1 := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file1.txt")
srcPath2 := gfile.Join(gdebug.CallerDirectory(), "testdata", "upload", "file2.txt")
srcPath1 := gfile.Join(gdebug.TestDataPath(), "upload", "file1.txt")
srcPath2 := gfile.Join(gdebug.TestDataPath(), "upload", "file2.txt")
content := client.PostContent("/upload/batch", g.Map{
"file[0]": "@file:" + srcPath1,
"file[1]": "@file:" + srcPath2,

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 分组路由测试
package ghttp_test
import (
@ -118,7 +117,7 @@ func Test_Router_GroupBasic1(t *testing.T) {
})
}
func Test_Router_Basic2(t *testing.T) {
func Test_Router_GroupBasic2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
@ -191,3 +190,27 @@ func Test_Router_GroupBuildInVar(t *testing.T) {
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}
func Test_Router_Group_Mthods(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
group := s.Group("/")
group.ALL("/obj", obj, "Show, Delete")
group.ALL("/ctl", ctl, "Show, Post")
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/ctl/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/ctl/post"), "1Controller Post2")
gtest.Assert(client.GetContent("/obj/show"), "1Object Show2")
gtest.Assert(client.GetContent("/obj/delete"), "1Object Delete2")
})
}

View File

@ -66,7 +66,7 @@ func Test_Static_ServerRoot_Security(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
s.SetServerRoot(gfile.Join(gdebug.CallerDirectory(), "testdata", "static1"))
s.SetServerRoot(gfile.Join(gdebug.TestDataPath(), "static1"))
s.SetPort(p)
s.Start()
defer s.Shutdown()

View File

@ -24,7 +24,7 @@ import (
func Test_Template_Layout1(t *testing.T) {
gtest.Case(t, func() {
v := gview.New(gfile.Join(gdebug.CallerDirectory(), "testdata", "template", "layout1"))
v := gview.New(gfile.Join(gdebug.TestDataPath(), "template", "layout1"))
p := ports.PopRand()
s := g.Server(p)
s.SetView(v)
@ -54,7 +54,7 @@ func Test_Template_Layout1(t *testing.T) {
func Test_Template_Layout2(t *testing.T) {
gtest.Case(t, func() {
v := gview.New(gfile.Join(gdebug.CallerDirectory(), "testdata", "template", "layout2"))
v := gview.New(gfile.Join(gdebug.TestDataPath(), "template", "layout2"))
p := ports.PopRand()
s := g.Server(p)
s.SetView(v)

View File

@ -94,7 +94,7 @@ func (c *Conn) Send(data []byte, retry ...Retry) error {
}
}
// Recv receives data from the connection.
// Recv receives and returns data from the connection.
//
// Note that,
// 1. If length = 0, which means it receives the data from current buffer and returns immediately.

View File

@ -23,10 +23,10 @@ type PoolConn struct {
}
const (
gDEFAULT_POOL_EXPIRE = 10000 // (Millisecond) Default TTL for connection in the pool.
gCONN_STATUS_UNKNOWN = 0 // Means it is unknown it's connective or not.
gCONN_STATUS_ACTIVE = 1 // Means it is now connective.
gCONN_STATUS_ERROR = 2 // Means it should be closed and removed from pool.
gDEFAULT_POOL_EXPIRE = 10 * time.Second // Default TTL for connection in the pool.
gCONN_STATUS_UNKNOWN = 0 // Means it is unknown it's connective or not.
gCONN_STATUS_ACTIVE = 1 // Means it is now connective.
gCONN_STATUS_ERROR = 2 // Means it should be closed and removed from pool.
)
var (

View File

@ -23,7 +23,7 @@ type Conn struct {
const (
gDEFAULT_RETRY_INTERVAL = 100 * time.Millisecond // Retry interval.
gDEFAULT_READ_BUFFER_SIZE = 64 // (KB)Buffer size.
gDEFAULT_READ_BUFFER_SIZE = 1024 // (Byte)Buffer size.
gRECV_ALL_WAIT_TIMEOUT = time.Millisecond // Default interval for reading buffer.
)
@ -82,69 +82,33 @@ func (c *Conn) Send(data []byte, retry ...Retry) (err error) {
}
}
// Recv receives data from remote address.
// Recv receives and returns data from remote address.
// The parameter <buffer> is used for customizing the receiving buffer size. If <buffer> <= 0,
// it uses the default buffer size, which is 1024 byte.
//
// Note that,
// 1. There's package border in UDP protocol, so we can receive a complete package if it specifies length < 0.
// 2. If length = 0, it means it receives the data from current buffer and returns immediately.
// 3. If length > 0, it means it blocks reading data from connection until length size was received.
func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) {
// There's package border in UDP protocol, we can receive a complete package if specified
// buffer size is big enough. VERY NOTE that we should receive the complete package in once
// or else the leftover package data would be dropped.
func (c *Conn) Recv(buffer int, retry ...Retry) ([]byte, error) {
var err error // Reading error.
var size int // Reading size.
var index int // Received size.
var buffer []byte // Buffer object.
var bufferWait bool // Whether buffer reading timeout set.
var data []byte // Buffer object.
var remoteAddr *net.UDPAddr // Current remote address for reading.
if length > 0 {
buffer = make([]byte, length)
if buffer > 0 {
data = make([]byte, buffer)
} else {
buffer = make([]byte, gDEFAULT_READ_BUFFER_SIZE)
data = make([]byte, gDEFAULT_READ_BUFFER_SIZE)
}
for {
if length < 0 && index > 0 {
bufferWait = true
if err = c.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil {
return nil, err
}
}
size, remoteAddr, err = c.ReadFromUDP(buffer[index:])
size, remoteAddr, err = c.ReadFromUDP(data)
if err == nil {
c.remoteAddr = remoteAddr
}
if size > 0 {
index += size
if length > 0 {
// It reads til <length> size if <length> is specified.
if index == length {
break
}
} else {
if index >= gDEFAULT_READ_BUFFER_SIZE {
// If it exceeds the buffer size, it then automatically increases its buffer size.
buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...)
} else {
// It returns immediately if received size is lesser than buffer size.
if !bufferWait {
break
}
}
}
}
if err != nil {
// Connection closed.
if err == io.EOF {
break
}
// Re-set the timeout when reading data.
if bufferWait && isTimeout(err) {
if err = c.SetReadDeadline(c.recvDeadline); err != nil {
return nil, err
}
err = nil
break
}
if len(retry) > 0 {
// It fails even it retried.
if retry[0].Count == 0 {
@ -159,12 +123,9 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) {
}
break
}
// Just read once from buffer.
if length == 0 {
break
}
break
}
return buffer[:index], err
return data[:size], err
}
// SendRecv writes data to connection and blocks reading response.

View File

@ -52,14 +52,3 @@ func SendRecv(address string, data []byte, receive int, retry ...Retry) ([]byte,
defer conn.Close()
return conn.SendRecv(data, receive, retry...)
}
// isTimeout checks whether given <err> is a timeout error.
func isTimeout(err error) bool {
if err == nil {
return false
}
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
return true
}
return false
}

View File

@ -0,0 +1,106 @@
// 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 gudp_test
import (
"fmt"
"github.com/gogf/gf/net/gudp"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"time"
)
func Test_Basic(t *testing.T) {
p := ports.PopRand()
s := gudp.NewServer(fmt.Sprintf("127.0.0.1:%d", p), func(conn *gudp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
if err := conn.Send(append([]byte("> "), data...)); err != nil {
glog.Error(err)
}
}
if err != nil {
break
}
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
// gudp.Conn.Send
gtest.Case(t, func() {
for i := 0; i < 100; i++ {
conn, err := gudp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
gtest.Assert(conn.Send([]byte(gconv.String(i))), nil)
conn.Close()
}
})
// gudp.Conn.SendRecv
gtest.Case(t, func() {
for i := 0; i < 100; i++ {
conn, err := gudp.NewConn(fmt.Sprintf("127.0.0.1:%d", p))
gtest.Assert(err, nil)
result, err := conn.SendRecv([]byte(gconv.String(i)), -1)
gtest.Assert(err, nil)
gtest.Assert(string(result), fmt.Sprintf(`> %d`, i))
conn.Close()
}
})
// gudp.Send
gtest.Case(t, func() {
for i := 0; i < 100; i++ {
err := gudp.Send(fmt.Sprintf("127.0.0.1:%d", p), []byte(gconv.String(i)))
gtest.Assert(err, nil)
}
})
// gudp.SendRecv
gtest.Case(t, func() {
for i := 0; i < 100; i++ {
result, err := gudp.SendRecv(fmt.Sprintf("127.0.0.1:%d", p), []byte(gconv.String(i)), -1)
gtest.Assert(err, nil)
gtest.Assert(string(result), fmt.Sprintf(`> %d`, i))
}
})
}
// If the read buffer size is less than the sent package size,
// the rest data would be dropped.
func Test_Buffer(t *testing.T) {
p := ports.PopRand()
s := gudp.NewServer(fmt.Sprintf("127.0.0.1:%d", p), func(conn *gudp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(1)
if len(data) > 0 {
if err := conn.Send(data); err != nil {
glog.Error(err)
}
}
if err != nil {
break
}
}
})
go s.Run()
defer s.Close()
time.Sleep(100 * time.Millisecond)
gtest.Case(t, func() {
result, err := gudp.SendRecv(fmt.Sprintf("127.0.0.1:%d", p), []byte("123"), -1)
gtest.Assert(err, nil)
gtest.Assert(string(result), "1")
})
gtest.Case(t, func() {
result, err := gudp.SendRecv(fmt.Sprintf("127.0.0.1:%d", p), []byte("456"), -1)
gtest.Assert(err, nil)
gtest.Assert(string(result), "4")
})
}

View File

@ -0,0 +1,21 @@
// 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 gudp_test
import (
"github.com/gogf/gf/container/garray"
)
var (
ports = garray.NewIntArray(true)
)
func init() {
for i := 9000; i <= 10000; i++ {
ports.Append(i)
}
}

View File

@ -17,7 +17,6 @@ import (
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/internal/cmdenv"
"github.com/gogf/gf/os/gfile"
@ -33,10 +32,10 @@ const (
// Configuration struct.
type Config struct {
name *gtype.String // Default configuration file name.
name string // Default configuration file name.
paths *garray.StrArray // Searching path array.
jsons *gmap.StrAnyMap // The pared JSON objects for configuration files.
vc *gtype.Bool // Whether do violence check in value index searching. It affects the performance when set true(false in default).
vc bool // Whether do violence check in value index searching. It affects the performance when set true(false in default).
}
var (
@ -51,10 +50,9 @@ func New(file ...string) *Config {
name = file[0]
}
c := &Config{
name: gtype.NewString(name),
name: name,
paths: garray.NewStrArray(true),
jsons: gmap.NewStrAnyMap(true),
vc: gtype.NewBool(),
}
// Customized dir path from env/cmd.
if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" {
@ -82,7 +80,7 @@ func New(file ...string) *Config {
// filePath returns the absolute configuration file path for the given filename by <file>.
func (c *Config) filePath(file ...string) (path string) {
name := c.name.Val()
name := c.name
if len(file) > 0 {
name = file[0]
}
@ -182,7 +180,7 @@ func (c *Config) SetPath(path string) error {
// and it is not recommended to allow separators in the key names.
// It is best to avoid this on the application side.
func (c *Config) SetViolenceCheck(check bool) {
c.vc.Set(check)
c.vc = check
c.Clear()
}
@ -250,7 +248,7 @@ func (c *Config) AddPath(path string) error {
// If the specified configuration file does not exist,
// an empty string is returned.
func (c *Config) FilePath(file ...string) (path string) {
name := c.name.Val()
name := c.name
if len(file) > 0 {
name = file[0]
}
@ -290,13 +288,13 @@ func (c *Config) FilePath(file ...string) (path string) {
// SetFileName sets the default configuration file name.
func (c *Config) SetFileName(name string) *Config {
c.name.Set(name)
c.name = name
return c
}
// GetFileName returns the default configuration file name.
func (c *Config) GetFileName() string {
return c.name.Val()
return c.name
}
// Available checks and returns whether configuration of given <file> is available.
@ -305,7 +303,7 @@ func (c *Config) Available(file ...string) bool {
if len(file) > 0 && file[0] != "" {
name = file[0]
} else {
name = c.name.Val()
name = c.name
}
if c.FilePath(name) != "" {
return true
@ -323,7 +321,7 @@ func (c *Config) getJson(file ...string) *gjson.Json {
if len(file) > 0 && file[0] != "" {
name = file[0]
} else {
name = c.name.Val()
name = c.name
}
r := c.jsons.GetOrSetFuncLock(name, func() interface{} {
content := ""
@ -340,7 +338,7 @@ func (c *Config) getJson(file ...string) *gjson.Json {
}
}
if j, err := gjson.LoadContent(content, true); err == nil {
j.SetViolenceCheck(c.vc.Val())
j.SetViolenceCheck(c.vc)
// Add monitor for this configuration file,
// any changes of this file will refresh its cache in Config object.
if filePath != "" && !gres.Contains(filePath) {

View File

@ -7,6 +7,7 @@
package gcfg
import (
"fmt"
"github.com/gogf/gf/container/gmap"
)
@ -16,18 +17,25 @@ const (
)
var (
// Instances map.
// Instances map containing configuration instances.
instances = gmap.NewStrAnyMap(true)
)
// Instance returns an instance of Config with default settings.
// The parameter <name> is the name for the instance.
// The parameter <name> is the name for the instance. But very note that, if the file "name.toml"
// exists in the configuration directory, it then sets it as the default configuration file. The
// toml file type is the default configuration file type.
func Instance(name ...string) *Config {
key := DEFAULT_NAME
if len(name) > 0 && name[0] != "" {
key = name[0]
}
return instances.GetOrSetFuncLock(key, func() interface{} {
return New()
c := New()
file := fmt.Sprintf(`%s.toml`, key)
if c.Available(file) {
c.SetFileName(file)
}
return c
}).(*Config)
}

View File

@ -9,7 +9,8 @@
package gcfg_test
import (
"io/ioutil"
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/os/gtime"
"os"
"testing"
@ -39,9 +40,7 @@ array = [1,2,3]
path := gcfg.DEFAULT_CONFIG_FILE
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer func() {
_ = gfile.Remove(path)
}()
defer gfile.Remove(path)
c := gcfg.New()
gtest.Assert(c.Get("v1"), 1)
@ -309,9 +308,8 @@ func TestCfg_New(t *testing.T) {
configPath := gfile.Pwd() + gfile.Separator + "config"
_ = gfile.Mkdir(configPath)
defer func() {
_ = gfile.Remove(configPath)
}()
defer gfile.Remove(configPath)
c = gcfg.New("config.yml")
gtest.Assert(c.Get("name"), nil)
@ -363,12 +361,21 @@ func TestCfg_FilePath(t *testing.T) {
func TestCfg_Get(t *testing.T) {
gtest.Case(t, func() {
configPath := gfile.Pwd() + gfile.Separator + "config"
_ = gfile.Mkdir(configPath)
defer func() {
_ = gfile.Remove(configPath)
}()
_ = ioutil.WriteFile(configPath+gfile.Separator+"config.yml", []byte("wrong config"), 0644)
var err error
configPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
err = gfile.Mkdir(configPath)
gtest.Assert(err, nil)
defer gfile.Remove(configPath)
defer gfile.Chdir(gfile.Pwd())
err = gfile.Chdir(configPath)
gtest.Assert(err, nil)
err = gfile.PutContents(
gfile.Join(configPath, "config.yml"),
"wrong config",
)
gtest.Assert(err, nil)
c := gcfg.New("config.yml")
gtest.Assert(c.Get("name"), nil)
gtest.Assert(c.GetVar("name").Val(), nil)
@ -403,13 +410,24 @@ func TestCfg_Get(t *testing.T) {
c.Clear()
arr, _ := gjson.Encode(g.Map{"name": "gf", "time": "2019-06-12", "person": g.Map{"name": "gf"}, "floats": g.Slice{1, 2, 3}})
_ = ioutil.WriteFile(configPath+gfile.Separator+"config.yml", arr, 0644)
arr, _ := gjson.Encode(
g.Map{
"name": "gf",
"time": "2019-06-12",
"person": g.Map{"name": "gf"},
"floats": g.Slice{1, 2, 3},
},
)
err = gfile.PutBytes(
gfile.Join(configPath, "config.yml"),
arr,
)
gtest.Assert(err, nil)
gtest.Assert(c.GetTime("time").Format("2006-01-02"), "2019-06-12")
gtest.Assert(c.GetGTime("time").Format("Y-m-d"), "2019-06-12")
gtest.Assert(c.GetDuration("time").String(), "0s")
//t.Log(c.GetString("person"))
err := c.GetStruct("person", &name)
err = c.GetStruct("person", &name)
gtest.Assert(err, nil)
gtest.Assert(name.Name, "gf")
gtest.Assert(c.GetFloats("floats") == nil, false)
@ -420,6 +438,14 @@ func TestCfg_Instance(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gcfg.Instance("gf") != nil, true)
})
gtest.Case(t, func() {
pwd := gfile.Pwd()
gfile.Chdir(gfile.Join(gdebug.TestDataPath()))
defer gfile.Chdir(pwd)
gtest.Assert(gcfg.Instance("c1") != nil, true)
gtest.Assert(gcfg.Instance("c1").Get("my-config"), "1")
gtest.Assert(gcfg.Instance("folder1/c1").Get("my-config"), "2")
})
}
func TestCfg_Config(t *testing.T) {

2
os/gcfg/testdata/c1.toml vendored Normal file
View File

@ -0,0 +1,2 @@
my-config = "1"

2
os/gcfg/testdata/folder1/c1.toml vendored Normal file
View File

@ -0,0 +1,2 @@
my-config = "2"

View File

@ -312,7 +312,7 @@ func SelfDir() string {
return filepath.Dir(SelfPath())
}
// Basename returns the last element of path.
// Basename returns the last element of path, which contains file extension.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of separators, Basename returns a single separator.
@ -320,7 +320,7 @@ func Basename(path string) string {
return filepath.Base(path)
}
// Name returns the last element of path without extension.
// Name returns the last element of path without file extension.
func Name(path string) string {
base := filepath.Base(path)
if i := strings.LastIndexByte(base, '.'); i != -1 {

View File

@ -25,5 +25,5 @@ func MTimeMillisecond(path string) int64 {
if e != nil {
return 0
}
return int64(s.ModTime().Nanosecond() / 1000000)
return s.ModTime().UnixNano() / 1000000
}

View File

@ -9,6 +9,7 @@ package gfile_test
import (
"os"
"testing"
"time"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/test/gtest"
@ -18,7 +19,7 @@ func Test_MTime(t *testing.T) {
gtest.Case(t, func() {
var (
file1 string = "/testfile_t1.txt"
file1 = "/testfile_t1.txt"
err error
fileobj os.FileInfo
)
@ -36,7 +37,7 @@ func Test_MTime(t *testing.T) {
func Test_MTimeMillisecond(t *testing.T) {
gtest.Case(t, func() {
var (
file1 string = "/testfile_t1.txt"
file1 = "/testfile_t1.txt"
err error
fileobj os.FileInfo
)
@ -46,7 +47,11 @@ func Test_MTimeMillisecond(t *testing.T) {
fileobj, err = os.Stat(testpath() + file1)
gtest.Assert(err, nil)
gtest.AssertGE(gfile.MTimeMillisecond(testpath()+file1), fileobj.ModTime().Nanosecond()/1000000)
time.Sleep(time.Millisecond * 100)
gtest.AssertGE(
gfile.MTimeMillisecond(testpath()+file1),
fileobj.ModTime().UnixNano()/1000000,
)
gtest.Assert(gfile.MTimeMillisecond(""), 0)
})
}

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