87 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			87 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package session
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"errors"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/redis/go-redis/v9"
 | ||
| )
 | ||
| 
 | ||
| // 为所有 Redis 操作定义一个合理的超时时间。
 | ||
| // 这个值应该根据你的服务SLA和网络状况来定,通常在50-500毫秒之间是比较合理的。
 | ||
| // 最佳实践:这个值应该来自配置文件,而不是硬编码。
 | ||
| const redisTimeout = 200 * time.Millisecond
 | ||
| 
 | ||
| // RedisStore 表示一个使用 go-redis/v9 客户端的 scs.Store 实现。
 | ||
| type RedisStore struct {
 | ||
| 	// 内嵌 go-redis 客户端
 | ||
| 	client *redis.Client
 | ||
| }
 | ||
| 
 | ||
| // NewRedisStore 是 RedisStore 的构造函数。
 | ||
| func NewRedisStore(client *redis.Client) *RedisStore {
 | ||
| 	return &RedisStore{
 | ||
| 		client: client,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Find 方法根据 session token 从 Redis 中查找 session 数据。
 | ||
| // 如果 token 不存在或已过期,exists 返回 false。
 | ||
| func (s *RedisStore) Find(token string) ([]byte, bool, error) {
 | ||
| 	// ✅ 最佳实践: 为数据库操作创建带超时的上下文
 | ||
| 	ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
 | ||
| 	// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
 | ||
| 	defer cancel()
 | ||
| 
 | ||
| 	// 使用 go-redis 的 Get 方法
 | ||
| 	data, err := s.client.Get(ctx, token).Bytes()
 | ||
| 	if err != nil {
 | ||
| 		// 如果 key 不存在,go-redis 会返回 redis.Nil 错误
 | ||
| 		if errors.Is(err, redis.Nil) {
 | ||
| 			return nil, false, nil
 | ||
| 		}
 | ||
| 		return nil, false, err
 | ||
| 	}
 | ||
| 
 | ||
| 	return data, true, nil
 | ||
| }
 | ||
| 
 | ||
| // Commit 方法将 session 数据和过期时间存入 Redis。
 | ||
| // 如果 token 已存在,则更新其数据和过期时间。
 | ||
| func (s *RedisStore) Commit(token string, b []byte, expiry time.Time) error {
 | ||
| 	// ✅ 最佳实践: 为数据库操作创建带超时的上下文
 | ||
| 	ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
 | ||
| 	// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
 | ||
| 	defer cancel()
 | ||
| 
 | ||
| 	// 计算 Redis 的 TTL (Time To Live)
 | ||
| 	// time.Until(expiry) 会计算出当前时间到 expiry 之间的时间差
 | ||
| 	ttl := time.Until(expiry)
 | ||
| 
 | ||
| 	// 使用 go-redis 的 Set 方法,并设置过期时间
 | ||
| 	// 如果 expiry 时间已经过去,ttl 会是负数,Redis 会立即删除这个 key,这正是我们期望的行为。
 | ||
| 	err := s.client.Set(ctx, token, b, ttl).Err()
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // Delete 方法根据 session token 从 Redis 中删除 session 数据。
 | ||
| func (s *RedisStore) Delete(token string) error {
 | ||
| 	// ✅ 最佳实践: 为数据库操作创建带超时的上下文
 | ||
| 	ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
 | ||
| 	// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
 | ||
| 	defer cancel()
 | ||
| 
 | ||
| 	// 使用 go-redis 的 Del 方法
 | ||
| 	err := s.client.Del(ctx, token).Err()
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 |