Files
2026-09-11 01:47:20 +08:00

120 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}