package redisdao import ( "context" "fmt" "time" "git.lzcwy.cn/zhengchiliu/zt-closet-backend/global" "git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/redismodel" "github.com/redis/go-redis/v9" ) // authTokenSAddScript 向用户 token 集合加入 refresh_token,仅在集合由空变为非空时设置 TTL, // 避免每次登录都刷新集合 TTL 导致旧 token 条目长期驻留。 const authTokenSAddScript = ` local added = redis.call('SADD', KEYS[1], ARGV[1]) if added == 1 and redis.call('SCARD', KEYS[1]) == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end return added ` // AuthTokenSet 登录成功后写入会话: // - access_token -> user_id // - refresh_token -> user_id // - user_id 的 token 集合加入 refresh_token(用于多端管理 / 登出) func AuthTokenSet(ctx context.Context, userID int64, accessToken, refreshToken string) error { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthTokenSet") defer span.End() accessKey := fmt.Sprintf(redismodel.KeyAuthAccess, accessToken) refreshKey := fmt.Sprintf(redismodel.KeyAuthRefresh, refreshToken) userTokensKey := fmt.Sprintf(redismodel.KeyAuthUserTokens, userID) accessTTL := time.Duration(global.JWTSetting.AccessTokenExpire) * time.Second refreshTTL := time.Duration(global.JWTSetting.RefreshTokenExpire) * time.Second pipe := global.RedisEngine.TxPipeline() pipe.Set(ctx, accessKey, userID, accessTTL) pipe.Set(ctx, refreshKey, userID, refreshTTL) pipe.Eval(ctx, authTokenSAddScript, []string{userTokensKey}, refreshToken, int(refreshTTL.Seconds())) if _, err := pipe.Exec(ctx); err != nil { span.RecordError(err) return err } return nil } // AuthTokenDel 登出/失效:删除 access、refresh 映射,并从用户 token 集合移除 refresh_token func AuthTokenDel(ctx context.Context, userID int64, accessToken, refreshToken string) error { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthTokenDel") defer span.End() accessKey := fmt.Sprintf(redismodel.KeyAuthAccess, accessToken) refreshKey := fmt.Sprintf(redismodel.KeyAuthRefresh, refreshToken) userTokensKey := fmt.Sprintf(redismodel.KeyAuthUserTokens, userID) pipe := global.RedisEngine.TxPipeline() pipe.Del(ctx, accessKey) pipe.Del(ctx, refreshKey) pipe.SRem(ctx, userTokensKey, refreshToken) if _, err := pipe.Exec(ctx); err != nil { span.RecordError(err) return err } return nil } // AuthUserIDByAccess 通过 access token 查询 user_id,token 不存在或已过期返回 redis.Nil func AuthUserIDByAccess(ctx context.Context, accessToken string) (int64, error) { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthUserIDByAccess") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthAccess, accessToken) userID, err := global.RedisEngine.Get(ctx, key).Int64() if err != nil && err != redis.Nil { span.RecordError(err) } return userID, err } // AuthUserIDByRefresh 通过 refresh token 查询 user_id,token 不存在或已过期返回 redis.Nil func AuthUserIDByRefresh(ctx context.Context, refreshToken string) (int64, error) { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthUserIDByRefresh") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthRefresh, refreshToken) userID, err := global.RedisEngine.Get(ctx, key).Int64() if err != nil && err != redis.Nil { span.RecordError(err) } return userID, err } // sessionKeyTTL session_key 缓存时长(30 天,与微信 session_key 有效期对齐)。 const sessionKeyTTL = 30 * 24 * time.Hour // AuthSessionKeySet 写入用户的 session_key(登录时 code2session 返回,用于虚拟支付 signature 计算)。 func AuthSessionKeySet(ctx context.Context, userID int64, sessionKey string) error { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthSessionKeySet") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthSessionKey, userID) if err := global.RedisEngine.Set(ctx, key, sessionKey, sessionKeyTTL).Err(); err != nil { span.RecordError(err) return err } return nil } // AuthSessionKeyGet 读取用户的 session_key,不存在返回 redis.Nil。 func AuthSessionKeyGet(ctx context.Context, userID int64) (string, error) { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthSessionKeyGet") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthSessionKey, userID) sessionKey, err := global.RedisEngine.Get(ctx, key).Result() if err != nil && err != redis.Nil { span.RecordError(err) } return sessionKey, err }