Files
yunzerwebsiteallinone/go/controllers/backend_menu.go
T
2026-09-09 17:02:29 +08:00

502 lines
15 KiB
Go

package controllers
import (
"encoding/json"
"server/models"
"strconv"
"strings"
beego "github.com/beego/beego/v2/server/web"
)
type BackendMenuController struct {
beego.Controller
}
type AdminMenuController = BackendMenuController
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"`
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
}
func (c *BackendMenuController) GetMenu() {
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()
}
func (c *BackendMenuController) GetBackendMenu() {
// 确保租户用户 group_id 列存在(老库兼容)
models.EnsureTenantUserGroupColumn()
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
}
backendMenus := filterMenusByView(menus, 2)
// 按当前登录用户角色过滤可见功能菜单:
// 角色 rights 为空(或未分配角色)→ 全权限,返回全部后端菜单;
// 否则仅返回角色允许访问的菜单(含其祖先节点,保证树结构完整)。
idStr := c.Ctx.Input.Param(":id")
uid, _ := strconv.ParseUint(idStr, 10, 64)
if uid > 0 {
var tu models.SystemTenantUser
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("id", uid).
Filter("delete_time__isnull", true).
One(&tu); e == nil && tu.GroupID > 0 {
var role models.AdminRole
if re := models.Orm.QueryTable(new(models.AdminRole)).
Filter("id", tu.GroupID).
Filter("cid", 2).
One(&role); re == nil {
backendMenus = filterMenusByRights(backendMenus, role.Rights)
}
}
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(backendMenus, 0)}
_ = c.ServeJSON()
}
// parseRightsToSet 将角色 rights 解析为菜单 ID 集合(兼容 JSON 数组 / 逗号分隔)
func parseRightsToSet(raw string) map[uint64]bool {
set := map[uint64]bool{}
var arr []string
if err := json.Unmarshal([]byte(raw), &arr); err == nil {
for _, s := range arr {
s = strings.TrimSpace(s)
if s == "" {
continue
}
if n, e := strconv.ParseUint(s, 10, 64); e == nil {
set[n] = true
}
}
return set
}
for _, s := range strings.Split(raw, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
if n, e := strconv.ParseUint(s, 10, 64); e == nil {
set[n] = true
}
}
return set
}
// filterMenusByRights 按角色 rights(菜单 ID 集合)过滤菜单,并补全祖先节点,保证树结构完整。
// rights 为空(nil / 空串)→ 原样返回(全权限)。
func filterMenusByRights(menus []models.SystemMenu, rights *string) []models.SystemMenu {
if rights == nil || strings.TrimSpace(*rights) == "" {
return menus
}
allowed := parseRightsToSet(*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()
}
func (c *BackendMenuController) GetAllMenus() {
var menus []models.SystemMenu
cid, _ := c.GetInt("cid")
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 获取可分配给角色的菜单树
// 过滤规则(均在服务端完成):
// 1. cid:按菜单所属端过滤(1=平台端 2=租户端,不传则不过滤)
// 2. status=1:仅已启用的菜单
// 3. is_visible != 0:仅已显示的菜单(为空视为显示)
//
// 父级菜单被过滤时,其子菜单一并不可分配(菜单树按 pid 构建,父级缺失则整条分支不会返回)。
// GET /backend/assignableMenus?cid=2
func (c *BackendMenuController) GetAssignableMenus() {
var menus []models.SystemMenu
cid, _ := c.GetInt("cid")
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)
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"`
Type int8 `json:"type"`
Permission string `json:"permission,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),
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() {
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() {
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, "")),
}
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() {
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),
"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() {
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 }