641 lines
21 KiB
Go
641 lines
21 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"server/models"
|
||
"server/pkg/identity"
|
||
"server/services"
|
||
authsvc "server/services/auth"
|
||
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
type BackendMenuController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
type AdminMenuController = BackendMenuController
|
||
|
||
// 菜单接口被平台端和租户端共用同一个控制器(AdminMenuController 是别名),
|
||
// 所以按「调用者所属端」而不是路由前缀来定权限:
|
||
// - 写接口(增删改、改状态)只有平台端能用,租户端能改菜单等于给自己开功能;
|
||
// - 读接口两端都能用,但租户端只能读租户端那一份(cid 强制为 2)。
|
||
func (c *BackendMenuController) denyMenu(code int, msg string) {
|
||
// 必须同时设置 HTTP 状态码:只把 code 写进 body 而返回 200 时,
|
||
// 前端 axios 拦截器会把这次鉴权失败当成成功响应,既不静默续期也不跳登录,
|
||
// 菜单等页面只会停在「token已过期」的报错里。
|
||
c.Ctx.Output.SetStatus(code)
|
||
c.Data["json"] = map[string]interface{}{"code": code, "msg": msg}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// requirePlatformCaller 菜单写操作专用:失败时已写好响应,调用方必须立即 return。
|
||
func (c *BackendMenuController) requirePlatformCaller() bool {
|
||
if _, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndPlatform); err != nil {
|
||
code := identity.Status(err)
|
||
c.denyMenu(code, err.Error())
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// menuCallerEnd 解析菜单读接口的调用端,同时放行平台端与租户端。
|
||
// 取不到合法令牌时返回 ok=false,调用方必须立即 return。
|
||
func (c *BackendMenuController) menuCallerEnd() (identity.End, bool) {
|
||
claims, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndPlatform, identity.EndBackend)
|
||
if err != nil {
|
||
code := identity.Status(err)
|
||
c.denyMenu(code, err.Error())
|
||
return "", false
|
||
}
|
||
return identity.End(claims.UserType), true
|
||
}
|
||
|
||
// requireBackendCaller 仅租户端可用的读接口。
|
||
func (c *BackendMenuController) requireBackendCaller() bool {
|
||
end, ok := c.menuCallerEnd()
|
||
if !ok {
|
||
return false
|
||
}
|
||
if end != identity.EndBackend {
|
||
c.denyMenu(403, identity.ErrWrongEnd.Error())
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
type menuPayload struct {
|
||
Pid *int64 `json:"pid"`
|
||
Title *string `json:"title"`
|
||
Path *string `json:"path"`
|
||
ComponentPath *string `json:"component_path"`
|
||
Icon *string `json:"icon"`
|
||
Sort *int64 `json:"sort"`
|
||
Status *int8 `json:"status"`
|
||
IsVisible *int8 `json:"is_visible"`
|
||
Views []int `json:"views"`
|
||
Platforms []string `json:"platforms"` // 展示端:["web"] ["app"] ["web","app"];空=继承所属模块
|
||
Type *int8 `json:"type"`
|
||
Permission *string `json:"permission"`
|
||
}
|
||
|
||
func parseViews(raw *string) []int {
|
||
if raw == nil || strings.TrimSpace(*raw) == "" {
|
||
return nil
|
||
}
|
||
var arr []int
|
||
if err := json.Unmarshal([]byte(*raw), &arr); err != nil {
|
||
return nil
|
||
}
|
||
return arr
|
||
}
|
||
|
||
func hasView(arr []int, v int) bool {
|
||
for _, n := range arr {
|
||
if n == v {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu {
|
||
out := make([]models.SystemMenu, 0, len(menus))
|
||
for _, m := range menus {
|
||
views := parseViews(m.Views)
|
||
if v == 1 && len(views) == 0 {
|
||
out = append(out, m)
|
||
continue
|
||
}
|
||
if hasView(views, v) {
|
||
out = append(out, m)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// GetMenu 平台端菜单树(views=1),只归平台端。
|
||
func (c *BackendMenuController) GetMenu() {
|
||
if !c.requirePlatformCaller() {
|
||
return
|
||
}
|
||
var menus []models.SystemMenu
|
||
_, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 1), 0)}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// GetBackendMenu 租户端当前用户的可见菜单树,只归租户端令牌。
|
||
func (c *BackendMenuController) GetBackendMenu() {
|
||
if !c.requireBackendCaller() {
|
||
return
|
||
}
|
||
var menus []models.SystemMenu
|
||
_, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
// 先按 views 过滤租户端,再按展示端过滤出 Web 端可见菜单。
|
||
// 菜单未单独配置 platforms 时继承所属模块,因此要传入全部模块。
|
||
allModules, _ := services.ListAllModulesForScope()
|
||
backendMenus := services.FilterMenusByPlatformWithModules(filterMenusByView(menus, 2), allModules, models.PlatformWeb)
|
||
|
||
// 按当前登录用户角色过滤可见功能菜单:
|
||
// 角色 rights 为空(或未分配角色)→ 全权限,返回全部后端菜单;
|
||
// 否则仅返回角色允许访问的菜单(含其祖先节点,保证树结构完整)。
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
uid, _ := strconv.ParseUint(idStr, 10, 64)
|
||
tid := uint64(0)
|
||
groupID := uint64(0)
|
||
if uid > 0 {
|
||
// URL 参数是认证中心 identity_id,不能直接当 yz_system_tenant_user 主键查:
|
||
// 主键恰好与 identity_id 相同时会套用**别人**的角色权限——
|
||
// 如 identity_id=1(李增炎)命中主键 1(李志强 tid=1、group_id=0),
|
||
// 反而免过滤拿到全量菜单(越权);identity_id=2 则命中李增炎的受限角色,
|
||
// 菜单被错误过滤(基础设置 404)。
|
||
// 解析顺序:认证绑定表(identity_id+tid → group_id,与 /auth/userinfo 同源)
|
||
// → 业务表身份解析链(identity_id 换算 → 主键 → 老 uid)。
|
||
jwtTid := backendJWTTenantID(c.Ctx.Request.Header.Get("Authorization"))
|
||
if bind, e := authsvc.GetTenantUser(uid, jwtTid); e == nil {
|
||
tid = bind.Tid
|
||
groupID = bind.GroupID
|
||
} else if tu, e := findBackendTenantUser(uid, jwtTid, false); e == nil {
|
||
tid = tu.Tid
|
||
groupID = tu.GroupID
|
||
}
|
||
}
|
||
if groupID > 0 {
|
||
var role models.AdminRole
|
||
if re := models.Orm.QueryTable(new(models.AdminRole)).
|
||
Filter("id", groupID).
|
||
Filter("cid", 2).
|
||
One(&role); re == nil {
|
||
backendMenus = filterMenusByRights(backendMenus, role.Rights)
|
||
}
|
||
}
|
||
|
||
// 套餐功能开通标记:未开通模块的菜单保留但标记 locked(前端展示锁定态与升级提示,不再 404)
|
||
backendMenus, lockedMenuIDs := filterMenusByTenantPackageLocked(c.Ctx.Request.Header.Get("Authorization"), backendMenus, tid, true)
|
||
menuTree := buildMenuTree(backendMenus, 0)
|
||
markTreeLocked(menuTree, lockedMenuIDs)
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": menuTree}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// backendJWTTenantID 从 Authorization 的 Bearer Token 中解析租户ID;取不到时返回 0。
|
||
// 说明:backend 端登录时 token 里写入了租户ID(GenerateToken 的 tenantId),
|
||
// 用它做套餐功能过滤比依赖前端传参更可靠。只认 backend 端令牌——
|
||
// 平台令牌本就无租户,app 端令牌的租户也不能拿来决定 Web 端能看到哪些菜单。
|
||
func backendJWTTenantID(auth string) uint64 {
|
||
claims, err := identity.Claims(auth, identity.EndBackend)
|
||
if err != nil || claims == nil || claims.TenantId <= 0 {
|
||
return 0
|
||
}
|
||
return uint64(claims.TenantId)
|
||
}
|
||
|
||
// filterMenusByTenantPackage 按租户套餐包含的功能模块过滤菜单(剔除未开通模块,用于角色菜单分配等场景)。
|
||
// tid 优先取 token 中的租户ID,其次用调用方传入的兜底值;两者都取不到时不处理(保持原行为)。
|
||
func filterMenusByTenantPackage(authHeader string, menus []models.SystemMenu, tid uint64) []models.SystemMenu {
|
||
filtered, _ := filterMenusByTenantPackageLocked(authHeader, menus, tid, false)
|
||
return filtered
|
||
}
|
||
|
||
// filterMenusByTenantPackageLocked 按租户套餐处理菜单。
|
||
// markLocked=false:剔除未开通模块的菜单(原行为);markLocked=true:保留菜单并返回锁定菜单ID集合(含后代)。
|
||
func filterMenusByTenantPackageLocked(authHeader string, menus []models.SystemMenu, tid uint64, markLocked bool) ([]models.SystemMenu, map[uint64]bool) {
|
||
if jwtTid := backendJWTTenantID(authHeader); jwtTid > 0 {
|
||
tid = jwtTid
|
||
}
|
||
if tid == 0 {
|
||
return menus, nil
|
||
}
|
||
allModules, err := services.ListEnabledModules()
|
||
if err != nil || len(allModules) == 0 {
|
||
return menus, nil
|
||
}
|
||
allowed := services.GetTenantModuleCodes(tid)
|
||
// 安全兜底:租户没有绑定套餐或套餐未配置任何功能模块时不做过滤,
|
||
// 避免因套餐数据缺失导致租户端菜单整体消失。
|
||
if len(allowed) == 0 {
|
||
return menus, nil
|
||
}
|
||
if markLocked {
|
||
return services.MarkMenusLockedByTenantModules(menus, allModules, allowed)
|
||
}
|
||
return services.FilterMenusByTenantModules(menus, allModules, allowed), nil
|
||
}
|
||
|
||
// markTreeLocked 将菜单树中位于锁定集合的节点标记为 locked
|
||
func markTreeLocked(nodes []*menuNode, locked map[uint64]bool) {
|
||
if len(nodes) == 0 || len(locked) == 0 {
|
||
return
|
||
}
|
||
for _, n := range nodes {
|
||
if locked[n.ID] {
|
||
n.Locked = true
|
||
}
|
||
if len(n.Children) > 0 {
|
||
markTreeLocked(n.Children, locked)
|
||
}
|
||
}
|
||
}
|
||
|
||
// filterMenusByRights 按角色 rights(菜单 ID 集合)过滤菜单,并补全祖先节点,保证树结构完整。
|
||
// rights 为空(nil / 空串)→ 原样返回(全权限)。
|
||
func filterMenusByRights(menus []models.SystemMenu, rights *string) []models.SystemMenu {
|
||
if rights == nil || strings.TrimSpace(*rights) == "" {
|
||
return menus
|
||
}
|
||
allowed := services.ParseMenuRights(*rights)
|
||
if len(allowed) == 0 {
|
||
return menus
|
||
}
|
||
|
||
pidOf := make(map[uint64]int64, len(menus))
|
||
byID := make(map[uint64]models.SystemMenu, len(menus))
|
||
for _, m := range menus {
|
||
pidOf[m.ID] = m.Pid
|
||
byID[m.ID] = m
|
||
}
|
||
|
||
final := make(map[uint64]bool, len(allowed))
|
||
for id := range allowed {
|
||
cur := id
|
||
for {
|
||
if _, ok := byID[cur]; !ok {
|
||
break
|
||
}
|
||
if final[cur] {
|
||
break
|
||
}
|
||
final[cur] = true
|
||
p := pidOf[cur]
|
||
if p == 0 {
|
||
break
|
||
}
|
||
cur = uint64(p)
|
||
}
|
||
}
|
||
|
||
out := make([]models.SystemMenu, 0, len(final))
|
||
for id := range final {
|
||
out = append(out, byID[id])
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (c *BackendMenuController) GetTenantList() {
|
||
var tid uint64
|
||
if jwtTid := c.Ctx.Input.GetData("tid"); jwtTid != nil {
|
||
tid = jwtTid.(uint64)
|
||
}
|
||
if tid == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
var menus []models.SystemMenu
|
||
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取失败:" + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
tree := buildMenuTree(filterMenusByView(menus, 2), 0)
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "获取成功",
|
||
"data": map[string]interface{}{"list": tree, "total": len(tree)},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// GetAllMenus 两端共用:平台端可查任意端的菜单树,租户端只固定看自己那一份。
|
||
// 此前 cid 完全由查询参数决定,租户传 ?cid=1 就能整棵拉走平台端菜单树。
|
||
func (c *BackendMenuController) GetAllMenus() {
|
||
end, ok := c.menuCallerEnd()
|
||
if !ok {
|
||
return
|
||
}
|
||
var menus []models.SystemMenu
|
||
cid, _ := c.GetInt("cid")
|
||
if end != identity.EndPlatform {
|
||
cid = 2
|
||
}
|
||
|
||
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
if cid == 1 {
|
||
menus = filterMenusByView(menus, 1)
|
||
} else if cid == 2 {
|
||
menus = filterMenusByView(menus, 2)
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// filterAssignableMenus 仅保留「已启用且已显示」的菜单。
|
||
// 停用或隐藏的菜单不参与角色权限分配,直接在服务端屏蔽,不依赖前端过滤。
|
||
func filterAssignableMenus(menus []models.SystemMenu) []models.SystemMenu {
|
||
out := make([]models.SystemMenu, 0, len(menus))
|
||
for _, m := range menus {
|
||
if m.Status != 1 {
|
||
continue
|
||
}
|
||
// is_visible 为空视为显示
|
||
if m.IsVisible != nil && *m.IsVisible == 0 {
|
||
continue
|
||
}
|
||
out = append(out, m)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// GetAssignableMenus 角色权限树(租户端角色编辑器用),只归租户端。
|
||
// cid 同样固定为 2:平台端菜单不在租户可分配范围内,传 cid=1 会整棵泄露平台菜单。
|
||
// GET /backend/assignableMenus?cid=2
|
||
func (c *BackendMenuController) GetAssignableMenus() {
|
||
if !c.requireBackendCaller() {
|
||
return
|
||
}
|
||
var menus []models.SystemMenu
|
||
cid := 2
|
||
|
||
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
if cid == 1 || cid == 2 {
|
||
menus = filterMenusByView(menus, cid)
|
||
}
|
||
menus = filterAssignableMenus(menus)
|
||
// 租户端:仅可分配当前租户套餐已开通的功能模块下的菜单
|
||
if cid == 2 {
|
||
menus = filterMenusByTenantPackage(c.Ctx.Request.Header.Get("Authorization"), menus, 0)
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendMenuController) GetAllBackendMenus() {
|
||
var menus []models.SystemMenu
|
||
if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
type menuNode struct {
|
||
ID uint64 `json:"id"`
|
||
Pid int64 `json:"pid"`
|
||
Title string `json:"title"`
|
||
Path string `json:"path,omitempty"`
|
||
ComponentPath string `json:"component_path,omitempty"`
|
||
Icon string `json:"icon,omitempty"`
|
||
Sort int64 `json:"sort"`
|
||
Status int8 `json:"status"`
|
||
IsVisible *int8 `json:"is_visible,omitempty"`
|
||
Views []int `json:"views,omitempty"`
|
||
Platforms []string `json:"platforms,omitempty"` // 展示端,空=继承所属模块
|
||
Type int8 `json:"type"`
|
||
Permission string `json:"permission,omitempty"`
|
||
Locked bool `json:"locked,omitempty"`
|
||
Children []*menuNode `json:"children,omitempty"`
|
||
}
|
||
|
||
func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode {
|
||
var tree []*menuNode
|
||
for _, m := range menus {
|
||
if m.Pid == pid {
|
||
node := &menuNode{
|
||
ID: m.ID,
|
||
Pid: m.Pid,
|
||
Title: m.Title,
|
||
Sort: m.Sort,
|
||
Status: m.Status,
|
||
IsVisible: m.IsVisible,
|
||
Views: parseViews(m.Views),
|
||
Platforms: services.ParsePlatforms(m.Platforms),
|
||
Type: m.Type,
|
||
}
|
||
if m.Path != nil {
|
||
node.Path = *m.Path
|
||
}
|
||
if m.ComponentPath != nil {
|
||
node.ComponentPath = *m.ComponentPath
|
||
}
|
||
if m.Icon != nil {
|
||
node.Icon = *m.Icon
|
||
}
|
||
if m.Permission != nil {
|
||
node.Permission = *m.Permission
|
||
}
|
||
if children := buildMenuTree(menus, int64(m.ID)); len(children) > 0 {
|
||
node.Children = children
|
||
}
|
||
tree = append(tree, node)
|
||
}
|
||
}
|
||
return tree
|
||
}
|
||
|
||
func (c *BackendMenuController) UpdateMenuStatus() {
|
||
if !c.requirePlatformCaller() {
|
||
return
|
||
}
|
||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
var body struct {
|
||
Status *int8 `json:"status"`
|
||
}
|
||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil || body.Status == nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(map[string]interface{}{"status": *body.Status}); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "success": true}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendMenuController) CreateMenu() {
|
||
if !c.requirePlatformCaller() {
|
||
return
|
||
}
|
||
payload, ok := c.parseMenuPayload(true)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var viewsStr string
|
||
views := payload.Views
|
||
if len(views) == 0 {
|
||
views = []int{1}
|
||
}
|
||
if b, err := json.Marshal(views); err == nil {
|
||
viewsStr = string(b)
|
||
}
|
||
|
||
menu := models.SystemMenu{
|
||
Pid: valueInt64(payload.Pid, 0),
|
||
Title: strings.TrimSpace(valueString(payload.Title, "")),
|
||
Sort: valueInt64(payload.Sort, 0),
|
||
Status: valueInt8(payload.Status, 1),
|
||
IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)),
|
||
Views: &viewsStr,
|
||
Type: valueInt8(payload.Type, 1),
|
||
Path: ptrString(valueString(payload.Path, "")),
|
||
ComponentPath: ptrString(valueString(payload.ComponentPath, "")),
|
||
Icon: ptrString(valueString(payload.Icon, "")),
|
||
Permission: ptrString(valueString(payload.Permission, "")),
|
||
// 空串表示「继承所属模块的展示端」(模块也未配置则全端)
|
||
Platforms: ptrString(services.NormalizePlatforms(payload.Platforms)),
|
||
}
|
||
|
||
id, err := models.Orm.Insert(&menu)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendMenuController) UpdateMenu() {
|
||
if !c.requirePlatformCaller() {
|
||
return
|
||
}
|
||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
payload, ok := c.parseMenuPayload(false)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
views := payload.Views
|
||
if len(views) == 0 {
|
||
views = []int{1}
|
||
}
|
||
viewsBytes, _ := json.Marshal(views)
|
||
|
||
update := map[string]interface{}{
|
||
"pid": valueInt64(payload.Pid, 0),
|
||
"title": strings.TrimSpace(valueString(payload.Title, "")),
|
||
"path": valueString(payload.Path, ""),
|
||
"component_path": valueString(payload.ComponentPath, ""),
|
||
"icon": valueString(payload.Icon, ""),
|
||
"sort": valueInt64(payload.Sort, 0),
|
||
"status": valueInt8(payload.Status, 1),
|
||
"is_visible": valueInt8(payload.IsVisible, 1),
|
||
"views": string(viewsBytes),
|
||
"platforms": services.NormalizePlatforms(payload.Platforms),
|
||
"type": valueInt8(payload.Type, 1),
|
||
"permission": valueString(payload.Permission, ""),
|
||
}
|
||
|
||
if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(update); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendMenuController) DeleteMenu() {
|
||
if !c.requirePlatformCaller() {
|
||
return
|
||
}
|
||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete(); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功", "success": true}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) {
|
||
var payload menuPayload
|
||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||
_ = c.ServeJSON()
|
||
return nil, false
|
||
}
|
||
if needTitle && strings.TrimSpace(valueString(payload.Title, "")) == "" {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "菜单名称不能为空"}
|
||
_ = c.ServeJSON()
|
||
return nil, false
|
||
}
|
||
return &payload, true
|
||
}
|
||
|
||
func valueString(v *string, def string) string {
|
||
if v == nil {
|
||
return def
|
||
}
|
||
return *v
|
||
}
|
||
|
||
func valueInt8(v *int8, def int8) int8 {
|
||
if v == nil {
|
||
return def
|
||
}
|
||
return *v
|
||
}
|
||
|
||
func valueInt64(v *int64, def int64) int64 {
|
||
if v == nil {
|
||
return def
|
||
}
|
||
return *v
|
||
}
|
||
|
||
func ptrString(v string) *string { return &v }
|
||
func ptrInt8(v int8) *int8 { return &v }
|