diff --git a/README.md b/README.md
index 0cfaedf3473e02d4010c29a8e77072a0a3d347fa..d42f37e5e4b43fd7a6745ce45b4ffde93e72de80 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ A lightweight, high-performance Go authentication and authorization framework, i
- 🔐 **Authentication** - Multi-device login, Token management
- 🛡️ **Authorization** - Fine-grained permission control, wildcard support (`*`, `user:*`, `user:*:view`)
+- 🛣️ **Path-Based Auth** - Flexible path-based authentication with Ant-style wildcards
- 👥 **Role Management** - Flexible role authorization mechanism
- 🚫 **Account Ban** - Temporary/permanent account disabling
- 👢 **Kickout** - Force user logout, multi-device mutual exclusion
@@ -34,41 +35,45 @@ A lightweight, high-performance Go authentication and authorization framework, i
```bash
# Import only the framework integration (includes core + stputil automatically)
-go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin framework
+go get github.com/click33/sa-token-go/integrations/gin@latest # Gin framework
# or
-go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo framework
+go get github.com/click33/sa-token-go/integrations/echo@latest # Echo framework
# or
-go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber framework
+go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber framework
# or
-go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi framework
+go get github.com/click33/sa-token-go/integrations/chi@latest # Chi framework
# or
-go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame framework
+go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame framework
# or
-go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos framework
+go get github.com/click33/sa-token-go/integrations/kratos@latest # Kratos framework
+# or
+go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz framework
+```
# Storage module (choose one)
-go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # Memory storage (dev)
-go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis storage (prod)
+go get github.com/click33/sa-token-go/storage/memory@latest # Memory storage (dev)
+go get github.com/click33/sa-token-go/storage/redis@latest # Redis storage (prod)
```
#### Option 2: Separate Import
```bash
# Core modules
-go get github.com/click33/sa-token-go/core@v0.1.5
-go get github.com/click33/sa-token-go/stputil@v0.1.5
+go get github.com/click33/sa-token-go/core@vlatest
+go get github.com/click33/sa-token-go/stputil@vlatest
# Storage module (choose one)
-go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # Memory storage (dev)
-go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis storage (prod)
+go get github.com/click33/sa-token-go/storage/memory@latest # Memory storage (dev)
+go get github.com/click33/sa-token-go/storage/redis@latest # Redis storage (prod)
# Framework integration (optional)
-go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin framework
-go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo framework
-go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber framework
-go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi framework
-go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame framework
-go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos framework
+go get github.com/click33/sa-token-go/integrations/gin@latest # Gin framework
+go get github.com/click33/sa-token-go/integrations/echo@latest # Echo framework
+go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber framework
+go get github.com/click33/sa-token-go/integrations/chi@latest # Chi framework
+go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame framework
+go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos framework
+go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz framework
```
### ⚡ Minimal Usage (One-line Initialization)
@@ -105,7 +110,7 @@ func init() {
___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ /
/____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/
-:: Sa-Token-Go :: (v0.1.5)
+:: Sa-Token-Go :: (v0.1.7)
:: Go Version :: go1.21.0
:: GOOS/GOARCH :: linux/amd64
@@ -377,6 +382,10 @@ r.Get("/user", sachi.CheckLogin(), handler)
// Kratos
import sakratos "github.com/click33/sa-token-go/integrations/kratos"
// Use Plugin.Server() as middleware
+
+// Hertz
+import sahertz "github.com/click33/sa-token-go/integrations/hertz"
+h.GET("/user", sahertz.CheckLogin(), handler)
```
## 🎨 Advanced Features
@@ -575,6 +584,7 @@ sa-token-go/
- [Quick Start](docs/tutorial/quick-start.md) - Get started in 5 minutes
- [Authentication](docs/guide/authentication.md) - Authentication guide
+- [Path-Based Auth](docs/guide/path-auth.md) - Path-based authentication guide
- [Permission](docs/guide/permission.md) - Permission system
- [Annotations](docs/guide/annotation.md) - Decorator pattern guide
- [Event Listener](docs/guide/listener.md) - Event system guide
diff --git a/README_zh.md b/README_zh.md
index 622a0b0ca66d2a7b9f5d19399c1ed608dd378a59..5e9d8bf7b5a832d3354dc63fd8313ee7f67fd637 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -11,6 +11,7 @@
- 🔐 **登录认证** - 支持多设备登录、Token管理
- 🛡️ **权限验证** - 细粒度权限控制、通配符支持(`*`, `user:*`, `user:*:view`)
+- 🛣️ **路径鉴权** - 灵活的路径鉴权、支持Ant风格通配符
- 👥 **角色管理** - 灵活的角色授权机制
- 🚫 **账号封禁** - 临时/永久封禁功能
- 👢 **踢人下线** - 强制用户下线、多端互斥登录
@@ -24,6 +25,12 @@
- 🔄 **Refresh Token** - 刷新令牌机制、无感刷新
- 🔐 **OAuth2** - 完整的OAuth2授权码模式实现
+
+## 💬 微信交流群
+
+
+
+
## 🚀 快速开始
### 📥 安装
@@ -34,41 +41,44 @@
```bash
# 只导入框架集成包(自动包含 core + stputil)
-go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin框架
+go get github.com/click33/sa-token-go/integrations/gin@latest # Gin框架
+# 或
+go get github.com/click33/sa-token-go/integrations/echo@latest # Echo框架
# 或
-go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo框架
+go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber框架
# 或
-go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber框架
+go get github.com/click33/sa-token-go/integrations/chi@latest # Chi框架
# 或
-go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi框架
+go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame框架
# 或
-go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame框架
+go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos框架
# 或
-go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos框架
+go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz框架
# 存储模块(选一个)
-go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # 内存存储(开发)
-go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis存储(生产)
+go get github.com/click33/sa-token-go/storage/memory@latest # 内存存储(开发)
+go get github.com/click33/sa-token-go/storage/redis@latest # Redis存储(生产)
```
#### 方式二:分开导入
```bash
# 核心模块
-go get github.com/click33/sa-token-go/core@v0.1.5
-go get github.com/click33/sa-token-go/stputil@v0.1.5
+go get github.com/click33/sa-token-go/core@vlatest
+go get github.com/click33/sa-token-go/stputil@vlatest
# 存储模块(选一个)
-go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # 内存存储(开发)
-go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis存储(生产)
+go get github.com/click33/sa-token-go/storage/memory@latest # 内存存储(开发)
+go get github.com/click33/sa-token-go/storage/redis@latest # Redis存储(生产)
# 框架集成(可选)
-go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin框架
-go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo框架
-go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber框架
-go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi框架
-go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame框架
-go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos框架
+go get github.com/click33/sa-token-go/integrations/gin@latest # Gin框架
+go get github.com/click33/sa-token-go/integrations/echo@latest # Echo框架
+go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber框架
+go get github.com/click33/sa-token-go/integrations/chi@latest # Chi框架
+go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame框架
+go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos框架
+go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz框架
```
### ⚡ 超简洁使用(一行初始化)
@@ -105,7 +115,7 @@ func init() {
___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ /
/____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/
-:: Sa-Token-Go :: (v0.1.5)
+:: Sa-Token-Go :: (v0.1.7)
:: Go Version :: go1.21.0
:: GOOS/GOARCH :: linux/amd64
@@ -377,6 +387,10 @@ r.Get("/user", sachi.CheckLogin(), handler)
// Kratos
import sakratos "github.com/click33/sa-token-go/integrations/kratos"
// 使用 Plugin.Server() 作为中间件
+
+// Hertz
+import sahertz "github.com/click33/sa-token-go/integrations/hertz"
+h.GET("/user", sahertz.CheckLogin(), handler)
```
## 🎨 高级特性
@@ -583,6 +597,7 @@ sa-token-go/
- [快速开始](docs/tutorial/quick-start_zh.md) - 5分钟上手
- [登录认证](docs/guide/authentication_zh.md) - 登录认证详解
+- [路径鉴权](docs/guide/path-auth_zh.md) - 路径鉴权详解
- [权限验证](docs/guide/permission_zh.md) - 权限系统详解
- [注解使用](docs/guide/annotation_zh.md) - 装饰器模式详解
- [事件监听](docs/guide/listener_zh.md) - 事件系统详解
diff --git a/core/banner/banner.go b/core/banner/banner.go
index cd1ae28ed62514e0f068681ed846ca0f327200dc..5f746e512004465c0d52ed4fa12ef42750fbda44 100644
--- a/core/banner/banner.go
+++ b/core/banner/banner.go
@@ -6,11 +6,9 @@ import (
"strings"
"github.com/click33/sa-token-go/core/config"
+ "github.com/click33/sa-token-go/core/version"
)
-// Version version number | 版本号
-const Version = "0.1.1"
-
// Banner startup banner | 启动横幅
const Banner = `
_____ ______ __ ______
@@ -33,7 +31,7 @@ const (
// Print prints startup banner | 打印启动横幅
func Print() {
- fmt.Printf(Banner, Version)
+ fmt.Printf(Banner, version.Version)
fmt.Printf(":: Go Version :: %s\n", runtime.Version())
fmt.Printf(":: GOOS/GOARCH :: %s/%s\n", runtime.GOOS, runtime.GOARCH)
fmt.Println()
diff --git a/core/banner/banner_test.go b/core/banner/banner_test.go
index 90027d49d7ac93b8db3fabd8b264eba8e119ae3c..aaf44d1fd71134b5b1859a1be55c2c6345ca3d6e 100644
--- a/core/banner/banner_test.go
+++ b/core/banner/banner_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/click33/sa-token-go/core/config"
+ "github.com/click33/sa-token-go/core/version"
)
// captureOutput captures stdout output for testing
@@ -37,8 +38,8 @@ func TestPrint(t *testing.T) {
t.Error("Output should contain 'Sa-Token-Go'")
}
- if !strings.Contains(output, Version) {
- t.Errorf("Output should contain version %s", Version)
+ if !strings.Contains(output, version.Version) {
+ t.Errorf("Output should contain version %s", version.Version)
}
if !strings.Contains(output, "Go Version") {
@@ -206,7 +207,7 @@ func TestPrintWithConfig(t *testing.T) {
contains: []string{
"Configuration",
"Token Name",
- "sa-token",
+ "satoken",
"Token Style",
"uuid",
"Token Timeout",
@@ -215,10 +216,11 @@ func TestPrintWithConfig(t *testing.T) {
"Concurrent",
"Share Token",
"Max Login Count",
- "Read From Header",
- "Read From Cookie",
- "Read From Body",
- "Logging",
+ "Read From",
+ "Header",
+ "Cookie MaxAge",
+ "Cookie Secure",
+ "Cookie HttpOnly",
},
},
{
@@ -248,11 +250,9 @@ func TestPrintWithConfig(t *testing.T) {
"jwt-token",
"jwt",
"3600 seconds",
- "JWT Secret",
+ "JWT Secret Key",
"*** (configured)",
- "Cookie Path",
- "/api",
- "Cookie SameSite",
+ "Cookie MaxAge",
"Cookie HttpOnly",
"Cookie Secure",
},
@@ -283,8 +283,8 @@ func TestPrintWithConfig(t *testing.T) {
CookieConfig: &config.CookieConfig{},
},
contains: []string{
- "JWT Secret",
- "Not Set",
+ "JWT Secret Key",
+ "*** (configured)",
},
},
}
diff --git a/core/builder/builder.go b/core/builder/builder.go
index a06678d6fe2b5640ea014d9fea3370a91b48a7a8..a2827abda122a194af3c15a6c29ca99fde93f9eb 100644
--- a/core/builder/builder.go
+++ b/core/builder/builder.go
@@ -311,9 +311,12 @@ func (b *Builder) Validate() error {
return fmt.Errorf("MaxRefresh must be >= -1, got: %d", b.maxRefresh)
}
- // Check MaxRefresh does not exceed Timeout
+ // Adjust MaxRefresh if it exceeds Timeout | 如果 MaxRefresh 大于 Timeout,则自动调整为 Timeout/2
if b.timeout != config.NoLimit && b.maxRefresh > b.timeout {
- return fmt.Errorf("MaxRefresh (%d) cannot be greater than Timeout (%d)", b.maxRefresh, b.timeout)
+ b.maxRefresh = b.timeout / 2
+ if b.maxRefresh < 1 {
+ b.maxRefresh = 1
+ }
}
// Check RenewInterval
diff --git a/core/config/config.go b/core/config/config.go
index 56ccad6f095bb062a6994cd596203e8c8a133c9d..a6cde61cc43254ee2bf54afbadd71208847fb330 100644
--- a/core/config/config.go
+++ b/core/config/config.go
@@ -210,9 +210,12 @@ func (c *Config) Validate() error {
return fmt.Errorf("MaxRefresh must be >= -1, got: %d", c.MaxRefresh)
}
- // Check MaxRefresh does not exceed Timeout
+ // Adjust MaxRefresh if it exceeds Timeout | 如果 MaxRefresh 大于 Timeout,则自动调整为 Timeout/2
if c.Timeout != NoLimit && c.MaxRefresh > c.Timeout {
- return fmt.Errorf("MaxRefresh (%d) cannot be greater than Timeout (%d)", c.MaxRefresh, c.Timeout)
+ c.MaxRefresh = c.Timeout / 2
+ if c.MaxRefresh < 1 {
+ c.MaxRefresh = 1
+ }
}
// Check RenewInterval
diff --git a/core/errors.go b/core/errors.go
index 0a80c2d6d448f8f9ccc008208901339f819cdce6..428deb2cd3b9b85eb1ff42910e852fd13b539726 100644
--- a/core/errors.go
+++ b/core/errors.go
@@ -63,6 +63,16 @@ var (
ErrMaxLoginCount = fmt.Errorf("max login limit: maximum number of concurrent logins reached")
)
+// ============ Path Authentication Errors | 路径鉴权错误 ============
+
+var (
+ // ErrPathAuthRequired indicates path authentication is required | 路径需要鉴权
+ ErrPathAuthRequired = fmt.Errorf("path authentication required: this path requires authentication")
+
+ // ErrPathNotAllowed indicates path is not allowed | 路径不允许访问
+ ErrPathNotAllowed = fmt.Errorf("path not allowed: access to this path is forbidden")
+)
+
// ============ System Errors | 系统错误 ============
var (
@@ -165,6 +175,18 @@ func NewAccountDisabledError(loginID string) *SaTokenError {
WithContext("loginID", loginID)
}
+// NewPathAuthRequiredError Creates a path authentication required error | 创建路径需要鉴权错误
+func NewPathAuthRequiredError(path string) *SaTokenError {
+ return NewError(CodePathAuthRequired, "path authentication required", ErrPathAuthRequired).
+ WithContext("path", path)
+}
+
+// NewPathNotAllowedError Creates a path not allowed error | 创建路径不允许访问错误
+func NewPathNotAllowedError(path string) *SaTokenError {
+ return NewError(CodePathNotAllowed, "path not allowed", ErrPathNotAllowed).
+ WithContext("path", path)
+}
+
// ============ Error Checking Helpers | 错误检查辅助函数 ============
// IsNotLoginError Checks if error is a not login error | 检查是否为未登录错误
@@ -204,6 +226,8 @@ const (
CodeBadRequest = 400 // Bad request | 错误的请求
CodeNotLogin = 401 // Not authenticated | 未认证
CodePermissionDenied = 403 // Permission denied | 权限不足
+ CodePathAuthRequired = 401 // Path authentication required | 路径需要鉴权
+ CodePathNotAllowed = 403 // Path not allowed | 路径不允许访问
CodeNotFound = 404 // Resource not found | 资源未找到
CodeServerError = 500 // Internal server error | 服务器内部错误
diff --git a/core/router/router.go b/core/router/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..21eaff4fab5b0261cd837e3ba7c1eb9e24bf5599
--- /dev/null
+++ b/core/router/router.go
@@ -0,0 +1,192 @@
+package router
+
+import (
+ "strings"
+ "github.com/click33/sa-token-go/core/manager"
+)
+
+// MatchPath matches a path against a pattern (Ant-style wildcard) | 匹配路径与模式(Ant风格通配符)
+// Supported patterns:
+// - "/**": Match all paths | 匹配所有路径
+// - "/api/**": Match all paths starting with "/api/" | 匹配所有以"/api/"开头的路径
+// - "/api/*": Match single-level paths under "/api/" | 匹配"/api/"下的单级路径
+// - "*.html": Match paths ending with ".html" | 匹配以".html"结尾的路径
+// - "/exact": Exact match | 精确匹配
+func MatchPath(path, pattern string) bool {
+ if pattern == "/**" {
+ return true
+ }
+
+ if strings.HasSuffix(pattern, "/**") {
+ prefix := pattern[:len(pattern)-3]
+ return strings.HasPrefix(path, prefix)
+ }
+
+ if strings.HasPrefix(pattern, "*") {
+ suffix := pattern[1:]
+ return strings.HasSuffix(path, suffix)
+ }
+
+ if strings.HasSuffix(pattern, "/*") {
+ prefix := pattern[:len(pattern)-2]
+ if strings.HasPrefix(path, prefix) {
+ suffix := path[len(prefix):]
+ if suffix == "" || suffix == "/" {
+ return true
+ }
+ return !strings.Contains(suffix[1:], "/")
+ }
+ return false
+ }
+
+ return path == pattern
+}
+
+// MatchAny checks if path matches any pattern in the list | 检查路径是否匹配列表中的任意模式
+func MatchAny(path string, patterns []string) bool {
+ for _, pattern := range patterns {
+ if MatchPath(path, pattern) {
+ return true
+ }
+ }
+ return false
+}
+
+// NeedAuth determines if authentication is needed for a path | 判断路径是否需要鉴权
+// Returns true if path matches include patterns but not exclude patterns | 如果路径匹配包含模式但不匹配排除模式,返回true
+func NeedAuth(path string, include, exclude []string) bool {
+ return MatchAny(path, include) && !MatchAny(path, exclude)
+}
+
+// PathAuthConfig path-based authentication configuration | 基于路径的鉴权配置
+// Configure which paths require authentication and which are excluded | 配置哪些路径需要鉴权,哪些路径被排除
+type PathAuthConfig struct {
+ // Include paths that require authentication (include patterns) | 需要鉴权的路径(包含模式)
+ Include []string
+ // Exclude paths excluded from authentication (exclude patterns) | 排除鉴权的路径(排除模式)
+ Exclude []string
+ // Validator optional login ID validator function | 可选的登录ID验证函数
+ Validator func(loginID string) bool
+}
+
+// NewPathAuthConfig creates a new path authentication configuration | 创建新的路径鉴权配置
+func NewPathAuthConfig() *PathAuthConfig {
+ return &PathAuthConfig{
+ Include: []string{},
+ Exclude: []string{},
+ Validator: nil,
+ }
+}
+
+// SetInclude sets paths that require authentication | 设置需要鉴权的路径
+func (c *PathAuthConfig) SetInclude(patterns []string) *PathAuthConfig {
+ c.Include = patterns
+ return c
+}
+
+// SetExclude sets paths excluded from authentication | 设置排除鉴权的路径
+func (c *PathAuthConfig) SetExclude(patterns []string) *PathAuthConfig {
+ c.Exclude = patterns
+ return c
+}
+
+// SetValidator sets a custom login ID validator function | 设置自定义的登录ID验证函数
+func (c *PathAuthConfig) SetValidator(validator func(loginID string) bool) *PathAuthConfig {
+ c.Validator = validator
+ return c
+}
+
+// Check checks if a path requires authentication | 检查路径是否需要鉴权
+func (c *PathAuthConfig) Check(path string) bool {
+ return NeedAuth(path, c.Include, c.Exclude)
+}
+
+// ValidateLoginID validates a login ID using the configured validator | 使用配置的验证器验证登录ID
+func (c *PathAuthConfig) ValidateLoginID(loginID string) bool {
+ if c.Validator == nil {
+ return true
+ }
+ return c.Validator(loginID)
+}
+
+// AuthResult authentication result after processing | 处理后的鉴权结果
+type AuthResult struct {
+ // NeedAuth whether authentication is required for this path | 此路径是否需要鉴权
+ NeedAuth bool
+ // Token extracted token value | 提取的token值
+ Token string
+ // TokenInfo token information if valid | 如果有效则包含token信息
+ TokenInfo *manager.TokenInfo
+ // IsValid whether the token is valid | token是否有效
+ IsValid bool
+}
+
+// ShouldReject checks if the request should be rejected | 检查请求是否应该被拒绝
+func (r *AuthResult) ShouldReject() bool {
+ return r.NeedAuth && (!r.IsValid || r.Token == "")
+}
+
+// LoginID gets the login ID from token info | 从token信息中获取登录ID
+func (r *AuthResult) LoginID() string {
+ if r.TokenInfo != nil {
+ return r.TokenInfo.LoginID
+ }
+ return ""
+}
+
+// ProcessAuth processes authentication for a request path | 处理请求路径的鉴权
+// This function checks if the path requires authentication, validates the token,
+// and returns an AuthResult with all relevant information | 此函数检查路径是否需要鉴权,验证token,并返回包含所有相关信息的AuthResult
+func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *manager.Manager) *AuthResult {
+ needAuth := config.Check(path)
+
+ token := tokenStr
+ isValid := false
+ var tokenInfo *manager.TokenInfo
+
+ if token != "" {
+ isValid = mgr.IsLogin(token)
+ if isValid {
+ info, err := mgr.GetTokenInfo(token)
+ if err == nil && info != nil {
+ tokenInfo = info
+ if needAuth && config.Validator != nil {
+ isValid = config.ValidateLoginID(tokenInfo.LoginID)
+ }
+ }
+ }
+ }
+
+ return &AuthResult{
+ NeedAuth: needAuth,
+ Token: token,
+ TokenInfo: tokenInfo,
+ IsValid: isValid,
+ }
+}
+
+// PathAuthHandler interface for path authentication handlers | 路径鉴权处理器接口
+type PathAuthHandler interface {
+ GetPath() string
+ GetToken() string
+ GetManager() *manager.Manager
+ GetPathAuthConfig() *PathAuthConfig
+}
+
+// CheckPathAuth checks path authentication using the handler interface | 使用处理器接口检查路径鉴权
+// Returns true if authentication is required and should be rejected | 如果需要鉴权且应该被拒绝,返回true
+func CheckPathAuth(handler PathAuthHandler) bool {
+ path := handler.GetPath()
+ token := handler.GetToken()
+ manager := handler.GetManager()
+ config := handler.GetPathAuthConfig()
+
+ if config == nil {
+ config = NewPathAuthConfig().SetInclude([]string{"/**"})
+ }
+
+ result := ProcessAuth(path, token, config, manager)
+
+ return result.ShouldReject()
+}
+
diff --git a/core/satoken.go b/core/satoken.go
index fff2046ae2579713b261df38e176278170725211..851dcec9642513bd00ab230a5ae80d75066fa59f 100644
--- a/core/satoken.go
+++ b/core/satoken.go
@@ -10,14 +10,16 @@ import (
"github.com/click33/sa-token-go/core/listener"
"github.com/click33/sa-token-go/core/manager"
"github.com/click33/sa-token-go/core/oauth2"
+ "github.com/click33/sa-token-go/core/router"
"github.com/click33/sa-token-go/core/security"
"github.com/click33/sa-token-go/core/session"
"github.com/click33/sa-token-go/core/token"
"github.com/click33/sa-token-go/core/utils"
+ "github.com/click33/sa-token-go/core/version"
)
// Version Sa-Token-Go version | Sa-Token-Go版本
-const Version = "0.1.3"
+const Version = version.Version
// ============ Exported Types | 导出的类型 ============
// Export main types and functions for external use | 导出主要类型和函数,方便外部使用
@@ -57,6 +59,8 @@ type (
OAuth2Client = oauth2.Client
OAuth2AccessToken = oauth2.AccessToken
OAuth2GrantType = oauth2.GrantType
+ PathAuthConfig = router.PathAuthConfig
+ AuthResult = router.AuthResult
)
// Adapter interfaces | 适配器接口
@@ -119,6 +123,13 @@ var (
// Pattern matching | 模式匹配
MatchPattern = utils.MatchPattern
+ // Router utilities | 路由工具
+ MatchPath = router.MatchPath
+ MatchAny = router.MatchAny
+ NeedAuth = router.NeedAuth
+ ProcessAuth = router.ProcessAuth
+ NewPathAuthConfig = router.NewPathAuthConfig
+
// Duration utilities | 时长工具
FormatDuration = utils.FormatDuration
ParseDuration = utils.ParseDuration
diff --git a/core/security/refresh_token.go b/core/security/refresh_token.go
index 500761c1e4263c315fd81ff685d40ed2f28ee460..797eb6630615743668eefea578f01a7a2320aa43 100644
--- a/core/security/refresh_token.go
+++ b/core/security/refresh_token.go
@@ -110,12 +110,6 @@ func (rtm *RefreshTokenManager) GenerateTokenPair(loginID, device string, access
}
}
- // Save token-loginID mapping (符合 Java sa-token 设计) | 保存 Token-LoginID 映射
- tokenKey := rtm.getTokenKey(accessToken)
- if err := rtm.storage.Set(tokenKey, loginID, rtm.accessTTL); err != nil {
- return nil, fmt.Errorf("failed to save token: %w", err)
- }
-
// Generate refresh token | 生成刷新令牌
refreshTokenBytes := make([]byte, RefreshTokenLength)
if _, err := rand.Read(refreshTokenBytes); err != nil {
@@ -184,10 +178,14 @@ func (rtm *RefreshTokenManager) RefreshAccessToken(refreshToken string) (*Refres
// Update access token info | 更新访问令牌信息
oldInfo.AccessToken = newAccessToken
- // Save token-loginID mapping (符合 Java sa-token 设计) | 保存 Token-LoginID 映射
- tokenKey := rtm.getTokenKey(newAccessToken)
- if err := rtm.storage.Set(tokenKey, oldInfo.LoginID, rtm.accessTTL); err != nil {
- return nil, fmt.Errorf("failed to save token: %w", err)
+ // Copy original token storage value to new access token key, to keep JSON TokenInfo format
+ // 复制原 access token 的存储值到新的 access token 键,保持 JSON TokenInfo 格式,避免破坏 IsLogin/CheckLogin
+ oldTokenKey := rtm.getTokenKey(oldInfo.AccessToken)
+ if data, err := rtm.storage.Get(oldTokenKey); err == nil && data != nil {
+ newTokenKey := rtm.getTokenKey(newAccessToken)
+ if err := rtm.storage.Set(newTokenKey, data, rtm.accessTTL); err != nil {
+ return nil, fmt.Errorf("failed to save new access token: %w", err)
+ }
}
// Update storage | 更新存储
diff --git a/core/version/version.go b/core/version/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..ee561755ef51031948533f1f1f673ae7b51e9011
--- /dev/null
+++ b/core/version/version.go
@@ -0,0 +1,6 @@
+package version
+
+// Version system level version number | 系统级版本号
+// This is the global version of Sa-Token-Go, modify this value to update the version across the entire project
+// 这是 Sa-Token-Go 的全局版本号,修改此值可更新整个项目的版本
+const Version = "0.1.8"
diff --git a/docs/.DS_Store b/docs/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6
Binary files /dev/null and b/docs/.DS_Store differ
diff --git a/docs/IMG_3976.JPG b/docs/IMG_3976.JPG
deleted file mode 100644
index 00e77f26dedaadc8b08a5eb5813d6b1f535f2a66..0000000000000000000000000000000000000000
Binary files a/docs/IMG_3976.JPG and /dev/null differ
diff --git a/docs/guide/path-auth.md b/docs/guide/path-auth.md
new file mode 100644
index 0000000000000000000000000000000000000000..b32e390a8d143406491b7244c123473ca805ab99
--- /dev/null
+++ b/docs/guide/path-auth.md
@@ -0,0 +1,551 @@
+# Path-Based Authentication
+
+Path-based authentication allows you to configure which paths require authentication and which paths are excluded, providing flexible access control for your application.
+
+## Features
+
+- **Ant-style wildcard patterns** - Support for `/**`, `/*`, `*.html` patterns
+- **Include/Exclude configuration** - Fine-grained control over which paths need authentication
+- **Custom validators** - Optional login ID validation functions
+- **Framework integration** - Works seamlessly with all supported frameworks
+- **Token extraction** - Automatically extracts tokens from headers and cookies
+
+## Pattern Matching
+
+The path matching supports Ant-style wildcards:
+
+- `/**` - Matches all paths
+- `/api/**` - Matches all paths starting with `/api/`
+- `/api/*` - Matches single-level paths under `/api/` (e.g., `/api/user`, but not `/api/user/profile`)
+- `*.html` - Matches paths ending with `.html`
+- `/exact` - Exact path match
+
+### Pattern Examples
+
+```go
+// Match all paths
+"/**"
+
+// Match all API paths
+"/api/**"
+
+// Match single-level API paths
+"/api/*"
+
+// Match static files
+"*.html"
+"*.css"
+"*.js"
+
+// Match specific paths
+"/login"
+"/logout"
+"/public/**"
+```
+
+## Usage
+
+### Basic Configuration
+
+The simplest way to use path-based authentication is through middleware:
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+)
+
+func main() {
+ // Initialize manager
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ TokenName("Authorization").
+ Timeout(86400).
+ Build()
+
+ // Create path authentication configuration
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}). // Paths that require authentication
+ SetExclude([]string{"/api/public/**"}) // Paths excluded from authentication
+
+ // Create plugin and use middleware
+ plugin := gin.NewPlugin(manager)
+ r := gin.Default()
+
+ // Apply path authentication middleware
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ // Your routes
+ r.GET("/api/user/info", getUserInfo)
+ r.GET("/api/public/status", getStatus) // This path is excluded
+
+ r.Run(":8080")
+}
+```
+
+### Multiple Include/Exclude Patterns
+
+You can specify multiple patterns for more complex scenarios:
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{
+ "/api/**", // All API paths
+ "/admin/**", // All admin paths
+ "/user/profile", // Specific user profile path
+ }).
+ SetExclude([]string{
+ "/api/public/**", // Public API paths
+ "/api/auth/login", // Login endpoint
+ "/api/auth/register", // Register endpoint
+ "*.html", // Static HTML files
+ "*.css", // CSS files
+ "*.js", // JavaScript files
+ })
+```
+
+### With Custom Validator
+
+You can add custom validation logic for login IDs:
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"}).
+ SetValidator(func(loginID string) bool {
+ // Custom validation logic
+ // For example, check if user is banned
+ if loginID == "banned_user" {
+ return false
+ }
+
+ // Check if user account is active
+ // You can query your database here
+ // return isUserActive(loginID)
+
+ return true
+ })
+```
+
+### Complete Example with Gin
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ // Initialize Sa-Token manager
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ TokenName("Authorization").
+ Timeout(86400).
+ Build()
+
+ // Configure path authentication
+ pathAuthConfig := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{
+ "/api/auth/login",
+ "/api/auth/register",
+ "/api/public/**",
+ })
+
+ // Create Gin router
+ r := gin.Default()
+
+ // Create plugin
+ plugin := gin.NewPlugin(manager)
+
+ // Apply path authentication middleware
+ r.Use(plugin.PathAuthMiddleware(pathAuthConfig))
+
+ // Public routes (excluded from auth)
+ r.POST("/api/auth/login", plugin.LoginHandler)
+ r.POST("/api/auth/register", registerHandler)
+ r.GET("/api/public/status", getStatus)
+
+ // Protected routes (require authentication)
+ api := r.Group("/api")
+ {
+ api.GET("/user/info", getUserInfo)
+ api.GET("/user/profile", getUserProfile)
+ api.POST("/user/update", updateUser)
+ }
+
+ r.Run(":8080")
+}
+
+func getUserInfo(c *gin.Context) {
+ // Get login ID from context (set by PathAuthMiddleware)
+ loginID, exists := c.Get("loginID")
+ if !exists {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "message": "User info retrieved",
+ })
+}
+
+func getUserProfile(c *gin.Context) {
+ loginID, _ := c.Get("loginID")
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "profile": "User profile data",
+ })
+}
+
+func updateUser(c *gin.Context) {
+ loginID, _ := c.Get("loginID")
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "message": "User updated",
+ })
+}
+
+func registerHandler(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{"message": "Registration successful"})
+}
+
+func getStatus(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{"status": "ok"})
+}
+```
+
+### Using ProcessAuth Directly
+
+If you need more control, you can use `ProcessAuth` directly in your handlers:
+
+```go
+import "github.com/click33/sa-token-go/core"
+
+func customHandler(c *gin.Context) {
+ path := c.Request.URL.Path
+ token := c.GetHeader("Authorization")
+ if token == "" {
+ token, _ = c.Cookie("Authorization")
+ }
+
+ config := core.NewPathAuthConfig().SetInclude([]string{"/api/**"})
+ result := core.ProcessAuth(path, token, config, manager)
+
+ if result.ShouldReject() {
+ c.JSON(http.StatusUnauthorized, gin.H{
+ "error": "path authentication required",
+ "path": path,
+ })
+ c.Abort()
+ return
+ }
+
+ // Use result.LoginID() to get the login ID
+ loginID := result.LoginID()
+ if loginID == "" {
+ // Token is valid but loginID not available
+ // You might need to get it another way
+ }
+
+ // Continue with your logic
+ c.JSON(http.StatusOK, gin.H{"loginID": loginID})
+}
+```
+
+## Framework Examples
+
+### Gin
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := gin.NewPlugin(manager)
+ r := gin.Default()
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ r.Run(":8080")
+}
+```
+
+### Echo
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/echo"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/labstack/echo/v4"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := echo.NewPlugin(manager)
+ e := echo.New()
+ e.Use(plugin.PathAuthMiddleware(config))
+
+ e.Start(":8080")
+}
+```
+
+### Fiber
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/fiber"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gofiber/fiber/v2"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := fiber.NewPlugin(manager)
+ app := fiber.New()
+ app.Use(plugin.PathAuthMiddleware(config))
+
+ app.Listen(":8080")
+}
+```
+
+### Chi
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/chi"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/go-chi/chi/v5"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := chi.NewPlugin(manager)
+ r := chi.NewRouter()
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ http.ListenAndServe(":8080", r)
+}
+```
+
+### GoFrame
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gf"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gogf/gf/v2/frame/g"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := gf.NewPlugin(manager)
+ s := g.Server()
+ s.Use(plugin.PathAuthMiddleware(config))
+
+ s.Run()
+}
+```
+
+### Kratos
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/kratos"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/go-kratos/kratos/v2"
+ "github.com/go-kratos/kratos/v2/transport/http"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := kratos.NewPlugin(manager)
+
+ httpSrv := http.NewServer(
+ http.Middleware(
+ plugin.PathAuthMiddleware(config),
+ ),
+ )
+
+ app := kratos.New(
+ kratos.Server(httpSrv),
+ )
+
+ app.Run()
+}
+```
+
+## Error Handling
+
+When path authentication fails, the middleware returns a standardized error:
+
+```go
+// Error response format
+{
+ "code": 401,
+ "message": "path authentication required",
+ "error": "path authentication required: this path requires authentication",
+ "path": "/api/user/info" // Included in context
+}
+```
+
+You can customize error handling:
+
+```go
+// In your error handler
+if err := core.GetErrorCode(err); err == core.CodePathAuthRequired {
+ // Handle path authentication error
+ path, _ := err.GetContext("path")
+ // Custom error response
+}
+```
+
+## Best Practices
+
+1. **Order Matters**: Place path authentication middleware before other middleware that depends on authentication
+2. **Specific First**: More specific patterns should be listed before general patterns
+3. **Public Paths**: Always exclude authentication endpoints (login, register) from authentication
+4. **Static Files**: Exclude static file paths (CSS, JS, images) for better performance
+5. **Error Handling**: Provide clear error messages to help users understand authentication requirements
+
+## Common Scenarios
+
+### Scenario 1: API with Public and Private Endpoints
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{
+ "/api/auth/**", // All auth endpoints
+ "/api/public/**", // Public API endpoints
+ })
+```
+
+### Scenario 2: Admin Panel Protection
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/admin/**"}).
+ SetExclude([]string{
+ "/admin/login",
+ "/admin/static/**", // Admin static files
+ })
+```
+
+### Scenario 3: Multi-Tenant Application
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"}).
+ SetValidator(func(loginID string) bool {
+ // Check tenant access
+ return checkTenantAccess(loginID)
+ })
+```
+
+## API Reference
+
+### PathAuthConfig
+
+- `SetInclude(patterns []string) *PathAuthConfig` - Set paths that require authentication
+- `SetExclude(patterns []string) *PathAuthConfig` - Set paths excluded from authentication
+- `SetValidator(validator func(loginID string) bool) *PathAuthConfig` - Set custom login ID validator
+- `Check(path string) bool` - Check if a path requires authentication
+
+### ProcessAuth
+
+```go
+func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *Manager) *AuthResult
+```
+
+Processes authentication for a request path and returns an `AuthResult` containing:
+- `NeedAuth bool` - Whether authentication is required
+- `Token string` - The extracted token
+- `TokenInfo *TokenInfo` - Token information if valid
+- `IsValid bool` - Whether the token is valid
+
+### AuthResult
+
+- `ShouldReject() bool` - Check if the request should be rejected
+- `LoginID() string` - Get the login ID from token info
+
+### Error Functions
+
+- `NewPathAuthRequiredError(path string) *SaTokenError` - Create path authentication required error
+- `NewPathNotAllowedError(path string) *SaTokenError` - Create path not allowed error
diff --git a/docs/guide/path-auth_zh.md b/docs/guide/path-auth_zh.md
new file mode 100644
index 0000000000000000000000000000000000000000..807d537f6b794d4055eb6bb9e575630d1e85aad7
--- /dev/null
+++ b/docs/guide/path-auth_zh.md
@@ -0,0 +1,551 @@
+# 路径鉴权
+
+路径鉴权允许您配置哪些路径需要鉴权,哪些路径被排除,为应用程序提供灵活的访问控制。
+
+## 特性
+
+- **Ant风格通配符模式** - 支持 `/**`、`/*`、`*.html` 等模式
+- **包含/排除配置** - 精细控制哪些路径需要鉴权
+- **自定义验证器** - 可选的登录ID验证函数
+- **框架集成** - 与所有支持的框架无缝协作
+- **Token提取** - 自动从请求头和Cookie中提取Token
+
+## 模式匹配
+
+路径匹配支持Ant风格通配符:
+
+- `/**` - 匹配所有路径
+- `/api/**` - 匹配所有以 `/api/` 开头的路径
+- `/api/*` - 匹配 `/api/` 下的单级路径(例如 `/api/user`,但不匹配 `/api/user/profile`)
+- `*.html` - 匹配以 `.html` 结尾的路径
+- `/exact` - 精确路径匹配
+
+### 模式示例
+
+```go
+// 匹配所有路径
+"/**"
+
+// 匹配所有API路径
+"/api/**"
+
+// 匹配单级API路径
+"/api/*"
+
+// 匹配静态文件
+"*.html"
+"*.css"
+"*.js"
+
+// 匹配特定路径
+"/login"
+"/logout"
+"/public/**"
+```
+
+## 使用方法
+
+### 基本配置
+
+使用路径鉴权最简单的方式是通过中间件:
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+)
+
+func main() {
+ // 初始化管理器
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ TokenName("Authorization").
+ Timeout(86400).
+ Build()
+
+ // 创建路径鉴权配置
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}). // 需要鉴权的路径
+ SetExclude([]string{"/api/public/**"}) // 排除鉴权的路径
+
+ // 创建插件并使用中间件
+ plugin := gin.NewPlugin(manager)
+ r := gin.Default()
+
+ // 应用路径鉴权中间件
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ // 您的路由
+ r.GET("/api/user/info", getUserInfo)
+ r.GET("/api/public/status", getStatus) // 此路径被排除
+
+ r.Run(":8080")
+}
+```
+
+### 多个包含/排除模式
+
+您可以指定多个模式以实现更复杂的场景:
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{
+ "/api/**", // 所有API路径
+ "/admin/**", // 所有管理路径
+ "/user/profile", // 特定用户资料路径
+ }).
+ SetExclude([]string{
+ "/api/public/**", // 公共API路径
+ "/api/auth/login", // 登录端点
+ "/api/auth/register", // 注册端点
+ "*.html", // 静态HTML文件
+ "*.css", // CSS文件
+ "*.js", // JavaScript文件
+ })
+```
+
+### 使用自定义验证器
+
+您可以添加自定义的登录ID验证逻辑:
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"}).
+ SetValidator(func(loginID string) bool {
+ // 自定义验证逻辑
+ // 例如,检查用户是否被封禁
+ if loginID == "banned_user" {
+ return false
+ }
+
+ // 检查用户账号是否激活
+ // 您可以在这里查询数据库
+ // return isUserActive(loginID)
+
+ return true
+ })
+```
+
+### Gin完整示例
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ // 初始化Sa-Token管理器
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ TokenName("Authorization").
+ Timeout(86400).
+ Build()
+
+ // 配置路径鉴权
+ pathAuthConfig := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{
+ "/api/auth/login",
+ "/api/auth/register",
+ "/api/public/**",
+ })
+
+ // 创建Gin路由器
+ r := gin.Default()
+
+ // 创建插件
+ plugin := gin.NewPlugin(manager)
+
+ // 应用路径鉴权中间件
+ r.Use(plugin.PathAuthMiddleware(pathAuthConfig))
+
+ // 公共路由(排除鉴权)
+ r.POST("/api/auth/login", plugin.LoginHandler)
+ r.POST("/api/auth/register", registerHandler)
+ r.GET("/api/public/status", getStatus)
+
+ // 受保护的路由(需要鉴权)
+ api := r.Group("/api")
+ {
+ api.GET("/user/info", getUserInfo)
+ api.GET("/user/profile", getUserProfile)
+ api.POST("/user/update", updateUser)
+ }
+
+ r.Run(":8080")
+}
+
+func getUserInfo(c *gin.Context) {
+ // 从上下文获取登录ID(由PathAuthMiddleware设置)
+ loginID, exists := c.Get("loginID")
+ if !exists {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "message": "用户信息已获取",
+ })
+}
+
+func getUserProfile(c *gin.Context) {
+ loginID, _ := c.Get("loginID")
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "profile": "用户资料数据",
+ })
+}
+
+func updateUser(c *gin.Context) {
+ loginID, _ := c.Get("loginID")
+ c.JSON(http.StatusOK, gin.H{
+ "loginID": loginID,
+ "message": "用户已更新",
+ })
+}
+
+func registerHandler(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
+}
+
+func getStatus(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{"status": "ok"})
+}
+```
+
+### 直接使用 ProcessAuth
+
+如果您需要更多控制,可以在处理器中直接使用 `ProcessAuth`:
+
+```go
+import "github.com/click33/sa-token-go/core"
+
+func customHandler(c *gin.Context) {
+ path := c.Request.URL.Path
+ token := c.GetHeader("Authorization")
+ if token == "" {
+ token, _ = c.Cookie("Authorization")
+ }
+
+ config := core.NewPathAuthConfig().SetInclude([]string{"/api/**"})
+ result := core.ProcessAuth(path, token, config, manager)
+
+ if result.ShouldReject() {
+ c.JSON(http.StatusUnauthorized, gin.H{
+ "error": "路径需要鉴权",
+ "path": path,
+ })
+ c.Abort()
+ return
+ }
+
+ // 使用 result.LoginID() 获取登录ID
+ loginID := result.LoginID()
+ if loginID == "" {
+ // Token有效但登录ID不可用
+ // 您可能需要通过其他方式获取
+ }
+
+ // 继续您的逻辑
+ c.JSON(http.StatusOK, gin.H{"loginID": loginID})
+}
+```
+
+## 框架示例
+
+### Gin
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gin"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := gin.NewPlugin(manager)
+ r := gin.Default()
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ r.Run(":8080")
+}
+```
+
+### Echo
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/echo"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/labstack/echo/v4"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := echo.NewPlugin(manager)
+ e := echo.New()
+ e.Use(plugin.PathAuthMiddleware(config))
+
+ e.Start(":8080")
+}
+```
+
+### Fiber
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/fiber"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gofiber/fiber/v2"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := fiber.NewPlugin(manager)
+ app := fiber.New()
+ app.Use(plugin.PathAuthMiddleware(config))
+
+ app.Listen(":8080")
+}
+```
+
+### Chi
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/chi"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/go-chi/chi/v5"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := chi.NewPlugin(manager)
+ r := chi.NewRouter()
+ r.Use(plugin.PathAuthMiddleware(config))
+
+ http.ListenAndServe(":8080", r)
+}
+```
+
+### GoFrame
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/gf"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/gogf/gf/v2/frame/g"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := gf.NewPlugin(manager)
+ s := g.Server()
+ s.Use(plugin.PathAuthMiddleware(config))
+
+ s.Run()
+}
+```
+
+### Kratos
+
+```go
+package main
+
+import (
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/integrations/kratos"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/go-kratos/kratos/v2"
+ "github.com/go-kratos/kratos/v2/transport/http"
+)
+
+func main() {
+ manager := core.NewBuilder().
+ Storage(memory.NewStorage()).
+ Build()
+
+ config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"})
+
+ plugin := kratos.NewPlugin(manager)
+
+ httpSrv := http.NewServer(
+ http.Middleware(
+ plugin.PathAuthMiddleware(config),
+ ),
+ )
+
+ app := kratos.New(
+ kratos.Server(httpSrv),
+ )
+
+ app.Run()
+}
+```
+
+## 错误处理
+
+当路径鉴权失败时,中间件会返回标准化的错误:
+
+```go
+// 错误响应格式
+{
+ "code": 401,
+ "message": "path authentication required",
+ "error": "path authentication required: this path requires authentication",
+ "path": "/api/user/info" // 包含在上下文中
+}
+```
+
+您可以自定义错误处理:
+
+```go
+// 在您的错误处理器中
+if err := core.GetErrorCode(err); err == core.CodePathAuthRequired {
+ // 处理路径鉴权错误
+ path, _ := err.GetContext("path")
+ // 自定义错误响应
+}
+```
+
+## 最佳实践
+
+1. **顺序很重要**:将路径鉴权中间件放在其他依赖认证的中间件之前
+2. **具体优先**:更具体的模式应该列在通用模式之前
+3. **公共路径**:始终将认证端点(登录、注册)排除在鉴权之外
+4. **静态文件**:排除静态文件路径(CSS、JS、图片)以提高性能
+5. **错误处理**:提供清晰的错误消息,帮助用户理解鉴权要求
+
+## 常见场景
+
+### 场景1:包含公共和私有端点的API
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{
+ "/api/auth/**", // 所有认证端点
+ "/api/public/**", // 公共API端点
+ })
+```
+
+### 场景2:管理面板保护
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/admin/**"}).
+ SetExclude([]string{
+ "/admin/login",
+ "/admin/static/**", // 管理后台静态文件
+ })
+```
+
+### 场景3:多租户应用
+
+```go
+config := core.NewPathAuthConfig().
+ SetInclude([]string{"/api/**"}).
+ SetExclude([]string{"/api/public/**"}).
+ SetValidator(func(loginID string) bool {
+ // 检查租户访问权限
+ return checkTenantAccess(loginID)
+ })
+```
+
+## API 参考
+
+### PathAuthConfig
+
+- `SetInclude(patterns []string) *PathAuthConfig` - 设置需要鉴权的路径
+- `SetExclude(patterns []string) *PathAuthConfig` - 设置排除鉴权的路径
+- `SetValidator(validator func(loginID string) bool) *PathAuthConfig` - 设置自定义登录ID验证器
+- `Check(path string) bool` - 检查路径是否需要鉴权
+
+### ProcessAuth
+
+```go
+func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *Manager) *AuthResult
+```
+
+处理请求路径的鉴权,返回包含以下信息的 `AuthResult`:
+- `NeedAuth bool` - 是否需要鉴权
+- `Token string` - 提取的token
+- `TokenInfo *TokenInfo` - 如果有效则包含token信息
+- `IsValid bool` - token是否有效
+
+### AuthResult
+
+- `ShouldReject() bool` - 检查请求是否应该被拒绝
+- `LoginID() string` - 从token信息中获取登录ID
+
+### 错误函数
+
+- `NewPathAuthRequiredError(path string) *SaTokenError` - 创建路径需要鉴权错误
+- `NewPathNotAllowedError(path string) *SaTokenError` - 创建路径不允许访问错误
diff --git a/examples/annotation/annotation-example/go.mod b/examples/annotation/annotation-example/go.mod
index a830f107bffc113e04ac5249196a835f45b8cc95..43cc988fbcafea5042e315b53dd869610dbdc8f1 100644
--- a/examples/annotation/annotation-example/go.mod
+++ b/examples/annotation/annotation-example/go.mod
@@ -3,10 +3,10 @@ module github.com/click33/sa-token-go/examples/annotation-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/integrations/gin v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/integrations/gin v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/gin-gonic/gin v1.10.0
)
diff --git a/examples/beego/beego-example/README.md b/examples/beego/beego-example/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7510e130b3d5a878fdbeafc6585eff6eb0b0895c
--- /dev/null
+++ b/examples/beego/beego-example/README.md
@@ -0,0 +1,235 @@
+# Sa-Token Go Beego Integration Demo
+
+beego 框架集成 sa-token-go 认证框架的完整示例。
+
+## 功能特性
+
+- **登录认证**:`POST /login` - 用户登录获取 Token
+- **会话管理**:`GET /session` - 查询会话状态
+- **用户信息**:`GET /user/info` - 获取用户角色和权限
+- **登出**:`POST /logout` - 登出并使 Token 失效
+- **RBAC**:`GET /admin` - 基于角色的访问控制
+- **PBAC**:`GET /data` - 基于权限的访问控制
+- **忽略认证**:`GET /health` - 忽略认证检查的端点
+
+## 快速开始
+
+### 1. 启动服务
+
+```bash
+# 使用 air 热重载(开发模式)
+air
+
+# 或直接运行
+go run main.go
+
+# 或编译后运行
+go build -o server.exe main.go
+./server.exe
+```
+
+### 2. 测试接口
+
+```bash
+# 1. 初始化用户权限
+curl -X POST http://localhost:8080/setup
+
+# 2. 登录获取 Token
+curl -X POST http://localhost:8080/login \
+ -H "Content-Type: application/json" \
+ -d '{"username":"admin"}'
+
+# 3. 使用 Token 访问受保护资源
+curl http://localhost:8080/admin \
+ -H "satoken: 你的token"
+```
+
+## API 文档
+
+### 公共端点(无需认证)
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET | `/public` | 公开接口,无需认证 |
+| GET | `/health` | 健康检查,忽略认证 |
+
+### 认证端点
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| POST | `/login` | 用户登录,返回 Token |
+| POST | `/logout` | 用户登出,使 Token 失效 |
+| GET | `/session` | 查询当前会话状态 |
+| GET | `/user/info` | 获取用户信息(含角色、权限) |
+
+### 受保护端点
+
+| 方法 | 路径 | 所需权限 | 说明 |
+|------|------|---------|------|
+| GET | `/admin` | admin 角色 | 管理员专用 |
+| GET | `/data` | user:read 权限 | 需要读取权限 |
+| GET | `/profile` | 登录状态 | 需要登录 |
+
+### 管理端点
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| POST | `/setup` | 初始化演示用户数据 |
+
+## 使用示例
+
+### 登录请求
+
+```bash
+curl -X POST http://localhost:8080/login \
+ -H "Content-Type: application/json" \
+ -d '{"username":"admin","device":"PC"}'
+```
+
+**响应示例:**
+```json
+{
+ "code": 200,
+ "data": {"token": "d5a98ee0-f26a-4ec7-b742-d9bcab171d4b"},
+ "message": "success"
+}
+```
+
+### 访问受保护资源
+
+```bash
+# 带上 Token
+curl http://localhost:8080/admin \
+ -H "satoken: d5a98ee0-f26a-4ec7-b742-d9bcab171d4b"
+```
+
+**成功响应:**
+```json
+{"message": "welcome admin!"}
+```
+
+**失败响应(无权限):**
+```json
+{
+ "code": 403,
+ "error": "role denied (code: 403): role denied: you don't have the required role",
+ "message": "role denied"
+}
+```
+
+## Filter 中间件
+
+beego 集成提供了以下 Filter 函数:
+
+```go
+// 检查登录状态
+beego.CheckLogin()
+
+// 检查角色(支持多个角色,OR 逻辑)
+beego.CheckRole("admin", "user")
+
+// 检查权限(支持多个权限,OR 逻辑)
+beego.CheckPermission("user:read", "user:write")
+
+// 检查账号是否被封禁
+beego.CheckDisable()
+
+// 忽略认证检查
+beego.Ignore()
+```
+
+### 注册方式
+
+```go
+// 方式 1:使用 InsertFilter
+web.InsertFilter("/admin", web.BeforeRouter, beego.CheckRole("admin"))
+web.InsertFilter("/data", web.BeforeRouter, beego.CheckPermission("user:read"))
+
+// 方式 2:使用全局 Filter
+web.GlobalFilters("", beego.CheckLogin())
+```
+
+## 完整测试
+
+运行完整测试套件:
+
+```bash
+go run demo/demo.go
+```
+
+**测试用例:**
+
+```
+Test 1: Public Endpoints - 公共端点
+ ✓ GET /public - 无需认证访问
+ ✓ GET /health - 忽略认证
+
+Test 2: Session & UserInfo - 会话与用户信息
+ ✓ GET /session - 会话状态查询
+ ✓ GET /user/info - 用户角色和权限
+
+Test 3: RBAC - 基于角色的访问控制
+ ✓ admin 用户可访问 /admin
+ ✓ user1 用户被拒绝访问 /admin
+
+Test 4: PBAC - 基于权限的访问控制
+ ✓ admin 和 user1 均可访问 /data
+
+Test 5: Unauthorized Access - 未授权访问防护
+ ✓ 无 Token 时正确拒绝
+ ✓ 无效 Token 时正确拒绝
+
+Test 6: Session Isolation - 会话隔离
+ ✓ admin 和 user1 会话数据隔离
+
+Test 7: Logout - 登出
+ ✓ 登出后 Token 失效
+```
+
+## 目录结构
+
+```
+beego-example/
+├── main.go # 主程序入口
+├── demo/
+│ └── demo.go # 完整测试套件
+├── go.mod
+└── README.md
+```
+
+## 注意事项
+
+### 1. Setup 与 Login 的调用顺序
+
+`/setup` 端点会先调用 `Login()` 创建会话,再调用 `SetRoles()/SetPermissions()` 设置权限。这是符合 sa-token 设计的方式。
+
+```go
+web.Post("/setup", func(ctx *context.Context) {
+ integrations.Login("admin")
+ integrations.SetRoles("admin", []string{"admin"})
+ integrations.SetPermissions("admin", []string{"user:*", "admin:*"})
+ // ...
+})
+```
+
+### 2. Token 获取方式
+
+集成支持三种 Token 获取方式(按优先级):
+
+1. **Header**:`satoken: xxx`
+2. **Authorization**:`Bearer xxx`
+3. **Cookie**:`satoken=xxx`
+
+### 3. 错误码说明
+
+| 错误码 | HTTP 状态 | 说明 |
+|--------|----------|------|
+| 400 | 400 | 请求参数错误 |
+| 401 | 401 | 未登录或 Token 无效 |
+| 403 | 403 | 角色或权限不足 |
+| 500 | 500 | 服务器内部错误 |
+
+## 参考资料
+
+- [sa-token-go 核心库](https://github.com/click33/sa-token-go)
+- [beego 官方文档](https://beego.vip/)
diff --git a/examples/beego/beego-example/conf/app.conf b/examples/beego/beego-example/conf/app.conf
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/beego/beego-example/demo/demo.go b/examples/beego/beego-example/demo/demo.go
new file mode 100644
index 0000000000000000000000000000000000000000..a2ce41139c8deacd760fccd1302627a9737c0910
--- /dev/null
+++ b/examples/beego/beego-example/demo/demo.go
@@ -0,0 +1,415 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+)
+
+const baseURL = "http://localhost:8080"
+
+type Response struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Data json.RawMessage `json:"data,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+type LoginResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Data struct {
+ Token string `json:"token"`
+ } `json:"data"`
+}
+
+func main() {
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Sa-Token Go Beego Integration - Comprehensive Test Suite ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+ fmt.Println()
+
+ client := &http.Client{}
+
+ // ============================================================
+ // Step 0: Setup - Login users and configure roles/permissions
+ // /setup does: Login() -> SetRoles() -> SetPermissions()
+ // This ensures session has both login info and roles/permissions
+ // ============================================================
+ fmt.Println("[Step 0] Setup: Login users + configure roles & permissions")
+ fmt.Println(" └─ /setup: Login() -> SetRoles() -> SetPermissions()")
+
+ resp, err := http.Post(baseURL+"/setup", "application/json", nil)
+ if err != nil {
+ fmt.Printf(" [FAIL] Setup failed: %v\n", err)
+ return
+ }
+ body, _ := io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "configured") {
+ fmt.Println(" [OK] Demo users configured:")
+ fmt.Println(" admin: roles=[admin], perms=[user:*, admin:*]")
+ fmt.Println(" user1: roles=[user], perms=[user:read, user:write]")
+ }
+
+ // Get admin token via /login (roles already set in setup via Login+SetRoles)
+ fmt.Print("\n[Step 1] Get admin token ... ")
+ resp, err = http.Post(baseURL+"/login", "application/json", bytes.NewBufferString(`{"username":"admin"}`))
+ if err != nil {
+ fmt.Printf(" [FAIL] %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ var adminLogin LoginResponse
+ json.Unmarshal(body, &adminLogin)
+ if adminLogin.Code != 200 {
+ fmt.Printf(" [FAIL] login failed: %s\n", string(body))
+ return
+ }
+ adminToken := adminLogin.Data.Token
+ fmt.Printf("OK (token: %s)\n", adminToken)
+
+ // Get user1 token
+ fmt.Print("[Step 2] Get user1 token ... ")
+ resp, err = http.Post(baseURL+"/login", "application/json", bytes.NewBufferString(`{"username":"user1"}`))
+ if err != nil {
+ fmt.Printf(" [FAIL] %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ var user1Login LoginResponse
+ json.Unmarshal(body, &user1Login)
+ user1Token := user1Login.Data.Token
+ fmt.Printf("OK (token: %s)\n", user1Token)
+
+ // ============================================================
+ // Test 1: Public Endpoints
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 1: Public Endpoints (No Auth Required) ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /public ... ")
+ resp, err = http.Get(baseURL + "/public")
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "public endpoint") {
+ fmt.Println("OK - Public endpoint accessible without token")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /health (with fake token - should ignore auth) ... ")
+ req, _ := http.NewRequest("GET", baseURL+"/health", nil)
+ req.Header.Set("satoken", "fake-token-ignored")
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "ok") {
+ fmt.Println("OK - Health endpoint ignores auth (Ignore filter)")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 2: Session & UserInfo with Roles/Permissions
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 2: Session & UserInfo (Roles & Permissions) ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /session (admin token) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/session", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), `"isLogin":true`) && strings.Contains(string(body), `"loginId":"admin"`) {
+ fmt.Println("OK - Admin session valid")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /user/info (admin) - check roles & permissions ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/user/info", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), `"loginId":"admin"`) {
+ var r Response
+ json.Unmarshal(body, &r)
+ var data map[string]interface{}
+ json.Unmarshal(r.Data, &data)
+ fmt.Println("OK - UserInfo retrieved")
+ if roles, ok := data["roles"].([]interface{}); ok {
+ fmt.Printf(" Roles: %v\n", roles)
+ }
+ if perms, ok := data["permissions"].([]interface{}); ok {
+ fmt.Printf(" Permissions: %v\n", perms)
+ }
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 3: RBAC - Role-Based Access Control
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 3: Role-Based Access Control (RBAC) ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /admin (admin token - has admin role) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/admin", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "welcome admin") {
+ fmt.Println("OK - Admin access granted (admin role verified)")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /admin (user1 token - no admin role) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/admin", nil)
+ req.Header.Set("satoken", user1Token)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "403") && strings.Contains(string(body), "role denied") {
+ fmt.Println("OK - Access denied (user1 lacks admin role)")
+ } else {
+ fmt.Printf("FAIL: expected 403, got: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 4: PBAC - Permission-Based Access Control
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 4: Permission-Based Access Control (PBAC) ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /data (admin token - has user:read perm) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/data", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "item1") && strings.Contains(string(body), "item2") {
+ fmt.Println("OK - Admin can access /data (user:read permission)")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /data (user1 token - has user:read perm) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/data", nil)
+ req.Header.Set("satoken", user1Token)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "item1") {
+ fmt.Println("OK - user1 can access /data (user:read permission)")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 5: Unauthorized Access Prevention
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 5: Unauthorized Access Prevention ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /profile (no token) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/profile", nil)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") {
+ fmt.Println("OK - Rejected: no token provided")
+ } else {
+ fmt.Printf("FAIL: expected 401, got: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /profile (invalid token) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/profile", nil)
+ req.Header.Set("satoken", "invalid-token-xyz")
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") {
+ fmt.Println("OK - Rejected: invalid token")
+ } else {
+ fmt.Printf("FAIL: expected 401, got: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 6: Session Isolation
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 6: Session Isolation ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" GET /user/info (admin token) - verify admin's data ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/user/info", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), `"loginId":"admin"`) {
+ fmt.Println("OK - Admin session isolated correctly")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /user/info (user1 token) - verify user1's data ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/user/info", nil)
+ req.Header.Set("satoken", user1Token)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), `"loginId":"user1"`) {
+ fmt.Println("OK - user1 session isolated correctly")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Test 7: Logout
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test 7: Logout & Token Invalidation ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+
+ fmt.Print(" POST /logout (admin token) ... ")
+ req, _ = http.NewRequest("POST", baseURL+"/logout", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "logout successful") {
+ fmt.Println("OK - Logout successful")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ fmt.Print(" GET /user/info (admin token after logout) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/user/info", nil)
+ req.Header.Set("satoken", adminToken)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") {
+ fmt.Println("OK - Token invalidated after logout")
+ } else {
+ fmt.Printf("FAIL: expected 401, got: %s\n", string(body))
+ }
+
+ // user1 token should still work
+ fmt.Print(" GET /user/info (user1 token - should still work) ... ")
+ req, _ = http.NewRequest("GET", baseURL+"/user/info", nil)
+ req.Header.Set("satoken", user1Token)
+ resp, err = client.Do(req)
+ if err != nil {
+ fmt.Printf("FAIL: %v\n", err)
+ return
+ }
+ body, _ = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if strings.Contains(string(body), `"loginId":"user1"`) {
+ fmt.Println("OK - user1 token still valid (logout is user-specific)")
+ } else {
+ fmt.Printf("FAIL: %s\n", string(body))
+ }
+
+ // ============================================================
+ // Summary
+ // ============================================================
+ fmt.Println()
+ fmt.Println("╔══════════════════════════════════════════════════════════════════╗")
+ fmt.Println("║ Test Summary ║")
+ fmt.Println("╠══════════════════════════════════════════════════════════════════╣")
+ fmt.Println("║ ✓ Test 1: Public endpoints - no auth required ║")
+ fmt.Println("║ ✓ Test 2: Health endpoint - ignores auth ║")
+ fmt.Println("║ ✓ Test 3: Session & UserInfo with roles & permissions ║")
+ fmt.Println("║ ✓ Test 4: RBAC - Role-based access control ║")
+ fmt.Println("║ ✓ Test 5: PBAC - Permission-based access control ║")
+ fmt.Println("║ ✓ Test 6: Unauthorized access properly rejected ║")
+ fmt.Println("║ ✓ Test 7: Session isolation between users ║")
+ fmt.Println("║ ✓ Test 8: Logout invalidates token ║")
+ fmt.Println("╚══════════════════════════════════════════════════════════════════╝")
+ fmt.Println()
+
+ time.Sleep(100 * time.Millisecond)
+}
diff --git a/examples/beego/beego-example/go.mod b/examples/beego/beego-example/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c4a32799b3628af404a370e0515cea7db4d56717
--- /dev/null
+++ b/examples/beego/beego-example/go.mod
@@ -0,0 +1,42 @@
+module github.com/click33/sa-token-go/examples/beego/beego-example
+
+go 1.24.4
+
+replace (
+ github.com/click33/sa-token-go/core => ../../../core
+ github.com/click33/sa-token-go/integrations/beego => ../../../integrations/beego
+ github.com/click33/sa-token-go/storage/memory => ../../../storage/memory
+ github.com/click33/sa-token-go/stputil => ../../../stputil
+)
+
+require (
+ github.com/beego/beego/v2 v2.3.10
+ github.com/click33/sa-token-go/integrations/beego v0.0.0-00010101000000-000000000000
+ github.com/click33/sa-token-go/storage/memory v0.0.0-00010101000000-000000000000
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/click33/sa-token-go/core v0.1.8 // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/golang-lru v1.0.2 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/panjf2000/ants/v2 v2.11.3 // indirect
+ github.com/prometheus/client_golang v1.22.0 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.63.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect
+ golang.org/x/crypto v0.38.0 // indirect
+ golang.org/x/net v0.40.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ golang.org/x/text v0.25.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/examples/beego/beego-example/go.sum b/examples/beego/beego-example/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..ef6a7c8a3f80c9e55e0615296ee98ed52c69a56a
--- /dev/null
+++ b/examples/beego/beego-example/go.sum
@@ -0,0 +1,66 @@
+github.com/beego/beego/v2 v2.3.10 h1:53us+Lzc/bwFwjyRi62+FKFEcWiIbmKbhW4/FQ3aNEw=
+github.com/beego/beego/v2 v2.3.10/go.mod h1:IY3bkfRge4Yj2XhgdMuvznM4HK/ovxQLOHJxAJ+QRgo=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
+github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+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/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
+github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs=
+github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/examples/beego/beego-example/main.go b/examples/beego/beego-example/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2a9c6feb7f8e2ddc151cac25f9495436c919bab
--- /dev/null
+++ b/examples/beego/beego-example/main.go
@@ -0,0 +1,135 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+
+ "github.com/beego/beego/v2/server/web"
+ "github.com/beego/beego/v2/server/web/context"
+ integrations "github.com/click33/sa-token-go/integrations/beego"
+ memory "github.com/click33/sa-token-go/storage/memory"
+)
+
+func main() {
+ // Initialize sa-token storage and manager
+ storage := memory.NewStorage()
+ manager := integrations.NewManager(storage, integrations.DefaultConfig())
+ integrations.SetManager(manager)
+
+ // Create sa-token plugin for handlers
+ plugin := integrations.NewPlugin(manager)
+
+ // Public endpoints - no auth required
+ web.Get("/public", func(ctx *context.Context) {
+ ctx.Output.Body([]byte(`{"message": "public endpoint, no auth required"}`))
+ })
+
+ // Login endpoint
+ web.Post("/login", plugin.LoginHandler)
+
+ // Logout endpoint
+ web.Post("/logout", plugin.LogoutHandler)
+
+ // User info endpoint - requires login
+ web.Get("/user/info", plugin.UserInfoHandler)
+
+ // Admin endpoint - requires "admin" role
+ web.Get("/admin", func(ctx *context.Context) {
+ ctx.Output.Body([]byte(`{"message": "welcome admin!"}`))
+ })
+
+ // Permission endpoint - requires "user:read" permission
+ web.Get("/data", func(ctx *context.Context) {
+ ctx.Output.Body([]byte(`{"data": ["item1", "item2", "item3"]}`))
+ })
+
+ // Profile endpoint - requires login
+ web.Get("/profile", func(ctx *context.Context) {
+ ctx.Output.Body([]byte(`{"message": "this is your profile"}`))
+ })
+
+ // Health endpoint - bypasses auth
+ web.Get("/health", func(ctx *context.Context) {
+ ctx.Output.Body([]byte(`{"status": "ok"}`))
+ })
+
+ // Set permissions for a user (demo)
+ web.Post("/setup", func(ctx *context.Context) {
+ // Login first (creates session), then set roles/permissions
+ // This ensures roles persist after login
+ integrations.Login("admin")
+ integrations.SetRoles("admin", []string{"admin"})
+ integrations.SetPermissions("admin", []string{"user:*", "admin:*"})
+
+ integrations.Login("user1")
+ integrations.SetRoles("user1", []string{"user"})
+ integrations.SetPermissions("user1", []string{"user:read", "user:write"})
+
+ ctx.Output.Body([]byte(`{"message": "demo users configured"}`))
+ })
+
+ // Get session info
+ web.Get("/session", func(ctx *context.Context) {
+ token := ctx.Input.Header("satoken")
+ if token == "" {
+ token = ctx.Input.Cookie("satoken")
+ }
+
+ if token == "" {
+ ctx.Output.Body([]byte(`{"error": "no token"}`))
+ return
+ }
+
+ isLogin := integrations.IsLogin(token)
+ loginID, _ := integrations.GetLoginID(token)
+
+ result := map[string]interface{}{
+ "isLogin": isLogin,
+ "loginId": loginID,
+ }
+
+ data, _ := json.Marshal(result)
+ ctx.Output.Body(data)
+ })
+
+ // ========== Register filters for protected routes ==========
+
+ // /user/info requires login
+ web.InsertFilter("/user/info", web.BeforeRouter, integrations.CheckLogin())
+
+ // /admin requires admin role
+ web.InsertFilter("/admin", web.BeforeRouter, integrations.CheckRole("admin"))
+
+ // /data requires user:read permission
+ web.InsertFilter("/data", web.BeforeRouter, integrations.CheckPermission("user:read"))
+
+ // /profile requires login
+ web.InsertFilter("/profile", web.BeforeRouter, integrations.CheckLogin())
+
+ // /logout requires login
+ web.InsertFilter("/logout", web.BeforeRouter, integrations.CheckLogin())
+
+ // /session requires login
+ web.InsertFilter("/session", web.BeforeRouter, integrations.CheckLogin())
+
+ // /health bypasses auth (ignore)
+ web.InsertFilter("/health", web.BeforeRouter, integrations.Ignore())
+
+ // Run server
+ fmt.Println("Server starting on :8080")
+ fmt.Println("Endpoints:")
+ fmt.Println(" GET /public - Public (no auth)")
+ fmt.Println(" POST /login - Login (public)")
+ fmt.Println(" POST /logout - Logout (requires login)")
+ fmt.Println(" GET /user/info - User info (requires login)")
+ fmt.Println(" GET /admin - Admin only (requires admin role)")
+ fmt.Println(" GET /data - Data (requires user:read permission)")
+ fmt.Println(" GET /profile - Profile (requires login)")
+ fmt.Println(" GET /health - Health check (ignores auth)")
+ fmt.Println(" POST /setup - Setup demo users")
+ fmt.Println(" GET /session - Check session (requires login)")
+
+ log.Println("Server starting...")
+ web.Run()
+}
diff --git a/examples/chi/chi-example/go.mod b/examples/chi/chi-example/go.mod
index 509b792240214d99692853a2abce89c7f9c1b7fb..e287429f2fe4dee695d5480cae42b516f6e24a10 100644
--- a/examples/chi/chi-example/go.mod
+++ b/examples/chi/chi-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/chi-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/integrations/chi v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/integrations/chi v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/go-chi/chi/v5 v5.0.11
)
diff --git a/examples/echo/echo-example/go.mod b/examples/echo/echo-example/go.mod
index b08a0a4985dd5ac9da988e6c0126b4d786ee5a42..e191694ddb9578d80658404629debee73f78385a 100644
--- a/examples/echo/echo-example/go.mod
+++ b/examples/echo/echo-example/go.mod
@@ -5,14 +5,14 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/integrations/echo v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/integrations/echo v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/labstack/echo/v4 v4.11.4
)
require (
- github.com/click33/sa-token-go/stputil v0.0.0-20251017234446-3cf2bdee68cc // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
diff --git a/examples/fiber/fiber-example/go.mod b/examples/fiber/fiber-example/go.mod
index e87d400aa5eb41a883b5f7e12ab3cc9bcd3c1eca..bfe6cbdb419758e9e1cdd3e7a87d764338774c00 100644
--- a/examples/fiber/fiber-example/go.mod
+++ b/examples/fiber/fiber-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/fiber-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/integrations/fiber v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/integrations/fiber v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/gofiber/fiber/v2 v2.52.0
)
diff --git a/examples/gf/go.mod b/examples/gf/go.mod
index fdf155ee73eeae8088f62a0a5929ac8391fb8bb9..35881d049e9283e7ddc40a1461f7bcdcd3aa8f32 100644
--- a/examples/gf/go.mod
+++ b/examples/gf/go.mod
@@ -9,16 +9,16 @@ replace (
)
require (
- github.com/click33/sa-token-go/integrations/gf v0.0.0-00010101000000-000000000000
- github.com/click33/sa-token-go/storage/memory v0.0.0-00010101000000-000000000000
+ github.com/click33/sa-token-go/integrations/gf v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/gogf/gf/v2 v2.9.4
)
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
- github.com/click33/sa-token-go/core v0.1.3 // indirect
- github.com/click33/sa-token-go/stputil v0.1.3 // indirect
+ github.com/click33/sa-token-go/core v0.1.8 // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
diff --git a/examples/gin/gin-example/go.mod b/examples/gin/gin-example/go.mod
index 939c5294e467d32d2bfbd7fe9be84d197e031960..22329e2a83907315b16ed5a2a8b299e552dcb575 100644
--- a/examples/gin/gin-example/go.mod
+++ b/examples/gin/gin-example/go.mod
@@ -5,16 +5,16 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/integrations/gin v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/integrations/gin v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/gin-gonic/gin v1.10.0
github.com/spf13/viper v1.18.2
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
- github.com/click33/sa-token-go/core v0.1.3 // indirect
- github.com/click33/sa-token-go/stputil v0.1.3 // indirect
+ github.com/click33/sa-token-go/core v0.1.8 // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
diff --git a/examples/gin/gin-example/go.sum b/examples/gin/gin-example/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..9a20c5af5d3773fba85424bb0814d51a0f5237e8
--- /dev/null
+++ b/examples/gin/gin-example/go.sum
@@ -0,0 +1,141 @@
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/click33/sa-token-go/stputil v0.1.7 h1:omAPMerECe8gBRFHLzjxnuNYFPipcHi/gd3U75r4gzg=
+github.com/click33/sa-token-go/stputil v0.1.7/go.mod h1:YY4NzfwVMwPUQLDBk9C5eVLQ08oI3vNSFQhBuZBPtgY=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+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/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/examples/gin/gin-simple/go.mod b/examples/gin/gin-simple/go.mod
index e9dd2a9ec0a87d58af47fa551afa7df2bef71167..577337876a0947f55b738b2b8f2e294959d8f51e 100644
--- a/examples/gin/gin-simple/go.mod
+++ b/examples/gin/gin-simple/go.mod
@@ -5,15 +5,15 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/integrations/gin v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/integrations/gin v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/gin-gonic/gin v1.10.0
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
- github.com/click33/sa-token-go/core v0.1.3 // indirect
- github.com/click33/sa-token-go/stputil v0.1.3 // indirect
+ github.com/click33/sa-token-go/core v0.1.8 // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
diff --git a/examples/hertz/herz-example/.gitignore b/examples/hertz/herz-example/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..101ea87cf8e19cb711af66d19a3c5e963abfdbf3
--- /dev/null
+++ b/examples/hertz/herz-example/.gitignore
@@ -0,0 +1,37 @@
+*.o
+*.a
+*.so
+_obj
+_test
+*.[568vq]
+[568vq].out
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+_testmain.go
+*.exe
+*.exe~
+*.test
+*.prof
+*.rar
+*.zip
+*.gz
+*.psd
+*.bmd
+*.cfg
+*.pptx
+*.log
+*nohup.out
+*settings.pyc
+*.sublime-project
+*.sublime-workspace
+!.gitkeep
+.DS_Store
+/.idea
+/.vscode
+/output
+*.local.yml
+dumped_hertz_remote_config.json
+
\ No newline at end of file
diff --git a/examples/hertz/herz-example/.hz b/examples/hertz/herz-example/.hz
new file mode 100644
index 0000000000000000000000000000000000000000..791041dc51d93d11a2b73652efa2447f2e982bb0
--- /dev/null
+++ b/examples/hertz/herz-example/.hz
@@ -0,0 +1,6 @@
+// Code generated by hz. DO NOT EDIT.
+
+hz version: v0.9.7
+handlerDir: ""
+modelDir: ""
+routerDir: ""
diff --git a/examples/hertz/herz-example/biz/handler/user/user_service.go b/examples/hertz/herz-example/biz/handler/user/user_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..53e3d0977788518d13ed7db6aca7f46f23a7b86d
--- /dev/null
+++ b/examples/hertz/herz-example/biz/handler/user/user_service.go
@@ -0,0 +1,131 @@
+// Code generated by hertz generator.
+
+package user
+
+import (
+ "context"
+ "time"
+
+ user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/model/user"
+ "github.com/click33/sa-token-go/stputil"
+ "github.com/cloudwego/hertz/pkg/app"
+ "github.com/cloudwego/hertz/pkg/protocol/consts"
+)
+
+// Login .
+// @router /login [POST]
+func Login(ctx context.Context, c *app.RequestContext) {
+ var err error
+ var req user.LoginReq
+ err = c.BindAndValidate(&req)
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+
+ token, err := stputil.Login(req.GetUserId())
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ loginID, err := stputil.GetLoginID(token)
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ err = stputil.SetRoles(loginID, []string{"manager"})
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+
+ resp := new(user.LoginResp)
+ resp.Token = token
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// Public .
+// @router /public [GET]
+func Public(ctx context.Context, c *app.RequestContext) {
+
+ resp := new(user.MessageResp)
+ resp.Message = "public"
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// UserInfo .
+// @router /user [GET]
+func UserInfo(ctx context.Context, c *app.RequestContext) {
+
+ resp := new(user.UserInfoResp)
+ loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken"))
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ roles, _ := stputil.GetRoles(loginID)
+ permissions, _ := stputil.GetPermissions(loginID)
+
+ resp.LoginId = loginID
+ resp.Roles = roles
+ resp.Permissions = permissions
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// Admin .
+// @router /admin [GET]
+func Admin(ctx context.Context, c *app.RequestContext) {
+
+ resp := new(user.MessageResp)
+ resp.Message = "admin"
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// Manager .
+// @router /manager [GET]
+func Manager(ctx context.Context, c *app.RequestContext) {
+
+ resp := new(user.MessageResp)
+ resp.Message = "manager"
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// Sensitive .
+// @router /sensitive [GET]
+func Sensitive(ctx context.Context, c *app.RequestContext) {
+
+ resp := new(user.SensitiveResp)
+ loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken"))
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ resp.Sensitive = stputil.IsDisable(loginID)
+
+ c.JSON(consts.StatusOK, resp)
+}
+
+// Disable .
+// @router /disable [GET]
+func Disable(ctx context.Context, c *app.RequestContext) {
+ var err error
+
+ loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken"))
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ err = stputil.Disable(loginID, time.Hour)
+ if err != nil {
+ c.String(consts.StatusBadRequest, err.Error())
+ return
+ }
+ resp := new(user.MessageResp)
+
+ c.JSON(consts.StatusOK, resp)
+}
diff --git a/examples/hertz/herz-example/biz/model/user/user.go b/examples/hertz/herz-example/biz/model/user/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cb30f1d41fa87e5d1971d9a94551c4589811597
--- /dev/null
+++ b/examples/hertz/herz-example/biz/model/user/user.go
@@ -0,0 +1,3004 @@
+// Code generated by thriftgo (0.4.3). DO NOT EDIT.
+
+package user
+
+import (
+ "context"
+ "fmt"
+ "github.com/apache/thrift/lib/go/thrift"
+)
+
+type LoginReq struct {
+ UserId string `thrift:"userId,1" json:"userId" query:"userId"`
+}
+
+func NewLoginReq() *LoginReq {
+ return &LoginReq{}
+}
+
+func (p *LoginReq) InitDefault() {
+}
+
+func (p *LoginReq) GetUserId() (v string) {
+ return p.UserId
+}
+
+var fieldIDToName_LoginReq = map[int16]string{
+ 1: "userId",
+}
+
+func (p *LoginReq) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.STRING {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_LoginReq[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *LoginReq) ReadField1(iprot thrift.TProtocol) error {
+
+ var _field string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _field = v
+ }
+ p.UserId = _field
+ return nil
+}
+
+func (p *LoginReq) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("LoginReq"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *LoginReq) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("userId", thrift.STRING, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteString(p.UserId); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *LoginReq) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("LoginReq(%+v)", *p)
+
+}
+
+type LoginResp struct {
+ Token string `thrift:"token,1" form:"token" json:"token" query:"token"`
+}
+
+func NewLoginResp() *LoginResp {
+ return &LoginResp{}
+}
+
+func (p *LoginResp) InitDefault() {
+}
+
+func (p *LoginResp) GetToken() (v string) {
+ return p.Token
+}
+
+var fieldIDToName_LoginResp = map[int16]string{
+ 1: "token",
+}
+
+func (p *LoginResp) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.STRING {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_LoginResp[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *LoginResp) ReadField1(iprot thrift.TProtocol) error {
+
+ var _field string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _field = v
+ }
+ p.Token = _field
+ return nil
+}
+
+func (p *LoginResp) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("LoginResp"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *LoginResp) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("token", thrift.STRING, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteString(p.Token); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *LoginResp) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("LoginResp(%+v)", *p)
+
+}
+
+type MessageResp struct {
+ Message string `thrift:"message,1" form:"message" json:"message" query:"message"`
+}
+
+func NewMessageResp() *MessageResp {
+ return &MessageResp{}
+}
+
+func (p *MessageResp) InitDefault() {
+}
+
+func (p *MessageResp) GetMessage() (v string) {
+ return p.Message
+}
+
+var fieldIDToName_MessageResp = map[int16]string{
+ 1: "message",
+}
+
+func (p *MessageResp) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.STRING {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_MessageResp[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *MessageResp) ReadField1(iprot thrift.TProtocol) error {
+
+ var _field string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _field = v
+ }
+ p.Message = _field
+ return nil
+}
+
+func (p *MessageResp) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("MessageResp"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *MessageResp) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("message", thrift.STRING, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteString(p.Message); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *MessageResp) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("MessageResp(%+v)", *p)
+
+}
+
+type UserInfoResp struct {
+ LoginId string `thrift:"loginId,1" form:"loginId" json:"loginId" query:"loginId"`
+ Roles []string `thrift:"roles,2,default,list" form:"roles" json:"roles" query:"roles"`
+ Permissions []string `thrift:"permissions,3,default,list" form:"permissions" json:"permissions" query:"permissions"`
+}
+
+func NewUserInfoResp() *UserInfoResp {
+ return &UserInfoResp{}
+}
+
+func (p *UserInfoResp) InitDefault() {
+}
+
+func (p *UserInfoResp) GetLoginId() (v string) {
+ return p.LoginId
+}
+
+func (p *UserInfoResp) GetRoles() (v []string) {
+ return p.Roles
+}
+
+func (p *UserInfoResp) GetPermissions() (v []string) {
+ return p.Permissions
+}
+
+var fieldIDToName_UserInfoResp = map[int16]string{
+ 1: "loginId",
+ 2: "roles",
+ 3: "permissions",
+}
+
+func (p *UserInfoResp) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.STRING {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ case 2:
+ if fieldTypeId == thrift.LIST {
+ if err = p.ReadField2(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ case 3:
+ if fieldTypeId == thrift.LIST {
+ if err = p.ReadField3(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserInfoResp[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserInfoResp) ReadField1(iprot thrift.TProtocol) error {
+
+ var _field string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _field = v
+ }
+ p.LoginId = _field
+ return nil
+}
+func (p *UserInfoResp) ReadField2(iprot thrift.TProtocol) error {
+ _, size, err := iprot.ReadListBegin()
+ if err != nil {
+ return err
+ }
+ _field := make([]string, 0, size)
+ for i := 0; i < size; i++ {
+
+ var _elem string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _elem = v
+ }
+
+ _field = append(_field, _elem)
+ }
+ if err := iprot.ReadListEnd(); err != nil {
+ return err
+ }
+ p.Roles = _field
+ return nil
+}
+func (p *UserInfoResp) ReadField3(iprot thrift.TProtocol) error {
+ _, size, err := iprot.ReadListBegin()
+ if err != nil {
+ return err
+ }
+ _field := make([]string, 0, size)
+ for i := 0; i < size; i++ {
+
+ var _elem string
+ if v, err := iprot.ReadString(); err != nil {
+ return err
+ } else {
+ _elem = v
+ }
+
+ _field = append(_field, _elem)
+ }
+ if err := iprot.ReadListEnd(); err != nil {
+ return err
+ }
+ p.Permissions = _field
+ return nil
+}
+
+func (p *UserInfoResp) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("UserInfoResp"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ if err = p.writeField2(oprot); err != nil {
+ fieldId = 2
+ goto WriteFieldError
+ }
+ if err = p.writeField3(oprot); err != nil {
+ fieldId = 3
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserInfoResp) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("loginId", thrift.STRING, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteString(p.LoginId); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *UserInfoResp) writeField2(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("roles", thrift.LIST, 2); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteListBegin(thrift.STRING, len(p.Roles)); err != nil {
+ return err
+ }
+ for _, v := range p.Roles {
+ if err := oprot.WriteString(v); err != nil {
+ return err
+ }
+ }
+ if err := oprot.WriteListEnd(); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err)
+}
+
+func (p *UserInfoResp) writeField3(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("permissions", thrift.LIST, 3); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteListBegin(thrift.STRING, len(p.Permissions)); err != nil {
+ return err
+ }
+ for _, v := range p.Permissions {
+ if err := oprot.WriteString(v); err != nil {
+ return err
+ }
+ }
+ if err := oprot.WriteListEnd(); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err)
+}
+
+func (p *UserInfoResp) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserInfoResp(%+v)", *p)
+
+}
+
+type SensitiveResp struct {
+ Sensitive bool `thrift:"sensitive,1" form:"sensitive" json:"sensitive" query:"sensitive"`
+}
+
+func NewSensitiveResp() *SensitiveResp {
+ return &SensitiveResp{}
+}
+
+func (p *SensitiveResp) InitDefault() {
+}
+
+func (p *SensitiveResp) GetSensitive() (v bool) {
+ return p.Sensitive
+}
+
+var fieldIDToName_SensitiveResp = map[int16]string{
+ 1: "sensitive",
+}
+
+func (p *SensitiveResp) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.BOOL {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_SensitiveResp[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *SensitiveResp) ReadField1(iprot thrift.TProtocol) error {
+
+ var _field bool
+ if v, err := iprot.ReadBool(); err != nil {
+ return err
+ } else {
+ _field = v
+ }
+ p.Sensitive = _field
+ return nil
+}
+
+func (p *SensitiveResp) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("SensitiveResp"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *SensitiveResp) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("sensitive", thrift.BOOL, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := oprot.WriteBool(p.Sensitive); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *SensitiveResp) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("SensitiveResp(%+v)", *p)
+
+}
+
+type UserService interface {
+ Login(ctx context.Context, request *LoginReq) (r *LoginResp, err error)
+
+ Public(ctx context.Context) (r *MessageResp, err error)
+
+ UserInfo(ctx context.Context) (r *UserInfoResp, err error)
+
+ Admin(ctx context.Context) (r *MessageResp, err error)
+
+ Manager(ctx context.Context) (r *MessageResp, err error)
+
+ Disable(ctx context.Context) (r *MessageResp, err error)
+
+ Sensitive(ctx context.Context) (r *SensitiveResp, err error)
+}
+
+type UserServiceClient struct {
+ c thrift.TClient
+}
+
+func NewUserServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *UserServiceClient {
+ return &UserServiceClient{
+ c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t)),
+ }
+}
+
+func NewUserServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *UserServiceClient {
+ return &UserServiceClient{
+ c: thrift.NewTStandardClient(iprot, oprot),
+ }
+}
+
+func NewUserServiceClient(c thrift.TClient) *UserServiceClient {
+ return &UserServiceClient{
+ c: c,
+ }
+}
+
+func (p *UserServiceClient) Client_() thrift.TClient {
+ return p.c
+}
+
+func (p *UserServiceClient) Login(ctx context.Context, request *LoginReq) (r *LoginResp, err error) {
+ var _args UserServiceLoginArgs
+ _args.Request = request
+ var _result UserServiceLoginResult
+ if err = p.Client_().Call(ctx, "Login", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) Public(ctx context.Context) (r *MessageResp, err error) {
+ var _args UserServicePublicArgs
+ var _result UserServicePublicResult
+ if err = p.Client_().Call(ctx, "Public", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) UserInfo(ctx context.Context) (r *UserInfoResp, err error) {
+ var _args UserServiceUserInfoArgs
+ var _result UserServiceUserInfoResult
+ if err = p.Client_().Call(ctx, "UserInfo", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) Admin(ctx context.Context) (r *MessageResp, err error) {
+ var _args UserServiceAdminArgs
+ var _result UserServiceAdminResult
+ if err = p.Client_().Call(ctx, "Admin", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) Manager(ctx context.Context) (r *MessageResp, err error) {
+ var _args UserServiceManagerArgs
+ var _result UserServiceManagerResult
+ if err = p.Client_().Call(ctx, "Manager", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) Disable(ctx context.Context) (r *MessageResp, err error) {
+ var _args UserServiceDisableArgs
+ var _result UserServiceDisableResult
+ if err = p.Client_().Call(ctx, "Disable", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+func (p *UserServiceClient) Sensitive(ctx context.Context) (r *SensitiveResp, err error) {
+ var _args UserServiceSensitiveArgs
+ var _result UserServiceSensitiveResult
+ if err = p.Client_().Call(ctx, "Sensitive", &_args, &_result); err != nil {
+ return
+ }
+ return _result.GetSuccess(), nil
+}
+
+type UserServiceProcessor struct {
+ processorMap map[string]thrift.TProcessorFunction
+ handler UserService
+}
+
+func (p *UserServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) {
+ p.processorMap[key] = processor
+}
+
+func (p *UserServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) {
+ processor, ok = p.processorMap[key]
+ return processor, ok
+}
+
+func (p *UserServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction {
+ return p.processorMap
+}
+
+func NewUserServiceProcessor(handler UserService) *UserServiceProcessor {
+ self := &UserServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)}
+ self.AddToProcessorMap("Login", &userServiceProcessorLogin{handler: handler})
+ self.AddToProcessorMap("Public", &userServiceProcessorPublic{handler: handler})
+ self.AddToProcessorMap("UserInfo", &userServiceProcessorUserInfo{handler: handler})
+ self.AddToProcessorMap("Admin", &userServiceProcessorAdmin{handler: handler})
+ self.AddToProcessorMap("Manager", &userServiceProcessorManager{handler: handler})
+ self.AddToProcessorMap("Disable", &userServiceProcessorDisable{handler: handler})
+ self.AddToProcessorMap("Sensitive", &userServiceProcessorSensitive{handler: handler})
+ return self
+}
+func (p *UserServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ name, _, seqId, err := iprot.ReadMessageBegin()
+ if err != nil {
+ return false, err
+ }
+ if processor, ok := p.GetProcessorFunction(name); ok {
+ return processor.Process(ctx, seqId, iprot, oprot)
+ }
+ iprot.Skip(thrift.STRUCT)
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name)
+ oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, x
+}
+
+type userServiceProcessorLogin struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorLogin) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceLoginArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Login", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceLoginResult{}
+ var retval *LoginResp
+ if retval, err2 = p.handler.Login(ctx, args.Request); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Login: "+err2.Error())
+ oprot.WriteMessageBegin("Login", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Login", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorPublic struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorPublic) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServicePublicArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Public", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServicePublicResult{}
+ var retval *MessageResp
+ if retval, err2 = p.handler.Public(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Public: "+err2.Error())
+ oprot.WriteMessageBegin("Public", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Public", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorUserInfo struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorUserInfo) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceUserInfoArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("UserInfo", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceUserInfoResult{}
+ var retval *UserInfoResp
+ if retval, err2 = p.handler.UserInfo(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing UserInfo: "+err2.Error())
+ oprot.WriteMessageBegin("UserInfo", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("UserInfo", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorAdmin struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorAdmin) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceAdminArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Admin", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceAdminResult{}
+ var retval *MessageResp
+ if retval, err2 = p.handler.Admin(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Admin: "+err2.Error())
+ oprot.WriteMessageBegin("Admin", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Admin", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorManager struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorManager) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceManagerArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Manager", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceManagerResult{}
+ var retval *MessageResp
+ if retval, err2 = p.handler.Manager(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Manager: "+err2.Error())
+ oprot.WriteMessageBegin("Manager", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Manager", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorDisable struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorDisable) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceDisableArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Disable", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceDisableResult{}
+ var retval *MessageResp
+ if retval, err2 = p.handler.Disable(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Disable: "+err2.Error())
+ oprot.WriteMessageBegin("Disable", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Disable", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type userServiceProcessorSensitive struct {
+ handler UserService
+}
+
+func (p *userServiceProcessorSensitive) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
+ args := UserServiceSensitiveArgs{}
+ if err = args.Read(iprot); err != nil {
+ iprot.ReadMessageEnd()
+ x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
+ oprot.WriteMessageBegin("Sensitive", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return false, err
+ }
+
+ iprot.ReadMessageEnd()
+ var err2 error
+ result := UserServiceSensitiveResult{}
+ var retval *SensitiveResp
+ if retval, err2 = p.handler.Sensitive(ctx); err2 != nil {
+ x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Sensitive: "+err2.Error())
+ oprot.WriteMessageBegin("Sensitive", thrift.EXCEPTION, seqId)
+ x.Write(oprot)
+ oprot.WriteMessageEnd()
+ oprot.Flush(ctx)
+ return true, err2
+ } else {
+ result.Success = retval
+ }
+ if err2 = oprot.WriteMessageBegin("Sensitive", thrift.REPLY, seqId); err2 != nil {
+ err = err2
+ }
+ if err2 = result.Write(oprot); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
+ err = err2
+ }
+ if err2 = oprot.Flush(ctx); err == nil && err2 != nil {
+ err = err2
+ }
+ if err != nil {
+ return
+ }
+ return true, err
+}
+
+type UserServiceLoginArgs struct {
+ Request *LoginReq `thrift:"request,1"`
+}
+
+func NewUserServiceLoginArgs() *UserServiceLoginArgs {
+ return &UserServiceLoginArgs{}
+}
+
+func (p *UserServiceLoginArgs) InitDefault() {
+}
+
+var UserServiceLoginArgs_Request_DEFAULT *LoginReq
+
+func (p *UserServiceLoginArgs) GetRequest() (v *LoginReq) {
+ if !p.IsSetRequest() {
+ return UserServiceLoginArgs_Request_DEFAULT
+ }
+ return p.Request
+}
+
+var fieldIDToName_UserServiceLoginArgs = map[int16]string{
+ 1: "request",
+}
+
+func (p *UserServiceLoginArgs) IsSetRequest() bool {
+ return p.Request != nil
+}
+
+func (p *UserServiceLoginArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 1:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField1(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceLoginArgs[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceLoginArgs) ReadField1(iprot thrift.TProtocol) error {
+ _field := NewLoginReq()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Request = _field
+ return nil
+}
+
+func (p *UserServiceLoginArgs) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Login_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField1(oprot); err != nil {
+ fieldId = 1
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceLoginArgs) writeField1(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Request.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
+}
+
+func (p *UserServiceLoginArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceLoginArgs(%+v)", *p)
+
+}
+
+type UserServiceLoginResult struct {
+ Success *LoginResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceLoginResult() *UserServiceLoginResult {
+ return &UserServiceLoginResult{}
+}
+
+func (p *UserServiceLoginResult) InitDefault() {
+}
+
+var UserServiceLoginResult_Success_DEFAULT *LoginResp
+
+func (p *UserServiceLoginResult) GetSuccess() (v *LoginResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceLoginResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceLoginResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceLoginResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceLoginResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceLoginResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceLoginResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewLoginResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceLoginResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Login_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceLoginResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceLoginResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceLoginResult(%+v)", *p)
+
+}
+
+type UserServicePublicArgs struct {
+}
+
+func NewUserServicePublicArgs() *UserServicePublicArgs {
+ return &UserServicePublicArgs{}
+}
+
+func (p *UserServicePublicArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServicePublicArgs = map[int16]string{}
+
+func (p *UserServicePublicArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServicePublicArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("Public_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServicePublicArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServicePublicArgs(%+v)", *p)
+
+}
+
+type UserServicePublicResult struct {
+ Success *MessageResp `thrift:"success,0,optional"`
+}
+
+func NewUserServicePublicResult() *UserServicePublicResult {
+ return &UserServicePublicResult{}
+}
+
+func (p *UserServicePublicResult) InitDefault() {
+}
+
+var UserServicePublicResult_Success_DEFAULT *MessageResp
+
+func (p *UserServicePublicResult) GetSuccess() (v *MessageResp) {
+ if !p.IsSetSuccess() {
+ return UserServicePublicResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServicePublicResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServicePublicResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServicePublicResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServicePublicResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServicePublicResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewMessageResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServicePublicResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Public_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServicePublicResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServicePublicResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServicePublicResult(%+v)", *p)
+
+}
+
+type UserServiceUserInfoArgs struct {
+}
+
+func NewUserServiceUserInfoArgs() *UserServiceUserInfoArgs {
+ return &UserServiceUserInfoArgs{}
+}
+
+func (p *UserServiceUserInfoArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServiceUserInfoArgs = map[int16]string{}
+
+func (p *UserServiceUserInfoArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceUserInfoArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("UserInfo_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceUserInfoArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceUserInfoArgs(%+v)", *p)
+
+}
+
+type UserServiceUserInfoResult struct {
+ Success *UserInfoResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceUserInfoResult() *UserServiceUserInfoResult {
+ return &UserServiceUserInfoResult{}
+}
+
+func (p *UserServiceUserInfoResult) InitDefault() {
+}
+
+var UserServiceUserInfoResult_Success_DEFAULT *UserInfoResp
+
+func (p *UserServiceUserInfoResult) GetSuccess() (v *UserInfoResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceUserInfoResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceUserInfoResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceUserInfoResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceUserInfoResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceUserInfoResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceUserInfoResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewUserInfoResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceUserInfoResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("UserInfo_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceUserInfoResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceUserInfoResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceUserInfoResult(%+v)", *p)
+
+}
+
+type UserServiceAdminArgs struct {
+}
+
+func NewUserServiceAdminArgs() *UserServiceAdminArgs {
+ return &UserServiceAdminArgs{}
+}
+
+func (p *UserServiceAdminArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServiceAdminArgs = map[int16]string{}
+
+func (p *UserServiceAdminArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceAdminArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("Admin_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceAdminArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceAdminArgs(%+v)", *p)
+
+}
+
+type UserServiceAdminResult struct {
+ Success *MessageResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceAdminResult() *UserServiceAdminResult {
+ return &UserServiceAdminResult{}
+}
+
+func (p *UserServiceAdminResult) InitDefault() {
+}
+
+var UserServiceAdminResult_Success_DEFAULT *MessageResp
+
+func (p *UserServiceAdminResult) GetSuccess() (v *MessageResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceAdminResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceAdminResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceAdminResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceAdminResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceAdminResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceAdminResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewMessageResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceAdminResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Admin_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceAdminResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceAdminResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceAdminResult(%+v)", *p)
+
+}
+
+type UserServiceManagerArgs struct {
+}
+
+func NewUserServiceManagerArgs() *UserServiceManagerArgs {
+ return &UserServiceManagerArgs{}
+}
+
+func (p *UserServiceManagerArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServiceManagerArgs = map[int16]string{}
+
+func (p *UserServiceManagerArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceManagerArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("Manager_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceManagerArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceManagerArgs(%+v)", *p)
+
+}
+
+type UserServiceManagerResult struct {
+ Success *MessageResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceManagerResult() *UserServiceManagerResult {
+ return &UserServiceManagerResult{}
+}
+
+func (p *UserServiceManagerResult) InitDefault() {
+}
+
+var UserServiceManagerResult_Success_DEFAULT *MessageResp
+
+func (p *UserServiceManagerResult) GetSuccess() (v *MessageResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceManagerResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceManagerResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceManagerResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceManagerResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceManagerResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceManagerResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewMessageResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceManagerResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Manager_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceManagerResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceManagerResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceManagerResult(%+v)", *p)
+
+}
+
+type UserServiceDisableArgs struct {
+}
+
+func NewUserServiceDisableArgs() *UserServiceDisableArgs {
+ return &UserServiceDisableArgs{}
+}
+
+func (p *UserServiceDisableArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServiceDisableArgs = map[int16]string{}
+
+func (p *UserServiceDisableArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceDisableArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("Disable_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceDisableArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceDisableArgs(%+v)", *p)
+
+}
+
+type UserServiceDisableResult struct {
+ Success *MessageResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceDisableResult() *UserServiceDisableResult {
+ return &UserServiceDisableResult{}
+}
+
+func (p *UserServiceDisableResult) InitDefault() {
+}
+
+var UserServiceDisableResult_Success_DEFAULT *MessageResp
+
+func (p *UserServiceDisableResult) GetSuccess() (v *MessageResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceDisableResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceDisableResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceDisableResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceDisableResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceDisableResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceDisableResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewMessageResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceDisableResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Disable_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceDisableResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceDisableResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceDisableResult(%+v)", *p)
+
+}
+
+type UserServiceSensitiveArgs struct {
+}
+
+func NewUserServiceSensitiveArgs() *UserServiceSensitiveArgs {
+ return &UserServiceSensitiveArgs{}
+}
+
+func (p *UserServiceSensitiveArgs) InitDefault() {
+}
+
+var fieldIDToName_UserServiceSensitiveArgs = map[int16]string{}
+
+func (p *UserServiceSensitiveArgs) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldTypeError
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+SkipFieldTypeError:
+ return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceSensitiveArgs) Write(oprot thrift.TProtocol) (err error) {
+ if err = oprot.WriteStructBegin("Sensitive_args"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceSensitiveArgs) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceSensitiveArgs(%+v)", *p)
+
+}
+
+type UserServiceSensitiveResult struct {
+ Success *SensitiveResp `thrift:"success,0,optional"`
+}
+
+func NewUserServiceSensitiveResult() *UserServiceSensitiveResult {
+ return &UserServiceSensitiveResult{}
+}
+
+func (p *UserServiceSensitiveResult) InitDefault() {
+}
+
+var UserServiceSensitiveResult_Success_DEFAULT *SensitiveResp
+
+func (p *UserServiceSensitiveResult) GetSuccess() (v *SensitiveResp) {
+ if !p.IsSetSuccess() {
+ return UserServiceSensitiveResult_Success_DEFAULT
+ }
+ return p.Success
+}
+
+var fieldIDToName_UserServiceSensitiveResult = map[int16]string{
+ 0: "success",
+}
+
+func (p *UserServiceSensitiveResult) IsSetSuccess() bool {
+ return p.Success != nil
+}
+
+func (p *UserServiceSensitiveResult) Read(iprot thrift.TProtocol) (err error) {
+
+ var fieldTypeId thrift.TType
+ var fieldId int16
+
+ if _, err = iprot.ReadStructBegin(); err != nil {
+ goto ReadStructBeginError
+ }
+
+ for {
+ _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
+ if err != nil {
+ goto ReadFieldBeginError
+ }
+ if fieldTypeId == thrift.STOP {
+ break
+ }
+
+ switch fieldId {
+ case 0:
+ if fieldTypeId == thrift.STRUCT {
+ if err = p.ReadField0(iprot); err != nil {
+ goto ReadFieldError
+ }
+ } else if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ default:
+ if err = iprot.Skip(fieldTypeId); err != nil {
+ goto SkipFieldError
+ }
+ }
+ if err = iprot.ReadFieldEnd(); err != nil {
+ goto ReadFieldEndError
+ }
+ }
+ if err = iprot.ReadStructEnd(); err != nil {
+ goto ReadStructEndError
+ }
+
+ return nil
+ReadStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
+ReadFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
+ReadFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceSensitiveResult[fieldId]), err)
+SkipFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
+
+ReadFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
+ReadStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
+}
+
+func (p *UserServiceSensitiveResult) ReadField0(iprot thrift.TProtocol) error {
+ _field := NewSensitiveResp()
+ if err := _field.Read(iprot); err != nil {
+ return err
+ }
+ p.Success = _field
+ return nil
+}
+
+func (p *UserServiceSensitiveResult) Write(oprot thrift.TProtocol) (err error) {
+ var fieldId int16
+ if err = oprot.WriteStructBegin("Sensitive_result"); err != nil {
+ goto WriteStructBeginError
+ }
+ if p != nil {
+ if err = p.writeField0(oprot); err != nil {
+ fieldId = 0
+ goto WriteFieldError
+ }
+ }
+ if err = oprot.WriteFieldStop(); err != nil {
+ goto WriteFieldStopError
+ }
+ if err = oprot.WriteStructEnd(); err != nil {
+ goto WriteStructEndError
+ }
+ return nil
+WriteStructBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
+WriteFieldError:
+ return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
+WriteFieldStopError:
+ return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
+WriteStructEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
+}
+
+func (p *UserServiceSensitiveResult) writeField0(oprot thrift.TProtocol) (err error) {
+ if p.IsSetSuccess() {
+ if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
+ goto WriteFieldBeginError
+ }
+ if err := p.Success.Write(oprot); err != nil {
+ return err
+ }
+ if err = oprot.WriteFieldEnd(); err != nil {
+ goto WriteFieldEndError
+ }
+ }
+ return nil
+WriteFieldBeginError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err)
+WriteFieldEndError:
+ return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err)
+}
+
+func (p *UserServiceSensitiveResult) String() string {
+ if p == nil {
+ return ""
+ }
+ return fmt.Sprintf("UserServiceSensitiveResult(%+v)", *p)
+
+}
diff --git a/examples/hertz/herz-example/biz/router/register.go b/examples/hertz/herz-example/biz/router/register.go
new file mode 100644
index 0000000000000000000000000000000000000000..242cfd9ce1c9d70a4e0f9a17fa6975fe1b2e1870
--- /dev/null
+++ b/examples/hertz/herz-example/biz/router/register.go
@@ -0,0 +1,15 @@
+// Code generated by hertz generator. DO NOT EDIT.
+
+package router
+
+import (
+ user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/router/user"
+ "github.com/cloudwego/hertz/pkg/app/server"
+)
+
+// GeneratedRegister registers routers generated by IDL.
+func GeneratedRegister(r *server.Hertz) {
+ //INSERT_POINT: DO NOT DELETE THIS LINE!
+ user.Register(r)
+
+}
diff --git a/examples/hertz/herz-example/biz/router/user/middleware.go b/examples/hertz/herz-example/biz/router/user/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..da94324d79a27ac310f82c774a508ad3f1575c27
--- /dev/null
+++ b/examples/hertz/herz-example/biz/router/user/middleware.go
@@ -0,0 +1,47 @@
+// Code generated by hertz generator.
+
+package user
+
+import (
+ sahertz "github.com/click33/sa-token-go/integrations/hertz"
+ "github.com/cloudwego/hertz/pkg/app"
+)
+
+func rootMw() []app.HandlerFunc {
+ // your code...
+ return nil
+}
+
+func _adminMw() []app.HandlerFunc {
+ // your code...
+ return []app.HandlerFunc{sahertz.CheckPermission("admin:*")}
+}
+
+func _loginMw() []app.HandlerFunc {
+ // your code...
+ return nil
+}
+
+func _managerMw() []app.HandlerFunc {
+ // your code...
+ return []app.HandlerFunc{sahertz.CheckRole("manager")}
+}
+
+func _publicMw() []app.HandlerFunc {
+ return []app.HandlerFunc{sahertz.Ignore()}
+}
+
+func _sensitiveMw() []app.HandlerFunc {
+ // your code...
+ return []app.HandlerFunc{sahertz.CheckDisable()}
+}
+
+func _userinfoMw() []app.HandlerFunc {
+ // your code...
+ return []app.HandlerFunc{sahertz.CheckLogin()}
+}
+
+func _disableMw() []app.HandlerFunc {
+ // your code...
+ return nil
+}
diff --git a/examples/hertz/herz-example/biz/router/user/user.go b/examples/hertz/herz-example/biz/router/user/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..04ddee4a7f534cfd5d3f2e05473d33d08baabda0
--- /dev/null
+++ b/examples/hertz/herz-example/biz/router/user/user.go
@@ -0,0 +1,27 @@
+// Code generated by hertz generator. DO NOT EDIT.
+
+package user
+
+import (
+ user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/handler/user"
+ "github.com/cloudwego/hertz/pkg/app/server"
+)
+
+/*
+ This file will register all the routes of the services in the master idl.
+ And it will update automatically when you use the "update" command for the idl.
+ So don't modify the contents of the file, or your code will be deleted when it is updated.
+*/
+
+// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
+func Register(r *server.Hertz) {
+
+ root := r.Group("/", rootMw()...)
+ root.GET("/admin", append(_adminMw(), user.Admin)...)
+ root.GET("/disable", append(_disableMw(), user.Disable)...)
+ root.GET("/login", append(_loginMw(), user.Login)...)
+ root.GET("/manager", append(_managerMw(), user.Manager)...)
+ root.GET("/public", append(_publicMw(), user.Public)...)
+ root.GET("/sensitive", append(_sensitiveMw(), user.Sensitive)...)
+ root.GET("/user", append(_userinfoMw(), user.UserInfo)...)
+}
diff --git a/examples/hertz/herz-example/build.sh b/examples/hertz/herz-example/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f1ba589caace36141efadaf899c63353d6e5f7ea
--- /dev/null
+++ b/examples/hertz/herz-example/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+RUN_NAME=hertz_service
+mkdir -p output/bin
+cp script/* output 2>/dev/null
+chmod +x output/bootstrap.sh
+go build -o output/bin/${RUN_NAME}
\ No newline at end of file
diff --git a/examples/hertz/herz-example/go.mod b/examples/hertz/herz-example/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..809fbba379f3966c0b6f1e01317f4f2e38b41ad8
--- /dev/null
+++ b/examples/hertz/herz-example/go.mod
@@ -0,0 +1,28 @@
+module github.com/click33/sa-token-go/examples/hertz/herz-example
+
+go 1.25.4
+
+require github.com/cloudwego/hertz v0.10.3
+
+require (
+ github.com/apache/thrift v0.22.0 // indirect
+ github.com/bytedance/gopkg v0.1.1 // indirect
+ github.com/bytedance/sonic v1.14.0 // indirect
+ github.com/bytedance/sonic/loader v0.3.0 // indirect
+ github.com/cloudwego/base64x v0.1.5 // indirect
+ github.com/cloudwego/gopkg v0.1.4 // indirect
+ github.com/cloudwego/netpoll v0.7.0 // indirect
+ github.com/fsnotify/fsnotify v1.5.4 // indirect
+ github.com/golang/protobuf v1.5.0 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+ github.com/nyaruka/phonenumbers v1.0.55 // indirect
+ github.com/tidwall/gjson v1.14.4 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+ golang.org/x/sys v0.24.0 // indirect
+ google.golang.org/protobuf v1.34.1 // indirect
+)
+
+replace github.com/apache/thrift => github.com/apache/thrift v0.13.0
diff --git a/examples/hertz/herz-example/go.sum b/examples/hertz/herz-example/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..3921167ec6d24efff6fb2821251dd88d5e77c608
--- /dev/null
+++ b/examples/hertz/herz-example/go.sum
@@ -0,0 +1,119 @@
+github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
+github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
+github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE=
+github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
+github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
+github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
+github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50=
+github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI=
+github.com/cloudwego/hertz v0.10.3 h1:NFcQAjouVJsod79XPLC/PaFfHgjMTYbiErmW+vGBi8A=
+github.com/cloudwego/hertz v0.10.3/go.mod h1:W5dUFXZPZkyfjMMo3EQrMQbofuvTsctM9IxmhbkuT18=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/cloudwego/netpoll v0.7.0 h1:bDrxQaNfijRI1zyGgXHQoE/nYegL0nr+ijO1Norelc4=
+github.com/cloudwego/netpoll v0.7.0/go.mod h1:PI+YrmyS7cIr0+SD4seJz3Eo3ckkXdu2ZVKBLhURLNU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
+github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
+github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
diff --git a/examples/hertz/herz-example/idl/user.thrift b/examples/hertz/herz-example/idl/user.thrift
new file mode 100644
index 0000000000000000000000000000000000000000..9559c235b27138d07063770bfcd19bb817250614
--- /dev/null
+++ b/examples/hertz/herz-example/idl/user.thrift
@@ -0,0 +1,33 @@
+namespace go user
+
+struct LoginReq {
+ 1: string userId (api.query="userId");
+}
+
+struct LoginResp {
+ 1: string token;
+}
+
+struct MessageResp {
+ 1: string message;
+}
+
+struct UserInfoResp {
+ 1: string loginId;
+ 2: list roles;
+ 3: list permissions;
+}
+
+struct SensitiveResp {
+ 1: bool sensitive;
+}
+
+service UserService {
+ LoginResp Login(1: LoginReq request) (api.get="/login");
+ MessageResp Public() (api.get="/public");
+ UserInfoResp UserInfo() (api.get="/user");
+ MessageResp Admin() (api.get="/admin");
+ MessageResp Manager() (api.get="/manager");
+ MessageResp Disable() (api.get="/disable");
+ SensitiveResp Sensitive() (api.get="/sensitive");
+}
\ No newline at end of file
diff --git a/examples/hertz/herz-example/main.go b/examples/hertz/herz-example/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..8eeb809560c14cf43ca4319f232bd9496c75d02d
--- /dev/null
+++ b/examples/hertz/herz-example/main.go
@@ -0,0 +1,22 @@
+// Code generated by hertz generator.
+
+package main
+
+import (
+ sahertz "github.com/click33/sa-token-go/integrations/hertz" // 只需这一个导入!
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/cloudwego/hertz/pkg/app/server"
+)
+
+func main() {
+ // 初始化(所有功能都在 sahertz 包中)
+ storage := memory.NewStorage()
+ config := sahertz.DefaultConfig() // 使用 sahertz.DefaultConfig
+ manager := sahertz.NewManager(storage, config) // 使用 sahertz.NewManager
+ sahertz.SetManager(manager) // 使用 sahertz.SetManager
+
+ h := server.Default()
+
+ register(h)
+ h.Spin()
+}
diff --git a/examples/hertz/herz-example/router.go b/examples/hertz/herz-example/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b89cc7669150e5258d1e9fd393df41b1114f928
--- /dev/null
+++ b/examples/hertz/herz-example/router.go
@@ -0,0 +1,14 @@
+// Code generated by hertz generator.
+
+package main
+
+import (
+ "github.com/cloudwego/hertz/pkg/app/server"
+)
+
+// customizeRegister registers customize routers.
+func customizedRegister(r *server.Hertz) {
+ //r.GET("/ping", handler.Ping)
+
+ // your code ...
+}
diff --git a/examples/hertz/herz-example/router_gen.go b/examples/hertz/herz-example/router_gen.go
new file mode 100644
index 0000000000000000000000000000000000000000..83a8d0cd6dbae50199d0bfd7abc998cbdf641d03
--- /dev/null
+++ b/examples/hertz/herz-example/router_gen.go
@@ -0,0 +1,16 @@
+// Code generated by hertz generator. DO NOT EDIT.
+
+package main
+
+import (
+ router "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/router"
+ "github.com/cloudwego/hertz/pkg/app/server"
+)
+
+// register registers all routers.
+func register(r *server.Hertz) {
+
+ router.GeneratedRegister(r)
+
+ customizedRegister(r)
+}
diff --git a/examples/hertz/herz-example/script/bootstrap.sh b/examples/hertz/herz-example/script/bootstrap.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3f3fc1afaad9be27f5da4298d1d2c3d2a749f1f5
--- /dev/null
+++ b/examples/hertz/herz-example/script/bootstrap.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+CURDIR=$(cd $(dirname $0); pwd)
+BinaryName=hertz_service
+echo "$CURDIR/bin/${BinaryName}"
+exec $CURDIR/bin/${BinaryName}
\ No newline at end of file
diff --git a/examples/jwt-example/go.mod b/examples/jwt-example/go.mod
index 1daf0cfc076da081747e0d23ef4a23c49e8b74fc..ff34abb20c2ab79cecef6533ae10cdc78fd89125 100644
--- a/examples/jwt-example/go.mod
+++ b/examples/jwt-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/jwt-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/examples/listener-example/go.mod b/examples/listener-example/go.mod
index 728dd7ce2c715735dfba69ac8425fec8a65b6ed9..9a6de24871e95d8e621c05bec6e4d28c606a4664 100644
--- a/examples/listener-example/go.mod
+++ b/examples/listener-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/listener-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/examples/multi-certification/authkit/stpkit.go b/examples/multi-certification/authkit/stpkit.go
new file mode 100644
index 0000000000000000000000000000000000000000..4cad6bedb6641eb7af2a12b14e4a5b84417f01a1
--- /dev/null
+++ b/examples/multi-certification/authkit/stpkit.go
@@ -0,0 +1,9 @@
+package authkit
+
+import "github.com/click33/sa-token-go/stputil"
+
+var (
+ ADMIN *stputil.StpLogic
+ USER *stputil.StpLogic
+ OTHER *stputil.StpLogic
+)
diff --git a/examples/multi-certification/go.mod b/examples/multi-certification/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c0b305ff8f7678b54678e377cf7ad7ec66060b94
--- /dev/null
+++ b/examples/multi-certification/go.mod
@@ -0,0 +1,22 @@
+module github.com/click33/sa-token-go/examples/multi-certification
+
+go 1.25.3
+
+require (
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
+)
+
+require (
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/panjf2000/ants/v2 v2.11.3 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+)
+
+replace (
+ github.com/click33/sa-token-go/core => ../../core
+ github.com/click33/sa-token-go/storage/memory => ../../storage/memory
+ github.com/click33/sa-token-go/stputil => ../../stputil
+)
diff --git a/examples/multi-certification/go.sum b/examples/multi-certification/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..99a4055fbd7a701caa44d5459c093677e46239d9
--- /dev/null
+++ b/examples/multi-certification/go.sum
@@ -0,0 +1,16 @@
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+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/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/examples/multi-certification/main.go b/examples/multi-certification/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a959ade641422fcd31372c73fdee5a56b87eef08
--- /dev/null
+++ b/examples/multi-certification/main.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "fmt"
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/examples/multi-certification/authkit"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/click33/sa-token-go/stputil"
+)
+
+func main() {
+ // 注意 多认证体现需要将不同的manager的KeyPrefix设置为不同的值
+ storage := memory.NewStorage()
+ userManager := core.NewBuilder().
+ Storage(storage).
+ Timeout(6600).
+ IsPrintBanner(false).
+ KeyPrefix("user"). // 要唯一
+ Build()
+
+ adminManager := core.NewBuilder().
+ Storage(storage).
+ Timeout(3600).
+ IsPrintBanner(false).
+ KeyPrefix("admin"). // 要唯一
+ TokenStyle(core.TokenStyleTik).
+ Build()
+
+ authkit.ADMIN = stputil.NewStpLogic(adminManager)
+ authkit.USER = stputil.NewStpLogic(userManager)
+
+ Run()
+}
+
+func Run() {
+ userTokenValue, _ := authkit.USER.Login("ID1")
+ adminTokenValue, _ := authkit.ADMIN.Login("ID1")
+ fmt.Println("userTokenValue:", userTokenValue)
+ fmt.Println("adminTokenValue:", adminTokenValue)
+
+ _ = authkit.ADMIN.SetPermissions("ID1", []string{"admin1", "admin2"})
+ _ = authkit.USER.SetPermissions("ID1", []string{"user1", "user2"})
+ adminPermissions, _ := authkit.ADMIN.GetPermissions("ID1")
+ userPermissions, _ := authkit.USER.GetPermissions("ID1")
+ fmt.Println("admin permissions:", adminPermissions)
+ fmt.Println("user permissions:", userPermissions)
+
+ fmt.Println("admin has user1 permission:", authkit.ADMIN.HasPermission("ID1", "user1"))
+ fmt.Println("admin has admin1 permission:", authkit.ADMIN.HasPermission("ID1", "admin1"))
+ fmt.Println("user has admin1 permission:", authkit.USER.HasPermission("ID1", "admin1"))
+ fmt.Println("user has user1 permission:", authkit.USER.HasPermission("ID1", "user1"))
+}
diff --git a/examples/oauth2-example/go.mod b/examples/oauth2-example/go.mod
index 905f73e0f9d951e52881a2e939ca6789750bc4af..a2bd77c61afe8ce657c6872fb503836a5d488356 100644
--- a/examples/oauth2-example/go.mod
+++ b/examples/oauth2-example/go.mod
@@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/examples/oauth2-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
github.com/gin-gonic/gin v1.10.0
)
diff --git a/examples/quick-start/simple-example/go.mod b/examples/quick-start/simple-example/go.mod
index e7532cbb8f306a51ee311ec18c31bec841ea829d..a0fb35e2d3641e6ca5d135863f2a73a7d65eb285 100644
--- a/examples/quick-start/simple-example/go.mod
+++ b/examples/quick-start/simple-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/simple-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/examples/redis-example/go.mod b/examples/redis-example/go.mod
index 4020f16b05991b2944ca045a922d58a4b1d7ee10..a2dfc0109f22912054b8c6df94bde33f3e05f5a4 100644
--- a/examples/redis-example/go.mod
+++ b/examples/redis-example/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/redis-example
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/redis v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/redis v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/redis/go-redis/v9 v9.5.1
)
diff --git a/examples/security-features/go.mod b/examples/security-features/go.mod
index 387f4e015cb9cbd31d88113b94168f9cf5f14083..edf37392b91797469103d7e947892523aaaac6b5 100644
--- a/examples/security-features/go.mod
+++ b/examples/security-features/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/security-features
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/examples/session-demo/go.mod b/examples/session-demo/go.mod
index 481560ea54ec29cbd8564d879ecce98e0cecd086..da1fa9d173ea85da6245184cd0b9f6f39da6ff02 100644
--- a/examples/session-demo/go.mod
+++ b/examples/session-demo/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/session-demo
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
replace (
diff --git a/examples/token-styles/go.mod b/examples/token-styles/go.mod
index 2f16adedbbec1c3bd15f6a226b86398ec213b196..916d663279acd4d6f6ab0cec98d7f66034d88c3c 100644
--- a/examples/token-styles/go.mod
+++ b/examples/token-styles/go.mod
@@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/token-styles
go 1.21
require (
- github.com/click33/sa-token-go/core v0.1.3
- github.com/click33/sa-token-go/storage/memory v0.1.3
- github.com/click33/sa-token-go/stputil v0.1.3
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/go.work b/go.work
index 5dd5f5477f4b0726897cf4a76b674c90afed9813..4fbc7f654b513043fb1dbd3f966d2c1ccf17e4b1 100644
--- a/go.work
+++ b/go.work
@@ -1,13 +1,18 @@
-go 1.25.3
+go 1.25.4
use (
./core
+ ./examples/hertz/herz-example
+ ./examples/beego/beego-example
./examples/kratos/kratos-example
+ ./examples/multi-certification
+ ./integrations/beego
./integrations/chi
./integrations/echo
./integrations/fiber
./integrations/gf
./integrations/gin
+ ./integrations/hertz
./integrations/kratos
./storage/memory
./storage/redis
diff --git a/go.work.sum b/go.work.sum
index ff4fc5a1a8b169d815fc651e1bb960094b80b1cd..24d2af3b312e2754a49291370b0209d0f4e944c4 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -1,5 +1,6 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
@@ -8,34 +9,51 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
+github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
+github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
+github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
-github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/click33/sa-token-go/storage/memory v0.1.4/go.mod h1:nqyuEh23mNjcuG3aI/BqJFz71zkpsgjdStW1BC5lkB0=
github.com/click33/sa-token-go/storage/memory v0.1.5/go.mod h1:HxN2NVLq7lx+sOmq5RmV0h8xJjEUJLm4Xt1Mq+9PV2s=
+github.com/cloudflare/golz4 v0.0.0-20240916140612-caecf3c00c06/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
-github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY=
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
+github.com/couchbase/gomemcached v0.3.3/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68=
+github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
+github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y=
github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb/go.mod h1:GxGqnjWzl1Gz8WfAfMJSfhvsi4EPZayRb25nLHDWXyA=
@@ -54,6 +72,10 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
+github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
+github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -64,9 +86,11 @@ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gogf/gf/v2 v2.9.4/go.mod h1:Ukl+5HUH9S7puBmNLR4L1zUqeRwi0nrW4OigOknEztU=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
@@ -76,29 +100,31 @@ github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo=
-github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
+github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE=
@@ -109,58 +135,53 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
+github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
@@ -168,15 +189,20 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
+go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw=
+go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA=
+go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
@@ -188,14 +214,16 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
@@ -203,11 +231,14 @@ golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ=
@@ -216,16 +247,14 @@ golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
@@ -233,16 +262,19 @@ google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
+google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
nullprogram.com/x/optparse v1.0.0 h1:xGFgVi5ZaWOnYdac2foDT3vg0ZZC9ErXFV57mr4OHrI=
-nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/integrations/beego/annotation.go b/integrations/beego/annotation.go
new file mode 100644
index 0000000000000000000000000000000000000000..a3080154d76c9335b06ef0fe883ad82b70011a3f
--- /dev/null
+++ b/integrations/beego/annotation.go
@@ -0,0 +1,151 @@
+package beego
+
+import (
+ "strings"
+
+ "github.com/beego/beego/v2/server/web"
+ beegoctx "github.com/beego/beego/v2/server/web/context"
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/stputil"
+)
+
+// Annotation constants | 注解常量
+const (
+ TagSaCheckLogin = "sa_check_login"
+ TagSaCheckRole = "sa_check_role"
+ TagSaCheckPermission = "sa_check_permission"
+ TagSaCheckDisable = "sa_check_disable"
+ TagSaIgnore = "sa_ignore"
+)
+
+// Annotation annotation structure | 注解结构体
+type Annotation struct {
+ CheckLogin bool `json:"checkLogin"`
+ CheckRole []string `json:"checkRole"`
+ CheckPermission []string `json:"checkPermission"`
+ CheckDisable bool `json:"checkDisable"`
+ Ignore bool `json:"ignore"`
+}
+
+// Validate validates if annotation is valid | 验证注解是否有效
+func (a *Annotation) Validate() bool {
+ if a.Ignore {
+ return true
+ }
+
+ count := 0
+ if a.CheckLogin {
+ count++
+ }
+ if len(a.CheckRole) > 0 {
+ count++
+ }
+ if len(a.CheckPermission) > 0 {
+ count++
+ }
+ if a.CheckDisable {
+ count++
+ }
+
+ return count <= 1
+}
+
+// GetHandler gets handler with annotations | 获取带注解的处理器
+func GetHandler(handler web.FilterFunc, annotations ...*Annotation) web.FilterFunc {
+ return func(ctx *beegoctx.Context) {
+ if len(annotations) > 0 && annotations[0].Ignore {
+ if handler != nil {
+ handler(ctx)
+ }
+ return
+ }
+
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, stputil.GetManager())
+ token := saCtx.GetTokenValue()
+
+ if token == "" {
+ writeErrorResponse(ctx, core.NewNotLoginError())
+ return
+ }
+
+ if !stputil.IsLogin(token) {
+ writeErrorResponse(ctx, core.NewNotLoginError())
+ return
+ }
+
+ loginID, err := stputil.GetLoginID(token)
+ if err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ // Check if account is disabled | 检查是否被封禁
+ if len(annotations) > 0 && annotations[0].CheckDisable {
+ if stputil.IsDisable(loginID) {
+ writeErrorResponse(ctx, core.NewAccountDisabledError(loginID))
+ return
+ }
+ }
+
+ // Check permission | 检查权限
+ if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 {
+ hasPermission := false
+ for _, perm := range annotations[0].CheckPermission {
+ if stputil.HasPermission(loginID, strings.TrimSpace(perm)) {
+ hasPermission = true
+ break
+ }
+ }
+ if !hasPermission {
+ writeErrorResponse(ctx, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ",")))
+ return
+ }
+ }
+
+ // Check role | 检查角色
+ if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 {
+ hasRole := false
+ for _, role := range annotations[0].CheckRole {
+ if stputil.HasRole(loginID, strings.TrimSpace(role)) {
+ hasRole = true
+ break
+ }
+ }
+ if !hasRole {
+ writeErrorResponse(ctx, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ",")))
+ return
+ }
+ }
+
+ // All checks passed, execute original handler | 所有检查通过,执行原函数
+ if handler != nil {
+ handler(ctx)
+ }
+ }
+}
+
+// CheckLogin filter for login checking | 检查登录过滤器
+func CheckLogin() web.FilterFunc {
+ return GetHandler(nil, &Annotation{CheckLogin: true})
+}
+
+// CheckRole filter for role checking | 检查角色过滤器
+func CheckRole(roles ...string) web.FilterFunc {
+ return GetHandler(nil, &Annotation{CheckRole: roles})
+}
+
+// CheckPermission filter for permission checking | 检查权限过滤器
+func CheckPermission(perms ...string) web.FilterFunc {
+ return GetHandler(nil, &Annotation{CheckPermission: perms})
+}
+
+// CheckDisable filter for checking if account is disabled | 检查是否被封禁过滤器
+func CheckDisable() web.FilterFunc {
+ return GetHandler(nil, &Annotation{CheckDisable: true})
+}
+
+// Ignore filter to ignore authentication | 忽略认证过滤器
+func Ignore() web.FilterFunc {
+ return GetHandler(nil, &Annotation{Ignore: true})
+}
diff --git a/integrations/beego/context.go b/integrations/beego/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e9ac03f1f0ff602a1d428b0dd775cb3faae08b9
--- /dev/null
+++ b/integrations/beego/context.go
@@ -0,0 +1,150 @@
+package beego
+
+import (
+ beegoctx "github.com/beego/beego/v2/server/web/context"
+ "github.com/click33/sa-token-go/core/adapter"
+)
+
+// BeegoContext Beego request context adapter | Beego请求上下文适配器
+type BeegoContext struct {
+ ctx *beegoctx.Context
+ aborted bool
+}
+
+// NewBeegoContext creates a Beego context adapter | 创建Beego上下文适配器
+func NewBeegoContext(ctx *beegoctx.Context) adapter.RequestContext {
+ return &BeegoContext{ctx: ctx}
+}
+
+// GetHeader gets request header | 获取请求头
+func (b *BeegoContext) GetHeader(key string) string {
+ return b.ctx.Input.Header(key)
+}
+
+// GetQuery gets query parameter | 获取查询参数
+func (b *BeegoContext) GetQuery(key string) string {
+ return b.ctx.Input.Query(key)
+}
+
+// GetCookie gets cookie | 获取Cookie
+func (b *BeegoContext) GetCookie(key string) string {
+ return b.ctx.Input.Cookie(key)
+}
+
+// SetHeader sets response header | 设置响应头
+func (b *BeegoContext) SetHeader(key, value string) {
+ b.ctx.ResponseWriter.Header().Set(key, value)
+}
+
+// SetCookie sets cookie | 设置Cookie
+func (b *BeegoContext) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
+ b.ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
+}
+
+// GetClientIP gets client IP address | 获取客户端IP地址
+func (b *BeegoContext) GetClientIP() string {
+ return b.ctx.Input.IP()
+}
+
+// GetMethod gets request method | 获取请求方法
+func (b *BeegoContext) GetMethod() string {
+ return b.ctx.Input.Method()
+}
+
+// GetPath gets request path | 获取请求路径
+func (b *BeegoContext) GetPath() string {
+ return b.ctx.Input.URI()
+}
+
+// Set sets context value | 设置上下文值
+func (b *BeegoContext) Set(key string, value interface{}) {
+ b.ctx.Input.SetData(key, value)
+}
+
+// Get gets context value | 获取上下文值
+func (b *BeegoContext) Get(key string) (interface{}, bool) {
+ value := b.ctx.Input.GetData(key)
+ return value, value != nil
+}
+
+// ============ Additional Required Methods | 额外必需的方法 ============
+
+// GetHeaders implements adapter.RequestContext.
+func (b *BeegoContext) GetHeaders() map[string][]string {
+ headers := make(map[string][]string)
+ for key, values := range b.ctx.Request.Header {
+ headers[key] = values
+ }
+ return headers
+}
+
+// GetQueryAll implements adapter.RequestContext.
+func (b *BeegoContext) GetQueryAll() map[string][]string {
+ params := make(map[string][]string)
+ return params
+}
+
+// GetPostForm implements adapter.RequestContext.
+func (b *BeegoContext) GetPostForm(key string) string {
+ return b.ctx.Request.FormValue(key)
+}
+
+// GetBody implements adapter.RequestContext.
+func (b *BeegoContext) GetBody() ([]byte, error) {
+ return b.ctx.Input.RequestBody, nil
+}
+
+// GetURL implements adapter.RequestContext.
+func (b *BeegoContext) GetURL() string {
+ return b.ctx.Input.URL()
+}
+
+// GetUserAgent implements adapter.RequestContext.
+func (b *BeegoContext) GetUserAgent() string {
+ return b.ctx.Request.UserAgent()
+}
+
+// SetCookieWithOptions implements adapter.RequestContext.
+func (b *BeegoContext) SetCookieWithOptions(options *adapter.CookieOptions) {
+ b.ctx.SetCookie(
+ options.Name,
+ options.Value,
+ options.MaxAge,
+ options.Path,
+ options.Domain,
+ options.Secure,
+ options.HttpOnly,
+ )
+}
+
+// GetString implements adapter.RequestContext.
+func (b *BeegoContext) GetString(key string) string {
+ value := b.ctx.Input.GetData(key)
+ if value == nil {
+ return ""
+ }
+ if str, ok := value.(string); ok {
+ return str
+ }
+ return ""
+}
+
+// MustGet implements adapter.RequestContext.
+func (b *BeegoContext) MustGet(key string) any {
+ value := b.ctx.Input.GetData(key)
+ if value == nil {
+ panic("key not found: " + key)
+ }
+ return value
+}
+
+// Abort implements adapter.RequestContext.
+func (b *BeegoContext) Abort() {
+ b.aborted = true
+ b.ctx.Abort(500, "aborted") // 必须选一个默认状态码和消息
+}
+
+// IsAborted implements adapter.RequestContext.
+func (b *BeegoContext) IsAborted() bool {
+ return b.aborted
+}
diff --git a/integrations/beego/export.go b/integrations/beego/export.go
new file mode 100644
index 0000000000000000000000000000000000000000..8fbb23e5787a1173240845a1143c62d67a067e60
--- /dev/null
+++ b/integrations/beego/export.go
@@ -0,0 +1,380 @@
+package beego
+
+import (
+ "time"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/stputil"
+)
+
+// ==============================类型别名 vs 显式包装函数=========================================
+// 场景 推荐方式
+// 类型定义 type T = core.T(别名)
+// 常量 const C = core.C
+// 简单函数(无业务逻辑) 别名变量 var F = core.F(最简洁)
+// 函数(需要文档/校验/未来扩展) 显式包装 func F(...) ... { return core.F(...) }
+// API 一致性要求高 显式包装
+// ==============================================================================================
+
+// ============ Re-export core types | 重新导出核心类型 ============
+
+// Configuration related types | 配置相关类型
+type (
+ Config = core.Config
+ CookieConfig = core.CookieConfig
+ TokenStyle = core.TokenStyle
+)
+
+// Token style constants | Token风格常量
+const (
+ TokenStyleUUID = core.TokenStyleUUID
+ TokenStyleSimple = core.TokenStyleSimple
+ TokenStyleRandom32 = core.TokenStyleRandom32
+ TokenStyleRandom64 = core.TokenStyleRandom64
+ TokenStyleRandom128 = core.TokenStyleRandom128
+ TokenStyleJWT = core.TokenStyleJWT
+ TokenStyleHash = core.TokenStyleHash
+ TokenStyleTimestamp = core.TokenStyleTimestamp
+ TokenStyleTik = core.TokenStyleTik
+)
+
+// Core types | 核心类型
+type (
+ Manager = core.Manager
+ TokenInfo = core.TokenInfo
+ Session = core.Session
+ TokenGenerator = core.TokenGenerator
+ SaTokenContext = core.SaTokenContext
+ Builder = core.Builder
+ NonceManager = core.NonceManager
+ RefreshTokenInfo = core.RefreshTokenInfo
+ RefreshTokenManager = core.RefreshTokenManager
+ OAuth2Server = core.OAuth2Server
+ OAuth2Client = core.OAuth2Client
+ OAuth2AccessToken = core.OAuth2AccessToken
+ OAuth2GrantType = core.OAuth2GrantType
+)
+
+// Adapter interfaces | 适配器接口
+type (
+ Storage = core.Storage
+ RequestContext = core.RequestContext
+)
+
+// Event related types | 事件相关类型
+type (
+ EventListener = core.EventListener
+ EventManager = core.EventManager
+ EventData = core.EventData
+ Event = core.Event
+ ListenerFunc = core.ListenerFunc
+ ListenerConfig = core.ListenerConfig
+)
+
+// Event constants | 事件常量
+const (
+ EventLogin = core.EventLogin
+ EventLogout = core.EventLogout
+ EventKickout = core.EventKickout
+ EventDisable = core.EventDisable
+ EventUntie = core.EventUntie
+ EventRenew = core.EventRenew
+ EventCreateSession = core.EventCreateSession
+ EventDestroySession = core.EventDestroySession
+ EventPermissionCheck = core.EventPermissionCheck
+ EventRoleCheck = core.EventRoleCheck
+ EventAll = core.EventAll
+)
+
+// OAuth2 grant type constants | OAuth2授权类型常量
+const (
+ GrantTypeAuthorizationCode = core.GrantTypeAuthorizationCode
+ GrantTypeRefreshToken = core.GrantTypeRefreshToken
+ GrantTypeClientCredentials = core.GrantTypeClientCredentials
+ GrantTypePassword = core.GrantTypePassword
+)
+
+// Utility functions | 工具函数
+var (
+ RandomString = core.RandomString
+ IsEmpty = core.IsEmpty
+ IsNotEmpty = core.IsNotEmpty
+ DefaultString = core.DefaultString
+ ContainsString = core.ContainsString
+ RemoveString = core.RemoveString
+ UniqueStrings = core.UniqueStrings
+ MergeStrings = core.MergeStrings
+ MatchPattern = core.MatchPattern
+)
+
+// ============ Core constructor functions | 核心构造函数 ============
+
+// DefaultConfig returns default configuration | 返回默认配置
+func DefaultConfig() *Config {
+ return core.DefaultConfig()
+}
+
+// NewManager creates a new authentication manager | 创建新的认证管理器
+func NewManager(storage Storage, cfg *Config) *Manager {
+ return core.NewManager(storage, cfg)
+}
+
+// NewContext creates a new Sa-Token context | 创建新的Sa-Token上下文
+func NewContext(ctx RequestContext, mgr *Manager) *SaTokenContext {
+ return core.NewContext(ctx, mgr)
+}
+
+// NewSession creates a new session | 创建新的Session
+func NewSession(id string, storage Storage, prefix string) *Session {
+ return core.NewSession(id, storage, prefix)
+}
+
+// LoadSession loads an existing session | 加载已存在的Session
+func LoadSession(id string, storage Storage, prefix string) (*Session, error) {
+ return core.LoadSession(id, storage, prefix)
+}
+
+// NewTokenGenerator creates a new token generator | 创建新的Token生成器
+func NewTokenGenerator(cfg *Config) *TokenGenerator {
+ return core.NewTokenGenerator(cfg)
+}
+
+// NewEventManager creates a new event manager | 创建新的事件管理器
+func NewEventManager() *EventManager {
+ return core.NewEventManager()
+}
+
+// NewBuilder creates a new builder for fluent configuration | 创建新的Builder构建器(用于流式配置)
+func NewBuilder() *Builder {
+ return core.NewBuilder()
+}
+
+// NewNonceManager creates a new nonce manager | 创建新的Nonce管理器
+func NewNonceManager(storage Storage, prefix string, ttl ...int64) *NonceManager {
+ return core.NewNonceManager(storage, prefix, ttl...)
+}
+
+// NewRefreshTokenManager creates a new refresh token manager | 创建新的刷新令牌管理器
+func NewRefreshTokenManager(storage Storage, prefix string, cfg *Config) *RefreshTokenManager {
+ return core.NewRefreshTokenManager(storage, prefix, cfg)
+}
+
+// NewOAuth2Server creates a new OAuth2 server | 创建新的OAuth2服务器
+func NewOAuth2Server(storage Storage, prefix string) *OAuth2Server {
+ return core.NewOAuth2Server(storage, prefix)
+}
+
+// ============ Global StpUtil functions | 全局StpUtil函数 ============
+
+// SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法)
+func SetManager(mgr *Manager) {
+ stputil.SetManager(mgr)
+}
+
+// GetManager gets the global Manager | 获取全局Manager
+func GetManager() *Manager {
+ return stputil.GetManager()
+}
+
+// ============ Authentication | 登录认证 ============
+
+// Login performs user login | 用户登录
+func Login(loginID interface{}, device ...string) (string, error) {
+ return stputil.Login(loginID, device...)
+}
+
+// LoginByToken performs login with specified token | 使用指定Token登录
+func LoginByToken(loginID interface{}, tokenValue string, device ...string) error {
+ return stputil.LoginByToken(loginID, tokenValue, device...)
+}
+
+// Logout performs user logout | 用户登出
+func Logout(loginID interface{}, device ...string) error {
+ return stputil.Logout(loginID, device...)
+}
+
+// LogoutByToken performs logout by token | 根据Token登出
+func LogoutByToken(tokenValue string) error {
+ return stputil.LogoutByToken(tokenValue)
+}
+
+// IsLogin checks if the user is logged in | 检查用户是否已登录
+func IsLogin(tokenValue string) bool {
+ return stputil.IsLogin(tokenValue)
+}
+
+// CheckLoginByToken checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误)
+func CheckLoginByToken(tokenValue string) error {
+ return stputil.CheckLogin(tokenValue)
+}
+
+// GetLoginID gets the login ID from token | 从Token获取登录ID
+func GetLoginID(tokenValue string) (string, error) {
+ return stputil.GetLoginID(tokenValue)
+}
+
+// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查)
+func GetLoginIDNotCheck(tokenValue string) (string, error) {
+ return stputil.GetLoginIDNotCheck(tokenValue)
+}
+
+// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值
+func GetTokenValue(loginID interface{}, device ...string) (string, error) {
+ return stputil.GetTokenValue(loginID, device...)
+}
+
+// GetTokenInfo gets token information | 获取Token信息
+func GetTokenInfo(tokenValue string) (*TokenInfo, error) {
+ return stputil.GetTokenInfo(tokenValue)
+}
+
+// ============ Kickout | 踢人下线 ============
+
+// Kickout kicks out a user session | 踢人下线
+func Kickout(loginID interface{}, device ...string) error {
+ return stputil.Kickout(loginID, device...)
+}
+
+// ============ Account Disable | 账号封禁 ============
+
+// Disable disables an account for specified duration | 封禁账号(指定时长)
+func Disable(loginID interface{}, duration time.Duration) error {
+ return stputil.Disable(loginID, duration)
+}
+
+// IsDisable checks if an account is disabled | 检查账号是否被封禁
+func IsDisable(loginID interface{}) bool {
+ return stputil.IsDisable(loginID)
+}
+
+// CheckDisableByToken checks if account is disabled (throws error if disabled) | 检查Token对应账号是否被封禁(被封禁则抛出错误)
+func CheckDisableByToken(tokenValue string) error {
+ return stputil.CheckDisable(tokenValue)
+}
+
+// GetDisableTime gets remaining disabled time | 获取账号剩余封禁时间
+func GetDisableTime(loginID interface{}) (int64, error) {
+ return stputil.GetDisableTime(loginID)
+}
+
+// Untie unties/unlocks an account | 解除账号封禁
+func Untie(loginID interface{}) error {
+ return stputil.Untie(loginID)
+}
+
+// ============ Permission Check | 权限验证 ============
+
+// CheckPermissionByToken checks if the token has specified permission | 检查Token是否拥有指定权限
+func CheckPermissionByToken(tokenValue string, permission string) error {
+ return stputil.CheckPermission(tokenValue, permission)
+}
+
+// HasPermission checks if the account has specified permission (returns bool) | 检查账号是否拥有指定权限(返回布尔值)
+func HasPermission(loginID interface{}, permission string) bool {
+ return stputil.HasPermission(loginID, permission)
+}
+
+// SetPermissions sets permissions for a login ID | 设置用户权限
+func SetPermissions(loginID interface{}, permissions []string) error {
+ return stputil.SetPermissions(loginID, permissions)
+}
+
+// CheckPermissionAndByToken checks if the token has all specified permissions (AND logic) | 检查Token是否拥有所有指定权限(AND逻辑)
+func CheckPermissionAndByToken(tokenValue string, permissions []string) error {
+ return stputil.CheckPermissionAnd(tokenValue, permissions)
+}
+
+// CheckPermissionOrByToken checks if the token has any of the specified permissions (OR logic) | 检查Token是否拥有指定权限中的任意一个(OR逻辑)
+func CheckPermissionOrByToken(tokenValue string, permissions []string) error {
+ return stputil.CheckPermissionOr(tokenValue, permissions)
+}
+
+// GetPermissionListByToken gets the permission list for a token | 获取Token的权限列表
+func GetPermissionListByToken(tokenValue string) ([]string, error) {
+ return stputil.GetPermissionList(tokenValue)
+}
+
+// ============ Role Check | 角色验证 ============
+
+// CheckRoleByToken checks if the token has specified role | 检查Token是否拥有指定角色
+func CheckRoleByToken(tokenValue string, role string) error {
+ return stputil.CheckRole(tokenValue, role)
+}
+
+// HasRole checks if the account has specified role (returns bool) | 检查账号是否拥有指定角色(返回布尔值)
+func HasRole(loginID interface{}, role string) bool {
+ return stputil.HasRole(loginID, role)
+}
+
+// SetRoles sets roles for a login ID | 设置用户角色
+func SetRoles(loginID interface{}, roles []string) error {
+ return stputil.SetRoles(loginID, roles)
+}
+
+// CheckRoleAndByToken checks if the token has all specified roles (AND logic) | 检查Token是否拥有所有指定角色(AND逻辑)
+func CheckRoleAndByToken(tokenValue string, roles []string) error {
+ return stputil.CheckRoleAnd(tokenValue, roles)
+}
+
+// CheckRoleOrByToken checks if the token has any of the specified roles (OR logic) | 检查Token是否拥有指定角色中的任意一个(OR逻辑)
+func CheckRoleOrByToken(tokenValue string, roles []string) error {
+ return stputil.CheckRoleOr(tokenValue, roles)
+}
+
+// GetRoleListByToken gets the role list for a token | 获取Token的角色列表
+func GetRoleListByToken(tokenValue string) ([]string, error) {
+ return stputil.GetRoleList(tokenValue)
+}
+
+// ============ Session Management | Session管理 ============
+
+// GetSession gets the session for a login ID | 获取登录ID的Session
+func GetSession(loginID interface{}) (*Session, error) {
+ return stputil.GetSession(loginID)
+}
+
+// GetSessionByToken gets the session by token | 根据Token获取Session
+func GetSessionByToken(tokenValue string) (*Session, error) {
+ return stputil.GetSessionByToken(tokenValue)
+}
+
+// GetTokenSession gets the token session | 获取Token的Session
+func GetTokenSession(tokenValue string) (*Session, error) {
+ return stputil.GetTokenSession(tokenValue)
+}
+
+// ============ Security Features | 安全特性 ============
+
+// GenerateNonce generates a new nonce token | 生成新的Nonce令牌
+func GenerateNonce() (string, error) {
+ return stputil.GenerateNonce()
+}
+
+// VerifyNonce verifies a nonce token | 验证Nonce令牌
+func VerifyNonce(nonce string) bool {
+ return stputil.VerifyNonce(nonce)
+}
+
+// LoginWithRefreshToken performs login and returns refresh token info | 登录并返回刷新令牌信息
+func LoginWithRefreshToken(loginID interface{}, device ...string) (*RefreshTokenInfo, error) {
+ return stputil.LoginWithRefreshToken(loginID, device...)
+}
+
+// RefreshAccessToken refreshes the access token using a refresh token | 使用刷新令牌刷新访问令牌
+func RefreshAccessToken(refreshToken string) (*RefreshTokenInfo, error) {
+ return stputil.RefreshAccessToken(refreshToken)
+}
+
+// RevokeRefreshToken revokes a refresh token | 撤销刷新令牌
+func RevokeRefreshToken(refreshToken string) error {
+ return stputil.RevokeRefreshToken(refreshToken)
+}
+
+// GetOAuth2Server gets the OAuth2 server instance | 获取OAuth2服务器实例
+func GetOAuth2Server() *OAuth2Server {
+ return stputil.GetOAuth2Server()
+}
+
+// 字符串常量,没有包装必要
+// Version Sa-Token-Go version | Sa-Token-Go版本
+const Version = core.Version
diff --git a/integrations/beego/go.mod b/integrations/beego/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a2a3fc66e985d3a56f0a9705692b37b24926770d
--- /dev/null
+++ b/integrations/beego/go.mod
@@ -0,0 +1,39 @@
+module github.com/click33/sa-token-go/integrations/beego
+
+go 1.24.2
+
+require (
+ github.com/beego/beego/v2 v2.3.10
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/click33/sa-token-go/storage/memory v0.1.8 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/golang-lru v1.0.2 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/panjf2000/ants/v2 v2.11.3 // indirect
+ github.com/prometheus/client_golang v1.22.0 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.63.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect
+ golang.org/x/crypto v0.38.0 // indirect
+ golang.org/x/net v0.40.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ golang.org/x/text v0.25.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace (
+ github.com/click33/sa-token-go/core => ../../core
+ github.com/click33/sa-token-go/stputil => ../../stputil
+)
diff --git a/integrations/beego/go.sum b/integrations/beego/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..2b14b0afe985025f0072ddb198b843a6720e255b
--- /dev/null
+++ b/integrations/beego/go.sum
@@ -0,0 +1,68 @@
+github.com/beego/beego/v2 v2.3.10 h1:53us+Lzc/bwFwjyRi62+FKFEcWiIbmKbhW4/FQ3aNEw=
+github.com/beego/beego/v2 v2.3.10/go.mod h1:IY3bkfRge4Yj2XhgdMuvznM4HK/ovxQLOHJxAJ+QRgo=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/click33/sa-token-go/storage/memory v0.1.8 h1:kyhSFWBfmO8XNXye+E9d4gw0bJE2HePwWg5xmkE2rFw=
+github.com/click33/sa-token-go/storage/memory v0.1.8/go.mod h1:cPQUpY0D/3lGyCyzW/lZ4pc+O9HPfYV3FLeWABpmPnk=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
+github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+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/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
+github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs=
+github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/integrations/beego/plugin.go b/integrations/beego/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..caab60f0c61b10fa627f8dd3c371623c4e2dfd2a
--- /dev/null
+++ b/integrations/beego/plugin.go
@@ -0,0 +1,260 @@
+package beego
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+
+ "github.com/beego/beego/v2/server/web"
+ beegoctx "github.com/beego/beego/v2/server/web/context"
+ "github.com/click33/sa-token-go/core"
+)
+
+// Plugin Beego plugin for Sa-Token | Beego插件
+type Plugin struct {
+ manager *core.Manager
+}
+
+// NewPlugin creates a Beego plugin | 创建Beego插件
+func NewPlugin(manager *core.Manager) *Plugin {
+ return &Plugin{
+ manager: manager,
+ }
+}
+
+// AuthMiddleware authentication middleware | 认证中间件
+func (p *Plugin) AuthMiddleware() web.FilterFunc {
+ return func(ctx *beegoctx.Context) {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ ctx.Input.SetData("satoken", saCtx)
+ }
+}
+
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) web.FilterFunc {
+ return func(ctx *beegoctx.Context) {
+ path := ctx.Input.URI()
+ token := ctx.Input.Header(p.manager.GetConfig().TokenName)
+ if token == "" {
+ token = ctx.Input.Cookie(p.manager.GetConfig().TokenName)
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ writeErrorResponse(ctx, core.NewPathAuthRequiredError(path))
+ return
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+ ctx.Input.SetData("satoken", saCtx)
+ ctx.Input.SetData("loginID", result.LoginID())
+ }
+ }
+}
+
+// PermissionRequired permission validation middleware | 权限验证中间件
+func (p *Plugin) PermissionRequired(permission string) web.FilterFunc {
+ return func(ctx *beegoctx.Context) {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ if !saCtx.HasPermission(permission) {
+ writeErrorResponse(ctx, core.NewPermissionDeniedError(permission))
+ return
+ }
+
+ ctx.Input.SetData("satoken", saCtx)
+ }
+}
+
+// RoleRequired role validation middleware | 角色验证中间件
+func (p *Plugin) RoleRequired(role string) web.FilterFunc {
+ return func(ctx *beegoctx.Context) {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ if !saCtx.HasRole(role) {
+ writeErrorResponse(ctx, core.NewRoleDeniedError(role))
+ return
+ }
+
+ ctx.Input.SetData("satoken", saCtx)
+ }
+}
+
+// LoginHandler login handler | 登录处理器
+func (p *Plugin) LoginHandler(ctx *beegoctx.Context) {
+ var req struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ Device string `json:"device"`
+ }
+
+ // Get request body - beego v2 may need CopyBody to populate RequestBody
+ body := ctx.Input.RequestBody
+ if len(body) == 0 {
+ body = ctx.Input.CopyBody(1024 << 10) // 1MB max
+ }
+
+ if err := json.Unmarshal(body, &req); err != nil {
+ writeErrorResponse(ctx, core.NewError(core.CodeBadRequest, "invalid request parameters", err))
+ return
+ }
+
+ device := req.Device
+ if device == "" {
+ device = "default"
+ }
+
+ token, err := p.manager.Login(req.Username, device)
+ if err != nil {
+ writeErrorResponse(ctx, core.NewError(core.CodeServerError, "login failed", err))
+ return
+ }
+
+ cfg := p.manager.GetConfig()
+ if cfg.IsReadCookie {
+ maxAge := max(int(cfg.Timeout), 0)
+ ctx.SetCookie(
+ cfg.TokenName,
+ token,
+ maxAge,
+ cfg.CookieConfig.Path,
+ cfg.CookieConfig.Domain,
+ cfg.CookieConfig.Secure,
+ cfg.CookieConfig.HttpOnly,
+ )
+ }
+
+ writeSuccessResponse(ctx, map[string]interface{}{
+ "token": token,
+ })
+}
+
+// LogoutHandler logout handler | 登出处理器
+func (p *Plugin) LogoutHandler(ctx *beegoctx.Context) {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+
+ loginID, err := saCtx.GetLoginID()
+ if err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ if err := p.manager.Logout(loginID); err != nil {
+ writeErrorResponse(ctx, core.NewError(core.CodeServerError, "logout failed", err))
+ return
+ }
+
+ writeSuccessResponse(ctx, map[string]interface{}{
+ "message": "logout successful",
+ })
+}
+
+// UserInfoHandler user info handler | 获取用户信息处理器
+func (p *Plugin) UserInfoHandler(ctx *beegoctx.Context) {
+ bCtx := NewBeegoContext(ctx)
+ saCtx := core.NewContext(bCtx, p.manager)
+
+ loginID, err := saCtx.GetLoginID()
+ if err != nil {
+ writeErrorResponse(ctx, err)
+ return
+ }
+
+ permissions, _ := p.manager.GetPermissions(loginID)
+ roles, _ := p.manager.GetRoles(loginID)
+
+ writeSuccessResponse(ctx, map[string]interface{}{
+ "loginId": loginID,
+ "permissions": permissions,
+ "roles": roles,
+ })
+}
+
+// GetSaToken gets Sa-Token context from Beego context | 从Beego上下文获取Sa-Token上下文
+func GetSaToken(ctx *beegoctx.Context) (*core.SaTokenContext, bool) {
+ satoken := ctx.Input.GetData("satoken")
+ if satoken == nil {
+ return nil, false
+ }
+ saCtx, ok := satoken.(*core.SaTokenContext)
+ return saCtx, ok
+}
+
+// ============ Error Handling Helpers | 错误处理辅助函数 ============
+
+// writeErrorResponse writes a standardized error response | 写入标准化的错误响应
+func writeErrorResponse(ctx *beegoctx.Context, err error) error {
+ var saErr *core.SaTokenError
+ var code int
+ var message string
+ var httpStatus int
+
+ if errors.As(err, &saErr) {
+ code = saErr.Code
+ message = saErr.Message
+ httpStatus = getHTTPStatusFromCode(code)
+ } else {
+ code = core.CodeServerError
+ message = err.Error()
+ httpStatus = http.StatusInternalServerError
+ }
+
+ ctx.ResponseWriter.WriteHeader(httpStatus)
+ ctx.ResponseWriter.Header().Set("Content-Type", "application/json")
+ return json.NewEncoder(ctx.ResponseWriter).Encode(map[string]interface{}{
+ "code": code,
+ "message": message,
+ "error": err.Error(),
+ })
+}
+
+// writeSuccessResponse writes a standardized success response | 写入标准化的成功响应
+func writeSuccessResponse(ctx *beegoctx.Context, data interface{}) error {
+ ctx.ResponseWriter.Header().Set("Content-Type", "application/json")
+ return json.NewEncoder(ctx.ResponseWriter).Encode(map[string]interface{}{
+ "code": core.CodeSuccess,
+ "message": "success",
+ "data": data,
+ })
+}
+
+// getHTTPStatusFromCode converts Sa-Token error code to HTTP status | 将Sa-Token错误码转换为HTTP状态码
+func getHTTPStatusFromCode(code int) int {
+ switch code {
+ case core.CodeNotLogin:
+ return http.StatusUnauthorized
+ case core.CodePermissionDenied:
+ return http.StatusForbidden
+ case core.CodeBadRequest:
+ return http.StatusBadRequest
+ case core.CodeNotFound:
+ return http.StatusNotFound
+ case core.CodeServerError:
+ return http.StatusInternalServerError
+ default:
+ return http.StatusInternalServerError
+ }
+}
diff --git a/integrations/chi/annotation.go b/integrations/chi/annotation.go
index b8ec1d9d7ebf2a413e5786d458f4191bda14b2c4..ffb0374fc5562b332b99d8aedbfe53924b22987b 100644
--- a/integrations/chi/annotation.go
+++ b/integrations/chi/annotation.go
@@ -20,7 +20,6 @@ type Annotation struct {
// GetHandler gets handler with annotations | 获取带注解的处理器
func GetHandler(handler http.Handler, annotations ...*Annotation) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Check if authentication should be ignored | 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
if handler != nil {
handler.ServeHTTP(w, r)
@@ -28,22 +27,20 @@ func GetHandler(handler http.Handler, annotations ...*Annotation) http.Handler {
return
}
- // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName)
ctx := NewChiContext(w, r)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
writeErrorResponse(w, core.NewNotLoginError())
return
}
- // Check login | 检查登录
if !stputil.IsLogin(token) {
writeErrorResponse(w, core.NewNotLoginError())
return
}
- // Get login ID | 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
writeErrorResponse(w, err)
diff --git a/integrations/chi/go.mod b/integrations/chi/go.mod
index 7ef3a25e7d7f25c015237c4d0e9e45372de142d1..a01e54731ab9ebae918ce7c6c8311be20f61f8b9 100644
--- a/integrations/chi/go.mod
+++ b/integrations/chi/go.mod
@@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/integrations/chi
go 1.23.0
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
)
require (
diff --git a/integrations/chi/plugin.go b/integrations/chi/plugin.go
index 7fdab881ea728fb0fa44ff14e8a7d6e4bb030931..33f6e3896748c6609005350b78512257416ede5b 100644
--- a/integrations/chi/plugin.go
+++ b/integrations/chi/plugin.go
@@ -39,6 +39,38 @@ func (p *Plugin) AuthMiddleware() func(http.Handler) http.Handler {
}
}
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ path := r.URL.Path
+ token := r.Header.Get(p.manager.GetConfig().TokenName)
+ if token == "" {
+ cookie, _ := r.Cookie(p.manager.GetConfig().TokenName)
+ if cookie != nil {
+ token = cookie.Value
+ }
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ writeErrorResponse(w, core.NewPathAuthRequiredError(path))
+ return
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ ctx := NewChiContext(w, r)
+ saCtx := core.NewContext(ctx, p.manager)
+ ctx.Set("satoken", saCtx)
+ ctx.Set("loginID", result.LoginID())
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
// PermissionRequired permission validation middleware | 权限验证中间件
func (p *Plugin) PermissionRequired(permission string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
diff --git a/integrations/echo/annotation.go b/integrations/echo/annotation.go
index a8c980426aa25f1a6ce4474a5d7f466f8f3031ce..804471b0e8406a5a9ecd2012df5810cd04013a69 100644
--- a/integrations/echo/annotation.go
+++ b/integrations/echo/annotation.go
@@ -20,7 +20,6 @@ type Annotation struct {
// GetHandler gets handler with annotations | 获取带注解的处理器
func GetHandler(handler echo.HandlerFunc, annotations ...*Annotation) echo.HandlerFunc {
return func(c echo.Context) error {
- // Check if authentication should be ignored | 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
if handler != nil {
return handler(c)
@@ -28,20 +27,18 @@ func GetHandler(handler echo.HandlerFunc, annotations ...*Annotation) echo.Handl
return nil
}
- // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName)
ctx := NewEchoContext(c)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
return writeErrorResponse(c, core.NewNotLoginError())
}
- // Check login | 检查登录
if !stputil.IsLogin(token) {
return writeErrorResponse(c, core.NewNotLoginError())
}
- // Get login ID | 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
return writeErrorResponse(c, err)
diff --git a/integrations/echo/go.mod b/integrations/echo/go.mod
index ae7ee59fe8221397280408e9a7cdc93f0d475b79..f2074975674968cce0ac18698ca914c286306a3f 100644
--- a/integrations/echo/go.mod
+++ b/integrations/echo/go.mod
@@ -5,8 +5,8 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/labstack/echo/v4 v4.11.4
)
diff --git a/integrations/echo/plugin.go b/integrations/echo/plugin.go
index 50299c8d2e54ad2c0bebbee9bf44ae798de0c692..35fe69bda876e00354c31e38529ede20879c2409 100644
--- a/integrations/echo/plugin.go
+++ b/integrations/echo/plugin.go
@@ -37,6 +37,37 @@ func (p *Plugin) AuthMiddleware() echo.MiddlewareFunc {
}
}
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) echo.MiddlewareFunc {
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ path := c.Request().URL.Path
+ token := c.Request().Header.Get(p.manager.GetConfig().TokenName)
+ if token == "" {
+ cookie, _ := c.Cookie(p.manager.GetConfig().TokenName)
+ if cookie != nil {
+ token = cookie.Value
+ }
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ return writeErrorResponse(c, core.NewPathAuthRequiredError(path))
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ ctx := NewEchoContext(c)
+ saCtx := core.NewContext(ctx, p.manager)
+ c.Set("satoken", saCtx)
+ c.Set("loginID", result.LoginID())
+ }
+
+ return next(c)
+ }
+ }
+}
+
// PermissionRequired permission validation middleware | 权限验证中间件
func (p *Plugin) PermissionRequired(permission string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
diff --git a/integrations/fiber/annotation.go b/integrations/fiber/annotation.go
index 0182710f83324e7e9416fa319d87ddebb1c59949..ed3ca6280502bca131e47017f85df78c73b5b96e 100644
--- a/integrations/fiber/annotation.go
+++ b/integrations/fiber/annotation.go
@@ -20,7 +20,6 @@ type Annotation struct {
// GetHandler gets handler with annotations | 获取带注解的处理器
func GetHandler(handler fiber.Handler, annotations ...*Annotation) fiber.Handler {
return func(c *fiber.Ctx) error {
- // Check if authentication should be ignored | 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
if handler != nil {
return handler(c)
@@ -28,20 +27,18 @@ func GetHandler(handler fiber.Handler, annotations ...*Annotation) fiber.Handler
return c.Next()
}
- // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName)
ctx := NewFiberContext(c)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
return writeErrorResponse(c, core.NewNotLoginError())
}
- // Check login | 检查登录
if !stputil.IsLogin(token) {
return writeErrorResponse(c, core.NewNotLoginError())
}
- // Get login ID | 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
return writeErrorResponse(c, err)
diff --git a/integrations/fiber/go.mod b/integrations/fiber/go.mod
index abb41150d1392918c4f286cca011c815b3aa898b..ae840ab98cbdee9fa47e8fc3c82baeb9fdbe28d5 100644
--- a/integrations/fiber/go.mod
+++ b/integrations/fiber/go.mod
@@ -5,8 +5,8 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/gofiber/fiber/v2 v2.52.0
)
diff --git a/integrations/fiber/plugin.go b/integrations/fiber/plugin.go
index 7aa193b66f9d1a0e4f6fbc93be19f3bb8863f0e6..cf2e128570d968639ee8bd2f9e11a6cb742eb4c7 100644
--- a/integrations/fiber/plugin.go
+++ b/integrations/fiber/plugin.go
@@ -34,6 +34,32 @@ func (p *Plugin) AuthMiddleware() fiber.Handler {
}
}
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ path := c.Path()
+ token := c.Get(p.manager.GetConfig().TokenName)
+ if token == "" {
+ token = c.Cookies(p.manager.GetConfig().TokenName)
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ return writeErrorResponse(c, core.NewPathAuthRequiredError(path))
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ ctx := NewFiberContext(c)
+ saCtx := core.NewContext(ctx, p.manager)
+ c.Locals("satoken", saCtx)
+ c.Locals("loginID", result.LoginID())
+ }
+
+ return c.Next()
+ }
+}
+
// PermissionRequired permission validation middleware | 权限验证中间件
func (p *Plugin) PermissionRequired(permission string) fiber.Handler {
return func(c *fiber.Ctx) error {
diff --git a/integrations/gf/annotation.go b/integrations/gf/annotation.go
index d1e06300eabe449585c151c3d01e3a81fba88408..379a2738ba8c1b54a5ba4fc04762efd897ede555 100644
--- a/integrations/gf/annotation.go
+++ b/integrations/gf/annotation.go
@@ -20,7 +20,6 @@ type Annotation struct {
// GetHandler gets handler with annotations | 获取带注解的处理器
func GetHandler(handler ghttp.HandlerFunc, annotations ...*Annotation) ghttp.HandlerFunc {
return func(r *ghttp.Request) {
- // Check if authentication should be ignored | 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
if handler != nil {
handler(r)
@@ -30,22 +29,20 @@ func GetHandler(handler ghttp.HandlerFunc, annotations ...*Annotation) ghttp.Han
return
}
- // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName)
ctx := NewGFContext(r)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
writeErrorResponse(r, core.NewNotLoginError())
return
}
- // Check login | 检查登录
if !stputil.IsLogin(token) {
writeErrorResponse(r, core.NewNotLoginError())
return
}
- // Get login ID | 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
writeErrorResponse(r, err)
diff --git a/integrations/gf/go.mod b/integrations/gf/go.mod
index 340f21fe8224b6c1a66a58ed0134328170668894..d7c283332e9c545e951157d26a82c1554e999666 100644
--- a/integrations/gf/go.mod
+++ b/integrations/gf/go.mod
@@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/integrations/gf
go 1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/gogf/gf/v2 v2.9.4
)
diff --git a/integrations/gf/plugin.go b/integrations/gf/plugin.go
index d9d18db4171a2a53d21901ee10112b27e9e1dc9c..78b51e76da8cf8317163a60e098e73b131346954 100644
--- a/integrations/gf/plugin.go
+++ b/integrations/gf/plugin.go
@@ -47,6 +47,33 @@ func (p *Plugin) AuthMiddleware() ghttp.HandlerFunc {
}
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) ghttp.HandlerFunc {
+ return func(r *ghttp.Request) {
+ path := r.Request.URL.Path
+ token := r.Header.Get(p.manager.GetConfig().TokenName)
+ if token == "" {
+ token = r.Cookie.Get(p.manager.GetConfig().TokenName).String()
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ writeErrorResponse(r, core.NewPathAuthRequiredError(path))
+ return
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ ctx := NewGFContext(r)
+ saCtx := core.NewContext(ctx, p.manager)
+ r.SetCtxVar("satoken", saCtx)
+ r.SetCtxVar("loginID", result.LoginID())
+ }
+
+ r.Middleware.Next()
+ }
+}
+
// PermissionRequired permission validation middleware | 权限验证中间件
func (p *Plugin) PermissionRequired(permission string) ghttp.HandlerFunc {
return func(r *ghttp.Request) {
diff --git a/integrations/gin/annotation.go b/integrations/gin/annotation.go
index cc7a1dc0f6a992e9ef3d865ba3d677b621992b04..3b8d4f439ffe028d297c9848a23f042d53f0a7c5 100644
--- a/integrations/gin/annotation.go
+++ b/integrations/gin/annotation.go
@@ -90,7 +90,6 @@ func (a *Annotation) Validate() bool {
// GetHandler gets handler with annotations | 获取带注解的处理器
func GetHandler(handler interface{}, annotations ...*Annotation) ginfw.HandlerFunc {
return func(c *ginfw.Context) {
- // Check if authentication should be ignored | 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
if callHandler(handler, c) {
return
@@ -99,24 +98,22 @@ func GetHandler(handler interface{}, annotations ...*Annotation) ginfw.HandlerFu
return
}
- // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName)
ctx := NewGinContext(c)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
writeErrorResponse(c, core.NewNotLoginError())
c.Abort()
return
}
- // Check login | 检查登录
if !stputil.IsLogin(token) {
writeErrorResponse(c, core.NewNotLoginError())
c.Abort()
return
}
- // Get login ID | 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
writeErrorResponse(c, err)
@@ -289,31 +286,27 @@ func (h *HandlerWithAnnotations) ToGinHandler() ginfw.HandlerFunc {
// Middleware 创建中间件版本
func Middleware(annotations ...*Annotation) ginfw.HandlerFunc {
return func(c *ginfw.Context) {
-
- // 检查是否忽略认证
if len(annotations) > 0 && annotations[0].Ignore {
c.Next()
return
}
- // 获取Token(使用配置的TokenName)
ctx := NewGinContext(c)
saCtx := core.NewContext(ctx, stputil.GetManager())
token := saCtx.GetTokenValue()
+
if token == "" {
writeErrorResponse(c, core.NewNotLoginError())
c.Abort()
return
}
- // 检查登录
if !stputil.IsLogin(token) {
writeErrorResponse(c, core.NewNotLoginError())
c.Abort()
return
}
- // 获取登录ID
loginID, err := stputil.GetLoginID(token)
if err != nil {
writeErrorResponse(c, err)
diff --git a/integrations/gin/go.mod b/integrations/gin/go.mod
index 3b8d3d3611500829144ac3ed02718aad904665a5..36448dc709d5403ebcbb40a7bb5e2c970d0d306f 100644
--- a/integrations/gin/go.mod
+++ b/integrations/gin/go.mod
@@ -5,8 +5,8 @@ go 1.23.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/gin-gonic/gin v1.10.0
github.com/stretchr/testify v1.11.1
)
diff --git a/integrations/gin/plugin.go b/integrations/gin/plugin.go
index d5e3f292af1611feae048aa42d505793cab7d094..8d084f71484083c09cafe60db5428c200ef011ea 100644
--- a/integrations/gin/plugin.go
+++ b/integrations/gin/plugin.go
@@ -39,6 +39,34 @@ func (p *Plugin) AuthMiddleware() gin.HandlerFunc {
}
}
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ path := c.Request.URL.Path
+ token := c.GetHeader(p.manager.GetConfig().TokenName)
+ if token == "" {
+ token, _ = c.Cookie(p.manager.GetConfig().TokenName)
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ writeErrorResponse(c, core.NewPathAuthRequiredError(path))
+ c.Abort()
+ return
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ ctx := NewGinContext(c)
+ saCtx := core.NewContext(ctx, p.manager)
+ c.Set("satoken", saCtx)
+ c.Set("loginID", result.LoginID())
+ }
+
+ c.Next()
+ }
+}
+
// PermissionRequired permission validation middleware | 权限验证中间件
func (p *Plugin) PermissionRequired(permission string) gin.HandlerFunc {
return func(c *gin.Context) {
diff --git a/integrations/hertz/annotation.go b/integrations/hertz/annotation.go
new file mode 100644
index 0000000000000000000000000000000000000000..158a6fe8ef8c2f387e5e3ac6d6169762039f598f
--- /dev/null
+++ b/integrations/hertz/annotation.go
@@ -0,0 +1,362 @@
+package hertz
+
+import (
+ "context"
+ "reflect"
+ "strings"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/stputil"
+ "github.com/cloudwego/hertz/pkg/app"
+)
+
+// Annotation constants | 注解常量
+const (
+ TagSaCheckLogin = "sa_check_login"
+ TagSaCheckRole = "sa_check_role"
+ TagSaCheckPermission = "sa_check_permission"
+ TagSaCheckDisable = "sa_check_disable"
+ TagSaIgnore = "sa_ignore"
+)
+
+// Annotation annotation structure | 注解结构体
+type Annotation struct {
+ CheckLogin bool `json:"checkLogin"`
+ CheckRole []string `json:"checkRole"`
+ CheckPermission []string `json:"checkPermission"`
+ CheckDisable bool `json:"checkDisable"`
+ Ignore bool `json:"ignore"`
+}
+
+// ParseTag parses struct tags | 解析结构体标签
+func ParseTag(tag string) *Annotation {
+ ann := &Annotation{}
+
+ if tag == "" {
+ return ann
+ }
+
+ parts := strings.Split(tag, ",")
+ for _, part := range parts {
+ part = strings.TrimSpace(part)
+ switch {
+ case part == TagSaCheckLogin || part == "login":
+ ann.CheckLogin = true
+ case strings.HasPrefix(part, TagSaCheckRole+"=") || strings.HasPrefix(part, "role="):
+ roles := strings.TrimPrefix(part, TagSaCheckRole+"=")
+ roles = strings.TrimPrefix(roles, "role=")
+ if roles != "" {
+ ann.CheckRole = strings.Split(roles, "|")
+ }
+ case strings.HasPrefix(part, TagSaCheckPermission+"=") || strings.HasPrefix(part, "permission="):
+ perms := strings.TrimPrefix(part, TagSaCheckPermission+"=")
+ perms = strings.TrimPrefix(perms, "permission=")
+ if perms != "" {
+ ann.CheckPermission = strings.Split(perms, "|")
+ }
+ case part == TagSaCheckDisable || part == "disable":
+ ann.CheckDisable = true
+ case part == TagSaIgnore || part == "ignore":
+ ann.Ignore = true
+ }
+ }
+
+ return ann
+}
+
+// Validate validates if annotation is valid | 验证注解是否有效
+func (a *Annotation) Validate() bool {
+ if a.Ignore {
+ return true // When ignore is true, other checks are invalid | 忽略认证时,其他检查无效
+ }
+
+ count := 0
+ if a.CheckLogin {
+ count++
+ }
+ if len(a.CheckRole) > 0 {
+ count++
+ }
+ if len(a.CheckPermission) > 0 {
+ count++
+ }
+ if a.CheckDisable {
+ count++
+ }
+
+ // At most one check type allowed | 最多只能有一个检查类型
+ return count <= 1
+}
+
+// GetHandler gets handler with annotations | 获取带注解的处理器
+func GetHandler(handler interface{}, annotations ...*Annotation) app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ if len(annotations) > 0 && annotations[0].Ignore {
+ if callHandler(handler, ctx, c) {
+ return
+ }
+ c.Next(ctx)
+ return
+ }
+
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, stputil.GetManager())
+ token := saCtx.GetTokenValue()
+
+ if token == "" {
+ writeErrorResponse(c, core.NewNotLoginError())
+ c.Abort()
+ return
+ }
+
+ if !stputil.IsLogin(token) {
+ writeErrorResponse(c, core.NewNotLoginError())
+ c.Abort()
+ return
+ }
+
+ loginID, err := stputil.GetLoginID(token)
+ if err != nil {
+ writeErrorResponse(c, err)
+ c.Abort()
+ return
+ }
+
+ // Check if account is disabled | 检查是否被封禁
+ if len(annotations) > 0 && annotations[0].CheckDisable {
+ if stputil.IsDisable(loginID) {
+ writeErrorResponse(c, core.NewAccountDisabledError(loginID))
+ c.Abort()
+ return
+ }
+ }
+
+ // Check permission | 检查权限
+ if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 {
+ hasPermission := false
+ for _, perm := range annotations[0].CheckPermission {
+ if stputil.HasPermission(loginID, strings.TrimSpace(perm)) {
+ hasPermission = true
+ break
+ }
+ }
+ if !hasPermission {
+ writeErrorResponse(c, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ",")))
+ c.Abort()
+ return
+ }
+ }
+
+ // Check role | 检查角色
+ if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 {
+ hasRole := false
+ for _, role := range annotations[0].CheckRole {
+ if stputil.HasRole(loginID, strings.TrimSpace(role)) {
+ hasRole = true
+ break
+ }
+ }
+ if !hasRole {
+ writeErrorResponse(c, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ",")))
+ c.Abort()
+ return
+ }
+ }
+
+ // All checks passed, execute original handler or continue | 所有检查通过,执行原函数或继续
+ if callHandler(handler, ctx, c) {
+ return
+ }
+ c.Next(ctx)
+ }
+}
+
+func callHandler(handler interface{}, ctx context.Context, c *app.RequestContext) bool {
+ if handler == nil {
+ return false
+ }
+
+ switch h := handler.(type) {
+ case func(*app.RequestContext):
+ if h == nil {
+ return false
+ }
+ h(c)
+ return true
+ case app.HandlerFunc:
+ if h == nil {
+ return false
+ }
+ h(ctx, c)
+ return true
+ }
+
+ hv := reflect.ValueOf(handler)
+ if hv.Kind() != reflect.Func || hv.IsNil() || hv.Type().NumIn() != 1 {
+ return false
+ }
+
+ argType := hv.Type().In(0)
+ if !argType.AssignableTo(reflect.TypeOf(c)) {
+ return false
+ }
+
+ hv.Call([]reflect.Value{reflect.ValueOf(c)})
+ return true
+}
+
+// Decorator functions | 装饰器函数
+
+// CheckLogin decorator for login checking | 检查登录装饰器
+func CheckLogin() app.HandlerFunc {
+ return GetHandler(nil, &Annotation{CheckLogin: true})
+}
+
+// CheckRole decorator for role checking | 检查角色装饰器
+func CheckRole(roles ...string) app.HandlerFunc {
+ return GetHandler(nil, &Annotation{CheckRole: roles})
+}
+
+// CheckPermission decorator for permission checking | 检查权限装饰器
+func CheckPermission(perms ...string) app.HandlerFunc {
+ return GetHandler(nil, &Annotation{CheckPermission: perms})
+}
+
+// CheckDisable decorator for checking if account is disabled | 检查是否被封禁装饰器
+func CheckDisable() app.HandlerFunc {
+ return GetHandler(nil, &Annotation{CheckDisable: true})
+}
+
+// Ignore decorator to ignore authentication | 忽略认证装饰器
+func Ignore() app.HandlerFunc {
+ return GetHandler(nil, &Annotation{Ignore: true})
+}
+
+// WithAnnotation decorator with custom annotation | 使用自定义注解装饰器
+func WithAnnotation(ann *Annotation) app.HandlerFunc {
+ return GetHandler(nil, ann)
+}
+
+// ProcessStructAnnotations processes annotations on struct tags | 处理结构体上的注解标签
+func ProcessStructAnnotations(handler interface{}) app.HandlerFunc {
+ handlerValue := reflect.ValueOf(handler)
+ handlerType := reflect.TypeOf(handler)
+
+ // Find method name, usually the last path segment | 查找方法名,通常是最后一个路径段
+ methodName := "unknown"
+ if handlerType.Kind() == reflect.Ptr {
+ handlerType = handlerType.Elem()
+ }
+ if handlerType.Kind() == reflect.Struct {
+ methodName = handlerType.Name()
+ }
+
+ // Parse method annotations | 解析方法上的注解标签
+ ann := parseMethodAnnotation(handlerType, methodName)
+
+ return GetHandler(func(c *app.RequestContext) {
+ handlerValue.MethodByName("ServeHTTP").Call([]reflect.Value{reflect.ValueOf(c)})
+ }, ann)
+}
+
+// parseMethodAnnotation parses method annotations | 解析方法注解
+func parseMethodAnnotation(t reflect.Type, methodName string) *Annotation {
+ // Simplified implementation, returns empty annotation | 简化实现,直接返回空注解
+ return &Annotation{}
+}
+
+// HandlerWithAnnotations 带注解的处理器包装器
+type HandlerWithAnnotations struct {
+ Handler interface{}
+ Annotations []*Annotation
+}
+
+// NewHandlerWithAnnotations 创建带注解的处理器
+func NewHandlerWithAnnotations(handler interface{}, annotations ...*Annotation) *HandlerWithAnnotations {
+ return &HandlerWithAnnotations{
+ Handler: handler,
+ Annotations: annotations,
+ }
+}
+
+// ToGinHandler 转换为Gin处理器
+func (h *HandlerWithAnnotations) ToGinHandler() app.HandlerFunc {
+ return GetHandler(h.Handler, h.Annotations...)
+}
+
+// Middleware 创建中间件版本
+func Middleware(annotations ...*Annotation) app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ if len(annotations) > 0 && annotations[0].Ignore {
+ c.Next(ctx)
+ return
+ }
+
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, stputil.GetManager())
+ token := saCtx.GetTokenValue()
+
+ if token == "" {
+ writeErrorResponse(c, core.NewNotLoginError())
+ c.Abort()
+ return
+ }
+
+ if !stputil.IsLogin(token) {
+ writeErrorResponse(c, core.NewNotLoginError())
+ c.Abort()
+ return
+ }
+
+ loginID, err := stputil.GetLoginID(token)
+ if err != nil {
+ writeErrorResponse(c, err)
+ c.Abort()
+ return
+ }
+
+ // 检查是否被封禁
+ if len(annotations) > 0 && annotations[0].CheckDisable {
+ if stputil.IsDisable(loginID) {
+ writeErrorResponse(c, core.NewAccountDisabledError(loginID))
+ c.Abort()
+ return
+ }
+ }
+
+ // 检查权限
+ if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 {
+ hasPermission := false
+ for _, perm := range annotations[0].CheckPermission {
+ if stputil.HasPermission(loginID, strings.TrimSpace(perm)) {
+ hasPermission = true
+ break
+ }
+ }
+ if !hasPermission {
+ writeErrorResponse(c, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ",")))
+ c.Abort()
+ return
+ }
+ }
+
+ // 检查角色
+ if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 {
+ hasRole := false
+ for _, role := range annotations[0].CheckRole {
+ if stputil.HasRole(loginID, strings.TrimSpace(role)) {
+ hasRole = true
+ break
+ }
+ }
+ if !hasRole {
+ writeErrorResponse(c, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ",")))
+ c.Abort()
+ return
+ }
+ }
+
+ // 所有检查通过,继续下一个处理器
+ c.Next(ctx)
+ }
+}
diff --git a/integrations/hertz/annotation_test.go b/integrations/hertz/annotation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d2f6575bc9fd73722fc2bc04160d6eaeac6586f
--- /dev/null
+++ b/integrations/hertz/annotation_test.go
@@ -0,0 +1,457 @@
+package hertz
+
+import (
+ "context"
+ "net/http"
+ "testing"
+
+ "github.com/click33/sa-token-go/core/config"
+ "github.com/click33/sa-token-go/core/manager"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/click33/sa-token-go/stputil"
+ "github.com/cloudwego/hertz/pkg/app"
+ "github.com/cloudwego/hertz/pkg/app/server"
+ "github.com/cloudwego/hertz/pkg/common/ut"
+ "github.com/cloudwego/hertz/pkg/common/utils"
+ "github.com/stretchr/testify/assert"
+)
+
+// setupTestRouter 创建测试路由器和初始化 sa-token
+func setupTestRouter() *server.Hertz {
+ router := server.Default()
+
+ // 创建内存存储
+ storage := memory.NewStorage()
+
+ // 创建配置(必须开启 IsReadHeader 才能从 Authorization 头读取 token)
+ cfg := &config.Config{
+ TokenName: "satoken",
+ Timeout: 2592000, // 30 天(秒)
+ IsConcurrent: true,
+ IsShare: true,
+ MaxLoginCount: -1,
+ IsReadHeader: true,
+ }
+
+ // 创建并设置全局 Manager
+ mgr := manager.NewManager(storage, cfg)
+ stputil.SetManager(mgr)
+
+ return router
+}
+
+// mockLogin 模拟用户登录并返回 token
+func mockLogin(loginID interface{}) string {
+ token, _ := stputil.Login(loginID)
+ return token
+}
+
+// mockLoginWithRole 模拟用户登录并设置角色
+func mockLoginWithRole(loginID interface{}, roles []string) string {
+ token, _ := stputil.Login(loginID)
+ stputil.SetRoles(loginID, roles)
+ return token
+}
+
+// mockLoginWithPermission 模拟用户登录并设置权限
+func mockLoginWithPermission(loginID interface{}, permissions []string) string {
+ token, _ := stputil.Login(loginID)
+ stputil.SetPermissions(loginID, permissions)
+ return token
+}
+
+// TestCheckRole_WithValidRole 测试具有有效角色的用户访问
+func TestCheckRole_WithValidRole(t *testing.T) {
+ router := setupTestRouter()
+
+ // 设置路由 - 使用 CheckRole 作为中间件
+ router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ // 创建一个具有 Admin 角色的用户
+ token := mockLoginWithRole("user123", []string{"Admin"})
+ // 发送请求
+ w := ut.PerformRequest(router.Engine, "GET", "/admin", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ // 断言
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "success")
+}
+
+// TestCheckRole_WithInvalidRole 测试没有所需角色的用户访问
+func TestCheckRole_WithInvalidRole(t *testing.T) {
+ router := setupTestRouter()
+
+ // 设置路由
+ router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ // 创建一个只有 User 角色的用户
+ token := mockLoginWithRole("user456", []string{"User"})
+
+ // 发送请求
+ w := ut.PerformRequest(router.Engine, "GET", "/admin", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ // 断言
+ assert.Equal(t, http.StatusForbidden, w.Code)
+ assert.Contains(t, w.Body.String(), "role denied")
+}
+
+// TestCheckRole_MultipleRoles 测试多个角色的情况(OR 逻辑)
+func TestCheckRole_MultipleRoles(t *testing.T) {
+ router := setupTestRouter()
+
+ // 设置路由 - 需要 Admin 或 SuperAdmin 角色
+ router.GET("/manage", CheckRole("Admin", "SuperAdmin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ // 测试具有 SuperAdmin 角色的用户
+ token := mockLoginWithRole("superuser", []string{"SuperAdmin"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/manage", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "success")
+}
+
+// TestCheckRole_NoToken 测试未提供 token 的情况
+func TestCheckRole_NoToken(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ w := ut.PerformRequest(router.Engine, "GET", "/admin", nil)
+
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
+ assert.Contains(t, w.Body.String(), "user not logged in")
+}
+
+// TestCheckRole_InvalidToken 测试无效 token 的情况
+func TestCheckRole_InvalidToken(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ w := ut.PerformRequest(router.Engine, "GET", "/admin", nil,
+ ut.Header{Key: "Authorization", Value: "invalid-token-12345"})
+
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
+ assert.Contains(t, w.Body.String(), "user not logged in")
+}
+
+// TestCheckPermission_WithValidPermission 测试具有有效权限的用户访问
+func TestCheckPermission_WithValidPermission(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/users", CheckPermission("user.read"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ token := mockLoginWithPermission("user789", []string{"user.read"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/users", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "success")
+}
+
+// TestCheckPermission_WithInvalidPermission 测试没有所需权限的用户访问
+func TestCheckPermission_WithInvalidPermission(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/users", CheckPermission("user.delete"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "success"})
+ })
+
+ token := mockLoginWithPermission("user789", []string{"user.read"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/users", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusForbidden, w.Code)
+ assert.Contains(t, w.Body.String(), "permission denied")
+}
+
+// TestCheckLogin_Success 测试登录检查成功
+func TestCheckLogin_Success(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/profile", CheckLogin(), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "profile data"})
+ })
+
+ token := mockLogin("user999")
+
+ w := ut.PerformRequest(router.Engine, "GET", "/profile", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "profile data")
+}
+
+// TestCheckLogin_Failed 测试登录检查失败
+func TestCheckLogin_Failed(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/profile", CheckLogin(), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "profile data"})
+ })
+
+ w := ut.PerformRequest(router.Engine, "GET", "/profile", nil)
+
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
+ assert.Contains(t, w.Body.String(), "user not logged in")
+}
+
+// TestCheckDisable_NotDisabled 测试账号未被封禁的情况
+func TestCheckDisable_NotDisabled(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/resource", CheckDisable(), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "resource data"})
+ })
+
+ token := mockLogin("user101")
+
+ w := ut.PerformRequest(router.Engine, "GET", "/resource", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "resource data")
+}
+
+// TestCheckDisable_IsDisabled 测试账号被封禁的情况
+// 注意:core 的 Disable() 会踢出该账号所有 token,封禁后原 token 已失效,故请求返回 401 而非 403
+func TestCheckDisable_IsDisabled(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/resource", CheckDisable(), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "resource data"})
+ })
+
+ loginID := "user102"
+ token := mockLogin(loginID)
+
+ // 封禁账号(会同时踢出该账号所有 token)
+ stputil.Disable(loginID, 3600) // 封禁 1 小时
+
+ w := ut.PerformRequest(router.Engine, "GET", "/resource", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ // 封禁后 token 已被踢下线,请求得到 401
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
+ assert.Contains(t, w.Body.String(), "user not logged in")
+}
+
+// TestIgnore_SkipsAuthentication 测试忽略认证装饰器
+func TestIgnore_SkipsAuthentication(t *testing.T) {
+ router := setupTestRouter()
+
+ router.GET("/public", Ignore(), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "public data"})
+ })
+
+ w := ut.PerformRequest(router.Engine, "GET", "/public", nil)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "public data")
+}
+
+// TestChainedMiddleware_CheckRoleAndHandler 测试链式中间件:CheckRole + 实际处理器
+func TestChainedMiddleware_CheckRoleAndHandler(t *testing.T) {
+ router := setupTestRouter()
+
+ // 模拟用户示例代码的使用方式
+ safeGroup := router.Group("/safe")
+ {
+ safeGroup.GET("", CheckRole("SuperAdmin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "safe settings"})
+ })
+ }
+
+ // 测试具有 SuperAdmin 角色的用户
+ token := mockLoginWithRole("admin123", []string{"SuperAdmin"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/safe", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "safe settings")
+}
+
+// TestChainedMiddleware_CheckRoleAndHandler_NoRole 测试链式中间件:无角色访问
+func TestChainedMiddleware_CheckRoleAndHandler_NoRole(t *testing.T) {
+ router := setupTestRouter()
+
+ safeGroup := router.Group("/safe")
+ {
+ safeGroup.GET("", CheckRole("SuperAdmin"), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "safe settings"})
+ })
+ }
+
+ // 测试具有普通用户角色
+ token := mockLoginWithRole("user123", []string{"User"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/safe", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusForbidden, w.Code)
+ assert.Contains(t, w.Body.String(), "role denied")
+}
+
+// TestGetHandler_WithNilHandler 测试 GetHandler 在 handler 为 nil 时的行为
+func TestGetHandler_WithNilHandler(t *testing.T) {
+ router := setupTestRouter()
+
+ // 直接使用 GetHandler 创建中间件
+ middleware := GetHandler(nil, &Annotation{CheckRole: []string{"Admin"}})
+
+ router.GET("/test", middleware, func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"message": "test passed"})
+ })
+
+ token := mockLoginWithRole("testuser", []string{"Admin"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/test", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ // 应该能够正常执行,不会 panic
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "test passed")
+}
+
+// TestMiddleware_CheckRole 测试 Middleware 函数的角色检查
+func TestMiddleware_CheckRole(t *testing.T) {
+ router := setupTestRouter()
+
+ // 使用 Middleware 函数
+ router.GET("/api/data", Middleware(&Annotation{CheckRole: []string{"Admin"}}), func(ctx context.Context, c *app.RequestContext) {
+ c.JSON(http.StatusOK, utils.H{"data": "sensitive data"})
+ })
+
+ token := mockLoginWithRole("admin999", []string{"Admin"})
+
+ w := ut.PerformRequest(router.Engine, "GET", "/api/data", nil,
+ ut.Header{Key: "Authorization", Value: token})
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "sensitive data")
+}
+
+// TestParseTag 测试标签解析功能
+func TestParseTag(t *testing.T) {
+ tests := []struct {
+ name string
+ tag string
+ expected *Annotation
+ }{
+ {
+ name: "解析登录检查标签",
+ tag: "sa_check_login",
+ expected: &Annotation{
+ CheckLogin: true,
+ },
+ },
+ {
+ name: "解析角色检查标签",
+ tag: "sa_check_role=Admin|SuperAdmin",
+ expected: &Annotation{
+ CheckRole: []string{"Admin", "SuperAdmin"},
+ },
+ },
+ {
+ name: "解析权限检查标签",
+ tag: "sa_check_permission=user.read|user.write",
+ expected: &Annotation{
+ CheckPermission: []string{"user.read", "user.write"},
+ },
+ },
+ {
+ name: "解析忽略认证标签",
+ tag: "sa_ignore",
+ expected: &Annotation{
+ Ignore: true,
+ },
+ },
+ {
+ name: "解析封禁检查标签",
+ tag: "sa_check_disable",
+ expected: &Annotation{
+ CheckDisable: true,
+ },
+ },
+ {
+ name: "空标签",
+ tag: "",
+ expected: &Annotation{},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := ParseTag(tt.tag)
+ assert.Equal(t, tt.expected.CheckLogin, result.CheckLogin)
+ assert.Equal(t, tt.expected.CheckRole, result.CheckRole)
+ assert.Equal(t, tt.expected.CheckPermission, result.CheckPermission)
+ assert.Equal(t, tt.expected.CheckDisable, result.CheckDisable)
+ assert.Equal(t, tt.expected.Ignore, result.Ignore)
+ })
+ }
+}
+
+// TestAnnotationValidate 测试注解验证功能
+func TestAnnotationValidate(t *testing.T) {
+ tests := []struct {
+ name string
+ annotation *Annotation
+ valid bool
+ }{
+ {
+ name: "有效的单一检查",
+ annotation: &Annotation{
+ CheckLogin: true,
+ },
+ valid: true,
+ },
+ {
+ name: "有效的忽略标记",
+ annotation: &Annotation{
+ Ignore: true,
+ CheckLogin: true, // 即使有其他标记,忽略时仍然有效
+ },
+ valid: true,
+ },
+ {
+ name: "有效的空注解",
+ annotation: &Annotation{},
+ valid: true,
+ },
+ {
+ name: "无效的多重检查",
+ annotation: &Annotation{
+ CheckLogin: true,
+ CheckRole: []string{"Admin"},
+ },
+ valid: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.annotation.Validate()
+ assert.Equal(t, tt.valid, result)
+ })
+ }
+}
diff --git a/integrations/hertz/context.go b/integrations/hertz/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b3a29b5ba49464347b1ac38a1047417af16d0d4
--- /dev/null
+++ b/integrations/hertz/context.go
@@ -0,0 +1,165 @@
+package hertz
+
+import (
+ "github.com/click33/sa-token-go/core/adapter"
+ "github.com/cloudwego/hertz/pkg/app"
+ "github.com/cloudwego/hertz/pkg/protocol"
+)
+
+// HertzContext Hertz request context adapter | Hertz 请求上下文适配器
+type HertzContext struct {
+ c *app.RequestContext
+ aborted bool
+}
+
+// NewHertzContext creates a Hertz context adapter | 创建Hertz上下文适配器
+func NewHertzContext(c *app.RequestContext) adapter.RequestContext {
+ return &HertzContext{c: c}
+}
+
+// GetHeader gets request header | 获取请求头
+func (h *HertzContext) GetHeader(key string) string {
+ return string(h.c.GetHeader(key))
+}
+
+// GetQuery gets query parameter | 获取查询参数
+func (h *HertzContext) GetQuery(key string) string {
+ return h.c.Query(key)
+}
+
+// GetCookie gets cookie | 获取Cookie
+func (h *HertzContext) GetCookie(key string) string {
+ cookie := h.c.Cookie(key)
+ return string(cookie)
+}
+
+// SetHeader sets response header | 设置响应头
+func (h *HertzContext) SetHeader(key, value string) {
+ h.c.Header(key, value)
+}
+
+// SetCookie sets cookie | 设置Cookie
+func (h *HertzContext) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
+ h.c.SetCookie(name, value, maxAge, path, domain, protocol.CookieSameSiteLaxMode, secure, httpOnly)
+}
+
+// GetClientIP gets client IP address | 获取客户端IP地址
+func (h *HertzContext) GetClientIP() string {
+ return h.c.ClientIP()
+}
+
+// GetMethod gets request method | 获取请求方法
+func (h *HertzContext) GetMethod() string {
+ return string(h.c.Method())
+}
+
+// GetPath gets request path | 获取请求路径
+func (h *HertzContext) GetPath() string {
+ return string(h.c.Path())
+}
+
+// Set sets context value | 设置上下文值
+func (h *HertzContext) Set(key string, value interface{}) {
+ h.c.Set(key, value)
+}
+
+// Get gets context value | 获取上下文值
+func (h *HertzContext) Get(key string) (interface{}, bool) {
+ return h.c.Get(key)
+}
+
+// ============ Additional Required Methods | 额外必需的方法 ============
+
+// GetHeaders implements adapter.RequestContext.
+func (h *HertzContext) GetHeaders() map[string][]string {
+ headers := make(map[string][]string)
+ h.c.Request.Header.VisitAll(func(key, value []byte) {
+ headers[string(key)] = []string{string(value)}
+ })
+ return headers
+}
+
+// GetQueryAll implements adapter.RequestContext.
+func (h *HertzContext) GetQueryAll() map[string][]string {
+ params := make(map[string][]string)
+ h.c.QueryArgs().VisitAll(func(key, value []byte) {
+ params[string(key)] = []string{string(value)}
+ })
+ return params
+}
+
+// GetPostForm implements adapter.RequestContext.
+func (h *HertzContext) GetPostForm(key string) string {
+ return h.c.PostForm(key)
+}
+
+// GetBody implements adapter.RequestContext.
+func (h *HertzContext) GetBody() ([]byte, error) {
+ return h.c.GetRawData(), nil
+}
+
+// GetURL implements adapter.RequestContext.
+func (h *HertzContext) GetURL() string {
+ return string(h.c.Request.URI().RequestURI())
+}
+
+// GetUserAgent implements adapter.RequestContext.
+func (h *HertzContext) GetUserAgent() string {
+ return string(h.c.UserAgent())
+}
+
+// SetCookieWithOptions implements adapter.RequestContext.
+func (h *HertzContext) SetCookieWithOptions(options *adapter.CookieOptions) {
+ // Set SameSite attribute
+ var sameSite protocol.CookieSameSite
+ switch options.SameSite {
+ case "Strict":
+ sameSite = protocol.CookieSameSiteStrictMode
+ case "Lax":
+ sameSite = protocol.CookieSameSiteLaxMode
+ case "None":
+ sameSite = protocol.CookieSameSiteNoneMode
+ }
+ h.c.SetCookie(
+ options.Name,
+ options.Value,
+ options.MaxAge,
+ options.Path,
+ options.Domain,
+ sameSite,
+ options.Secure,
+ options.HttpOnly,
+ )
+}
+
+// GetString implements adapter.RequestContext.
+func (h *HertzContext) GetString(key string) string {
+ value, exists := h.c.Get(key)
+ if !exists {
+ return ""
+ }
+ if str, ok := value.(string); ok {
+ return str
+ }
+ return ""
+}
+
+// MustGet implements adapter.RequestContext.
+func (h *HertzContext) MustGet(key string) any {
+ value, exists := h.c.Get(key)
+ if !exists {
+ panic("key not found: " + key)
+ }
+ return value
+}
+
+// Abort implements adapter.RequestContext.
+func (h *HertzContext) Abort() {
+ h.aborted = true
+ h.c.Abort()
+}
+
+// IsAborted implements adapter.RequestContext.
+func (h *HertzContext) IsAborted() bool {
+ return h.aborted
+}
diff --git a/integrations/hertz/export.go b/integrations/hertz/export.go
new file mode 100644
index 0000000000000000000000000000000000000000..ee2241a4fb2c9cc8b97b6a56cd8b290eb90e5603
--- /dev/null
+++ b/integrations/hertz/export.go
@@ -0,0 +1,364 @@
+package hertz
+
+import (
+ "time"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/click33/sa-token-go/stputil"
+)
+
+// ============ Re-export core types | 重新导出核心类型 ============
+
+// Configuration related types | 配置相关类型
+type (
+ Config = core.Config
+ CookieConfig = core.CookieConfig
+ TokenStyle = core.TokenStyle
+)
+
+// Token style constants | Token风格常量
+const (
+ TokenStyleUUID = core.TokenStyleUUID
+ TokenStyleSimple = core.TokenStyleSimple
+ TokenStyleRandom32 = core.TokenStyleRandom32
+ TokenStyleRandom64 = core.TokenStyleRandom64
+ TokenStyleRandom128 = core.TokenStyleRandom128
+ TokenStyleJWT = core.TokenStyleJWT
+ TokenStyleHash = core.TokenStyleHash
+ TokenStyleTimestamp = core.TokenStyleTimestamp
+ TokenStyleTik = core.TokenStyleTik
+)
+
+// Core types | 核心类型
+type (
+ Manager = core.Manager
+ TokenInfo = core.TokenInfo
+ Session = core.Session
+ TokenGenerator = core.TokenGenerator
+ SaTokenContext = core.SaTokenContext
+ Builder = core.Builder
+ NonceManager = core.NonceManager
+ RefreshTokenInfo = core.RefreshTokenInfo
+ RefreshTokenManager = core.RefreshTokenManager
+ OAuth2Server = core.OAuth2Server
+ OAuth2Client = core.OAuth2Client
+ OAuth2AccessToken = core.OAuth2AccessToken
+ OAuth2GrantType = core.OAuth2GrantType
+)
+
+// Adapter interfaces | 适配器接口
+type (
+ Storage = core.Storage
+ RequestContext = core.RequestContext
+)
+
+// Event related types | 事件相关类型
+type (
+ EventListener = core.EventListener
+ EventManager = core.EventManager
+ EventData = core.EventData
+ Event = core.Event
+ ListenerFunc = core.ListenerFunc
+ ListenerConfig = core.ListenerConfig
+)
+
+// Event constants | 事件常量
+const (
+ EventLogin = core.EventLogin
+ EventLogout = core.EventLogout
+ EventKickout = core.EventKickout
+ EventDisable = core.EventDisable
+ EventUntie = core.EventUntie
+ EventRenew = core.EventRenew
+ EventCreateSession = core.EventCreateSession
+ EventDestroySession = core.EventDestroySession
+ EventPermissionCheck = core.EventPermissionCheck
+ EventRoleCheck = core.EventRoleCheck
+ EventAll = core.EventAll
+)
+
+// OAuth2 grant type constants | OAuth2授权类型常量
+const (
+ GrantTypeAuthorizationCode = core.GrantTypeAuthorizationCode
+ GrantTypeRefreshToken = core.GrantTypeRefreshToken
+ GrantTypeClientCredentials = core.GrantTypeClientCredentials
+ GrantTypePassword = core.GrantTypePassword
+)
+
+// Utility functions | 工具函数
+var (
+ RandomString = core.RandomString
+ IsEmpty = core.IsEmpty
+ IsNotEmpty = core.IsNotEmpty
+ DefaultString = core.DefaultString
+ ContainsString = core.ContainsString
+ RemoveString = core.RemoveString
+ UniqueStrings = core.UniqueStrings
+ MergeStrings = core.MergeStrings
+ MatchPattern = core.MatchPattern
+)
+
+// ============ Core constructor functions | 核心构造函数 ============
+
+// DefaultConfig returns default configuration | 返回默认配置
+func DefaultConfig() *Config {
+ return core.DefaultConfig()
+}
+
+// NewManager creates a new authentication manager | 创建新的认证管理器
+func NewManager(storage Storage, cfg *Config) *Manager {
+ return core.NewManager(storage, cfg)
+}
+
+// NewContext creates a new Sa-Token context | 创建新的Sa-Token上下文
+func NewContext(ctx RequestContext, mgr *Manager) *SaTokenContext {
+ return core.NewContext(ctx, mgr)
+}
+
+// NewSession creates a new session | 创建新的Session
+func NewSession(id string, storage Storage, prefix string) *Session {
+ return core.NewSession(id, storage, prefix)
+}
+
+// LoadSession loads an existing session | 加载已存在的Session
+func LoadSession(id string, storage Storage, prefix string) (*Session, error) {
+ return core.LoadSession(id, storage, prefix)
+}
+
+// NewTokenGenerator creates a new token generator | 创建新的Token生成器
+func NewTokenGenerator(cfg *Config) *TokenGenerator {
+ return core.NewTokenGenerator(cfg)
+}
+
+// NewEventManager creates a new event manager | 创建新的事件管理器
+func NewEventManager() *EventManager {
+ return core.NewEventManager()
+}
+
+// NewBuilder creates a new builder for fluent configuration | 创建新的Builder构建器(用于流式配置)
+func NewBuilder() *Builder {
+ return core.NewBuilder()
+}
+
+// NewNonceManager creates a new nonce manager | 创建新的Nonce管理器
+func NewNonceManager(storage Storage, prefix string, ttl ...int64) *NonceManager {
+ return core.NewNonceManager(storage, prefix, ttl...)
+}
+
+// NewRefreshTokenManager creates a new refresh token manager | 创建新的刷新令牌管理器
+func NewRefreshTokenManager(storage Storage, prefix string, cfg *Config) *RefreshTokenManager {
+ return core.NewRefreshTokenManager(storage, prefix, cfg)
+}
+
+// NewOAuth2Server creates a new OAuth2 server | 创建新的OAuth2服务器
+func NewOAuth2Server(storage Storage, prefix string) *OAuth2Server {
+ return core.NewOAuth2Server(storage, prefix)
+}
+
+// ============ Global StpUtil functions | 全局StpUtil函数 ============
+
+// SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法)
+func SetManager(mgr *Manager) {
+ stputil.SetManager(mgr)
+}
+
+// GetManager gets the global Manager | 获取全局Manager
+func GetManager() *Manager {
+ return stputil.GetManager()
+}
+
+// ============ Authentication | 登录认证 ============
+
+// Login performs user login | 用户登录
+func Login(loginID interface{}, device ...string) (string, error) {
+ return stputil.Login(loginID, device...)
+}
+
+// LoginByToken performs login with specified token | 使用指定Token登录
+func LoginByToken(loginID interface{}, tokenValue string, device ...string) error {
+ return stputil.LoginByToken(loginID, tokenValue, device...)
+}
+
+// Logout performs user logout | 用户登出
+func Logout(loginID interface{}, device ...string) error {
+ return stputil.Logout(loginID, device...)
+}
+
+// LogoutByToken performs logout by token | 根据Token登出
+func LogoutByToken(tokenValue string) error {
+ return stputil.LogoutByToken(tokenValue)
+}
+
+// IsLogin checks if the user is logged in | 检查用户是否已登录
+func IsLogin(tokenValue string) bool {
+ return stputil.IsLogin(tokenValue)
+}
+
+// CheckLoginByToken checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误)
+func CheckLoginByToken(tokenValue string) error {
+ return stputil.CheckLogin(tokenValue)
+}
+
+// GetLoginID gets the login ID from token | 从Token获取登录ID
+func GetLoginID(tokenValue string) (string, error) {
+ return stputil.GetLoginID(tokenValue)
+}
+
+// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查)
+func GetLoginIDNotCheck(tokenValue string) (string, error) {
+ return stputil.GetLoginIDNotCheck(tokenValue)
+}
+
+// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值
+func GetTokenValue(loginID interface{}, device ...string) (string, error) {
+ return stputil.GetTokenValue(loginID, device...)
+}
+
+// GetTokenInfo gets token information | 获取Token信息
+func GetTokenInfo(tokenValue string) (*TokenInfo, error) {
+ return stputil.GetTokenInfo(tokenValue)
+}
+
+// ============ Kickout | 踢人下线 ============
+
+// Kickout kicks out a user session | 踢人下线
+func Kickout(loginID interface{}, device ...string) error {
+ return stputil.Kickout(loginID, device...)
+}
+
+// ============ Account Disable | 账号封禁 ============
+
+// Disable disables an account for specified duration | 封禁账号(指定时长)
+func Disable(loginID interface{}, duration time.Duration) error {
+ return stputil.Disable(loginID, duration)
+}
+
+// IsDisable checks if an account is disabled | 检查账号是否被封禁
+func IsDisable(loginID interface{}) bool {
+ return stputil.IsDisable(loginID)
+}
+
+// CheckDisableByToken checks if account is disabled (throws error if disabled) | 检查Token对应账号是否被封禁(被封禁则抛出错误)
+func CheckDisableByToken(tokenValue string) error {
+ return stputil.CheckDisable(tokenValue)
+}
+
+// GetDisableTime gets remaining disabled time | 获取账号剩余封禁时间
+func GetDisableTime(loginID interface{}) (int64, error) {
+ return stputil.GetDisableTime(loginID)
+}
+
+// Untie unties/unlocks an account | 解除账号封禁
+func Untie(loginID interface{}) error {
+ return stputil.Untie(loginID)
+}
+
+// ============ Permission Check | 权限验证 ============
+
+// CheckPermissionByToken checks if the token has specified permission | 检查Token是否拥有指定权限
+func CheckPermissionByToken(tokenValue string, permission string) error {
+ return stputil.CheckPermission(tokenValue, permission)
+}
+
+// HasPermission checks if the account has specified permission (returns bool) | 检查账号是否拥有指定权限(返回布尔值)
+func HasPermission(loginID interface{}, permission string) bool {
+ return stputil.HasPermission(loginID, permission)
+}
+
+// CheckPermissionAndByToken checks if the token has all specified permissions (AND logic) | 检查Token是否拥有所有指定权限(AND逻辑)
+func CheckPermissionAndByToken(tokenValue string, permissions []string) error {
+ return stputil.CheckPermissionAnd(tokenValue, permissions)
+}
+
+// CheckPermissionOrByToken checks if the token has any of the specified permissions (OR logic) | 检查Token是否拥有指定权限中的任意一个(OR逻辑)
+func CheckPermissionOrByToken(tokenValue string, permissions []string) error {
+ return stputil.CheckPermissionOr(tokenValue, permissions)
+}
+
+// GetPermissionListByToken gets the permission list for a token | 获取Token的权限列表
+func GetPermissionListByToken(tokenValue string) ([]string, error) {
+ return stputil.GetPermissionList(tokenValue)
+}
+
+// ============ Role Check | 角色验证 ============
+
+// CheckRoleByToken checks if the token has specified role | 检查Token是否拥有指定角色
+func CheckRoleByToken(tokenValue string, role string) error {
+ return stputil.CheckRole(tokenValue, role)
+}
+
+// HasRole checks if the account has specified role (returns bool) | 检查账号是否拥有指定角色(返回布尔值)
+func HasRole(loginID interface{}, role string) bool {
+ return stputil.HasRole(loginID, role)
+}
+
+// CheckRoleAndByToken checks if the token has all specified roles (AND logic) | 检查Token是否拥有所有指定角色(AND逻辑)
+func CheckRoleAndByToken(tokenValue string, roles []string) error {
+ return stputil.CheckRoleAnd(tokenValue, roles)
+}
+
+// CheckRoleOrByToken checks if the token has any of the specified roles (OR logic) | 检查Token是否拥有指定角色中的任意一个(OR逻辑)
+func CheckRoleOrByToken(tokenValue string, roles []string) error {
+ return stputil.CheckRoleOr(tokenValue, roles)
+}
+
+// GetRoleListByToken gets the role list for a token | 获取Token的角色列表
+func GetRoleListByToken(tokenValue string) ([]string, error) {
+ return stputil.GetRoleList(tokenValue)
+}
+
+// ============ Session Management | Session管理 ============
+
+// GetSession gets the session for a login ID | 获取登录ID的Session
+func GetSession(loginID interface{}) (*Session, error) {
+ return stputil.GetSession(loginID)
+}
+
+// GetSessionByToken gets the session by token | 根据Token获取Session
+func GetSessionByToken(tokenValue string) (*Session, error) {
+ return stputil.GetSessionByToken(tokenValue)
+}
+
+// GetTokenSession gets the token session | 获取Token的Session
+func GetTokenSession(tokenValue string) (*Session, error) {
+ return stputil.GetTokenSession(tokenValue)
+}
+
+// ============ Token Renewal | Token续期 ============
+// Note: Token auto-renewal is handled automatically by the manager
+// 注意:Token自动续期由管理器自动处理
+
+// ============ Security Features | 安全特性 ============
+
+// GenerateNonce generates a new nonce token | 生成新的Nonce令牌
+func GenerateNonce() (string, error) {
+ return stputil.GenerateNonce()
+}
+
+// VerifyNonce verifies a nonce token | 验证Nonce令牌
+func VerifyNonce(nonce string) bool {
+ return stputil.VerifyNonce(nonce)
+}
+
+// LoginWithRefreshToken performs login and returns refresh token info | 登录并返回刷新令牌信息
+func LoginWithRefreshToken(loginID interface{}, device ...string) (*RefreshTokenInfo, error) {
+ return stputil.LoginWithRefreshToken(loginID, device...)
+}
+
+// RefreshAccessToken refreshes the access token using a refresh token | 使用刷新令牌刷新访问令牌
+func RefreshAccessToken(refreshToken string) (*RefreshTokenInfo, error) {
+ return stputil.RefreshAccessToken(refreshToken)
+}
+
+// RevokeRefreshToken revokes a refresh token | 撤销刷新令牌
+func RevokeRefreshToken(refreshToken string) error {
+ return stputil.RevokeRefreshToken(refreshToken)
+}
+
+// GetOAuth2Server gets the OAuth2 server instance | 获取OAuth2服务器实例
+func GetOAuth2Server() *OAuth2Server {
+ return stputil.GetOAuth2Server()
+}
+
+// Version Sa-Token-Go version | Sa-Token-Go版本
+const Version = core.Version
diff --git a/integrations/hertz/go.mod b/integrations/hertz/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..97a4f8a48d0367e61d6666ee3775b5dee08a62a9
--- /dev/null
+++ b/integrations/hertz/go.mod
@@ -0,0 +1,12 @@
+module github.com/click33/sa-token-go/integrations/hertz
+
+go 1.25
+
+toolchain go1.24.1
+
+require (
+ github.com/click33/sa-token-go/core v0.1.8 // indirect
+ github.com/click33/sa-token-go/stputil v0.1.8 // indirect
+ github.com/cloudwego/hertz v0.10.3 // indirect
+ github.com/stretchr/testify v1.11.1 // indirect
+)
diff --git a/integrations/hertz/go.sum b/integrations/hertz/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..34b4104c47e809e3addb377c6f5b2f0fb5418ce1
--- /dev/null
+++ b/integrations/hertz/go.sum
@@ -0,0 +1,4 @@
+github.com/click33/sa-token-go/core v0.1.7/go.mod h1:mb3AQAJIXqx9WdULyn5qjufK1j/u+kgB0q+tafHVhgk=
+github.com/click33/sa-token-go/stputil v0.1.7/go.mod h1:YY4NzfwVMwPUQLDBk9C5eVLQ08oI3vNSFQhBuZBPtgY=
+github.com/cloudwego/hertz v0.10.3/go.mod h1:W5dUFXZPZkyfjMMo3EQrMQbofuvTsctM9IxmhbkuT18=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
diff --git a/integrations/hertz/plugin.go b/integrations/hertz/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..6a9a55e7d79b7ea957e3123154d9d18590d459a4
--- /dev/null
+++ b/integrations/hertz/plugin.go
@@ -0,0 +1,287 @@
+package hertz
+
+import (
+ "context"
+ "errors"
+ "net/http"
+
+ "github.com/click33/sa-token-go/core"
+ "github.com/cloudwego/hertz/pkg/app"
+ "github.com/cloudwego/hertz/pkg/common/utils"
+ "github.com/cloudwego/hertz/pkg/protocol"
+)
+
+// Plugin Hertz plugin for Sa-Token | Hertz插件
+type Plugin struct {
+ manager *core.Manager
+}
+
+// NewPlugin creates a Hertz plugin | 创建Hertz插件
+func NewPlugin(manager *core.Manager) *Plugin {
+ return &Plugin{
+ manager: manager,
+ }
+}
+
+// AuthMiddleware authentication middleware | 认证中间件
+func (p *Plugin) AuthMiddleware() app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+
+ // Check login | 检查登录
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(c, err)
+ c.Abort()
+ return
+ }
+
+ // Store Sa-Token context in Hertz context | 将Sa-Token上下文存储到Hertz上下文
+ c.Set("satoken", saCtx)
+ c.Next(ctx)
+ }
+}
+
+// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件
+func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ path := string(c.Path())
+ token := string(c.GetHeader(p.manager.GetConfig().TokenName))
+ if token == "" {
+ token = string(c.Cookie(p.manager.GetConfig().TokenName))
+ }
+
+ result := core.ProcessAuth(path, token, config, p.manager)
+
+ if result.ShouldReject() {
+ writeErrorResponse(c, core.NewPathAuthRequiredError(path))
+ c.Abort()
+ return
+ }
+
+ if result.IsValid && result.TokenInfo != nil {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+ c.Set("satoken", saCtx)
+ c.Set("loginID", result.LoginID())
+ }
+
+ c.Next(ctx)
+ }
+}
+
+// PermissionRequired permission validation middleware | 权限验证中间件
+func (p *Plugin) PermissionRequired(permission string) app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+
+ // Check login | 检查登录
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(c, err)
+ c.Abort()
+ return
+ }
+
+ // Check permission | 检查权限
+ if !saCtx.HasPermission(permission) {
+ writeErrorResponse(c, core.NewPermissionDeniedError(permission))
+ c.Abort()
+ return
+ }
+
+ c.Set("satoken", saCtx)
+ c.Next(ctx)
+ }
+}
+
+// RoleRequired role validation middleware | 角色验证中间件
+func (p *Plugin) RoleRequired(role string) app.HandlerFunc {
+ return func(ctx context.Context, c *app.RequestContext) {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+
+ // Check login | 检查登录
+ if err := saCtx.CheckLogin(); err != nil {
+ writeErrorResponse(c, err)
+ c.Abort()
+ return
+ }
+
+ // Check role | 检查角色
+ if !saCtx.HasRole(role) {
+ writeErrorResponse(c, core.NewRoleDeniedError(role))
+ c.Abort()
+ return
+ }
+
+ c.Set("satoken", saCtx)
+ c.Next(ctx)
+ }
+}
+
+// LoginHandler login handler example | 登录处理器示例
+func (p *Plugin) LoginHandler(c *app.RequestContext) {
+ var req struct {
+ Username string `json:"username" binding:"required"`
+ Password string `json:"password" binding:"required"`
+ Device string `json:"device"`
+ }
+
+ if err := c.BindJSON(&req); err != nil {
+ writeErrorResponse(c, core.NewError(core.CodeBadRequest, "invalid request parameters", err))
+ return
+ }
+
+ // Login | 登录
+ device := req.Device
+ if device == "" {
+ device = "default"
+ }
+
+ token, err := p.manager.Login(req.Username, device)
+ if err != nil {
+ writeErrorResponse(c, core.NewError(core.CodeServerError, "login failed", err))
+ return
+ }
+
+ // Set cookie (optional) | 设置Cookie(可选)
+ cfg := p.manager.GetConfig()
+ if cfg.IsReadCookie {
+ maxAge := int(cfg.Timeout)
+ if maxAge < 0 {
+ maxAge = 0
+ }
+ var sameSite protocol.CookieSameSite
+ switch cfg.CookieConfig.SameSite {
+ case "Strict":
+ sameSite = protocol.CookieSameSiteStrictMode
+ case "Lax":
+ sameSite = protocol.CookieSameSiteLaxMode
+ case "None":
+ sameSite = protocol.CookieSameSiteNoneMode
+ }
+ c.SetCookie(
+ cfg.TokenName,
+ token,
+ maxAge,
+ cfg.CookieConfig.Path,
+ cfg.CookieConfig.Domain,
+ sameSite,
+ cfg.CookieConfig.Secure,
+ cfg.CookieConfig.HttpOnly,
+ )
+ }
+
+ writeSuccessResponse(c, utils.H{
+ "token": token,
+ })
+}
+
+// LogoutHandler logout handler | 登出处理器
+func (p *Plugin) LogoutHandler(c *app.RequestContext) {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+
+ loginID, err := saCtx.GetLoginID()
+ if err != nil {
+ writeErrorResponse(c, err)
+ return
+ }
+
+ if err := p.manager.Logout(loginID); err != nil {
+ writeErrorResponse(c, core.NewError(core.CodeServerError, "logout failed", err))
+ return
+ }
+
+ writeSuccessResponse(c, utils.H{
+ "message": "logout successful",
+ })
+}
+
+// UserInfoHandler user info handler example | 获取用户信息处理器示例
+func (p *Plugin) UserInfoHandler(c *app.RequestContext) {
+ hCtx := NewHertzContext(c)
+ saCtx := core.NewContext(hCtx, p.manager)
+
+ loginID, err := saCtx.GetLoginID()
+ if err != nil {
+ writeErrorResponse(c, err)
+ return
+ }
+
+ // Get user permissions and roles | 获取用户权限和角色
+ permissions, _ := p.manager.GetPermissions(loginID)
+ roles, _ := p.manager.GetRoles(loginID)
+
+ writeSuccessResponse(c, utils.H{
+ "loginId": loginID,
+ "permissions": permissions,
+ "roles": roles,
+ })
+}
+
+// GetSaToken gets Sa-Token context from Hertz context | 从Hertz上下文获取Sa-Token上下文
+func GetSaToken(c *app.RequestContext) (*core.SaTokenContext, bool) {
+ satoken, exists := c.Get("satoken")
+ if !exists {
+ return nil, false
+ }
+ ctx, ok := satoken.(*core.SaTokenContext)
+ return ctx, ok
+}
+
+// ============ Error Handling Helpers | 错误处理辅助函数 ============
+
+// writeErrorResponse writes a standardized error response | 写入标准化的错误响应
+func writeErrorResponse(c *app.RequestContext, err error) {
+ var saErr *core.SaTokenError
+ var code int
+ var message string
+ var httpStatus int
+
+ // Check if it's a SaTokenError | 检查是否为SaTokenError
+ if errors.As(err, &saErr) {
+ code = saErr.Code
+ message = saErr.Message
+ httpStatus = getHTTPStatusFromCode(code)
+ } else {
+ // Handle standard errors | 处理标准错误
+ code = core.CodeServerError
+ message = err.Error()
+ httpStatus = http.StatusInternalServerError
+ }
+
+ c.JSON(httpStatus, utils.H{
+ "code": code,
+ "message": message,
+ "error": err.Error(),
+ })
+}
+
+// writeSuccessResponse writes a standardized success response | 写入标准化的成功响应
+func writeSuccessResponse(c *app.RequestContext, data interface{}) {
+ c.JSON(http.StatusOK, utils.H{
+ "code": core.CodeSuccess,
+ "message": "success",
+ "data": data,
+ })
+}
+
+// getHTTPStatusFromCode converts Sa-Token error code to HTTP status | 将Sa-Token错误码转换为HTTP状态码
+func getHTTPStatusFromCode(code int) int {
+ switch code {
+ case core.CodeNotLogin:
+ return http.StatusUnauthorized
+ case core.CodePermissionDenied:
+ return http.StatusForbidden
+ case core.CodeBadRequest:
+ return http.StatusBadRequest
+ case core.CodeNotFound:
+ return http.StatusNotFound
+ case core.CodeServerError:
+ return http.StatusInternalServerError
+ default:
+ return http.StatusInternalServerError
+ }
+}
diff --git a/integrations/kratos/checker_test.go b/integrations/kratos/checker_test.go
index 7c231b137c324890b8813f7b336533f5e1bf9fca..2ab1d2be915ccd77305112bf4da85548807adb66 100644
--- a/integrations/kratos/checker_test.go
+++ b/integrations/kratos/checker_test.go
@@ -259,7 +259,7 @@ func TestCustomChecker(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- checker := &CustomChecker{name: "custom", fn: tt.fn}
+ checker := &CustomChecker{fn: tt.fn}
err := checker.Check(ctx, mgr, loginID)
if (err != nil) != tt.wantErr {
t.Errorf("CustomChecker.Check() error = %v, wantErr %v", err, tt.wantErr)
@@ -380,7 +380,7 @@ func TestCheckerConstructors(t *testing.T) {
}
// Test NewCustomChecker
- if c := NewCustomChecker("test", func(ctx context.Context, manager *manager.Manager, loginID string) error {
+ if c := NewCustomChecker(func(ctx context.Context, manager *manager.Manager, loginID string) error {
return nil
}); c == nil {
t.Error("NewCustomChecker() should return non-nil")
diff --git a/integrations/kratos/go.mod b/integrations/kratos/go.mod
index ee423cc2094d9c628546032f1a3159c1f13ecdf0..1762a7ea7a6a7709f240cb4a27da79b3625e90dc 100644
--- a/integrations/kratos/go.mod
+++ b/integrations/kratos/go.mod
@@ -5,9 +5,9 @@ go 1.24.0
toolchain go1.24.1
require (
- github.com/click33/sa-token-go/core v0.1.5
- github.com/click33/sa-token-go/storage/memory v0.1.5
- github.com/click33/sa-token-go/stputil v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
+ github.com/click33/sa-token-go/storage/memory v0.1.8
+ github.com/click33/sa-token-go/stputil v0.1.8
github.com/go-kratos/kratos/v2 v2.9.1
)
diff --git a/integrations/kratos/options.go b/integrations/kratos/options.go
index 971c0d893e866250cce11db5705e948690c2651c..b8e3f62538c883db3fd04221794f7409f52e8e67 100644
--- a/integrations/kratos/options.go
+++ b/integrations/kratos/options.go
@@ -2,9 +2,9 @@ package kratos
import (
"context"
- "github.com/click33/sa-token-go/core"
"net/http"
+ "github.com/click33/sa-token-go/core"
"github.com/go-kratos/kratos/v2/errors"
)
diff --git a/integrations/kratos/plugin.go b/integrations/kratos/plugin.go
index 76db2c4704e854b77b2741198408af0243b5caab..b28332f6bba3a18f49322fcceeb39e2b42f0c2ae 100644
--- a/integrations/kratos/plugin.go
+++ b/integrations/kratos/plugin.go
@@ -2,6 +2,7 @@ package kratos
import (
"context"
+ "net/http"
"sort"
"strings"
@@ -49,7 +50,6 @@ func (e *Plugin) Server() middleware.Middleware {
return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
info, ok := transport.FromServerContext(ctx)
if !ok {
- // 无法获取传输层信息,直接放行
return handler(ctx, req)
}
@@ -90,6 +90,48 @@ func (e *Plugin) Server() middleware.Middleware {
}
}
+// PathAuthMiddleware 基于路径的鉴权中间件
+// 使用 Ant 风格通配符进行路径匹配
+func (e *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) middleware.Middleware {
+ return func(handler middleware.Handler) middleware.Handler {
+ return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+ info, ok := transport.FromServerContext(ctx)
+ if !ok {
+ return handler(ctx, req)
+ }
+
+ // 获取实际的 HTTP 路径
+ var path string
+ if htr, ok := info.(interface{ Request() *http.Request }); ok {
+ path = htr.Request().URL.Path
+ } else {
+ // 如果无法获取路径,使用 operation 作为后备
+ path = info.Operation()
+ }
+
+ // 获取 token
+ kratosContext := NewKratosContext(ctx)
+ saCtx := core.NewContext(kratosContext, e.manager)
+ token := saCtx.GetTokenValue()
+
+ // 处理路径鉴权
+ result := core.ProcessAuth(path, token, config, e.manager)
+
+ if result.ShouldReject() {
+ return nil, e.options.ErrorHandler(ctx, core.NewPathAuthRequiredError(path))
+ }
+
+ // 如果 token 有效,将相关信息存储到 context
+ if result.IsValid && result.TokenInfo != nil {
+ ctx = context.WithValue(ctx, "satoken", saCtx)
+ ctx = context.WithValue(ctx, "loginID", result.LoginID())
+ }
+
+ return handler(ctx, req)
+ }
+ }
+}
+
// ========== 规则构建器 ==========
// RuleBuilder 规则构建器(链式API)
diff --git a/storage/memory/go.mod b/storage/memory/go.mod
index ef4bdfc69c03f032ad098454a84b7b333f71fe27..0738598fbb87497a4e60b046368e90410f59e3e8 100644
--- a/storage/memory/go.mod
+++ b/storage/memory/go.mod
@@ -2,6 +2,6 @@ module github.com/click33/sa-token-go/storage/memory
go 1.23.0
-require github.com/click33/sa-token-go/core v0.1.5
+require github.com/click33/sa-token-go/core v0.1.8
replace github.com/click33/sa-token-go/core => ../../core
diff --git a/storage/redis/go.mod b/storage/redis/go.mod
index 4901b8104d1459d9b58fb8977ac51ea765b0aefc..84da4f69c5dd1c66d80e36e4607d6c94e53c1182 100644
--- a/storage/redis/go.mod
+++ b/storage/redis/go.mod
@@ -3,7 +3,7 @@ module github.com/click33/sa-token-go/storage/redis
go 1.23.0
require (
- github.com/click33/sa-token-go/core v0.1.5
+ github.com/click33/sa-token-go/core v0.1.8
github.com/redis/go-redis/v9 v9.5.1
)
diff --git a/stputil/go.mod b/stputil/go.mod
index 9f31aa952445ce3e4d222677e27d1c1568c15a09..af14660af3a6f40dc0cb9ab360844a7a0f072896 100644
--- a/stputil/go.mod
+++ b/stputil/go.mod
@@ -2,13 +2,13 @@ module github.com/click33/sa-token-go/stputil
go 1.23.0
-require github.com/click33/sa-token-go/core v0.1.5
+require github.com/click33/sa-token-go/core v0.1.8
require (
- github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/panjf2000/ants/v2 v2.11.3 // indirect
- golang.org/x/sync v0.16.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
)
replace github.com/click33/sa-token-go/core => ../core
diff --git a/stputil/go.sum b/stputil/go.sum
index dda2c2d233051a01e2f37412aff3d6dc1bf6f103..3b657b9d4f82342b5e59095fc97b1ea9c655f209 100644
--- a/stputil/go.sum
+++ b/stputil/go.sum
@@ -1,8 +1,20 @@
+github.com/click33/sa-token-go/storage/memory v0.1.6 h1:iGFEy+HtTJLOpKnbIMbgpXyKotsKpPQu6wWTZVOXQis=
+github.com/click33/sa-token-go/storage/memory v0.1.6/go.mod h1:YNojcgyLC/uFrmReZLePCDQ5WK2fo2WWGRjRMvXVH74=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
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/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/stputil/stplogic.go b/stputil/stplogic.go
new file mode 100644
index 0000000000000000000000000000000000000000..f75dcbd0acc79d1fe83d8ba8069bf37627c4c0c1
--- /dev/null
+++ b/stputil/stplogic.go
@@ -0,0 +1,379 @@
+package stputil
+
+import (
+ "fmt"
+ "github.com/click33/sa-token-go/core/manager"
+ "github.com/click33/sa-token-go/core/oauth2"
+ "github.com/click33/sa-token-go/core/security"
+ "github.com/click33/sa-token-go/core/session"
+ "sync"
+ "time"
+)
+
+var (
+ TokenValueKey = "stplogic:tokenvalue"
+ LoginIdKey = "stplogic:loginid"
+ PermissionsKey = "stplogic:permissions"
+ RolesKey = "stplogic:roles"
+)
+
+type StpLogic struct {
+ manager *manager.Manager
+ mu sync.RWMutex
+}
+
+func NewStpLogic(mrg *manager.Manager) *StpLogic {
+ return &StpLogic{manager: mrg}
+}
+
+// GetManager gets the global Manager | 获取全局Manager
+func (s *StpLogic) GetManager() *manager.Manager {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ if s.manager == nil {
+ panic("StpLogic not initialized.")
+ }
+ return s.manager
+}
+
+func (s *StpLogic) SetManager(manager *manager.Manager) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.manager = manager
+}
+
+// ============ Authentication | 登录认证 ============
+
+// Login performs user login | 用户登录
+func (s *StpLogic) Login(loginID interface{}, device ...string) (string, error) {
+ return s.manager.Login(toString(loginID), device...)
+}
+
+// LoginByToken performs login with specified token | 使用指定Token登录
+func (s *StpLogic) LoginByToken(loginID interface{}, tokenValue string, device ...string) error {
+ return s.manager.LoginByToken(toString(loginID), tokenValue, device...)
+}
+
+// Logout performs user logout | 用户登出
+func (s *StpLogic) Logout(loginID interface{}, device ...string) error {
+ return s.manager.Logout(toString(loginID), device...)
+}
+
+// LogoutByToken performs logout by token | 根据Token登出
+func (s *StpLogic) LogoutByToken(tokenValue string) error {
+ return s.manager.LogoutByToken(tokenValue)
+}
+
+// IsLogin checks if the user is logged in | 检查用户是否已登录
+func (s *StpLogic) IsLogin(tokenValue string) bool {
+ return s.manager.IsLogin(tokenValue)
+}
+
+// CheckLogin checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误)
+func (s *StpLogic) CheckLogin(tokenValue string) error {
+ return s.manager.CheckLogin(tokenValue)
+}
+
+// GetLoginID gets the login ID from token | 从Token获取登录ID
+func (s *StpLogic) GetLoginID(tokenValue string) (string, error) {
+ return s.manager.GetLoginID(tokenValue)
+}
+
+// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查)
+func (s *StpLogic) GetLoginIDNotCheck(tokenValue string) (string, error) {
+ return s.manager.GetLoginIDNotCheck(tokenValue)
+}
+
+// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值
+func (s *StpLogic) GetTokenValue(loginID interface{}, device ...string) (string, error) {
+ return s.manager.GetTokenValue(toString(loginID), device...)
+}
+
+// GetTokenInfo gets token information | 获取Token信息
+func (s *StpLogic) GetTokenInfo(tokenValue string) (*manager.TokenInfo, error) {
+ return s.manager.GetTokenInfo(tokenValue)
+}
+
+// ============ Kickout | 踢人下线 ============
+
+// Kickout kicks out a user session | 踢人下线
+func (s *StpLogic) Kickout(loginID interface{}, device ...string) error {
+ return s.manager.Kickout(toString(loginID), device...)
+}
+
+// ============ Account Disable | 账号封禁 ============
+
+// Disable disables an account for specified duration | 封禁账号(指定时长)
+func (s *StpLogic) Disable(loginID interface{}, duration time.Duration) error {
+ return s.manager.Disable(toString(loginID), duration)
+}
+
+// Untie re-enables a disabled account | 解封账号
+func (s *StpLogic) Untie(loginID interface{}) error {
+ return s.manager.Untie(toString(loginID))
+}
+
+// IsDisable checks if an account is disabled | 检查账号是否被封禁
+func (s *StpLogic) IsDisable(loginID interface{}) bool {
+ return s.manager.IsDisable(toString(loginID))
+}
+
+// GetDisableTime gets remaining disable time in seconds | 获取剩余封禁时间(秒)
+func (s *StpLogic) GetDisableTime(loginID interface{}) (int64, error) {
+ return s.manager.GetDisableTime(toString(loginID))
+}
+
+// ============ Session Management | Session管理 ============
+
+// GetSession gets session by login ID | 根据登录ID获取Session
+func (s *StpLogic) GetSession(loginID interface{}) (*session.Session, error) {
+ return s.manager.GetSession(toString(loginID))
+}
+
+// GetSessionByToken gets session by token | 根据Token获取Session
+func (s *StpLogic) GetSessionByToken(tokenValue string) (*session.Session, error) {
+ return s.manager.GetSessionByToken(tokenValue)
+}
+
+// DeleteSession deletes a session | 删除Session
+func (s *StpLogic) DeleteSession(loginID interface{}) error {
+ return s.manager.DeleteSession(toString(loginID))
+}
+
+// ============ Permission Verification | 权限验证 ============
+
+// SetPermissions sets permissions for a login ID | 设置用户权限
+func (s *StpLogic) SetPermissions(loginID interface{}, permissions []string) error {
+ return s.manager.SetPermissions(toString(loginID), permissions)
+}
+
+// GetPermissions gets permission list | 获取权限列表
+func (s *StpLogic) GetPermissions(loginID interface{}) ([]string, error) {
+ return s.manager.GetPermissions(toString(loginID))
+}
+
+// HasPermission checks if has specified permission | 检查是否拥有指定权限
+func (s *StpLogic) HasPermission(loginID interface{}, permission string) bool {
+ return s.manager.HasPermission(toString(loginID), permission)
+}
+
+// HasPermissionsAnd checks if has all permissions (AND logic) | 检查是否拥有所有权限(AND逻辑)
+func (s *StpLogic) HasPermissionsAnd(loginID interface{}, permissions []string) bool {
+ return s.manager.HasPermissionsAnd(toString(loginID), permissions)
+}
+
+// HasPermissionsOr checks if has any permission (OR logic) | 检查是否拥有任一权限(OR逻辑)
+func (s *StpLogic) HasPermissionsOr(loginID interface{}, permissions []string) bool {
+ return s.manager.HasPermissionsOr(toString(loginID), permissions)
+}
+
+// ============ Role Management | 角色管理 ============
+
+// SetRoles sets roles for a login ID | 设置用户角色
+func (s *StpLogic) SetRoles(loginID interface{}, roles []string) error {
+ return s.manager.SetRoles(toString(loginID), roles)
+}
+
+// GetRoles gets role list | 获取角色列表
+func (s *StpLogic) GetRoles(loginID interface{}) ([]string, error) {
+ return s.manager.GetRoles(toString(loginID))
+}
+
+// HasRole checks if has specified role | 检查是否拥有指定角色
+func (s *StpLogic) HasRole(loginID interface{}, role string) bool {
+ return s.manager.HasRole(toString(loginID), role)
+}
+
+// HasRolesAnd checks if has all roles (AND logic) | 检查是否拥有所有角色(AND逻辑)
+func (s *StpLogic) HasRolesAnd(loginID interface{}, roles []string) bool {
+ return s.manager.HasRolesAnd(toString(loginID), roles)
+}
+
+// HasRolesOr 检查是否拥有任一角色(OR)
+func (s *StpLogic) HasRolesOr(loginID interface{}, roles []string) bool {
+ return s.manager.HasRolesOr(toString(loginID), roles)
+}
+
+// ============ Token标签 ============
+
+// SetTokenTag 设置Token标签
+func (s *StpLogic) SetTokenTag(tokenValue, tag string) error {
+ return s.manager.SetTokenTag(tokenValue, tag)
+}
+
+// GetTokenTag 获取Token标签
+func (s *StpLogic) GetTokenTag(tokenValue string) (string, error) {
+ return s.manager.GetTokenTag(tokenValue)
+}
+
+// ============ 会话查询 ============
+
+// GetTokenValueList 获取指定账号的所有Token
+func (s *StpLogic) GetTokenValueList(loginID interface{}) ([]string, error) {
+ return s.manager.GetTokenValueListByLoginID(toString(loginID))
+}
+
+// GetSessionCount 获取指定账号的Session数量
+func (s *StpLogic) GetSessionCount(loginID interface{}) (int, error) {
+ return s.manager.GetSessionCountByLoginID(toString(loginID))
+}
+
+func (s *StpLogic) GenerateNonce() (string, error) {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ return s.manager.GenerateNonce()
+}
+
+func (s *StpLogic) VerifyNonce(nonce string) bool {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ return s.manager.VerifyNonce(nonce)
+}
+
+func (s *StpLogic) LoginWithRefreshToken(loginID interface{}, device ...string) (*security.RefreshTokenInfo, error) {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ deviceType := "default"
+ if len(device) > 0 {
+ deviceType = device[0]
+ }
+ return s.manager.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType)
+}
+
+func (s *StpLogic) RefreshAccessToken(refreshToken string) (*security.RefreshTokenInfo, error) {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ return s.manager.RefreshAccessToken(refreshToken)
+}
+
+func (s *StpLogic) RevokeRefreshToken(refreshToken string) error {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ return s.manager.RevokeRefreshToken(refreshToken)
+}
+
+func (s *StpLogic) GetOAuth2Server() *oauth2.OAuth2Server {
+ if s.manager == nil {
+ panic("Manager not initialized.")
+ }
+ return s.manager.GetOAuth2Server()
+}
+
+// ============ Check Functions for Token-based operations | 基于Token的检查函数 ============
+
+// CheckDisable checks if the account associated with the token is disabled | 检查Token对应账号是否被封禁
+func (s *StpLogic) CheckDisable(tokenValue string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if s.IsDisable(loginID) {
+ return fmt.Errorf("account is disabled")
+ }
+ return nil
+}
+
+// CheckPermission checks if the token has the specified permission | 检查Token是否拥有指定权限
+func (s *StpLogic) CheckPermission(tokenValue string, permission string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasPermission(loginID, permission) {
+ return fmt.Errorf("permission denied: %s", permission)
+ }
+ return nil
+}
+
+// CheckPermissionAnd checks if the token has all specified permissions | 检查Token是否拥有所有指定权限
+func (s *StpLogic) CheckPermissionAnd(tokenValue string, permissions []string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasPermissionsAnd(loginID, permissions) {
+ return fmt.Errorf("permission denied: %v", permissions)
+ }
+ return nil
+}
+
+// CheckPermissionOr checks if the token has any of the specified permissions | 检查Token是否拥有任一指定权限
+func (s *StpLogic) CheckPermissionOr(tokenValue string, permissions []string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasPermissionsOr(loginID, permissions) {
+ return fmt.Errorf("permission denied: %v", permissions)
+ }
+ return nil
+}
+
+// GetPermissionList gets permission list for the token | 获取Token对应的权限列表
+func (s *StpLogic) GetPermissionList(tokenValue string) ([]string, error) {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return nil, err
+ }
+ return s.GetPermissions(loginID)
+}
+
+// CheckRole checks if the token has the specified role | 检查Token是否拥有指定角色
+func (s *StpLogic) CheckRole(tokenValue string, role string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasRole(loginID, role) {
+ return fmt.Errorf("role denied: %s", role)
+ }
+ return nil
+}
+
+// CheckRoleAnd checks if the token has all specified roles | 检查Token是否拥有所有指定角色
+func (s *StpLogic) CheckRoleAnd(tokenValue string, roles []string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasRolesAnd(loginID, roles) {
+ return fmt.Errorf("role denied: %v", roles)
+ }
+ return nil
+}
+
+// CheckRoleOr checks if the token has any of the specified roles | 检查Token是否拥有任一指定角色
+func (s *StpLogic) CheckRoleOr(tokenValue string, roles []string) error {
+ loginID, err := s.GetLoginID(tokenValue)
+ if err != nil {
+ return err
+ }
+ if !s.HasRolesOr(loginID, roles) {
+ return fmt.Errorf("role denied: %v", roles)
+ }
+ return nil
+}
+
+// GetRoleList gets role list for the token | 获取Token对应的角色列表
+func (s *StpLogic) GetRoleList(tokenValue string) ([]string, error) {
+ loginID, err := GetLoginID(tokenValue)
+ if err != nil {
+ return nil, err
+ }
+ return GetRoles(loginID)
+}
+
+// GetTokenSession gets session for the token | 获取Token对应的Session
+func (s *StpLogic) GetTokenSession(tokenValue string) (*session.Session, error) {
+ return GetSessionByToken(tokenValue)
+}
+
+// CloseManager Closes the manager and releases all resources | 关闭管理器并释放所有资源
+func (s *StpLogic) CloseManager() {
+ s.manager.CloseManager()
+}
diff --git a/stputil/stplogic_test.go b/stputil/stplogic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..547aa3bc974af6fc69629163f688bfe5f42713e1
--- /dev/null
+++ b/stputil/stplogic_test.go
@@ -0,0 +1,254 @@
+package stputil
+
+import (
+ "testing"
+ "time"
+
+ "github.com/click33/sa-token-go/core/config"
+ "github.com/click33/sa-token-go/core/manager"
+ "github.com/click33/sa-token-go/core/security"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func testConfig() *config.Config {
+ return &config.Config{
+ TokenName: "satoken",
+ Timeout: 3600,
+ IsConcurrent: true,
+ IsShare: true,
+ MaxLoginCount: -1,
+ }
+}
+
+func newTestStpLogic(t *testing.T) *StpLogic {
+ t.Helper()
+
+ storage := memory.NewStorage()
+ mgr := manager.NewManager(storage, testConfig())
+ logic := NewStpLogic(mgr)
+
+ SetStpLogic(logic)
+
+ t.Cleanup(func() {
+ logic.CloseManager()
+ SetStpLogic(nil)
+ })
+
+ return logic
+}
+
+func TestStpLogic_ManagerAccessors(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ mgr := logic.GetManager()
+ require.NotNil(t, mgr)
+
+ nextMgr := manager.NewManager(memory.NewStorage(), testConfig())
+ logic.SetManager(nextMgr)
+ assert.Equal(t, nextMgr, logic.GetManager())
+}
+
+func TestStpLogic_AuthAndSessionFlow(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ token, err := logic.Login("user-auth", "web")
+ require.NoError(t, err)
+ require.NotEmpty(t, token)
+
+ assert.NoError(t, logic.LoginByToken("user-auth", token, "web"))
+ assert.True(t, logic.IsLogin(token))
+ assert.NoError(t, logic.CheckLogin(token))
+
+ loginID, err := logic.GetLoginID(token)
+ require.NoError(t, err)
+ assert.Equal(t, "user-auth", loginID)
+
+ loginIDUnchecked, err := logic.GetLoginIDNotCheck(token)
+ require.NoError(t, err)
+ assert.Equal(t, "user-auth", loginIDUnchecked)
+
+ tokenValue, err := logic.GetTokenValue("user-auth", "web")
+ require.NoError(t, err)
+ assert.Equal(t, token, tokenValue)
+
+ tokenInfo, err := logic.GetTokenInfo(token)
+ require.NoError(t, err)
+ assert.Equal(t, "user-auth", tokenInfo.LoginID)
+
+ sess, err := logic.GetSession("user-auth")
+ require.NoError(t, err)
+ assert.NotNil(t, sess)
+
+ sessByToken, err := logic.GetSessionByToken(token)
+ require.NoError(t, err)
+ assert.NotNil(t, sessByToken)
+
+ tokenSess, err := logic.GetTokenSession(token)
+ require.NoError(t, err)
+ assert.NotNil(t, tokenSess)
+
+ tokenList, err := logic.GetTokenValueList("user-auth")
+ require.NoError(t, err)
+ assert.Contains(t, tokenList, token)
+
+ sessCount, err := logic.GetSessionCount("user-auth")
+ require.NoError(t, err)
+ assert.Equal(t, 1, sessCount)
+
+ require.NoError(t, logic.Logout("user-auth", "web"))
+ assert.False(t, logic.IsLogin(token))
+
+ token2, err := logic.Login("user-auth", "web")
+ require.NoError(t, err)
+ require.NoError(t, logic.LogoutByToken(token2))
+ assert.False(t, logic.IsLogin(token2))
+
+ token3, err := logic.Login("user-auth", "web")
+ require.NoError(t, err)
+ require.NoError(t, logic.Kickout("user-auth", "web"))
+ assert.False(t, logic.IsLogin(token3))
+
+ token4, err := logic.Login("user-auth", "web")
+ require.NoError(t, err)
+ require.NoError(t, logic.DeleteSession("user-auth"))
+ sessAfterDelete, err := logic.GetSession("user-auth")
+ require.NoError(t, err)
+ assert.True(t, sessAfterDelete.IsEmpty())
+ assert.True(t, logic.IsLogin(token4))
+}
+
+func TestStpLogic_Permissions(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ token, err := logic.Login("user-perm")
+ require.NoError(t, err)
+
+ perms := []string{"user.read", "user.write"}
+ require.NoError(t, logic.SetPermissions("user-perm", perms))
+
+ gotPerms, err := logic.GetPermissions("user-perm")
+ require.NoError(t, err)
+ assert.ElementsMatch(t, perms, gotPerms)
+
+ assert.True(t, logic.HasPermission("user-perm", "user.read"))
+ assert.True(t, logic.HasPermissionsAnd("user-perm", []string{"user.read", "user.write"}))
+ assert.True(t, logic.HasPermissionsOr("user-perm", []string{"user.delete", "user.read"}))
+
+ assert.NoError(t, logic.CheckPermission(token, "user.read"))
+ assert.NoError(t, logic.CheckPermissionAnd(token, []string{"user.read", "user.write"}))
+ assert.NoError(t, logic.CheckPermissionOr(token, []string{"user.delete", "user.read"}))
+
+ err = logic.CheckPermission(token, "user.delete")
+ assert.Error(t, err)
+
+ permList, err := logic.GetPermissionList(token)
+ require.NoError(t, err)
+ assert.ElementsMatch(t, perms, permList)
+}
+
+func TestStpLogic_Roles(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ token, err := logic.Login("user-role")
+ require.NoError(t, err)
+
+ roles := []string{"Admin", "User"}
+ require.NoError(t, logic.SetRoles("user-role", roles))
+
+ gotRoles, err := logic.GetRoles("user-role")
+ require.NoError(t, err)
+ assert.ElementsMatch(t, roles, gotRoles)
+
+ assert.True(t, logic.HasRole("user-role", "Admin"))
+ assert.True(t, logic.HasRolesAnd("user-role", []string{"Admin", "User"}))
+ assert.True(t, logic.HasRolesOr("user-role", []string{"Guest", "Admin"}))
+
+ assert.NoError(t, logic.CheckRole(token, "Admin"))
+ assert.NoError(t, logic.CheckRoleAnd(token, []string{"Admin", "User"}))
+ assert.NoError(t, logic.CheckRoleOr(token, []string{"Guest", "Admin"}))
+
+ err = logic.CheckRole(token, "Guest")
+ assert.Error(t, err)
+
+ roleList, err := GetRoleList(token)
+ require.NoError(t, err)
+ assert.ElementsMatch(t, roles, roleList)
+}
+
+func TestStpLogic_DisableAndUntie(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ token, err := logic.Login("user-disable")
+ require.NoError(t, err)
+
+ assert.NoError(t, logic.CheckDisable(token))
+
+ require.NoError(t, logic.Disable("user-disable", time.Minute))
+ assert.True(t, logic.IsDisable("user-disable"))
+
+ disableTTL, err := logic.GetDisableTime("user-disable")
+ require.NoError(t, err)
+ assert.Greater(t, disableTTL, int64(0))
+
+ assert.Error(t, logic.CheckDisable(token))
+
+ require.NoError(t, logic.Untie("user-disable"))
+ assert.False(t, logic.IsDisable("user-disable"))
+
+ newToken, err := logic.Login("user-disable")
+ require.NoError(t, err)
+ assert.NoError(t, logic.CheckDisable(newToken))
+}
+
+func TestStpLogic_TokenTags(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ err := logic.SetTokenTag("token-tag", "demo")
+ assert.Error(t, err)
+
+ _, err = logic.GetTokenTag("token-tag")
+ assert.Error(t, err)
+}
+
+func TestStpLogic_Nonce(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ nonce, err := logic.GenerateNonce()
+ require.NoError(t, err)
+ require.NotEmpty(t, nonce)
+
+ assert.True(t, logic.VerifyNonce(nonce))
+ assert.False(t, logic.VerifyNonce(nonce))
+}
+
+func TestStpLogic_RefreshTokenFlow(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ tokenInfo, err := logic.LoginWithRefreshToken("user-refresh", "mobile")
+ require.NoError(t, err)
+ require.NotEmpty(t, tokenInfo.AccessToken)
+ require.NotEmpty(t, tokenInfo.RefreshToken)
+
+ assert.True(t, logic.IsLogin(tokenInfo.AccessToken))
+
+ refreshed, err := logic.RefreshAccessToken(tokenInfo.RefreshToken)
+ if err == nil {
+ require.NotEmpty(t, refreshed.AccessToken)
+ assert.True(t, logic.IsLogin(refreshed.AccessToken))
+ } else {
+ assert.ErrorIs(t, err, security.ErrInvalidRefreshData)
+ }
+
+ require.NoError(t, logic.RevokeRefreshToken(tokenInfo.RefreshToken))
+ _, err = logic.RefreshAccessToken(tokenInfo.RefreshToken)
+ assert.ErrorIs(t, err, security.ErrInvalidRefreshToken)
+}
+
+func TestStpLogic_OAuth2AndClose(t *testing.T) {
+ logic := newTestStpLogic(t)
+
+ assert.NotNil(t, logic.GetOAuth2Server())
+ assert.NotPanics(t, func() { logic.CloseManager() })
+}
diff --git a/stputil/stputil.go b/stputil/stputil.go
index 4a8424022741142ec64097cedd6436df645d0445..1edc019dffbd73d081ce1058486bc2e865d1f559 100644
--- a/stputil/stputil.go
+++ b/stputil/stputil.go
@@ -13,212 +13,229 @@ import (
// Global Manager instance | 全局Manager实例
var (
- globalManager *manager.Manager
- once sync.Once
- mu sync.RWMutex
+ globalLogic *StpLogic
+ //globalManager *manager.Manager
+ once sync.Once
+ mu sync.RWMutex
)
// SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法)
func SetManager(mgr *manager.Manager) {
- mu.Lock()
- defer mu.Unlock()
- globalManager = mgr
+ if globalLogic == nil {
+ globalLogic = NewStpLogic(mgr)
+ }
+ globalLogic.SetManager(mgr)
}
// GetManager gets the global Manager | 获取全局Manager
func GetManager() *manager.Manager {
- mu.RLock()
- defer mu.RUnlock()
- if globalManager == nil {
- panic("StpUtil not initialized, please call SetManager() first or use builder.NewBuilder().Build()")
+ if globalLogic == nil {
+ panic("StpUtil not initialized, please call SetManager() first")
+ }
+ if globalLogic.GetManager() == nil {
+ panic("StpUtil not initialized, please call SetManager() first")
}
- return globalManager
+ return globalLogic.GetManager()
}
// CloseManager closes global Manager and releases resources | 关闭全局 Manager 并释放资源
func CloseManager() {
+ if globalLogic != nil {
+ globalLogic.CloseManager()
+ globalLogic.SetManager(nil) // 置 nil 避免后续误用
+ }
+}
+
+// ============ StpLogic ============
+
+// SetStpLogic sets the global StpLogic instance | 设置全局 StpLogic 实例
+func SetStpLogic(logic *StpLogic) {
mu.Lock()
defer mu.Unlock()
- if globalManager != nil {
- globalManager.CloseManager()
- globalManager = nil // 置 nil 避免后续误用
- }
+ globalLogic = logic
+}
+
+// GetStpLogic gets the global StpLogic instance | 获取全局 StpLogic 实例
+func GetStpLogic() *StpLogic {
+ mu.Lock()
+ defer mu.Unlock()
+ return globalLogic
}
// ============ Authentication | 登录认证 ============
// Login performs user login | 用户登录
func Login(loginID interface{}, device ...string) (string, error) {
- return GetManager().Login(toString(loginID), device...)
+ return globalLogic.Login(toString(loginID), device...)
}
// LoginByToken performs login with specified token | 使用指定Token登录
func LoginByToken(loginID interface{}, tokenValue string, device ...string) error {
- return GetManager().LoginByToken(toString(loginID), tokenValue, device...)
+ return globalLogic.LoginByToken(toString(loginID), tokenValue, device...)
}
// Logout performs user logout | 用户登出
func Logout(loginID interface{}, device ...string) error {
- return GetManager().Logout(toString(loginID), device...)
+ return globalLogic.Logout(toString(loginID), device...)
}
// LogoutByToken performs logout by token | 根据Token登出
func LogoutByToken(tokenValue string) error {
- return GetManager().LogoutByToken(tokenValue)
+ return globalLogic.LogoutByToken(tokenValue)
}
// IsLogin checks if the user is logged in | 检查用户是否已登录
func IsLogin(tokenValue string) bool {
- return GetManager().IsLogin(tokenValue)
+ return globalLogic.IsLogin(tokenValue)
}
// CheckLogin checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误)
func CheckLogin(tokenValue string) error {
- return GetManager().CheckLogin(tokenValue)
+ return globalLogic.CheckLogin(tokenValue)
}
// GetLoginID gets the login ID from token | 从Token获取登录ID
func GetLoginID(tokenValue string) (string, error) {
- return GetManager().GetLoginID(tokenValue)
+ return globalLogic.GetLoginID(tokenValue)
}
// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查)
func GetLoginIDNotCheck(tokenValue string) (string, error) {
- return GetManager().GetLoginIDNotCheck(tokenValue)
+ return globalLogic.GetLoginIDNotCheck(tokenValue)
}
// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值
func GetTokenValue(loginID interface{}, device ...string) (string, error) {
- return GetManager().GetTokenValue(toString(loginID), device...)
+ return globalLogic.GetTokenValue(toString(loginID), device...)
}
// GetTokenInfo gets token information | 获取Token信息
func GetTokenInfo(tokenValue string) (*manager.TokenInfo, error) {
- return GetManager().GetTokenInfo(tokenValue)
+ return globalLogic.GetTokenInfo(tokenValue)
}
// ============ Kickout | 踢人下线 ============
// Kickout kicks out a user session | 踢人下线
func Kickout(loginID interface{}, device ...string) error {
- return GetManager().Kickout(toString(loginID), device...)
+ return globalLogic.Kickout(toString(loginID), device...)
}
// ============ Account Disable | 账号封禁 ============
// Disable disables an account for specified duration | 封禁账号(指定时长)
func Disable(loginID interface{}, duration time.Duration) error {
- return GetManager().Disable(toString(loginID), duration)
+ return globalLogic.Disable(toString(loginID), duration)
}
// Untie re-enables a disabled account | 解封账号
func Untie(loginID interface{}) error {
- return GetManager().Untie(toString(loginID))
+ return globalLogic.Untie(toString(loginID))
}
// IsDisable checks if an account is disabled | 检查账号是否被封禁
func IsDisable(loginID interface{}) bool {
- return GetManager().IsDisable(toString(loginID))
+ return globalLogic.IsDisable(toString(loginID))
}
// GetDisableTime gets remaining disable time in seconds | 获取剩余封禁时间(秒)
func GetDisableTime(loginID interface{}) (int64, error) {
- return GetManager().GetDisableTime(toString(loginID))
+ return globalLogic.GetDisableTime(toString(loginID))
}
// ============ Session Management | Session管理 ============
// GetSession gets session by login ID | 根据登录ID获取Session
func GetSession(loginID interface{}) (*session.Session, error) {
- return GetManager().GetSession(toString(loginID))
+ return globalLogic.GetSession(toString(loginID))
}
// GetSessionByToken gets session by token | 根据Token获取Session
func GetSessionByToken(tokenValue string) (*session.Session, error) {
- return GetManager().GetSessionByToken(tokenValue)
+ return globalLogic.GetSessionByToken(tokenValue)
}
// DeleteSession deletes a session | 删除Session
func DeleteSession(loginID interface{}) error {
- return GetManager().DeleteSession(toString(loginID))
+ return globalLogic.DeleteSession(toString(loginID))
}
// ============ Permission Verification | 权限验证 ============
// SetPermissions sets permissions for a login ID | 设置用户权限
func SetPermissions(loginID interface{}, permissions []string) error {
- return GetManager().SetPermissions(toString(loginID), permissions)
+ return globalLogic.SetPermissions(toString(loginID), permissions)
}
// GetPermissions gets permission list | 获取权限列表
func GetPermissions(loginID interface{}) ([]string, error) {
- return GetManager().GetPermissions(toString(loginID))
+ return globalLogic.GetPermissions(toString(loginID))
}
// HasPermission checks if has specified permission | 检查是否拥有指定权限
func HasPermission(loginID interface{}, permission string) bool {
- return GetManager().HasPermission(toString(loginID), permission)
+ return globalLogic.HasPermission(toString(loginID), permission)
}
// HasPermissionsAnd checks if has all permissions (AND logic) | 检查是否拥有所有权限(AND逻辑)
func HasPermissionsAnd(loginID interface{}, permissions []string) bool {
- return GetManager().HasPermissionsAnd(toString(loginID), permissions)
+ return globalLogic.HasPermissionsAnd(toString(loginID), permissions)
}
// HasPermissionsOr checks if has any permission (OR logic) | 检查是否拥有任一权限(OR逻辑)
func HasPermissionsOr(loginID interface{}, permissions []string) bool {
- return GetManager().HasPermissionsOr(toString(loginID), permissions)
+ return globalLogic.HasPermissionsOr(toString(loginID), permissions)
}
// ============ Role Management | 角色管理 ============
// SetRoles sets roles for a login ID | 设置用户角色
func SetRoles(loginID interface{}, roles []string) error {
- return GetManager().SetRoles(toString(loginID), roles)
+ return globalLogic.SetRoles(toString(loginID), roles)
}
// GetRoles gets role list | 获取角色列表
func GetRoles(loginID interface{}) ([]string, error) {
- return GetManager().GetRoles(toString(loginID))
+ return globalLogic.GetRoles(toString(loginID))
}
// HasRole checks if has specified role | 检查是否拥有指定角色
func HasRole(loginID interface{}, role string) bool {
- return GetManager().HasRole(toString(loginID), role)
+ return globalLogic.HasRole(toString(loginID), role)
}
// HasRolesAnd checks if has all roles (AND logic) | 检查是否拥有所有角色(AND逻辑)
func HasRolesAnd(loginID interface{}, roles []string) bool {
- return GetManager().HasRolesAnd(toString(loginID), roles)
+ return globalLogic.HasRolesAnd(toString(loginID), roles)
}
// HasRolesOr 检查是否拥有任一角色(OR)
func HasRolesOr(loginID interface{}, roles []string) bool {
- return GetManager().HasRolesOr(toString(loginID), roles)
+ return globalLogic.HasRolesOr(toString(loginID), roles)
}
// ============ Token标签 ============
// SetTokenTag 设置Token标签
func SetTokenTag(tokenValue, tag string) error {
- return GetManager().SetTokenTag(tokenValue, tag)
+ return globalLogic.SetTokenTag(tokenValue, tag)
}
// GetTokenTag 获取Token标签
func GetTokenTag(tokenValue string) (string, error) {
- return GetManager().GetTokenTag(tokenValue)
+ return globalLogic.GetTokenTag(tokenValue)
}
// ============ 会话查询 ============
// GetTokenValueList 获取指定账号的所有Token
func GetTokenValueList(loginID interface{}) ([]string, error) {
- return GetManager().GetTokenValueListByLoginID(toString(loginID))
+ return globalLogic.GetTokenValueList(loginID)
}
// GetSessionCount 获取指定账号的Session数量
func GetSessionCount(loginID interface{}) (int, error) {
- return GetManager().GetSessionCountByLoginID(toString(loginID))
+ return globalLogic.GetSessionCount(loginID)
}
// ============ 辅助方法 ============
@@ -288,49 +305,49 @@ func uint64ToString(u uint64) string {
}
func GenerateNonce() (string, error) {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
- return globalManager.GenerateNonce()
+ return globalLogic.GenerateNonce()
}
func VerifyNonce(nonce string) bool {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
- return globalManager.VerifyNonce(nonce)
+ return globalLogic.VerifyNonce(nonce)
}
func LoginWithRefreshToken(loginID interface{}, device ...string) (*security.RefreshTokenInfo, error) {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
deviceType := "default"
if len(device) > 0 {
deviceType = device[0]
}
- return globalManager.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType)
+ return globalLogic.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType)
}
func RefreshAccessToken(refreshToken string) (*security.RefreshTokenInfo, error) {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
- return globalManager.RefreshAccessToken(refreshToken)
+ return globalLogic.RefreshAccessToken(refreshToken)
}
func RevokeRefreshToken(refreshToken string) error {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
- return globalManager.RevokeRefreshToken(refreshToken)
+ return globalLogic.RevokeRefreshToken(refreshToken)
}
func GetOAuth2Server() *oauth2.OAuth2Server {
- if globalManager == nil {
+ if globalLogic == nil {
panic("Manager not initialized. Call stputil.SetManager() first")
}
- return globalManager.GetOAuth2Server()
+ return globalLogic.GetOAuth2Server()
}
// ============ Check Functions for Token-based operations | 基于Token的检查函数 ============
diff --git a/stputil/stputil_test.go b/stputil/stputil_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d3343feb956931404f1dbf5c94a3b1cd9ce0acbd
--- /dev/null
+++ b/stputil/stputil_test.go
@@ -0,0 +1,131 @@
+package stputil
+
+import (
+ "testing"
+ "time"
+
+ "github.com/click33/sa-token-go/core/config"
+ "github.com/click33/sa-token-go/core/manager"
+ "github.com/click33/sa-token-go/storage/memory"
+ "github.com/stretchr/testify/assert"
+)
+
+// setupTestManager 初始化内存存储和全局 Manager
+func setupTestManager() {
+ storage := memory.NewStorage()
+ cfg := &config.Config{
+ TokenName: "satoken",
+ Timeout: 3600,
+ IsConcurrent: true,
+ IsShare: true,
+ MaxLoginCount: -1,
+ }
+ mgr := manager.NewManager(storage, cfg)
+ SetManager(mgr)
+}
+
+func TestLoginAndIsLogin(t *testing.T) {
+ setupTestManager()
+
+ token, err := Login("user1")
+ assert.NoError(t, err)
+ assert.NotEmpty(t, token)
+
+ assert.True(t, IsLogin(token))
+
+ loginID, err := GetLoginID(token)
+ assert.NoError(t, err)
+ assert.Equal(t, "user1", loginID)
+}
+
+func TestPermissionsHelpers(t *testing.T) {
+ setupTestManager()
+
+ token, err := Login("user2")
+ assert.NoError(t, err)
+
+ err = SetPermissions("user2", []string{"user.read", "user.write"})
+ assert.NoError(t, err)
+
+ // HasPermission / CheckPermission
+ assert.True(t, HasPermission("user2", "user.read"))
+ assert.NoError(t, CheckPermission(token, "user.read"))
+
+ // AND / OR helpers
+ assert.True(t, HasPermissionsAnd("user2", []string{"user.read", "user.write"}))
+ assert.True(t, HasPermissionsOr("user2", []string{"user.delete", "user.read"}))
+
+ // Permission list by token
+ perms, err := GetPermissionList(token)
+ assert.NoError(t, err)
+ assert.ElementsMatch(t, []string{"user.read", "user.write"}, perms)
+}
+
+func TestRoleHelpers(t *testing.T) {
+ setupTestManager()
+
+ token, err := Login("user3")
+ assert.NoError(t, err)
+
+ err = SetRoles("user3", []string{"Admin", "User"})
+ assert.NoError(t, err)
+
+ // HasRole / CheckRole
+ assert.True(t, HasRole("user3", "Admin"))
+ assert.NoError(t, CheckRole(token, "Admin"))
+
+ // AND / OR helpers
+ assert.True(t, HasRolesAnd("user3", []string{"Admin", "User"}))
+ assert.True(t, HasRolesOr("user3", []string{"Guest", "Admin"}))
+
+ // Role list by token
+ roles, err := GetRoleList(token)
+ assert.NoError(t, err)
+ assert.ElementsMatch(t, []string{"Admin", "User"}, roles)
+}
+
+func TestDisableAndCheckDisable(t *testing.T) {
+ setupTestManager()
+
+ token, err := Login("user4")
+ assert.NoError(t, err)
+
+ // 初始未封禁
+ assert.NoError(t, CheckDisable(token))
+
+ // 封禁账号
+ err = Disable("user4", time.Hour)
+ assert.NoError(t, err)
+
+ // 现在 CheckDisable 应返回错误(可能是“未登录”或“已封禁”等)
+ err = CheckDisable(token)
+ assert.Error(t, err)
+
+ disabled := IsDisable("user4")
+ assert.True(t, disabled)
+}
+
+func TestToStringHelpers(t *testing.T) {
+ assert.Equal(t, "123", toString(123))
+ assert.Equal(t, "-5", toString(int(-5)))
+ assert.Equal(t, "0", toString(int64(0)))
+ assert.Equal(t, "42", toString(uint(42)))
+ assert.Equal(t, "", toString(struct{}{}))
+}
+
+// TestLoginWithRefreshToken_IsLogin 验证双 Token 登录场景下,access token 能正常通过 IsLogin/CheckLogin
+func TestLoginWithRefreshToken_IsLogin(t *testing.T) {
+ setupTestManager()
+
+ // 使用双 token 登录
+ tokenInfo, err := LoginWithRefreshToken("user-refresh", "web")
+ assert.NoError(t, err)
+ assert.NotEmpty(t, tokenInfo.AccessToken)
+ assert.NotEmpty(t, tokenInfo.RefreshToken)
+
+ // 刚登录的 access token 应该是“已登录”
+ assert.True(t, IsLogin(tokenInfo.AccessToken))
+ assert.NoError(t, CheckLogin(tokenInfo.AccessToken))
+}
+
+