github.com/gogf/gf@v1.16.9/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 "github.com/gogf/gf/container/gmap" 12 "github.com/gogf/gf/database/gredis" 13 "github.com/gogf/gf/internal/intlog" 14 "github.com/gogf/gf/internal/json" 15 "time" 16 17 "github.com/gogf/gf/os/gtimer" 18 ) 19 20 // StorageRedis implements the Session Storage interface with redis. 21 type StorageRedis struct { 22 redis *gredis.Redis // Redis client for session storage. 23 prefix string // Redis key prefix for session id. 24 updatingIdMap *gmap.StrIntMap // Updating TTL set for session id. 25 } 26 27 var ( 28 // DefaultStorageRedisLoopInterval is the interval updating TTL for session ids 29 // in last duration. 30 DefaultStorageRedisLoopInterval = 10 * time.Second 31 ) 32 33 // NewStorageRedis creates and returns a redis storage object for session. 34 func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { 35 if redis == nil { 36 panic("redis instance for storage cannot be empty") 37 return nil 38 } 39 s := &StorageRedis{ 40 redis: redis, 41 updatingIdMap: gmap.NewStrIntMap(true), 42 } 43 if len(prefix) > 0 && prefix[0] != "" { 44 s.prefix = prefix[0] 45 } 46 // Batch updates the TTL for session ids timely. 47 gtimer.AddSingleton(DefaultStorageRedisLoopInterval, func() { 48 intlog.Print(context.TODO(), "StorageRedis.timer start") 49 var ( 50 id string 51 err error 52 ttlSeconds int 53 ) 54 for { 55 if id, ttlSeconds = s.updatingIdMap.Pop(); id == "" { 56 break 57 } else { 58 if err = s.doUpdateTTL(context.TODO(), id, ttlSeconds); err != nil { 59 intlog.Error(context.TODO(), err) 60 } 61 } 62 } 63 intlog.Print(context.TODO(), "StorageRedis.timer end") 64 }) 65 return s 66 } 67 68 // New creates a session id. 69 // This function can be used for custom session creation. 70 func (s *StorageRedis) New(ctx context.Context, ttl time.Duration) (id string, err error) { 71 return "", ErrorDisabled 72 } 73 74 // Get retrieves session value with given key. 75 // It returns nil if the key does not exist in the session. 76 func (s *StorageRedis) Get(ctx context.Context, id string, key string) (value interface{}, err error) { 77 return nil, ErrorDisabled 78 } 79 80 // GetMap retrieves all key-value pairs as map from storage. 81 func (s *StorageRedis) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { 82 return nil, ErrorDisabled 83 } 84 85 // GetSize retrieves the size of key-value pairs from storage. 86 func (s *StorageRedis) GetSize(ctx context.Context, id string) (size int, err error) { 87 return -1, ErrorDisabled 88 } 89 90 // Set sets key-value session pair to the storage. 91 // The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). 92 func (s *StorageRedis) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { 93 return ErrorDisabled 94 } 95 96 // SetMap batch sets key-value session pairs with map to the storage. 97 // The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). 98 func (s *StorageRedis) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { 99 return ErrorDisabled 100 } 101 102 // Remove deletes key with its value from storage. 103 func (s *StorageRedis) Remove(ctx context.Context, id string, key string) error { 104 return ErrorDisabled 105 } 106 107 // RemoveAll deletes all key-value pairs from storage. 108 func (s *StorageRedis) RemoveAll(ctx context.Context, id string) error { 109 return ErrorDisabled 110 } 111 112 // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. 113 // 114 // The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. 115 // The parameter `data` is the current old session data stored in memory, 116 // and for some storage it might be nil if memory storage is disabled. 117 // 118 // This function is called ever when session starts. 119 func (s *StorageRedis) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { 120 intlog.Printf(ctx, "StorageRedis.GetSession: %s, %v", id, ttl) 121 r, err := s.redis.Ctx(ctx).DoVar("GET", s.key(id)) 122 if err != nil { 123 return nil, err 124 } 125 content := r.Bytes() 126 if len(content) == 0 { 127 return nil, nil 128 } 129 var m map[string]interface{} 130 if err = json.UnmarshalUseNumber(content, &m); err != nil { 131 return nil, err 132 } 133 if m == nil { 134 return nil, nil 135 } 136 if data == nil { 137 return gmap.NewStrAnyMapFrom(m, true), nil 138 } else { 139 data.Replace(m) 140 } 141 return data, nil 142 } 143 144 // SetSession updates the data map for specified session id. 145 // This function is called ever after session, which is changed dirty, is closed. 146 // This copy all session data map from memory to storage. 147 func (s *StorageRedis) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { 148 intlog.Printf(ctx, "StorageRedis.SetSession: %s, %v, %v", id, data, ttl) 149 content, err := json.Marshal(data) 150 if err != nil { 151 return err 152 } 153 _, err = s.redis.Ctx(ctx).DoVar("SETEX", s.key(id), int64(ttl.Seconds()), content) 154 return err 155 } 156 157 // UpdateTTL updates the TTL for specified session id. 158 // This function is called ever after session, which is not dirty, is closed. 159 // It just adds the session id to the async handling queue. 160 func (s *StorageRedis) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { 161 intlog.Printf(ctx, "StorageRedis.UpdateTTL: %s, %v", id, ttl) 162 if ttl >= DefaultStorageRedisLoopInterval { 163 s.updatingIdMap.Set(id, int(ttl.Seconds())) 164 } 165 return nil 166 } 167 168 // doUpdateTTL updates the TTL for session id. 169 func (s *StorageRedis) doUpdateTTL(ctx context.Context, id string, ttlSeconds int) error { 170 intlog.Printf(ctx, "StorageRedis.doUpdateTTL: %s, %d", id, ttlSeconds) 171 _, err := s.redis.Ctx(ctx).DoVar("EXPIRE", s.key(id), ttlSeconds) 172 return err 173 } 174 175 func (s *StorageRedis) key(id string) string { 176 return s.prefix + id 177 }