120 lines
4.2 KiB
Go
120 lines
4.2 KiB
Go
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
|
||
}
|