github.com/gogf/gf/v2@v2.7.4/os/gsession/gsession_storage_redis.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gsession 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/gogf/gf/v2/container/gmap" 14 "github.com/gogf/gf/v2/database/gredis" 15 "github.com/gogf/gf/v2/internal/intlog" 16 "github.com/gogf/gf/v2/internal/json" 17 "github.com/gogf/gf/v2/os/gtimer" 18 ) 19 20 // StorageRedis implements the Session Storage interface with redis. 21 type StorageRedis struct { 22 StorageBase 23 redis *gredis.Redis // Redis client for session storage. 24 prefix string // Redis key prefix for session id. 25 updatingIdMap *gmap.StrIntMap // Updating TTL set for session id. 26 } 27 28 const ( 29 // DefaultStorageRedisLoopInterval is the interval updating TTL for session ids 30 // in last duration. 31 DefaultStorageRedisLoopInterval = 10 * time.Second 32 ) 33 34 // NewStorageRedis creates and returns a redis storage object for session. 35 func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { 36 if redis == nil { 37 panic("redis instance for storage cannot be empty") 38 return nil 39 } 40 s := &StorageRedis{ 41 redis: redis, 42 updatingIdMap: gmap.NewStrIntMap(true), 43 } 44 if len(prefix) > 0 && prefix[0] != "" { 45 s.prefix = prefix[0] 46 } 47 // Batch updates the TTL for session ids timely. 48 gtimer.AddSingleton(context.Background(), DefaultStorageRedisLoopInterval, func(ctx context.Context) { 49 intlog.Print(context.TODO(), "StorageRedis.timer start") 50 var ( 51 err error 52 sessionId string 53 ttlSeconds int 54 ) 55 for { 56 if sessionId, ttlSeconds = s.updatingIdMap.Pop(); sessionId == "" { 57 break 58 } else { 59 if err = s.doUpdateExpireForSession(context.TODO(), sessionId, ttlSeconds); err != nil { 60 intlog.Errorf(context.TODO(), `%+v`, err) 61 } 62 } 63 } 64 intlog.Print(context.TODO(), "StorageRedis.timer end") 65 }) 66 return s 67 } 68 69 // RemoveAll deletes all key-value pairs from storage. 70 func (s *StorageRedis) RemoveAll(ctx context.Context, sessionId string) error { 71 _, err := s.redis.Del(ctx, s.sessionIdToRedisKey(sessionId)) 72 return err 73 } 74 75 // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. 76 // 77 // The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. 78 // The parameter `data` is the current old session data stored in memory, 79 // and for some storage it might be nil if memory storage is disabled. 80 // 81 // This function is called ever when session starts. 82 func (s *StorageRedis) GetSession(ctx context.Context, sessionId string, ttl time.Duration) (*gmap.StrAnyMap, error) { 83 intlog.Printf(ctx, "StorageRedis.GetSession: %s, %v", sessionId, ttl) 84 r, err := s.redis.Get(ctx, s.sessionIdToRedisKey(sessionId)) 85 if err != nil { 86 return nil, err 87 } 88 content := r.Bytes() 89 if len(content) == 0 { 90 return nil, nil 91 } 92 var m map[string]interface{} 93 if err = json.UnmarshalUseNumber(content, &m); err != nil { 94 return nil, err 95 } 96 if m == nil { 97 return nil, nil 98 } 99 return gmap.NewStrAnyMapFrom(m, true), nil 100 } 101 102 // SetSession updates the data map for specified session id. 103 // This function is called ever after session, which is changed dirty, is closed. 104 // This copy all session data map from memory to storage. 105 func (s *StorageRedis) SetSession(ctx context.Context, sessionId string, sessionData *gmap.StrAnyMap, ttl time.Duration) error { 106 intlog.Printf(ctx, "StorageRedis.SetSession: %s, %v, %v", sessionId, sessionData, ttl) 107 content, err := json.Marshal(sessionData) 108 if err != nil { 109 return err 110 } 111 err = s.redis.SetEX(ctx, s.sessionIdToRedisKey(sessionId), content, int64(ttl.Seconds())) 112 return err 113 } 114 115 // UpdateTTL updates the TTL for specified session id. 116 // This function is called ever after session, which is not dirty, is closed. 117 // It just adds the session id to the async handling queue. 118 func (s *StorageRedis) UpdateTTL(ctx context.Context, sessionId string, ttl time.Duration) error { 119 intlog.Printf(ctx, "StorageRedis.UpdateTTL: %s, %v", sessionId, ttl) 120 if ttl >= DefaultStorageRedisLoopInterval { 121 s.updatingIdMap.Set(sessionId, int(ttl.Seconds())) 122 } 123 return nil 124 } 125 126 // doUpdateExpireForSession updates the TTL for session id. 127 func (s *StorageRedis) doUpdateExpireForSession(ctx context.Context, sessionId string, ttlSeconds int) error { 128 intlog.Printf(ctx, "StorageRedis.doUpdateTTL: %s, %d", sessionId, ttlSeconds) 129 _, err := s.redis.Expire(ctx, s.sessionIdToRedisKey(sessionId), int64(ttlSeconds)) 130 return err 131 } 132 133 // sessionIdToRedisKey converts and returns the redis key for given session id. 134 func (s *StorageRedis) sessionIdToRedisKey(sessionId string) string { 135 return s.prefix + sessionId 136 }