diff --git a/.example/database/gkvdb/badger/main.go b/.example/database/gkvdb/badger/main.go new file mode 100644 index 000000000..6bcbc3fdd --- /dev/null +++ b/.example/database/gkvdb/badger/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "log" + + "github.com/dgraph-io/badger" +) + +func main() { + // Open the Badger database located in the /tmp/badger directory. + // It will be created if it doesn't exist. + db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) + if err != nil { + log.Fatal(err) + } + defer db.Close() + // Your code here… +} diff --git a/.example/database/gkvdb/main1.go b/.example/database/gkvdb/main1.go new file mode 100644 index 000000000..e8f5172a9 --- /dev/null +++ b/.example/database/gkvdb/main1.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "github.com/gogf/gf/database/gkvdb" +) + +func main() { + key := []byte("key") + //value := []byte("value") + + db := gkvdb.Instance() + db.SetPath("/tmp/gkvdb/test") + //db.Set(key, value) + fmt.Println(db.Get(key)) +} diff --git a/.example/net/ghttp/server/session/session.go b/.example/net/ghttp/server/session/session.go index ed25b849e..5d41d5712 100644 --- a/.example/net/ghttp/server/session/session.go +++ b/.example/net/ghttp/server/session/session.go @@ -8,7 +8,7 @@ import ( func main() { s := g.Server() - s.SetSessionMaxAge(72000000) + s.SetSessionMaxAge(60) s.SetSessionIdName("gpadminssid") s.BindHandler("/set", func(r *ghttp.Request) { r.Session.Set("time", gtime.Second()) diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index 12b2815fe..634a5bb15 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -172,6 +172,19 @@ func (v *Var) Interfaces() []interface{} { return gconv.Interfaces(v.Val()) } +// Vars converts and returns as []*Var. +func (v *Var) Vars() []*Var { + array := gconv.Interfaces(v.Val()) + if len(array) == 0 { + return nil + } + vars := make([]*Var, len(array)) + for k, v := range array { + vars[k] = New(v) + } + return vars +} + // Time converts and returns as time.Time. // The parameter specifies the format of the time string using gtime, // eg: Y-m-d H:i:s. diff --git a/database/gkvdb/gkvdb.go b/database/gkvdb/gkvdb.go new file mode 100644 index 000000000..fed37002b --- /dev/null +++ b/database/gkvdb/gkvdb.go @@ -0,0 +1,50 @@ +// 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 gkvdb provides a lightweight, embeddable and persistent key-value database. +package gkvdb + +import ( + "github.com/dgraph-io/badger" + "github.com/gogf/gf/container/gmap" +) + +type Options = badger.Options + +const ( + // Default instance name. + DEFAULT_NAME = "default" +) + +var ( + // Instance map + instances = gmap.NewStrAnyMap(true) +) + +// DefaultOptions returns the default options for gkvdb. +func DefaultOptions(path string) Options { + options := badger.DefaultOptions(path) + options.Logger = nil + options.SyncWrites = false + return options +} + +// Instance returns an instance of DB with specified name. +// The param is unnecessary, if is not passed, +// it returns a instance with default name. +func Instance(name ...string) *DB { + instanceName := DEFAULT_NAME + if len(name) > 0 && name[0] != "" { + instanceName = name[0] + } + v := instances.GetOrSetFuncLock(instanceName, func() interface{} { + return New() + }) + if v != nil { + return v.(*DB) + } + return nil +} diff --git a/database/gkvdb/gkvdb_db.go b/database/gkvdb/gkvdb_db.go new file mode 100644 index 000000000..36d39eb96 --- /dev/null +++ b/database/gkvdb/gkvdb_db.go @@ -0,0 +1,149 @@ +// 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 gkvdb + +import ( + "errors" + "time" + + "github.com/gogf/gf/os/gfile" + + "github.com/dgraph-io/badger" +) + +type DB struct { + options Options + badger *badger.DB +} + +// New creates and returns a new db object. +func New(options ...Options) *DB { + if len(options) > 0 { + return &DB{options: options[0]} + } + return &DB{options: DefaultOptions("")} +} + +// init does lazy initialization for db. +func (db *DB) init() (err error) { + if db.badger != nil { + return nil + } + if !gfile.Exists(db.options.Dir) { + err = gfile.Mkdir(db.options.Dir) + if err != nil { + return + } + } + db.badger, err = badger.Open(db.options) + return +} + +// Options return the options of current db object. +func (db *DB) Options() *Options { + return &db.options +} + +// SetOptions sets the options for db. +func (db *DB) SetOptions(options Options) error { + if db.badger != nil { + return errors.New("options cannot be changed after db is initialized") + } + db.options = options + return nil +} + +// SetPath sets the storage folder path for db. +func (db *DB) SetPath(path string) error { + if db.badger != nil { + return errors.New("options cannot be changed after db is initialized") + } + db.options.Dir = path + db.options.ValueDir = path + return nil +} + +// Size returns the data count of current db. +func (db *DB) Size() int64 { + if err := db.init(); err != nil { + return 0 + } + lsm, vlog := db.badger.Size() + return lsm + vlog +} + +// Set sets - pair data to current db with . +// The is optional, which is not expired in default. +func (db *DB) Set(key []byte, value []byte, ttl ...time.Duration) (err error) { + if err := db.init(); err != nil { + return err + } + tx := db.Begin(true) + defer tx.Commit() + return tx.Set(key, value, ttl...) +} + +// Get returns the value with given key. +// It returns nil if is not found in the db. +func (db *DB) Get(key []byte) (value []byte) { + if err := db.init(); err != nil { + return + } + tx := db.Begin(false) + defer tx.Rollback() + return tx.Get(key) +} + +// Delete removed data specified by from current db. +func (db *DB) Delete(key []byte) error { + if err := db.init(); err != nil { + return err + } + tx := db.Begin(true) + defer tx.Commit() + return tx.Delete(key) +} + +// Close closes the db. +func (db *DB) Close() error { + if db.badger == nil { + return nil + } + return db.badger.Close() +} + +// Iterate is alias of IterateAsc. +// See IterateAsc. +func (db *DB) Iterate(prefix []byte, f func(key, value []byte) bool) { + db.IterateAsc(prefix, f) +} + +// IteratorAsc iterates the db in ascending order +// with given callback function starting from . +// If is nil it iterates from the beginning of the db. +// If returns true, then it continues iterating; or false to stop. +func (db *DB) IterateAsc(prefix []byte, f func(key, value []byte) bool) { + if err := db.init(); err != nil { + return + } + tx := db.Begin(false) + defer tx.Rollback() + tx.IterateAsc(prefix, f) +} + +// IteratorDesc iterates the db in descending order +// with given callback function starting from . +// If is nil it iterates from the beginning of the db. +// If returns true, then it continues iterating; or false to stop. +func (db *DB) IterateDesc(prefix []byte, f func(key, value []byte) bool) { + if err := db.init(); err != nil { + return + } + tx := db.Begin(false) + defer tx.Rollback() + tx.IterateDesc(prefix, f) +} diff --git a/database/gkvdb/gkvdb_tx.go b/database/gkvdb/gkvdb_tx.go new file mode 100644 index 000000000..3cd54eb36 --- /dev/null +++ b/database/gkvdb/gkvdb_tx.go @@ -0,0 +1,116 @@ +// 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 gkvdb + +import ( + "time" + + "github.com/dgraph-io/badger" +) + +// TX is the transaction object for db. +type TX struct { + db *DB + txn *badger.Txn +} + +// Begin starts the transaction for db. +func (db *DB) Begin(update bool) *TX { + if err := db.init(); err != nil { + return nil + } + return &TX{ + db: db, + txn: db.badger.NewTransaction(update), + } +} + +// Commit commits the changes and close the transaction. +func (tx *TX) Commit() error { + return tx.txn.Commit() +} + +// Rollback discards the changes and close the transaction. +func (tx *TX) Rollback() { + tx.txn.Discard() +} + +// Set sets - pair data to current db with in this transaction. +// The is optional, which is not expired in default. +func (tx *TX) Set(key []byte, value []byte, ttl ...time.Duration) error { + if len(ttl) > 0 && ttl[0] > 0 { + return tx.txn.SetEntry(badger.NewEntry(key, value).WithTTL(ttl[0])) + } + return tx.txn.Set(key, value) +} + +// Get returns the value with given key in this transaction. +// It returns nil if is not found in the db. +func (tx *TX) Get(key []byte) (value []byte) { + item, err := tx.txn.Get(key) + if err != nil { + return nil + } + if item.IsDeletedOrExpired() { + return nil + } + value, _ = item.ValueCopy(nil) + return +} + +// Delete removed data specified by from current db in this transaction. +func (tx *TX) Delete(key []byte) error { + return tx.txn.Delete(key) +} + +// Iterate is alias of IterateAsc. +// See IterateAsc. +func (tx *TX) Iterate(prefix []byte, f func(key, value []byte) bool) { + tx.IterateAsc(prefix, f) +} + +// IteratorAsc iterates the db in ascending order +// with given callback function starting from . +// If is nil it iterates from the beginning of the db. +// If returns true, then it continues iterating; or false to stop. +func (tx *TX) IterateAsc(prefix []byte, f func(key, value []byte) bool) { + tx.doIterate(false, prefix, f) +} + +// IteratorDesc iterates the db in descending order +// with given callback function starting from . +// If is nil it iterates from the beginning of the db. +// If returns true, then it continues iterating; or false to stop. +func (tx *TX) IterateDesc(prefix []byte, f func(key, value []byte) bool) { + tx.doIterate(true, prefix, f) +} + +func (tx *TX) doIterate(reverse bool, prefix []byte, f func(key, value []byte) bool) { + options := badger.DefaultIteratorOptions + if prefix != nil { + options.Prefix = prefix + } + options.Reverse = reverse + options.PrefetchSize = 10 + options.PrefetchValues = true + it := tx.txn.NewIterator(options) + defer it.Close() + var k, v []byte + var err error + var item *badger.Item + for it.Rewind(); it.Valid(); it.Next() { + item = it.Item() + k = item.Key() + v, err = item.ValueCopy(nil) + if err != nil { + return + } + if !f(k, v) { + return + } + } +} diff --git a/database/gkvdb/gkvdb_unit_test.go b/database/gkvdb/gkvdb_unit_test.go new file mode 100644 index 000000000..188d308ca --- /dev/null +++ b/database/gkvdb/gkvdb_unit_test.go @@ -0,0 +1,114 @@ +// 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 gkvdb_test + +import ( + "strings" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + + "github.com/gogf/gf/container/garray" + + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/util/gconv" + + "github.com/gogf/gf/database/gkvdb" + + "github.com/gogf/gf/test/gtest" +) + +func Test_New(t *testing.T) { + gtest.Case(t, func() { + path := "/tmp/gkvdb/" + gconv.String(gtime.Nanosecond()) + key := []byte("key") + value := []byte("value") + + db := gkvdb.Instance() + db.SetPath(path) + err := db.Set(key, value) + gtest.Assert(err, nil) + + gtest.Assert(db.Get(key), value) + gtest.Assert(db.Delete(key), nil) + gtest.Assert(db.Get(key), nil) + }) +} + +func Test_Set(t *testing.T) { + gtest.Case(t, func() { + path := "/tmp/gkvdb/" + gconv.String(gtime.Nanosecond()) + key := []byte("key") + value := []byte("value") + + db := gkvdb.Instance() + db.SetPath(path) + err := db.Set(key, value, 100*time.Millisecond) + gtest.Assert(err, nil) + + gtest.Assert(db.Get(key), value) + time.Sleep(200 * time.Millisecond) + gtest.Assert(db.Get(key), nil) + }) +} + +func Test_Iterate(t *testing.T) { + gtest.Case(t, func() { + path := "/tmp/gkvdb/" + gconv.String(gtime.Nanosecond()) + db := gkvdb.Instance() + db.SetPath(path) + + strArray := garray.NewSortedStringArray() + strArrayReverse := garray.NewSortedStringArrayComparator(func(a, b string) int { + switch strings.Compare(a, b) { + case 0: + return 0 + case 1: + return -1 + case -1: + return 1 + } + return 0 + }) + for i := 1; i <= 10; i++ { + key := []byte("key_" + gconv.String(i)) + strArray.Add(string(key)) + strArrayReverse.Add(string(key)) + db.Set(key, key) + } + + array := garray.New() + db.Iterate(nil, func(key, value []byte) bool { + array.Append(string(key)) + return true + }) + gtest.Assert(array.Slice(), strArray.Slice()) + + array = garray.New() + db.IterateDesc(nil, func(key, value []byte) bool { + array.Append(string(key)) + return true + }) + gtest.Assert(array.Slice(), strArrayReverse.Slice()) + + array = garray.New() + db.Iterate([]byte("key_1"), func(key, value []byte) bool { + array.Append(key) + return true + }) + gtest.Assert(array.Slice(), g.Slice{[]byte("key_1"), []byte("key_10")}) + + array = garray.New() + db.IterateAsc([]byte("key_1"), func(key, value []byte) bool { + array.Append(key) + return true + }) + gtest.Assert(array.Slice(), g.Slice{[]byte("key_1"), []byte("key_10")}) + + }) +} diff --git a/database/gredis/gredis.go b/database/gredis/gredis.go index 8c0a5a8ce..083607197 100644 --- a/database/gredis/gredis.go +++ b/database/gredis/gredis.go @@ -22,11 +22,6 @@ import ( "github.com/gomodule/redigo/redis" ) -const ( - gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second - gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second -) - // Redis client. type Redis struct { pool *redis.Pool // Underlying connection pool. @@ -56,9 +51,12 @@ type PoolStats struct { redis.PoolStats } +const ( + gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second + gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second +) + var ( - // Instance map - instances = gmap.NewStrAnyMap(true) // Pool map. pools = gmap.NewStrAnyMap(true) ) @@ -106,28 +104,6 @@ func New(config Config) *Redis { } } -// Instance returns an instance of redis client with specified group. -// The param is unnecessary, if is not passed, -// it returns a redis instance with default group. -func Instance(name ...string) *Redis { - group := DEFAULT_GROUP_NAME - if len(name) > 0 && name[0] != "" { - group = name[0] - } - v := instances.GetOrSetFuncLock(group, func() interface{} { - if config, ok := GetConfig(group); ok { - r := New(config) - r.group = group - return r - } - return nil - }) - if v != nil { - return v.(*Redis) - } - return nil -} - // Close closes the redis connection pool, // it will release all connections reserved by this pool. // It is not necessary to call Close manually. diff --git a/database/gredis/gredis_instance.go b/database/gredis/gredis_instance.go new file mode 100644 index 000000000..a7c13e87d --- /dev/null +++ b/database/gredis/gredis_instance.go @@ -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 gredis + +import "github.com/gogf/gf/container/gmap" + +var ( + // Instance map + instances = gmap.NewStrAnyMap(true) +) + +// Instance returns an instance of redis client with specified group. +// The param is unnecessary, if is not passed, +// it returns a redis instance with default configuration group. +func Instance(name ...string) *Redis { + group := DEFAULT_GROUP_NAME + if len(name) > 0 && name[0] != "" { + group = name[0] + } + v := instances.GetOrSetFuncLock(group, func() interface{} { + if config, ok := GetConfig(group); ok { + r := New(config) + r.group = group + return r + } + return nil + }) + if v != nil { + return v.(*Redis) + } + return nil +} diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index 5c36c1eec..13b455bcc 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -73,6 +73,11 @@ func (j *Json) GetVar(pattern string, def ...interface{}) *gvar.Var { return gvar.New(j.Get(pattern, def...)) } +// GetVars returns []*gvar.Var with value by given . +func (j *Json) GetVars(pattern string, def ...interface{}) []*gvar.Var { + return gvar.New(j.Get(pattern, def...)).Vars() +} + // GetMap gets the value by specified , // and converts it to map[string]interface{}. func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{} { diff --git a/frame/g/g_object.go b/frame/g/g_object.go index 1046dc899..2abdfe826 100644 --- a/frame/g/g_object.go +++ b/frame/g/g_object.go @@ -8,6 +8,7 @@ package g import ( "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/database/gkvdb" "github.com/gogf/gf/database/gredis" "github.com/gogf/gf/frame/gins" "github.com/gogf/gf/net/ghttp" @@ -43,6 +44,12 @@ func Config(name ...string) *gcfg.Config { return gins.Config(name...) } +// Cfg is alias of Config. +// See Config. +func Cfg(name ...string) *gcfg.Config { + return Config(name...) +} + // Resource returns an instance of Resource. // The parameter is the name for the instance. func Resource(name ...string) *gres.Resource { @@ -66,6 +73,11 @@ func DB(name ...string) gdb.DB { return gins.Database(name...) } +// KV returns an instance of gkvdb with specified configuration group name. +func KV(name ...string) *gkvdb.DB { + return gins.KV(name...) +} + // Redis returns an instance of redis client with specified configuration group name. func Redis(name ...string) *gredis.Redis { return gins.Redis(name...) diff --git a/frame/gins/gins.go b/frame/gins/gins.go index 1ac847f90..5d48999ad 100644 --- a/frame/gins/gins.go +++ b/frame/gins/gins.go @@ -11,6 +11,10 @@ import ( "fmt" "time" + "github.com/gogf/gf/os/gfile" + + "github.com/gogf/gf/database/gkvdb" + "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/database/gredis" @@ -26,6 +30,7 @@ import ( const ( gFRAME_CORE_COMPONENT_NAME_REDIS = "gf.core.component.redis" + gFRAME_CORE_COMPONENT_NAME_GKVDB = "gf.core.component.gkvdb" gFRAME_CORE_COMPONENT_NAME_DATABASE = "gf.core.component.database" ) @@ -80,15 +85,16 @@ func Resource(name ...string) *gres.Resource { return gres.Instance(name...) } -// 数据库操作对象,使用了连接池 +// Database returns an instance of database ORM object +// with specified configuration group name. func Database(name ...string) gdb.DB { config := Config() group := gdb.DEFAULT_GROUP_NAME if len(name) > 0 && name[0] != "" { group = name[0] } - key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group) - db := instances.GetOrSetFuncLock(key, func() interface{} { + instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group) + db := instances.GetOrSetFuncLock(instanceKey, func() interface{} { if gdb.GetConfig(group) == nil { m := config.GetMap("database") if m == nil { @@ -124,7 +130,7 @@ func Database(name ...string) gdb.DB { gdb.AddConfigGroup(group, cg) } } - addConfigMonitor(key, config) + addConfigMonitor(instanceKey, config) } if db, err := gdb.New(name...); err == nil { return db @@ -139,7 +145,6 @@ func Database(name ...string) gdb.DB { return nil } -// 解析数据库配置节点项 func parseDBConfigNode(value interface{}) *gdb.ConfigNode { nodeMap, ok := value.(map[string]interface{}) if !ok { @@ -217,15 +222,15 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode { return node } -// Redis操作对象,使用了连接池 +// Redis returns an instance of redis client with specified configuration group name. func Redis(name ...string) *gredis.Redis { config := Config() group := "default" if len(name) > 0 && name[0] != "" { group = name[0] } - key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group) - result := instances.GetOrSetFuncLock(key, func() interface{} { + instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group) + result := instances.GetOrSetFuncLock(instanceKey, func() interface{} { if m := config.GetMap("redis"); m != nil { // host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x] if v, ok := m[group]; ok { @@ -251,12 +256,12 @@ func Redis(name ...string) *gredis.Redis { if v, ok := parse["maxConnLifetime"]; ok { redisConfig.MaxConnLifetime = gconv.Duration(v) * time.Second } - addConfigMonitor(key, config) + addConfigMonitor(instanceKey, config) return gredis.New(redisConfig) } array, _ = gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line) if len(array) == 5 { - addConfigMonitor(key, config) + addConfigMonitor(instanceKey, config) return gredis.New(gredis.Config{ Host: array[1], Port: gconv.Int(array[2]), @@ -280,17 +285,42 @@ func Redis(name ...string) *gredis.Redis { return nil } -// 添加对单例对象的配置文件inotify监控 +// KV returns an instance of gkvdb with specified configuration group name. +func KV(name ...string) *gkvdb.DB { + config := Config() + group := "default" + if len(name) > 0 && name[0] != "" { + group = name[0] + } + instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_GKVDB, group) + result := instances.GetOrSetFuncLock(instanceKey, func() interface{} { + key := fmt.Sprintf("kvdb.%s", group) + if s := config.GetString(key); s != "" { + db := gkvdb.Instance(group) + parse, _ := gstr.Parse(s) + if value, ok := parse["path"]; ok { + db.SetPath(gconv.String(value)) + } + if value, ok := parse["sync"]; ok { + db.Options().SyncWrites = gconv.Bool(value) + } + addConfigMonitor(instanceKey, config) + return db + } else { + glog.Errorf(`incomplete configuration for gkvdb: "%s" node not found in config file "%s"`, key, config.FilePath()) + } + return nil + }) + if result != nil { + return result.(*gkvdb.DB) + } + return nil +} + func addConfigMonitor(key string, config *gcfg.Config) { - // 使用gfsnotify进行文件监控,当配置文件有任何变化时,清空对象单例缓存 - if path := config.FilePath(); path != "" { + if path := config.FilePath(); path != "" && gfile.Exists(path) { gfsnotify.Add(path, func(event *gfsnotify.Event) { instances.Remove(key) }) } } - -// 模板内置方法:config -func funcConfig(pattern string, file ...interface{}) string { - return Config().GetString(pattern, file...) -} diff --git a/frame/gins/gins_kvdb_test.go b/frame/gins/gins_kvdb_test.go new file mode 100644 index 000000000..3a562ad47 --- /dev/null +++ b/frame/gins/gins_kvdb_test.go @@ -0,0 +1,44 @@ +// 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 gins_test + +import ( + "testing" + "time" + + "github.com/gogf/gf/frame/gins" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/test/gtest" +) + +func Test_KV(t *testing.T) { + config := ` +[kvdb] + default = "path=/tmp/gkvdb&sync=false" + cache = "path=/tmp/gkvdb-cache&sync=true" +` + path := "config.toml" + err := gfile.PutContents(path, config) + gtest.Assert(err, nil) + defer gfile.Remove(path) + defer gins.Config().Clear() + + // for gfsnotify callbacks to refresh cache of config file + time.Sleep(500 * time.Millisecond) + + gtest.Case(t, func() { + kvDefault := gins.KV() + kvCache := gins.KV("cache") + key := []byte("key") + value := []byte("value") + err := kvDefault.Set(key, value) + gtest.Assert(err, nil) + + gtest.Assert(kvDefault.Get(key), value) + gtest.Assert(kvCache.Get(key), nil) + }) +} diff --git a/go.mod b/go.mod index 3bc1d5487..c8b12f44d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gogf/gf require ( github.com/BurntSushi/toml v0.3.1 github.com/clbanning/mxj v1.8.4 + github.com/dgraph-io/badger v1.6.0 github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.4.7 github.com/gf-third/mysql v1.4.2 diff --git a/go.sum b/go.sum index 8198874a6..5bc74472c 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,31 @@ +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.0 h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Evo= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gf-third/mysql v1.4.2 h1:f1M5CNFUG3WkE07UOomtu4o0n/KJKeuUUf5Nc9ZFXs4= github.com/gf-third/mysql v1.4.2/go.mod h1:+dd90V663ppI2fV5uQ6+rHk0u8KCyU6FkG8Um8Cx3ms= +github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= @@ -16,23 +33,52 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grokify/html-strip-tags-go v0.0.0-20190424092004-025bd760b278 h1:DZo48DQFIDo/YWjUeFip1dfJztBhRuaxfUnPd+gAfcs= github.com/grokify/html-strip-tags-go v0.0.0-20190424092004-025bd760b278/go.mod h1:Xk7G0nwBiIloTMbLddk4WWJOqi4i/JLhadLd0HUXO30= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/theckman/go-flock v0.7.1 h1:YdJyIjDuQdEU7voZ9YaeXSO4OnrxdI+WejPUwyZ/Txs= github.com/theckman/go-flock v0.7.1/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -40,8 +86,11 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 h1:0efs3hwEZhFKsCoP8l6dDB1AZWMgnEl3yWXWRZTOaEA= gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index b423dbb7d..c11e9d1ee 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -17,6 +17,8 @@ import ( "strings" "time" + "github.com/gogf/gf/database/gkvdb" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/container/gtype" @@ -45,7 +47,8 @@ type ( serveCache *gcache.Cache // 服务注册路由内存缓存 routesMap map[string][]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断) statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) - sessions *gcache.Cache // Session内存缓存 + sessions *gcache.Cache // Session内存存储 + sessionStorage *gkvdb.DB // Session物理存储 logger *glog.Logger // 日志管理对象 } @@ -209,6 +212,7 @@ func GetServer(name ...interface{}) *Server { serveCache: gcache.New(), routesMap: make(map[string][]registeredRouteItem), sessions: gcache.New(), + sessionStorage: gkvdb.New(gkvdb.DefaultOptions(defaultServerConfig.SessionStoragePath)), servedCount: gtype.NewInt(), logger: glog.New(), } diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 35aa64a9a..294b1b8c6 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -38,73 +38,75 @@ type LogHandler func(r *Request, error ...interface{}) // HTTP Server 设置结构体,静态配置 type ServerConfig struct { - Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔) - HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔) - HTTPSCertPath string // HTTPS证书文件路径 - HTTPSKeyPath string // HTTPS签名文件路径 - Handler http.Handler // 默认的处理函数 - ReadTimeout time.Duration // 读取超时 - WriteTimeout time.Duration // 写入超时 - IdleTimeout time.Duration // 等待超时 - MaxHeaderBytes int // 最大的header长度 - TLSConfig tls.Config // HTTPS证书配置 - KeepAlive bool // 是否开启长连接 - ServerAgent string // Server Agent - View *gview.View // 模板引擎对象 - Rewrites map[string]string // URI Rewrite重写配置 - IndexFiles []string // Static: 默认访问的文件列表 - IndexFolder bool // Static: 如果访问目录是否显示目录列表 - ServerRoot string // Static: 服务器服务的本地目录根路径(检索优先级比StaticPaths低) - SearchPaths []string // Static: 静态文件搜索目录(包含ServerRoot,按照优先级进行排序) - StaticPaths []staticPathItem // Static: 静态文件目录映射(按照优先级进行排序) - FileServerEnabled bool // Static: 是否允许静态文件服务(通过静态文件服务方法调用自动识别) - CookieMaxAge int64 // Cookie: 有效期(秒) - CookiePath string // Cookie: 有效Path(注意同时也会影响SessionID) - CookieDomain string // Cookie: 有效Domain(注意同时也会影响SessionID) - SessionMaxAge int64 // Session: 有效期(秒) - SessionIdName string // Session: SessionId - DenyIps []string // Security: 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问 - AllowIps []string // Security: 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问 - DenyRoutes []string // Security: 不允许访问的路由规则列表 - LogPath string // Logging: 存放日志的目录路径(默认为空,表示不写文件) - LogHandler LogHandler // Logging: 日志配置: 自定义日志处理回调方法(默认为空) - LogStdout bool // Logging: 是否打印日志到终端(默认开启) - ErrorLogEnabled bool // Logging: 是否开启error log(默认开启) - AccessLogEnabled bool // Logging: 是否开启access log(默认关闭) - NameToUriType int // Mess: 服务注册时对象和方法名称转换为URI时的规则 - GzipContentTypes []string // Mess: 允许进行gzip压缩的文件类型 - DumpRouteMap bool // Mess: 是否在程序启动时默认打印路由表信息 - RouterCacheExpire int // Mess: 路由检索缓存过期时间(秒) + Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔) + HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔) + HTTPSCertPath string // HTTPS证书文件路径 + HTTPSKeyPath string // HTTPS签名文件路径 + Handler http.Handler // 默认的处理函数 + ReadTimeout time.Duration // 读取超时 + WriteTimeout time.Duration // 写入超时 + IdleTimeout time.Duration // 等待超时 + MaxHeaderBytes int // 最大的header长度 + TLSConfig tls.Config // HTTPS证书配置 + KeepAlive bool // 是否开启长连接 + ServerAgent string // Server Agent + View *gview.View // 模板引擎对象 + Rewrites map[string]string // URI Rewrite重写配置 + IndexFiles []string // Static: 默认访问的文件列表 + IndexFolder bool // Static: 如果访问目录是否显示目录列表 + ServerRoot string // Static: 服务器服务的本地目录根路径(检索优先级比StaticPaths低) + SearchPaths []string // Static: 静态文件搜索目录(包含ServerRoot,按照优先级进行排序) + StaticPaths []staticPathItem // Static: 静态文件目录映射(按照优先级进行排序) + FileServerEnabled bool // Static: 是否允许静态文件服务(通过静态文件服务方法调用自动识别) + CookieMaxAge int64 // Cookie: 有效期(秒) + CookiePath string // Cookie: 有效Path(注意同时也会影响SessionID) + CookieDomain string // Cookie: 有效Domain(注意同时也会影响SessionID) + SessionMaxAge int64 // Session: 有效期(秒) + SessionIdName string // Session: SessionId + SessionStoragePath string // Session: 存储路径 + DenyIps []string // Security: 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问 + AllowIps []string // Security: 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问 + DenyRoutes []string // Security: 不允许访问的路由规则列表 + LogPath string // Logging: 存放日志的目录路径(默认为空,表示不写文件) + LogHandler LogHandler // Logging: 日志配置: 自定义日志处理回调方法(默认为空) + LogStdout bool // Logging: 是否打印日志到终端(默认开启) + ErrorLogEnabled bool // Logging: 是否开启error log(默认开启) + AccessLogEnabled bool // Logging: 是否开启access log(默认关闭) + NameToUriType int // Mess: 服务注册时对象和方法名称转换为URI时的规则 + GzipContentTypes []string // Mess: 允许进行gzip压缩的文件类型 + DumpRouteMap bool // Mess: 是否在程序启动时默认打印路由表信息 + RouterCacheExpire int // Mess: 路由检索缓存过期时间(秒) } // 默认HTTP Server配置 var defaultServerConfig = ServerConfig{ - Addr: "", - HTTPSAddr: "", - Handler: nil, - ReadTimeout: 60 * time.Second, - WriteTimeout: 60 * time.Second, - IdleTimeout: 60 * time.Second, - MaxHeaderBytes: 1024, - KeepAlive: true, - View: gview.Instance(), - IndexFiles: []string{"index.html", "index.htm"}, - IndexFolder: false, - ServerAgent: "gf http server", - ServerRoot: "", - StaticPaths: make([]staticPathItem, 0), - FileServerEnabled: false, - CookieMaxAge: gDEFAULT_COOKIE_MAX_AGE, - CookiePath: gDEFAULT_COOKIE_PATH, - CookieDomain: "", - SessionMaxAge: gDEFAULT_SESSION_MAX_AGE, - SessionIdName: gDEFAULT_SESSION_ID_NAME, - LogStdout: true, - ErrorLogEnabled: true, - AccessLogEnabled: false, - DumpRouteMap: true, - RouterCacheExpire: 60, - Rewrites: make(map[string]string), + Addr: "", + HTTPSAddr: "", + Handler: nil, + ReadTimeout: 60 * time.Second, + WriteTimeout: 60 * time.Second, + IdleTimeout: 60 * time.Second, + MaxHeaderBytes: 1024, + KeepAlive: true, + View: gview.Instance(), + IndexFiles: []string{"index.html", "index.htm"}, + IndexFolder: false, + ServerAgent: "gf http server", + ServerRoot: "", + StaticPaths: make([]staticPathItem, 0), + FileServerEnabled: false, + CookieMaxAge: gDEFAULT_COOKIE_MAX_AGE, + CookiePath: gDEFAULT_COOKIE_PATH, + CookieDomain: "", + SessionMaxAge: gDEFAULT_SESSION_MAX_AGE, + SessionIdName: gDEFAULT_SESSION_ID_NAME, + SessionStoragePath: gfile.TempDir() + gfile.Separator + "gfsessions", + LogStdout: true, + ErrorLogEnabled: true, + AccessLogEnabled: false, + DumpRouteMap: true, + RouterCacheExpire: 60, + Rewrites: make(map[string]string), } // 获取默认的http server设置 diff --git a/net/ghttp/ghttp_server_config_session.go b/net/ghttp/ghttp_server_config_session.go index 84f4e813a..823381392 100644 --- a/net/ghttp/ghttp_server_config_session.go +++ b/net/ghttp/ghttp_server_config_session.go @@ -6,7 +6,12 @@ package ghttp -import "github.com/gogf/gf/os/glog" +import ( + "fmt" + + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/os/glog" +) // 设置http server参数 - SessionMaxAge func (s *Server) SetSessionMaxAge(age int64) { @@ -26,6 +31,22 @@ func (s *Server) SetSessionIdName(name string) { s.config.SessionIdName = name } +// 设置http server参数 - SessionStoragePath +func (s *Server) SetSessionStoragePath(path string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR) + return + } + realPath, _ := gfile.Search(path) + if realPath != "" { + glog.Fatal(fmt.Sprintf(`[ghttp] SetSessionStoragePath failed: '%s' does not exist`, path)) + } + s.config.SessionStoragePath = realPath + if err := s.sessionStorage.SetPath(realPath); err != nil { + glog.Fatal(fmt.Sprintf(`[ghttp] SetSessionStoragePath failed: %s`, err.Error())) + } +} + // 获取http server参数 - SessionMaxAge func (s *Server) GetSessionMaxAge() int64 { return s.config.SessionMaxAge @@ -35,3 +56,8 @@ func (s *Server) GetSessionMaxAge() int64 { func (s *Server) GetSessionIdName() string { return s.config.SessionIdName } + +// 获取http server参数 - SessionStoragePath +func (s *Server) GetSessionStoragePath() string { + return s.config.SessionStoragePath +} diff --git a/net/ghttp/ghttp_server_session.go b/net/ghttp/ghttp_server_session.go index 30686936e..5963d7355 100644 --- a/net/ghttp/ghttp_server_session.go +++ b/net/ghttp/ghttp_server_session.go @@ -57,9 +57,19 @@ func (s *Session) init() { s.server = s.request.Server if id := s.request.GetSessionId(); id != "" { if v := s.server.sessions.Get(id); v != nil { + // 纯内存查询 s.id = id s.data = v.(*gmap.StrAnyMap) return + } else { + // 持久化恢复 + data := s.server.sessionStorage.Get([]byte(id)) + if data != nil { + s.id = id + s.data = gmap.NewStrAnyMap(true) + s.Restore(data) + return + } } } // 否则执行初始化创建 @@ -165,6 +175,16 @@ func (s *Session) Clear() { // 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除) func (s *Session) UpdateExpire() { if len(s.id) > 0 && s.data.Size() > 0 { + // 优先持久化存储 + if s.dirty { + data, _ := s.Export() + s.server.sessionStorage.Set( + []byte(s.id), + data, + time.Duration(s.server.GetSessionMaxAge())*time.Second, + ) + } + // 其次更新内存TTL s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) } }