feat: introduce key prefix management for storage components, enhancing compatibility with Java sa-token and improving key handling across core functionalities

This commit is contained in:
Moling
2025-10-25 18:01:50 +08:00
parent 7603ce3cbf
commit 6c943326a6
12 changed files with 207 additions and 76 deletions
+18
View File
@@ -2,6 +2,7 @@ package builder
import (
"fmt"
"strings"
"time"
"github.com/click33/sa-token-go/core/adapter"
@@ -29,6 +30,7 @@ type Builder struct {
isReadCookie bool
dataRefreshPeriod int64
tokenSessionCheckLogin bool
keyPrefix string
cookieConfig *config.CookieConfig
}
@@ -50,6 +52,7 @@ func NewBuilder() *Builder {
isReadCookie: false,
dataRefreshPeriod: config.NoLimit,
tokenSessionCheckLogin: true,
keyPrefix: "satoken:",
cookieConfig: &config.CookieConfig{
Domain: "",
Path: config.DefaultCookiePath,
@@ -229,6 +232,20 @@ func (b *Builder) CookieConfig(cfg *config.CookieConfig) *Builder {
return b
}
// KeyPrefix sets storage key prefix | 设置存储键前缀
// Automatically adds ":" suffix if not present (except for empty string) | 自动添加 ":" 后缀(空字符串除外)
// Examples: "satoken" -> "satoken:", "myapp" -> "myapp:", "" -> ""
// Use empty string "" for Java sa-token compatibility | 使用空字符串 "" 兼容 Java sa-token
func (b *Builder) KeyPrefix(prefix string) *Builder {
// 如果前缀不为空且不以 : 结尾,自动添加 :
if prefix != "" && !strings.HasSuffix(prefix, ":") {
b.keyPrefix = prefix + ":"
} else {
b.keyPrefix = prefix
}
return b
}
// NeverExpire sets token to never expire | 设置Token永不过期
func (b *Builder) NeverExpire() *Builder {
b.timeout = config.NoLimit
@@ -292,6 +309,7 @@ func (b *Builder) Build() *manager.Manager {
JwtSecretKey: b.jwtSecretKey,
IsLog: b.isLog,
IsPrintBanner: b.isPrintBanner,
KeyPrefix: b.keyPrefix,
CookieConfig: b.cookieConfig,
}
+17 -6
View File
@@ -40,11 +40,11 @@ const (
// Default configuration constants | 默认配置常量
const (
DefaultTokenName = "satoken"
DefaultTimeout = 2592000 // 30 days in seconds | 30天(秒)
DefaultMaxLoginCount = 12 // Maximum concurrent logins | 最大并发登录数
DefaultCookiePath = "/"
NoLimit = -1 // No limit flag | 不限制标志
DefaultTokenName = "satoken"
DefaultTimeout = 2592000 // 30 days in seconds | 30天(秒)
DefaultMaxLoginCount = 12 // Maximum concurrent logins | 最大并发登录数
DefaultCookiePath = "/"
NoLimit = -1 // No limit flag | 不限制标志
)
// IsValid checks if the TokenStyle is valid | 检查TokenStyle是否有效
@@ -109,6 +109,10 @@ type Config struct {
// IsPrintBanner Print startup banner (default: true) | 是否打印启动 Banner(默认:true)
IsPrintBanner bool
// KeyPrefix Storage key prefix for Redis isolation (default: "satoken:") | 存储键前缀,用于Redis隔离(默认:"satoken:"
// Set to empty "" to be compatible with Java sa-token default behavior | 设置为空""以兼容Java sa-token默认行为
KeyPrefix string
// CookieConfig Cookie configuration | Cookie配置
CookieConfig *CookieConfig
}
@@ -153,6 +157,7 @@ func DefaultConfig() *Config {
JwtSecretKey: "",
IsLog: false,
IsPrintBanner: true,
KeyPrefix: "satoken:",
CookieConfig: &CookieConfig{
Domain: "",
Path: DefaultCookiePath,
@@ -310,8 +315,14 @@ func (c *Config) SetIsPrintBanner(isPrint bool) *Config {
return c
}
// SetKeyPrefix Set storage key prefix | 设置存储键前缀
func (c *Config) SetKeyPrefix(prefix string) *Config {
c.KeyPrefix = prefix
return c
}
// SetCookieConfig Set cookie configuration | 设置Cookie配置
func (c *Config) SetCookieConfig(cookieConfig *CookieConfig) *Config {
c.CookieConfig = cookieConfig
return c
}
}
+11 -5
View File
@@ -17,7 +17,7 @@ import (
// Constants for storage keys and default values | 存储键和默认值常量
const (
DefaultDevice = "default"
DefaultPrefix = "satoken:"
DefaultPrefix = "satoken"
DisableValue = "1"
DefaultNonceTTL = 5 * time.Minute
@@ -72,14 +72,20 @@ func NewManager(storage adapter.Storage, cfg *config.Config) *Manager {
cfg = config.DefaultConfig()
}
// Use configured prefix, fallback to default | 使用配置的前缀,回退到默认值
prefix := cfg.KeyPrefix
if prefix == "" {
prefix = DefaultPrefix
}
return &Manager{
storage: storage,
config: cfg,
generator: token.NewGenerator(cfg),
prefix: DefaultPrefix,
nonceManager: security.NewNonceManager(storage, DefaultNonceTTL),
refreshManager: security.NewRefreshTokenManager(storage, cfg),
oauth2Server: oauth2.NewOAuth2Server(storage),
prefix: prefix,
nonceManager: security.NewNonceManager(storage, prefix, DefaultNonceTTL),
refreshManager: security.NewRefreshTokenManager(storage, prefix, cfg),
oauth2Server: oauth2.NewOAuth2Server(storage, prefix),
}
}
+10 -7
View File
@@ -36,9 +36,9 @@ const (
AccessTokenLength = 32 // Access token byte length | 访问令牌字节长度
RefreshTokenLength = 32 // Refresh token byte length | 刷新令牌字节长度
CodeKeyPrefix = "satoken:oauth2:code:" // Code storage key prefix | 授权码存储键前
TokenKeyPrefix = "satoken:oauth2:token:" // Token storage key prefix | 令牌存储键前
RefreshKeyPrefix = "satoken:oauth2:refresh:" // Refresh storage key prefix | 刷新令牌存储键前
CodeKeySuffix = "oauth2:code:" // Code key suffix after prefix | 授权码键后
TokenKeySuffix = "oauth2:token:" // Token key suffix after prefix | 令牌键后
RefreshKeySuffix = "oauth2:refresh:" // Refresh key suffix after prefix | 刷新令牌键后
TokenTypeBearer = "Bearer" // Token type | 令牌类型
)
@@ -102,6 +102,7 @@ type AccessToken struct {
// OAuth2Server OAuth2 authorization server | OAuth2授权服务器
type OAuth2Server struct {
storage adapter.Storage
keyPrefix string // Configurable prefix | 可配置的前缀
clients map[string]*Client
clientsMu sync.RWMutex // Clients map lock | 客户端映射锁
codeExpiration time.Duration // Authorization code expiration (10min) | 授权码过期时间(10分钟)
@@ -109,9 +110,11 @@ type OAuth2Server struct {
}
// NewOAuth2Server Creates a new OAuth2 server | 创建新的OAuth2服务器
func NewOAuth2Server(storage adapter.Storage) *OAuth2Server {
// prefix: key prefix (e.g., "satoken:" or "" for Java compatibility) | 键前缀(如:"satoken:" 或 "" 兼容Java
func NewOAuth2Server(storage adapter.Storage, prefix string) *OAuth2Server {
return &OAuth2Server{
storage: storage,
keyPrefix: prefix,
clients: make(map[string]*Client),
codeExpiration: DefaultCodeExpiration,
tokenExpiration: DefaultTokenExpiration,
@@ -374,15 +377,15 @@ func (s *OAuth2Server) RevokeToken(tokenString string) error {
// getCodeKey Gets storage key for authorization code | 获取授权码的存储键
func (s *OAuth2Server) getCodeKey(code string) string {
return CodeKeyPrefix + code
return s.keyPrefix + CodeKeySuffix + code
}
// getTokenKey Gets storage key for access token | 获取访问令牌的存储键
func (s *OAuth2Server) getTokenKey(token string) string {
return TokenKeyPrefix + token
return s.keyPrefix + TokenKeySuffix + token
}
// getRefreshKey Gets storage key for refresh token | 获取刷新令牌的存储键
func (s *OAuth2Server) getRefreshKey(refreshToken string) string {
return RefreshKeyPrefix + refreshToken
return s.keyPrefix + RefreshKeySuffix + refreshToken
}
+7 -7
View File
@@ -17,7 +17,7 @@ import (
)
// Version Sa-Token-Go version | Sa-Token-Go版本
const Version = "0.1.0"
const Version = "0.1.1"
// ============ Exported Types | 导出的类型 ============
// Export main types and functions for external use | 导出主要类型和函数,方便外部使用
@@ -172,20 +172,20 @@ func NewBuilder() *Builder {
}
// NewNonceManager Creates a new nonce manager | 创建新的Nonce管理器
func NewNonceManager(storage Storage, ttl ...int64) *NonceManager {
func NewNonceManager(storage Storage, prefix string, ttl ...int64) *NonceManager {
var duration time.Duration
if len(ttl) > 0 && ttl[0] > 0 {
duration = time.Duration(ttl[0]) * time.Second
}
return security.NewNonceManager(storage, duration)
return security.NewNonceManager(storage, prefix, duration)
}
// NewRefreshTokenManager Creates a new refresh token manager | 创建新的刷新令牌管理器
func NewRefreshTokenManager(storage Storage, cfg *Config) *RefreshTokenManager {
return security.NewRefreshTokenManager(storage, cfg)
func NewRefreshTokenManager(storage Storage, prefix string, cfg *Config) *RefreshTokenManager {
return security.NewRefreshTokenManager(storage, prefix, cfg)
}
// NewOAuth2Server Creates a new OAuth2 server | 创建新的OAuth2服务器
func NewOAuth2Server(storage Storage) *OAuth2Server {
return oauth2.NewOAuth2Server(storage)
func NewOAuth2Server(storage Storage, prefix string) *OAuth2Server {
return oauth2.NewOAuth2Server(storage, prefix)
}
+13 -10
View File
@@ -25,9 +25,9 @@ import (
// Constants for nonce | Nonce常量
const (
DefaultNonceTTL = 5 * time.Minute // Default nonce expiration | 默认nonce过期时间
NonceLength = 32 // Nonce byte length | Nonce字节长度
NonceKeyPrefix = "satoken:nonce:" // Storage key prefix | 存储键前缀
DefaultNonceTTL = 5 * time.Minute // Default nonce expiration | 默认nonce过期时间
NonceLength = 32 // Nonce byte length | Nonce字节长度
NonceKeySuffix = "nonce:" // Key suffix after prefix | 前缀后的键后
)
// Error variables | 错误变量
@@ -37,20 +37,23 @@ var (
// NonceManager Nonce manager for anti-replay attacks | Nonce管理器,用于防重放攻击
type NonceManager struct {
storage adapter.Storage
ttl time.Duration
mu sync.RWMutex
storage adapter.Storage
keyPrefix string // Configurable prefix | 可配置的前缀
ttl time.Duration
mu sync.RWMutex
}
// NewNonceManager Creates a new nonce manager | 创建新的Nonce管理器
// prefix: key prefix (e.g., "satoken:" or "" for Java compatibility) | 键前缀(如:"satoken:" 或 "" 兼容Java
// ttl: time to live, default 5 minutes | 过期时间,默认5分钟
func NewNonceManager(storage adapter.Storage, ttl time.Duration) *NonceManager {
func NewNonceManager(storage adapter.Storage, prefix string, ttl time.Duration) *NonceManager {
if ttl == 0 {
ttl = DefaultNonceTTL
}
return &NonceManager{
storage: storage,
ttl: ttl,
storage: storage,
keyPrefix: prefix,
ttl: ttl,
}
}
@@ -115,5 +118,5 @@ func (nm *NonceManager) IsValid(nonce string) bool {
// getNonceKey Gets storage key for nonce | 获取nonce的存储键
func (nm *NonceManager) getNonceKey(nonce string) string {
return NonceKeyPrefix + nonce
return nm.keyPrefix + NonceKeySuffix + nonce
}
+6 -3
View File
@@ -30,7 +30,7 @@ const (
DefaultRefreshTTL = 30 * 24 * time.Hour // 30 days | 30天
DefaultAccessTTL = 2 * time.Hour // 2 hours | 2小时
RefreshTokenLength = 32 // Refresh token byte length | 刷新令牌字节长度
RefreshKeyPrefix = "satoken:refresh:" // Storage key prefix | 存储键前缀
RefreshKeySuffix = "refresh:" // Key suffix after prefix | 前缀后的键后
)
// Error variables | 错误变量
@@ -53,14 +53,16 @@ type RefreshTokenInfo struct {
// RefreshTokenManager Refresh token manager | 刷新令牌管理器
type RefreshTokenManager struct {
storage adapter.Storage
keyPrefix string // Configurable prefix | 可配置的前缀
tokenGen *token.Generator
refreshTTL time.Duration // Refresh token TTL (30 days) | 刷新令牌有效期(30天)
accessTTL time.Duration // Access token TTL (configurable) | 访问令牌有效期(可配置)
}
// NewRefreshTokenManager Creates a new refresh token manager | 创建新的刷新令牌管理器
// prefix: key prefix (e.g., "satoken:" or "" for Java compatibility) | 键前缀(如:"satoken:" 或 "" 兼容Java
// cfg: configuration, uses Timeout for access token TTL | 配置,使用Timeout作为访问令牌有效期
func NewRefreshTokenManager(storage adapter.Storage, cfg *config.Config) *RefreshTokenManager {
func NewRefreshTokenManager(storage adapter.Storage, prefix string, cfg *config.Config) *RefreshTokenManager {
accessTTL := time.Duration(cfg.Timeout) * time.Second
if accessTTL == 0 {
@@ -69,6 +71,7 @@ func NewRefreshTokenManager(storage adapter.Storage, cfg *config.Config) *Refres
return &RefreshTokenManager{
storage: storage,
keyPrefix: prefix,
tokenGen: token.NewGenerator(cfg),
refreshTTL: DefaultRefreshTTL,
accessTTL: accessTTL,
@@ -194,5 +197,5 @@ func (rtm *RefreshTokenManager) IsValid(refreshToken string) bool {
// getRefreshKey Gets storage key for refresh token | 获取刷新令牌的存储键
func (rtm *RefreshTokenManager) getRefreshKey(refreshToken string) string {
return RefreshKeyPrefix + refreshToken
return rtm.keyPrefix + RefreshKeySuffix + refreshToken
}
+87
View File
@@ -0,0 +1,87 @@
package main
import (
"fmt"
"github.com/click33/sa-token-go/core"
"github.com/click33/sa-token-go/storage/memory"
)
func main() {
fmt.Println("🔄 Java sa-token 兼容性演示")
fmt.Println("=" + "────────────────────────────────────────────────────────────" + "=")
fmt.Println()
storage := memory.NewStorage()
// 方式1: Go 默认配置(带前缀 "satoken:"
fmt.Println("【方式1】Go 默认配置 - 使用前缀 'satoken:'")
mgr1 := core.NewBuilder().
Storage(storage).
TokenName("satoken"). // 使用默认的 token 名称
KeyPrefix("satoken:"). // 显式设置前缀(默认值)
IsPrintBanner(false).
Build()
token1, _ := mgr1.Login("user001", "pc")
fmt.Printf("✅ 登录成功,Token: %s\n", token1)
fmt.Println(" Redis Keys 示例:")
fmt.Println(" - satoken:token:" + token1)
fmt.Println(" - satoken:account:user001:pc")
fmt.Println(" - satoken:session:user001")
fmt.Println()
// 方式2: Java sa-token 兼容配置(无前缀)
fmt.Println("【方式2】Java 兼容配置 - 无前缀(与Java默认行为一致)")
storage2 := memory.NewStorage()
mgr2 := core.NewBuilder().
Storage(storage2).
TokenName("satoken"). // 必须与 Java 端配置一致
KeyPrefix(""). // 空前缀,兼容 Java sa-token
IsPrintBanner(false).
Build()
token2, _ := mgr2.Login("user002", "web")
fmt.Printf("✅ 登录成功,Token: %s\n", token2)
fmt.Println(" Redis Keys 示例(兼容Java:")
fmt.Println(" - token:" + token2)
fmt.Println(" - account:user002:web")
fmt.Println(" - session:user002")
fmt.Println()
// 方式3: 自定义前缀(多应用隔离)
fmt.Println("【方式3】自定义前缀 - 用于多应用隔离")
storage3 := memory.NewStorage()
mgr3 := core.NewBuilder().
Storage(storage3).
TokenName("satoken").
KeyPrefix("myapp:sa:"). // 自定义前缀
IsPrintBanner(false).
Build()
token3, _ := mgr3.Login("user003", "app")
fmt.Printf("✅ 登录成功,Token: %s\n", token3)
fmt.Println(" Redis Keys 示例:")
fmt.Println(" - myapp:sa:token:" + token3)
fmt.Println(" - myapp:sa:account:user003:app")
fmt.Println(" - myapp:sa:session:user003")
fmt.Println()
// 关键配置说明
fmt.Println("=" + "────────────────────────────────────────────────────────────" + "=")
fmt.Println("📝 关键配置说明:")
fmt.Println()
fmt.Println("1. 与 Java sa-token 互通:")
fmt.Println(" cfg.SetKeyPrefix(\"\") // 设置为空字符串")
fmt.Println(" 或")
fmt.Println(" builder.KeyPrefix(\"\") // Builder 方式")
fmt.Println()
fmt.Println("2. 多应用隔离:")
fmt.Println(" cfg.SetKeyPrefix(\"app1:\") // 应用1")
fmt.Println(" cfg.SetKeyPrefix(\"app2:\") // 应用2")
fmt.Println()
fmt.Println("3. 默认 Go 行为:")
fmt.Println(" cfg.SetKeyPrefix(\"satoken:\") // 默认值")
fmt.Println()
fmt.Println("=" + "────────────────────────────────────────────────────────────" + "=")
}
+1
View File
@@ -0,0 +1 @@
+9
View File
@@ -1,7 +1,16 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+16 -6
View File
@@ -13,7 +13,7 @@ import (
)
func main() {
fmt.Println("=== Sa-Token-Go Redis Storage Example ===\n")
fmt.Println("=== Sa-Token-Go Redis Storage Example ===")
// Get Redis configuration from environment variables | 从环境变量获取 Redis 配置
redisAddr := os.Getenv("REDIS_ADDR")
@@ -38,21 +38,31 @@ func main() {
fmt.Printf("✅ Connected to Redis: %s\n\n", redisAddr)
// Initialize Sa-Token with Redis storage | 使用 Redis 存储初始化 Sa-Token
redisStorage, err := redis.NewStorage(redisAddr, redisPassword)
redisURL := fmt.Sprintf("redis://:%s@%s/0", redisPassword, redisAddr)
redisStorage, err := redis.NewStorage(redisURL) // Storage 层不处理前缀,符合 Java sa-token 设计
if err != nil {
log.Fatalf("❌ Failed to create Redis storage: %v\n", err)
}
// 创建 Manager(符合 Java sa-token 标准设计)
stputil.SetManager(
core.NewBuilder().
Storage(redisStorage).
TokenName("Authorization").
TokenStyle(core.TokenStyleRandom64).
Timeout(3600). // 1 hour | 1小时
Timeout(3600). // 1 hour | 1小时
KeyPrefix("satoken"). // ✅ 自动添加冒号,实际为 "satoken:"(符合 Java 设计)
IsPrintBanner(true).
Build(),
)
fmt.Println("📌 当前配置(符合 Java sa-token 标准):")
fmt.Println(" - Storage 层前缀: \"\" (空)")
fmt.Println(" - Manager 层前缀: \"satoken\" → 自动变为 \"satoken:\"")
fmt.Println(" - Redis Key 示例: satoken:login:token:xxx")
fmt.Println(" - ✅ 完全兼容 Java sa-token")
fmt.Println()
// Test authentication | 测试认证功能
fmt.Println("1. Login user | 登录用户")
token, err := stputil.Login(1000)
@@ -64,14 +74,14 @@ func main() {
// Check login status | 检查登录状态
fmt.Println("2. Check login status | 检查登录状态")
if stputil.IsLogin(token) {
fmt.Println("✅ User is logged in\n")
fmt.Println("✅ User is logged in")
}
// Set permissions and roles | 设置权限和角色
fmt.Println("3. Set permissions and roles | 设置权限和角色")
stputil.SetPermissions(1000, []string{"user:read", "user:write", "admin:*"})
stputil.SetRoles(1000, []string{"admin", "user"})
fmt.Println("✅ Permissions and roles set\n")
fmt.Println("✅ Permissions and roles set")
// Check permission | 检查权限
fmt.Println("4. Check permissions | 检查权限")
@@ -106,7 +116,7 @@ func main() {
fmt.Println("✅ User logged out")
if !stputil.IsLogin(token) {
fmt.Println("✅ Token is now invalid\n")
fmt.Println("✅ Token is now invalid")
}
// Close Redis connection | 关闭 Redis 连接
+12 -32
View File
@@ -13,7 +13,6 @@ import (
type Storage struct {
client *redis.Client
ctx context.Context
keyPrefix string
opTimeout time.Duration
}
@@ -34,7 +33,7 @@ type Config struct {
}
// NewStorage 通过Redis URL创建存储
func NewStorage(url string, keyPrefix string) (adapter.Storage, error) {
func NewStorage(url string) (adapter.Storage, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, fmt.Errorf("failed to parse redis url: %w", err)
@@ -51,13 +50,12 @@ func NewStorage(url string, keyPrefix string) (adapter.Storage, error) {
return &Storage{
client: client,
ctx: ctx,
keyPrefix: keyPrefix,
opTimeout: 3 * time.Second,
}, nil
}
// NewStorageFromConfig 通过配置创建存储
func NewStorageFromConfig(cfg *Config, keyPrefix string) (adapter.Storage, error) {
func NewStorageFromConfig(cfg *Config) (adapter.Storage, error) {
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
Password: cfg.Password,
@@ -83,24 +81,22 @@ func NewStorageFromConfig(cfg *Config, keyPrefix string) (adapter.Storage, error
return &Storage{
client: client,
ctx: ctx,
keyPrefix: keyPrefix,
opTimeout: opTimeout,
}, nil
}
// NewStorageFromClient 从已有的Redis客户端创建存储
func NewStorageFromClient(client *redis.Client, keyPrefix string) adapter.Storage {
func NewStorageFromClient(client *redis.Client) adapter.Storage {
return &Storage{
client: client,
ctx: context.Background(),
keyPrefix: keyPrefix,
opTimeout: 3 * time.Second,
}
}
// getKey 获取完整的键名
// getKey 获取完整的键名(Storage 层不处理前缀,前缀由 Manager 层统一管理)
func (s *Storage) getKey(key string) string {
return s.keyPrefix + key
return key
}
// Set 设置键值对
@@ -157,25 +153,17 @@ func (s *Storage) Keys(pattern string) ([]string, error) {
defer cancel()
var (
cursor uint64
result []string
prefixLen = len(s.keyPrefix)
fullPattern = s.getKey(pattern)
cursor uint64
result []string
)
for {
keys, next, err := s.client.Scan(ctx, cursor, fullPattern, 1000).Result()
keys, next, err := s.client.Scan(ctx, cursor, pattern, 1000).Result()
if err != nil {
return nil, err
}
if len(keys) > 0 {
for _, k := range keys {
if len(k) > prefixLen {
result = append(result, k[prefixLen:])
} else {
result = append(result, k)
}
}
result = append(result, keys...)
}
cursor = next
if cursor == 0 {
@@ -199,14 +187,14 @@ func (s *Storage) TTL(key string) (time.Duration, error) {
return s.client.TTL(ctx, s.getKey(key)).Result()
}
// Clear 清空所有数据(使用前缀匹配删除
// Clear 清空所有数据(⚠️ 警告:会清空整个 Redis,谨慎使用!应由 Manager 层控制
func (s *Storage) Clear() error {
ctx, cancel := s.withTimeout()
defer cancel()
var cursor uint64
for {
keys, next, err := s.client.Scan(ctx, cursor, s.keyPrefix+"*", 1000).Result()
keys, next, err := s.client.Scan(ctx, cursor, "*", 1000).Result()
if err != nil {
return err
}
@@ -256,7 +244,6 @@ type Builder struct {
password string
database int
poolSize int
prefix string
}
// NewBuilder 创建构建器
@@ -267,7 +254,6 @@ func NewBuilder() *Builder {
password: "",
database: 0,
poolSize: 10,
prefix: "satoken:",
}
}
@@ -301,12 +287,6 @@ func (b *Builder) PoolSize(poolSize int) *Builder {
return b
}
// KeyPrefix 设置键前缀
func (b *Builder) KeyPrefix(prefix string) *Builder {
b.prefix = prefix
return b
}
// Build 构建存储
func (b *Builder) Build() (adapter.Storage, error) {
return NewStorageFromConfig(&Config{
@@ -315,5 +295,5 @@ func (b *Builder) Build() (adapter.Storage, error) {
Password: b.password,
Database: b.database,
PoolSize: b.poolSize,
}, b.prefix)
})
}