Files
portal-mini-go/internal/middleware/permission.go
T
2026-10-25 22:47:13 +08:00

134 lines
3.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 的权限授权部分:定义权限提供接口、权限加载中间件与接口守卫。
// 设计为“认证后授权”: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
}