// Package middleware 的权限授权部分:定义权限提供接口、权限加载中间件与接口守卫。 // 设计为“认证后授权”:PermissionLoader 在 AdminAuth 之后运行,把管理员角色与权限集 // 注入 gin.Context;RequirePerm / RequireRole 在路由层比对集合,缺失即返回 403。 package middleware import ( "context" "log/slog" "github.com/gin-gonic/gin" "portal-mini-go/internal/model/admin" "portal-mini-go/internal/pkg/errors" ) // PermissionProvider 是权限数据的提供方(由 service/admin/rbac 实现)。 // 中间件仅依赖此接口,便于单测以 mock 替换。 type PermissionProvider interface { // Load 返回管理员的角色 code 与权限 code 集合(优先走缓存)。 Load(ctx context.Context, adminID uint64) (roles, perms []string, err error) RolesOf(ctx context.Context, adminID uint64) ([]string, error) PermissionsOf(ctx context.Context, adminID uint64) ([]string, error) // MenuTree 返回该管理员可见的菜单树(按权限过滤,超级管理员返回完整树)。 MenuTree(ctx context.Context, adminID uint64) ([]admin.MenuNode, error) } // 后台权限 code 常量(接口级),供路由守卫引用,避免散落字符串。 const ( PermMiniView = "mini:view" PermMiniManage = "mini:manage" PermUserView = "user:view" PermUserManage = "user:manage" PermAdminManage = "admin:manage" PermRoleManage = "role:manage" PermPermManage = "perm:manage" ) const ( adminRolesCtxKey = "admin_roles" adminPermsCtxKey = "admin_perms" ) // PermissionLoader 在 AdminAuth 之后加载管理员权限集并写入 context。 // 加载失败时记告警日志并放行(fail-closed 由守卫层保证:空集必然不通过)。 func PermissionLoader(p PermissionProvider) gin.HandlerFunc { return func(c *gin.Context) { id, ok := AdminIDUint(c) if !ok { c.Next() return } roles, perms, err := p.Load(c.Request.Context(), id) if err != nil { slog.Warn("load admin permissions failed", slog.Uint64("admin_id", id), slog.Any("err", err)) c.Next() return } c.Set(adminRolesCtxKey, roles) set := make(map[string]struct{}, len(perms)) for _, pc := range perms { set[pc] = struct{}{} } c.Set(adminPermsCtxKey, set) c.Next() } } // RequirePerm 接口守卫:管理员需同时拥有全部给定权限(超级管理员短路放行)。 func RequirePerm(perms ...string) gin.HandlerFunc { return func(c *gin.Context) { if HasRole(c, admin.SuperAdminRoleCode) { c.Next() return } got := permsOf(c) for _, need := range perms { if _, ok := got[need]; !ok { c.Error(errors.ErrForbidden) c.Abort() return } } c.Next() } } // RequireRole 角色守卫:管理员需拥有任一给定角色(超级管理员始终通过)。 func RequireRole(roles ...string) gin.HandlerFunc { return func(c *gin.Context) { if HasRole(c, admin.SuperAdminRoleCode) { c.Next() return } for _, need := range roles { if HasRole(c, need) { c.Next() return } } c.Error(errors.ErrForbidden) c.Abort() } } // HasRole 判断 context 中是否含指定角色。 func HasRole(c *gin.Context, code string) bool { if v, ok := c.Get(adminRolesCtxKey); ok { if roles, ok := v.([]string); ok { for _, r := range roles { if r == code { return true } } } } return false } // HasPerm 判断 context 中是否含指定权限。 func HasPerm(c *gin.Context, code string) bool { _, ok := permsOf(c)[code] return ok } func permsOf(c *gin.Context) map[string]struct{} { if v, ok := c.Get(adminPermsCtxKey); ok { if set, ok := v.(map[string]struct{}); ok { return set } } return nil }