修复个人中心问题
This commit is contained in:
@@ -183,6 +183,11 @@ func JWTAuthMiddleware() beego.FilterFunc {
|
||||
ctx.Input.SetData("userId", claims.UserID)
|
||||
ctx.Input.SetData("username", claims.Username)
|
||||
ctx.Input.SetData("tenantId", claims.TenantId)
|
||||
// 兼容历史键名:部分控制器(menu / admin_user)读的是 uid / tid,
|
||||
// 且按 uint64 断言,这里补写一份,避免它们静默拿到 0。
|
||||
// 值语义与令牌一致:uid 为认证中心 identity_id。
|
||||
ctx.Input.SetData("uid", uint64(claims.UserID))
|
||||
ctx.Input.SetData("tid", uint64(claims.TenantId))
|
||||
|
||||
// 从token中获取用户类型(如果token中没有,则默认为"user")
|
||||
userType := claims.UserType
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"server/pkg/jwtutil"
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
)
|
||||
|
||||
// 模块套餐鉴权(PlanAuth)
|
||||
//
|
||||
// 把「接口前缀」映射到「功能模块编码」(yz_system_modules.code),
|
||||
// 当前租户生效套餐(含已开通的单品订单)未包含该模块时拒绝访问。
|
||||
//
|
||||
// 与 JWT 鉴权一致支持三档模式(app.conf: plan_enforce):
|
||||
// - off :完全不启用
|
||||
// - warn:观察模式,只记录「本会被拦截」的请求(默认,便于上线前核对映射)
|
||||
// - on :真正拦截,未开通返回 403
|
||||
//
|
||||
// 映射表(app.conf: plan_module_paths)示例:
|
||||
// plan_module_paths = /backend/crm:crm,/backend/erp:erp,/backend/oa:oa
|
||||
// 按最长前缀匹配;未出现在映射表中的接口不做模块校验(默认放行),
|
||||
// 因此可以按模块逐个灰度开启,不会误伤用户/角色/文件/日志等通用接口。
|
||||
|
||||
const planMappingSeparator = ":"
|
||||
|
||||
var (
|
||||
errPlanNoToken = errors.New("未提供认证信息")
|
||||
errPlanBadToken = errors.New("认证信息格式错误")
|
||||
|
||||
planOnce sync.Once
|
||||
planMode = AuthModeWarn
|
||||
planRoutes []planRoute
|
||||
)
|
||||
|
||||
// planRoute 接口前缀 → 模块编码
|
||||
type planRoute struct {
|
||||
prefix string
|
||||
module string
|
||||
}
|
||||
|
||||
func planInit() {
|
||||
planOnce.Do(func() {
|
||||
if v, err := beego.AppConfig.String("plan_enforce"); err == nil {
|
||||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||
case AuthModeOff, AuthModeWarn, AuthModeOn:
|
||||
planMode = strings.ToLower(strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
|
||||
raw, _ := beego.AppConfig.String("plan_module_paths")
|
||||
for _, item := range strings.Split(raw, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(item, planMappingSeparator, 2)
|
||||
if len(parts) != 2 {
|
||||
log.Printf("[plan] 忽略无效映射(应为 前缀:模块编码): %s", item)
|
||||
continue
|
||||
}
|
||||
prefix := strings.TrimSpace(parts[0])
|
||||
module := services.NormalizeModuleCode(parts[1])
|
||||
if prefix == "" || module == "" {
|
||||
log.Printf("[plan] 忽略无效映射(前缀或模块编码为空): %s", item)
|
||||
continue
|
||||
}
|
||||
planRoutes = append(planRoutes, planRoute{prefix: prefix, module: module})
|
||||
}
|
||||
|
||||
// 最长前缀优先:/backend/crm/bidding 必须先于 /backend/crm 命中
|
||||
sort.Slice(planRoutes, func(i, j int) bool {
|
||||
return len(planRoutes[i].prefix) > len(planRoutes[j].prefix)
|
||||
})
|
||||
log.Printf("[plan] 模块套餐鉴权模式=%s,接口映射=%d 条", planMode, len(planRoutes))
|
||||
})
|
||||
}
|
||||
|
||||
// planModuleOf 返回路径归属的模块编码;未配置归属时返回空字符串
|
||||
func planModuleOf(path string) string {
|
||||
for _, r := range planRoutes {
|
||||
if strings.HasPrefix(path, r.prefix) {
|
||||
return r.module
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PlanAuthMiddleware 模块套餐鉴权中间件
|
||||
func PlanAuthMiddleware() beego.FilterFunc {
|
||||
return func(ctx *context.Context) {
|
||||
planInit()
|
||||
if planMode == AuthModeOff || len(planRoutes) == 0 {
|
||||
return
|
||||
}
|
||||
if ctx.Input.Method() == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
|
||||
path := cleanPath(ctx.Request.RequestURI)
|
||||
if !isProtected(path) || isWhitelisted(path) {
|
||||
return
|
||||
}
|
||||
|
||||
module := planModuleOf(path)
|
||||
if module == "" {
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := planClaims(ctx)
|
||||
if err != nil {
|
||||
// 未登录 / 令牌无效:交给 JWTAuthMiddleware 与各控制器按原逻辑处理
|
||||
return
|
||||
}
|
||||
if claims.TenantId <= 0 {
|
||||
return
|
||||
}
|
||||
if services.IsModuleEnabled(uint64(claims.TenantId), module) {
|
||||
return
|
||||
}
|
||||
|
||||
reason := "模块未开通: " + module
|
||||
if planMode == AuthModeWarn {
|
||||
warnLog(path, reason)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Output.SetStatus(http.StatusForbidden)
|
||||
_ = ctx.Output.JSON(map[string]interface{}{
|
||||
"code": 403,
|
||||
"msg": "该功能未开通,请先购买对应套餐",
|
||||
}, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
// planClaims 解析 Authorization 头中的 JWT(仅用于本中间件的模块归属判断)
|
||||
func planClaims(ctx *context.Context) (*jwtutil.Claims, error) {
|
||||
authHeader := ctx.Request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return nil, errPlanNoToken
|
||||
}
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, errPlanBadToken
|
||||
}
|
||||
return jwtutil.ParseToken(parts[1])
|
||||
}
|
||||
Reference in New Issue
Block a user