diff --git a/TODO.MD b/TODO.MD index 357d77c94..2851f5b60 100644 --- a/TODO.MD +++ b/TODO.MD @@ -50,6 +50,11 @@ 1. grpool增加支持阻塞添加任务接口; 1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用; 1. 增加g.Table快捷方法以方便操作数据表,但是得考虑后续模型操作设计,特别是脚手架的模型管理; +1. 改进ghttp分组路由中对hook的支持方式,以便格式与BindHookHandler统一; + + + + # DONE 1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 13fdb098e..4d6baa601 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -170,7 +170,7 @@ var ( // which is DEFAULT_GROUP_NAME in default. func New(name ...string) (db DB, err error) { group := configs.defaultGroup - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { group = name[0] } configs.RLock() diff --git a/g/database/gredis/gredis.go b/g/database/gredis/gredis.go index 0edd55e43..9250a1328 100644 --- a/g/database/gredis/gredis.go +++ b/g/database/gredis/gredis.go @@ -110,7 +110,7 @@ func New(config Config) *Redis { // it returns a redis instance with default group. func Instance(name ...string) *Redis { group := DEFAULT_GROUP_NAME - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { group = name[0] } v := instances.GetOrSetFuncLock(group, func() interface{} { diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 07c29cfba..7d252ff77 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -77,7 +77,7 @@ func Config(name ...string) *gcfg.Config { func Database(name ...string) gdb.DB { config := Config() group := gdb.DEFAULT_GROUP_NAME - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { group = name[0] } key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group) @@ -214,7 +214,7 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode { func Redis(name ...string) *gredis.Redis { config := Config() group := "default" - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { group = name[0] } key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index ce96d7df2..afba81842 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -192,7 +192,7 @@ func serverProcessInit() { // 单例模式,请保证name的唯一性 func GetServer(name ...interface{}) *Server { sname := gDEFAULT_SERVER - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { sname = gconv.String(name[0]) } if s := serverMapping.Get(sname); s != nil { diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go index 0a5b31e90..cb8fee3b5 100644 --- a/g/net/ghttp/ghttp_server_config.go +++ b/g/net/ghttp/ghttp_server_config.go @@ -9,11 +9,12 @@ package ghttp import ( "crypto/tls" "fmt" - "github.com/gogf/gf/g/os/gfile" - "github.com/gogf/gf/g/os/glog" "net/http" "strconv" "time" + + "github.com/gogf/gf/g/os/gfile" + "github.com/gogf/gf/g/os/glog" ) const ( @@ -25,7 +26,7 @@ const ( NAME_TO_URI_TYPE_CAMEL = 3 // 采用驼峰命名方式 gDEFAULT_COOKIE_PATH = "/" // 默认path gDEFAULT_COOKIE_MAX_AGE = 86400 * 365 // 默认cookie有效期(一年) - gDEFAULT_SESSION_MAX_AGE = 600000 // 默认session有效期(600秒) + gDEFAULT_SESSION_MAX_AGE = 86400 // 默认session有效期(一天) gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 gCHANGE_CONFIG_WHILE_RUNNING_ERROR = "cannot be changed while running" ) diff --git a/g/net/ghttp/ghttp_server_router_group.go b/g/net/ghttp/ghttp_server_router_group.go index be919e0d0..1b8a2d894 100644 --- a/g/net/ghttp/ghttp_server_router_group.go +++ b/g/net/ghttp/ghttp_server_router_group.go @@ -38,13 +38,13 @@ func (s *Server) Group(prefix ...string) *RouterGroup { // 获取分组路由对象 func (d *Domain) Group(prefix ...string) *RouterGroup { - if len(prefix) > 0 { - return &RouterGroup{ - domain: d, - prefix: prefix[0], - } + group := &RouterGroup{ + domain: d, } - return &RouterGroup{} + if len(prefix) > 0 { + group.prefix = prefix[0] + } + return group } // 执行分组路由批量绑定 diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 4afa99be7..c53086763 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -3,32 +3,34 @@ // 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. -// 并发安全的Session管理器 package ghttp import ( + "encoding/json" + "strconv" + "strings" + "time" + "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gvar" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/grand" - "strconv" - "strings" - "time" ) -// SESSION对象 +// SESSION对象,并发安全 type Session struct { id string // SessionId data *gmap.StrAnyMap // Session数据 + dirty bool // 数据是否被修改 server *Server // 所属Server request *Request // 关联的请求 } // 生成一个唯一的SessionId字符串,长度18位。 func makeSessionId() string { - return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6)) + return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.Str(6)) } // 获取或者生成一个session对象(延迟初始化) @@ -41,17 +43,22 @@ func GetSession(r *Request) *Session { } } -// 执行初始化(用于延迟初始化). +// UpdateSession updates the session with custom map. +func (s *Server) UpdateSession(id string, data map[string]interface{}) { + v := s.sessions.GetOrSetFuncLock(id, func() interface{} { + return gmap.NewStrAnyMap(true) + }, s.GetSessionMaxAge()*1000) + v.(*gmap.StrAnyMap).Sets(data) +} + +// 延迟初始化 func (s *Session) init() { if len(s.id) == 0 { s.server = s.request.Server - // 根据提交的SESSION ID获取已存在SESSION - id := s.request.Cookie.GetSessionId() - if id != "" { - data := s.server.sessions.Get(id) - if data != nil { + if id := s.request.Cookie.GetSessionId(); id != "" { + if v := s.server.sessions.Get(id); v != nil { s.id = id - s.data = data.(*gmap.StrAnyMap) + s.data = v.(*gmap.StrAnyMap) return } } @@ -59,6 +66,7 @@ func (s *Session) init() { s.id = s.request.Cookie.MakeSessionId() s.data = gmap.NewStrAnyMap(true) s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) + s.dirty = true } } @@ -68,7 +76,7 @@ func (s *Session) Id() string { return s.id } -// 获取当前session所有数据 +// 获取当前session所有数据,注意是值拷贝 func (s *Session) Map() map[string]interface{} { if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { s.init() @@ -77,16 +85,27 @@ func (s *Session) Map() map[string]interface{} { return nil } +// 获得session map大小 +func (s *Session) Size() int { + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + return s.data.Size() + } + return 0 +} + // 设置session func (s *Session) Set(key string, value interface{}) { s.init() s.data.Set(key, value) + s.dirty = true } // 批量设置 func (s *Session) Sets(m map[string]interface{}) { s.init() s.data.Sets(m) + s.dirty = true } // 判断键名是否存在 @@ -98,6 +117,58 @@ func (s *Session) Contains(key string) bool { return false } +// 判断session是否有修改(包括新创建) +func (s *Session) IsDirty() bool { + return s.dirty +} + +// 删除指定session键值对 +func (s *Session) Remove(key string) { + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.Remove(key) + s.dirty = true + } +} + +// 将session数据导出为[]byte数据(目前使用json进行序列化) +func (s *Session) Export() (data []byte, err error) { + if s.Size() > 0 { + data, err = json.Marshal(s.data) + } + return +} + +// 从[]byte中恢复session数据(目前使用json进行序列化) +func (s *Session) Restore(data []byte) (err error) { + if len(data) == 0 { + return nil + } + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.LockFunc(func(m map[string]interface{}) { + err = json.Unmarshal(data, &m) + }) + } + return +} + +// 清空session +func (s *Session) Clear() { + if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { + s.init() + s.data.Clear() + s.dirty = true + } +} + +// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除) +func (s *Session) UpdateExpire() { + if len(s.id) > 0 && s.data.Size() > 0 { + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) + } +} + // 获取SESSION变量 func (s *Session) Get(key string, def ...interface{}) interface{} { if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { @@ -114,30 +185,7 @@ func (s *Session) Get(key string, def ...interface{}) interface{} { // 获取SESSION,建议都用该方法获取参数 func (s *Session) GetVar(key string, def ...interface{}) *gvar.Var { - return gvar.New(s.Get(key, def...)) -} - -// 删除session -func (s *Session) Remove(key string) { - if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { - s.init() - s.data.Remove(key) - } -} - -// 清空session -func (s *Session) Clear() { - if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" { - s.init() - s.data.Clear() - } -} - -// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除) -func (s *Session) UpdateExpire() { - if len(s.id) > 0 && s.data.Size() > 0 { - s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) - } + return gvar.New(s.Get(key, def...), true) } func (s *Session) GetString(key string, def ...interface{}) string { @@ -228,7 +276,34 @@ func (s *Session) GetDuration(key string, def ...interface{}) time.Duration { return gconv.Duration(s.Get(key, def...)) } -// 将变量转换为对象,注意 pointer 参数必须为struct指针 +func (s *Session) GetMap(value interface{}, tags ...string) map[string]interface{} { + return gconv.Map(value, tags...) +} + +func (s *Session) GetMapDeep(value interface{}, tags ...string) map[string]interface{} { + return gconv.MapDeep(value, tags...) +} + +func (s *Session) GetMaps(value interface{}, tags ...string) []map[string]interface{} { + return gconv.Maps(value, tags...) +} + +func (s *Session) GetMapsDeep(value interface{}, tags ...string) []map[string]interface{} { + return gconv.MapsDeep(value, tags...) +} + func (s *Session) GetStruct(key string, pointer interface{}, mapping ...map[string]string) error { return gconv.Struct(s.Get(key), pointer, mapping...) } + +func (s *Session) GetStructDeep(key string, pointer interface{}, mapping ...map[string]string) error { + return gconv.StructDeep(s.Get(key), pointer, mapping...) +} + +func (s *Session) GetStructs(key string, pointer interface{}, mapping ...map[string]string) error { + return gconv.Structs(s.Get(key), pointer, mapping...) +} + +func (s *Session) GetStructsDeep(key string, pointer interface{}, mapping ...map[string]string) error { + return gconv.StructsDeep(s.Get(key), pointer, mapping...) +} diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index e299fae30..da0197e31 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -35,7 +35,7 @@ var serverMapping = gmap.NewStrAnyMap(true) // The parameter is used to specify the TCP server func GetServer(name ...interface{}) *Server { serverName := gDEFAULT_SERVER - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { serverName = gconv.String(name[0]) } return serverMapping.GetOrSetFuncLock(serverName, func() interface{} { diff --git a/g/net/gudp/gudp_server.go b/g/net/gudp/gudp_server.go index 0bbe65907..ccd3b9cae 100644 --- a/g/net/gudp/gudp_server.go +++ b/g/net/gudp/gudp_server.go @@ -32,7 +32,7 @@ var serverMapping = gmap.NewStrAnyMap(true) // 单例模式,请保证name的唯一性 func GetServer(name ...interface{}) *Server { serverName := gDEFAULT_SERVER - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { serverName = gconv.String(name[0]) } if s := serverMapping.Get(serverName); s != nil { diff --git a/g/os/gcache/gcache_z_bench_test.go b/g/os/gcache/gcache_z_bench_test.go index ffef27c30..066585624 100644 --- a/g/os/gcache/gcache_z_bench_test.go +++ b/g/os/gcache/gcache_z_bench_test.go @@ -10,104 +10,70 @@ package gcache_test import ( "github.com/gogf/gf/g/os/gcache" - "sync" "testing" ) var ( - c = gcache.New() - clru = gcache.New(10000) - mInt = make(map[int]int) - mMap = make(map[interface{}]interface{}) - - muInt = sync.RWMutex{} - muMap = sync.RWMutex{} + cache = gcache.New() + cacheLru = gcache.New(10000) ) func Benchmark_CacheSet(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Set(i, i, 0) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Set(i, i, 0) + i++ + } + }) } func Benchmark_CacheGet(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Get(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Get(i) + i++ + } + }) } func Benchmark_CacheRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - c.Remove(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cache.Remove(i) + i++ + } + }) } func Benchmark_CacheLruSet(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Set(i, i, 0) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Set(i, i, 0) + i++ + } + }) } func Benchmark_CacheLruGet(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Get(i) - } + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Get(i) + i++ + } + }) } func Benchmark_CacheLruRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - clru.Remove(i) - } -} - -func Benchmark_InterfaceMapWithLockSet(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.Lock() - mMap[i] = i - muMap.Unlock() - } -} - -func Benchmark_InterfaceMapWithLockGet(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.RLock() - if _, ok := mMap[i]; ok { - + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + cacheLru.Remove(i) + i++ } - muMap.RUnlock() - } -} - -func Benchmark_InterfaceMapWithLockRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - muMap.Lock() - delete(mMap, i) - muMap.Unlock() - } -} - -func Benchmark_IntMapWithLockWithLockSet(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.Lock() - mInt[i] = i - muInt.Unlock() - } -} - -func Benchmark_IntMapWithLockGet(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.RLock() - if _, ok := mInt[i]; ok { - - } - muInt.RUnlock() - } -} - -func Benchmark_IntMapWithLockRemove(b *testing.B) { - for i := 0; i < b.N; i++ { - muInt.Lock() - delete(mInt, i) - muInt.Unlock() - } + }) } diff --git a/g/os/gcfg/gcfg_instance.go b/g/os/gcfg/gcfg_instance.go index 0ffb76423..cece36c59 100644 --- a/g/os/gcfg/gcfg_instance.go +++ b/g/os/gcfg/gcfg_instance.go @@ -24,7 +24,7 @@ var ( // The parameter is the name for the instance. func Instance(name ...string) *Config { key := DEFAULT_GROUP_NAME - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { key = name[0] } return instances.GetOrSetFuncLock(key, func() interface{} { diff --git a/g/os/gview/gview_instance.go b/g/os/gview/gview_instance.go index ddc644be2..9e1d7dac4 100644 --- a/g/os/gview/gview_instance.go +++ b/g/os/gview/gview_instance.go @@ -22,7 +22,7 @@ var ( // The parameter is the name for the instance. func Instance(name ...string) *View { key := DEFAULT_INSTANCE_NAME - if len(name) > 0 { + if len(name) > 0 && name[0] != "" { key = name[0] } return instances.GetOrSetFuncLock(key, func() interface{} { diff --git a/g/util/gconv/gconv_slice.go b/g/util/gconv/gconv_slice.go index 0592e5362..137754333 100644 --- a/g/util/gconv/gconv_slice.go +++ b/g/util/gconv/gconv_slice.go @@ -379,40 +379,40 @@ func Interfaces(i interface{}) []interface{} { } // Maps converts to []map[string]interface{}. -func Maps(i interface{}) []map[string]interface{} { - if i == nil { +func Maps(value interface{}, tags ...string) []map[string]interface{} { + if value == nil { return nil } - if r, ok := i.([]map[string]interface{}); ok { + if r, ok := value.([]map[string]interface{}); ok { return r } else { - array := Interfaces(i) + array := Interfaces(value) if len(array) == 0 { return nil } list := make([]map[string]interface{}, len(array)) for k, v := range array { - list[k] = Map(v) + list[k] = Map(v, tags...) } return list } } // MapsDeep converts to []map[string]interface{} recursively. -func MapsDeep(i interface{}) []map[string]interface{} { - if i == nil { +func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { + if value == nil { return nil } - if r, ok := i.([]map[string]interface{}); ok { + if r, ok := value.([]map[string]interface{}); ok { return r } else { - array := Interfaces(i) + array := Interfaces(value) if len(array) == 0 { return nil } list := make([]map[string]interface{}, len(array)) for k, v := range array { - list[k] = MapDeep(v) + list[k] = MapDeep(v, tags...) } return list } diff --git a/geg/net/ghttp/server/session/config.toml b/geg/net/ghttp/server/session/config.toml new file mode 100644 index 000000000..d5195ad6d --- /dev/null +++ b/geg/net/ghttp/server/session/config.toml @@ -0,0 +1,2 @@ +[redis] + default = "127.0.0.1:6379,10" \ No newline at end of file diff --git a/geg/net/ghttp/server/session/session.go b/geg/net/ghttp/server/session/session.go index b7893bd56..3d901fdd3 100644 --- a/geg/net/ghttp/server/session/session.go +++ b/geg/net/ghttp/server/session/session.go @@ -22,7 +22,7 @@ func (c *Controller) DoLogin() { } func (c *Controller) Main() { - c.Response.WriteJson(c.Session.Data()) + c.Response.WriteJson(c.Session.Map()) } func main() { diff --git a/geg/net/ghttp/server/session/session_redis.go b/geg/net/ghttp/server/session/session_redis.go new file mode 100644 index 000000000..b70f64edf --- /dev/null +++ b/geg/net/ghttp/server/session/session_redis.go @@ -0,0 +1,82 @@ +package main + +import ( + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/os/gtime" +) + +// 测试,SESSION写入 +func SessionSet(r *ghttp.Request) { + r.Session.Set("time", gtime.Second()) + r.Response.WriteJson("ok") +} + +// 测试,SESSION读取 +func SessionGet(r *ghttp.Request) { + r.Response.WriteJson(r.Session.Map()) +} + +// 请求处理之前将Redis中的数据读取出来并存储到SESSION对象中。 +func RedisHandlerGet(r *ghttp.Request) { + if !r.IsFileRequest() { + id := r.Cookie.GetSessionId() + if id == "" { + return + } + // 应用服务器一般是多个节点构成的集群, + // 当请求中带有SESSION ID时,自动从Redis读取并恢复数据。 + value, err := g.Redis().DoVar("GET", id) + if err != nil { + panic(err) + } + if !value.IsNil() { + if err := r.Session.Restore(value.Bytes()); err != nil { + panic(err) + } + } + } +} + +// 请求结束时将SESSION数据存储到Redis中,或者在SESSION删除时也删除Redis中的数据。 +func RedisHandlerSet(r *ghttp.Request) { + if !r.IsFileRequest() { + id := r.Cookie.GetSessionId() + if id == "" { + return + } + err := (error)(nil) + value := ([]byte)(nil) + if r.Session.Size() > 0 { + if value, err = r.Session.Export(); err == nil { + if len(value) == 0 { + return + } else if !r.Session.IsDirty() { + // 更新过期时间 + _, err = g.Redis().Do("EXPIRE", id, r.Server.GetSessionMaxAge()) + } else { + // 更新Redis数据 + _, err = g.Redis().Do("SETEX", id, r.Server.GetSessionMaxAge(), value) + } + } + } else { + // 清空SESSION后自动删除Redis数据 + _, err = g.Redis().Do("DEL", id) + } + if err != nil { + panic(err) + } + } +} + +func main() { + s := g.Server() + s.BindHandler("/set", SessionSet) + s.BindHandler("/get", SessionGet) + s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{ + ghttp.HOOK_BEFORE_SERVE: RedisHandlerGet, + ghttp.HOOK_AFTER_SERVE: RedisHandlerSet, + }) + s.SetPort(8199) + s.Run() +} diff --git a/version.go b/version.go index 87865121b..e5fd06629 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.8.2" +const VERSION = "v1.8.3" const AUTHORS = "john"