github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/gf.
     6  
     7  package gsession
     8  
     9  import (
    10  	"context"
    11  	"time"
    12  
    13  	"github.com/wangyougui/gf/v2/container/gmap"
    14  	"github.com/wangyougui/gf/v2/database/gredis"
    15  	"github.com/wangyougui/gf/v2/internal/intlog"
    16  	"github.com/wangyougui/gf/v2/internal/json"
    17  	"github.com/wangyougui/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  }