Files
hfb_sys/backend/internal/middleware/permission.go
T
yml2213 cb8aceb433 fix(distribution): 支持客服在主站后台查看与管理分站二维码
- 路由权限支持:新增 RequireAnyPermission 中间件,允许持有 qrcode:view 或 distribution:site_view 的管理员调用 GET /admin/distribution/sites 获取分站列表
- 服务层站点穿透:ListSitesForAdmin 允许持有 qrcode:view 权限的管理员(客服)获取全部可用分销站点用于码池维护;站长/分销商等站点成员仍按其绑定站点收窄
- 前端交互与权限分组:分站二维码池 loadSites 补充异常捕获,分配权限弹窗补充「分销管理」分组展示名
- 权限来源:cs 角色的 qrcode:view/manage 由既有迁移 000016 提供,故本次不新增数据库迁移
- 权限边界:不采用 distribution:all_view 实现该需求——它是跨站写操作的总开关(改站/删站/重置站长密码),而该权限依赖的 ops 角色同时是分站站长,授予后会击穿多租户隔离;并补充回归断言锁定该边界
- 验证方式:全量门禁 ./scripts/check.sh 通过(SQL 约束检查、Go 单测与覆盖率、路由测试、前端类型检查、单测与生产构建)
2026-10-11 17:11:46 +08:00

106 lines
2.5 KiB
Go

package middleware
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"hfb_sys/backend/pkg/response"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
)
// RequirePermission 检查当前管理员是否拥有指定权限。
// 超级管理员(拥有 super_admin 角色的管理员)自动放行。
func RequirePermission(permCode string, rdb *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
if checkPermission(c, permCode, rdb) {
c.Next()
}
}
}
// RequireAnyPermission 检查当前管理员是否拥有任意一个指定的权限。
// 超级管理员(拥有 super_admin 角色或 * 权限)自动放行。
func RequireAnyPermission(rdb *redis.Client, permCodes ...string) gin.HandlerFunc {
return func(c *gin.Context) {
if checkAnyPermission(c, rdb, permCodes...) {
c.Next()
}
}
}
func RequirePermissionIf(permCode string, rdb *redis.Client, predicate func(*gin.Context) bool) gin.HandlerFunc {
return func(c *gin.Context) {
if predicate == nil || !predicate(c) {
c.Next()
return
}
if checkPermission(c, permCode, rdb) {
c.Next()
}
}
}
func checkAnyPermission(c *gin.Context, rdb *redis.Client, permCodes ...string) bool {
value, ok := c.Get(ContextAdminID)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
c.Abort()
return false
}
adminID, ok := value.(uint64)
if !ok {
response.Unauthorized(c, "管理员上下文无效")
c.Abort()
return false
}
codes, err := getPermCodes(c, rdb, adminID)
if err != nil {
response.Error(c, http.StatusInternalServerError, "perm_check_failed", "权限校验服务暂时不可用")
c.Abort()
return false
}
for _, code := range codes {
if code == "*" {
return true
}
for _, target := range permCodes {
if code == target {
return true
}
}
}
response.Error(c, http.StatusForbidden, "forbidden", "没有操作权限")
c.Abort()
return false
}
func checkPermission(c *gin.Context, permCode string, rdb *redis.Client) bool {
return checkAnyPermission(c, rdb, permCode)
}
func getPermCodes(c *gin.Context, rdb *redis.Client, adminID uint64) ([]string, error) {
if rdb == nil {
return nil, errors.New("redis unavailable")
}
key := fmt.Sprintf("admin:perms:%d", adminID)
raw, err := rdb.Get(c.Request.Context(), key).Result()
if errors.Is(err, redis.Nil) {
return nil, nil // 缓存未命中,视为无权限
}
if err != nil {
return nil, err
}
var codes []string
if err := json.Unmarshal([]byte(raw), &codes); err != nil {
return nil, err
}
return codes, nil
}