Files
xlgo-core/middleware/auth.go
T
杭州明婳科技 dcfd24b624 release: v1.0.3 bug fix release
v1.0.3 定位为 bug fix release,收口 v1.0.2 引入的破坏性清理并修复
4 个轻量 bug + 依赖复查 + 版本号治理 + 文档对齐。同时包含此前未提交
的 v1.0.2 全部工作。

v1.0.3 变更:

Fixed:
- generateJTI 忽略 rand.Read 错误(jwt)— 改为 (string, error) 并传播
- QueryBuilder.Page Count 受残留 Limit 截断(repository)— countDB 加 Limit(-1).Offset(-1)
- OSS/本地存储文件名冲突(storage)— 新增 uniqueFilename 加 8 字节随机后缀
- 数据库重试策略对不可恢复错误无效(database)— 新增 isTransientDBError

Dependencies:
- go mod tidy 补全 postgres 方言传递依赖;安全补丁升级
  x/crypto v0.49→v0.53、golang-jwt/jwt/v5 v5.2.1→v5.3.1、gorilla/websocket v1.5.1→v1.5.3

Removed:
- Breaking: 清理 v1.0.2 兼容别名(InitMySQL* / driverName)
- 死代码 database.DBResolver
- 代码中 292 行"评分/理由"自夸注释(#26,23 个文件)

Changed:
- Breaking: 错误码体系重构 CodeSuccess 1→0、CodeFail 0→1,删除 CodeInvalidParams,加编译期防撞码
- database/mysql.go → manager.go;Logger 拆分三独立 core 修复 Tee 重复写入
- 版本号常量化:app.go 新增 const Version 作为唯一来源,CLI/脚手架模板引用之,不再散落字面量

Security:
- CORS 中间件按 W3C 规范修复 Allow-Credentials / Vary: Origin / 非白名单不回显

Added:
- console 包显式 level 控制(SetLevel/WithLevel/LevelSilent,atomic.Int32 并发安全)
- 新增测试 jwt/repository/storage/database/router/middleware/app
- 文档:新增 CHANGELOG.md、Version_v1.0.2_report.md;更新 README/GUIDE 顶部更新日志
- 文档对齐:删除对不存在的 config.DefaultManager() getter 的描述(保持 API 与文档一致)

详见 CHANGELOG.md 与 README.md 更新日志。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:40:10 +08:00

259 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"strings"
"github.com/EthanCodeCraft/xlgo-core/jwt"
"github.com/EthanCodeCraft/xlgo-core/response"
"github.com/gin-gonic/gin"
)
const (
// ContextKeyUserID 用户ID上下文键
ContextKeyUserID = "user_id"
// ContextKeyUsername 用户名上下文键
ContextKeyUsername = "username"
// ContextKeyRole 角色上下文键
ContextKeyRole = "role"
// ContextKeyUserType 用户类型上下文键
ContextKeyUserType = "user_type"
// DefaultUserTypeSuperAdmin 默认超级管理员用户类型
DefaultUserTypeSuperAdmin = "super_admin"
// DefaultUserTypeAdmin 默认管理员用户类型
DefaultUserTypeAdmin = "admin"
// DefaultUserTypeStaff 默认员工用户类型
DefaultUserTypeStaff = "staff"
)
// AuthUser 当前认证用户
type AuthUser struct {
UserID uint
Username string
Role string
UserType string
}
// AuthChecker 自定义权限检查函数
type AuthChecker func(user AuthUser, c *gin.Context) bool
// AuthRequired JWT 认证中间件
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
response.Unauthorized(c, "请先登录")
c.Abort()
return
}
// 解析 Bearer Token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
response.Unauthorized(c, "认证格式错误")
c.Abort()
return
}
tokenString := parts[1]
claims, err := jwt.ParseToken(tokenString)
if err != nil {
response.Unauthorized(c, "登录已过期,请重新登录")
c.Abort()
return
}
// 将用户信息存入上下文
c.Set(ContextKeyUserID, claims.UserID)
c.Set(ContextKeyUsername, claims.Username)
c.Set(ContextKeyRole, claims.Role)
c.Set(ContextKeyUserType, claims.UserType)
c.Next()
}
}
// RequireAuth 自定义权限中间件
func RequireAuth(checker AuthChecker, messages ...string) gin.HandlerFunc {
return func(c *gin.Context) {
user, ok := GetAuthUser(c)
if !ok {
abortAuthContext(c)
return
}
if checker == nil || !checker(user, c) {
abortForbidden(c, messages...)
return
}
c.Next()
}
}
// RequireUserTypes 用户类型权限中间件
func RequireUserTypes(userTypes ...string) gin.HandlerFunc {
allowed := make(map[string]struct{}, len(userTypes))
for _, userType := range userTypes {
allowed[userType] = struct{}{}
}
return RequireAuth(func(user AuthUser, c *gin.Context) bool {
_, ok := allowed[user.UserType]
return ok
})
}
// RequireRoles 角色权限中间件
func RequireRoles(roles ...string) gin.HandlerFunc {
allowed := make(map[string]struct{}, len(roles))
for _, role := range roles {
allowed[role] = struct{}{}
}
return RequireAuth(func(user AuthUser, c *gin.Context) bool {
_, ok := allowed[user.Role]
return ok
})
}
// AdminRequired 管理员权限中间件
func AdminRequired() gin.HandlerFunc {
return RequireUserTypes(DefaultUserTypeSuperAdmin, DefaultUserTypeAdmin)
}
// SuperAdminRequired 超级管理员权限中间件
func SuperAdminRequired() gin.HandlerFunc {
return RequireUserTypes(DefaultUserTypeSuperAdmin)
}
// StaffRequired 员工权限中间件
func StaffRequired() gin.HandlerFunc {
return RequireUserTypes(DefaultUserTypeStaff)
}
// AnyUserRequired 任意用户权限中间件(默认允许 admin/super_admin/staff
func AnyUserRequired() gin.HandlerFunc {
return RequireUserTypes(DefaultUserTypeSuperAdmin, DefaultUserTypeAdmin, DefaultUserTypeStaff)
}
func abortAuthContext(c *gin.Context) {
if _, exists := c.Get(ContextKeyUserID); !exists {
response.Unauthorized(c, "请先登录")
} else {
response.Unauthorized(c, "用户信息异常")
}
c.Abort()
}
func abortForbidden(c *gin.Context, messages ...string) {
if len(messages) > 0 && messages[0] != "" {
response.Fail(c, messages[0])
} else {
response.FailWithError(c, response.ErrForbidden)
}
c.Abort()
}
// GetAuthUser 从上下文获取认证用户
func GetAuthUser(c *gin.Context) (AuthUser, bool) {
userID, exists := c.Get(ContextKeyUserID)
if !exists {
return AuthUser{}, false
}
id, ok := userID.(uint)
if !ok {
return AuthUser{}, false
}
username, exists := c.Get(ContextKeyUsername)
if !exists {
return AuthUser{}, false
}
name, ok := username.(string)
if !ok {
return AuthUser{}, false
}
role, exists := c.Get(ContextKeyRole)
if !exists {
return AuthUser{}, false
}
r, ok := role.(string)
if !ok {
return AuthUser{}, false
}
userType, exists := c.Get(ContextKeyUserType)
if !exists {
return AuthUser{}, false
}
ut, ok := userType.(string)
if !ok {
return AuthUser{}, false
}
return AuthUser{
UserID: id,
Username: name,
Role: r,
UserType: ut,
}, true
}
// GetUserID 从上下文获取用户ID
func GetUserID(c *gin.Context) uint {
userID, exists := c.Get(ContextKeyUserID)
if !exists {
return 0
}
// 安全的类型断言
id, ok := userID.(uint)
if !ok {
return 0
}
return id
}
// GetUsername 从上下文获取用户名
func GetUsername(c *gin.Context) string {
username, exists := c.Get(ContextKeyUsername)
if !exists {
return ""
}
// 安全的类型断言
name, ok := username.(string)
if !ok {
return ""
}
return name
}
// GetRole 从上下文获取角色
func GetRole(c *gin.Context) string {
role, exists := c.Get(ContextKeyRole)
if !exists {
return ""
}
// 安全的类型断言
r, ok := role.(string)
if !ok {
return ""
}
return r
}
// GetUserType 从上下文获取用户类型
func GetUserType(c *gin.Context) string {
userType, exists := c.Get(ContextKeyUserType)
if !exists {
return ""
}
// 安全的类型断言
ut, ok := userType.(string)
if !ok {
return ""
}
return ut
}