package redisdao import ( "context" "fmt" "time" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-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 -> admin_id // - refresh_token -> admin_id // - admin_id 的 token 集合加入 refresh_token(用于多端管理 / 登出) func AuthTokenSet(ctx context.Context, adminID 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) adminTokensKey := fmt.Sprintf(redismodel.KeyAuthUserTokens, adminID) accessTTL := time.Duration(global.JWTSetting.AccessTokenExpire) * time.Second refreshTTL := time.Duration(global.JWTSetting.RefreshTokenExpire) * time.Second pipe := global.RedisEngine.TxPipeline() pipe.Set(ctx, accessKey, adminID, accessTTL) pipe.Set(ctx, refreshKey, adminID, refreshTTL) pipe.Eval(ctx, authTokenSAddScript, []string{adminTokensKey}, 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, adminID 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) adminTokensKey := fmt.Sprintf(redismodel.KeyAuthUserTokens, adminID) pipe := global.RedisEngine.TxPipeline() pipe.Del(ctx, accessKey) pipe.Del(ctx, refreshKey) pipe.SRem(ctx, adminTokensKey, refreshToken) if _, err := pipe.Exec(ctx); err != nil { span.RecordError(err) return err } return nil } // AuthAdminIDByAccess 通过 access token 查询 admin_id,token 不存在或已过期返回 redis.Nil func AuthAdminIDByAccess(ctx context.Context, accessToken string) (int64, error) { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthAdminIDByAccess") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthAccess, accessToken) adminID, err := global.RedisEngine.Get(ctx, key).Int64() if err != nil && err != redis.Nil { span.RecordError(err) } return adminID, err } // AuthAdminIDByRefresh 通过 refresh token 查询 admin_id,token 不存在或已过期返回 redis.Nil func AuthAdminIDByRefresh(ctx context.Context, refreshToken string) (int64, error) { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthAdminIDByRefresh") defer span.End() key := fmt.Sprintf(redismodel.KeyAuthRefresh, refreshToken) adminID, err := global.RedisEngine.Get(ctx, key).Int64() if err != nil && err != redis.Nil { span.RecordError(err) } return adminID, err } // AuthAdminTokensDelAll 失效某管理员全部 token(禁用/删除账号时调用)。 func AuthAdminTokensDelAll(ctx context.Context, adminID int64) error { ctx, span := global.Tracer.Start(ctx, "redisdao.AuthAdminTokensDelAll") defer span.End() adminTokensKey := fmt.Sprintf(redismodel.KeyAuthUserTokens, adminID) members, err := global.RedisEngine.SMembers(ctx, adminTokensKey).Result() if err != nil && err != redis.Nil { span.RecordError(err) return err } pipe := global.RedisEngine.TxPipeline() for _, refreshToken := range members { pipe.Del(ctx, fmt.Sprintf(redismodel.KeyAuthRefresh, refreshToken)) } // access token 没有全集索引(TTL 短,最多 AccessTokenExpire 秒后自然过期), // 由 JWTAuth 的 DB 状态回查兜底拦截。 pipe.Del(ctx, adminTokensKey) if _, err := pipe.Exec(ctx); err != nil { span.RecordError(err) return err } return nil }