package admin import ( "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" "github.com/goravel/framework/support/str" "github.com/spf13/cast" apperrors "goravel/app/errors" "goravel/app/http/helpers" adminrequests "goravel/app/http/requests/admin" "goravel/app/http/response" "goravel/app/models" "goravel/app/services" ) type MenuController struct { treeService services.TreeService menuService services.MenuService } func NewMenuController() *MenuController { return &MenuController{ treeService: services.NewTreeServiceImpl(), menuService: services.NewMenuService(), } } // findMenuByID 根据ID查找菜单,如果不存在则返回错误响应 func (r *MenuController) findMenuByID(ctx http.Context, id uint) (*models.Menu, http.Response) { return response.FindByID[models.Menu](ctx, id, &response.FindByIDOptions{ NotFoundMessageKey: apperrors.ErrMenuNotFound.Code, }) } // Index 菜单列表(树形结构) func (r *MenuController) Index(ctx http.Context) http.Response { menus, err := r.treeService.BuildMenuTree(0) if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrQueryFailed.Code) } // 检查是否需要隐藏服务监控菜单 // 只有当配置值不为空且不等于 "0" 时才隐藏("0" 表示不隐藏) monitorHidden := facades.Config().GetString("admin.monitor_hidden", "") if monitorHidden != "" && monitorHidden != "0" { // 获取当前管理员ID adminValue := ctx.Value("admin") var adminID uint if adminValue != nil { if admin, ok := adminValue.(models.Admin); ok { adminID = admin.ID } else if adminPtr, ok := adminValue.(*models.Admin); ok && adminPtr != nil { adminID = adminPtr.ID } } // 检查是否是开发者管理员 developerIDsStr := facades.Config().GetString("admin.developer_ids", "2") isDeveloperAdmin := r.isDeveloperAdmin(adminID, developerIDsStr) // 如果不是开发者管理员,则过滤掉服务监控菜单 if !isDeveloperAdmin { menus = r.filterMonitorMenu(menus) } } // 检查是否需要隐藏开发工具菜单 enableDevTool := facades.Config().GetBool("app.enable_dev_tool") if !enableDevTool { menus = r.filterDevMenu(menus) } return response.Success(ctx, http.Json{ "menus": menus, }) } // Show 菜单详情 func (r *MenuController) Show(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") menu, resp := r.findMenuByID(ctx, id) if resp != nil { return resp } return response.Success(ctx, http.Json{ "menu": *menu, }) } // Store 创建菜单 func (r *MenuController) Store(ctx http.Context) http.Response { // 使用请求验证 var menuCreate adminrequests.MenuCreate errors, err := ctx.Request().ValidateRequest(&menuCreate) if err != nil { return response.Error(ctx, http.StatusBadRequest, err.Error()) } if errors != nil { return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All()) } // 检查 slug 是否已存在 exists, err := facades.Orm().Query().Model(&models.Menu{}).Where("slug", menuCreate.Slug).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrCreateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrMenuSlugExists.Code) } menu, err := r.menuService.Create( menuCreate.ParentID, menuCreate.Title, menuCreate.Slug, menuCreate.Icon, menuCreate.Path, menuCreate.Component, menuCreate.Permission, menuCreate.Type, menuCreate.Status, menuCreate.Sort, menuCreate.IsHidden, menuCreate.LinkType, menuCreate.OpenType, ) if err != nil { return response.ErrorWithLog(ctx, "menu", err, map[string]any{ "title": menuCreate.Title, "slug": menuCreate.Slug, }) } return response.Success(ctx, http.Json{ "menu": *menu, }) } func (r *MenuController) Update(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") menu, resp := r.findMenuByID(ctx, id) if resp != nil { return resp } // 使用请求验证 var menuUpdate adminrequests.MenuUpdate errors, err := ctx.Request().ValidateRequest(&menuUpdate) if err != nil { return response.Error(ctx, http.StatusBadRequest, err.Error()) } if errors != nil { return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All()) } // 使用 All() 方法检查字段是否存在 allInputs := ctx.Request().All() if _, exists := allInputs["title"]; exists { menu.Title = menuUpdate.Title } if _, exists := allInputs["slug"]; exists { // 检查 slug 是否已被其他菜单使用 exists, err := facades.Orm().Query().Model(&models.Menu{}).Where("slug", menuUpdate.Slug).Where("id != ?", id).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrUpdateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrMenuSlugExists.Code) } menu.Slug = menuUpdate.Slug } // 处理 parent_id,需要根据是否存在来更新,如果存在但为空或0,则设为0 if val, exists := allInputs["parent_id"]; exists { if val == nil { menu.ParentID = 0 } else { menu.ParentID = menuUpdate.ParentID } } if _, exists := allInputs["icon"]; exists { menu.Icon = menuUpdate.Icon } if _, exists := allInputs["path"]; exists { menu.Path = menuUpdate.Path } if _, exists := allInputs["component"]; exists { menu.Component = menuUpdate.Component } if _, exists := allInputs["permission"]; exists { menu.Permission = menuUpdate.Permission } if _, exists := allInputs["type"]; exists { menu.Type = menuUpdate.Type } if _, exists := allInputs["status"]; exists { menu.Status = menuUpdate.Status } if _, exists := allInputs["sort"]; exists { menu.Sort = menuUpdate.Sort } if _, exists := allInputs["is_hidden"]; exists { menu.IsHidden = menuUpdate.IsHidden } if _, exists := allInputs["link_type"]; exists { menu.LinkType = menuUpdate.LinkType } if _, exists := allInputs["open_type"]; exists { menu.OpenType = menuUpdate.OpenType } if err := r.menuService.Update(menu); err != nil { return response.ErrorWithLog(ctx, "menu", err, map[string]any{ "menu_id": menu.ID, }) } return response.Success(ctx, http.Json{ "menu": *menu, }) } func (r *MenuController) Destroy(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") menu, resp := r.findMenuByID(ctx, id) if resp != nil { return resp } hasChildren, err := r.treeService.HasMenuChildren(id) if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrQueryFailed.Code) } if hasChildren { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrMenuHasChildren.Code) } if err := r.menuService.Delete(menu); err != nil { return response.ErrorWithLog(ctx, "menu", err, map[string]any{ "menu_id": menu.ID, }) } return response.Success(ctx) } // isDeveloperAdmin 检查是否是开发者管理员 func (r *MenuController) isDeveloperAdmin(adminID uint, developerIDsStr string) bool { if developerIDsStr == "" { return false } // 解析开发者ID列表 parts := str.Of(developerIDsStr).Split(",") for _, part := range parts { part = str.Of(part).Trim().String() if !str.Of(part).IsEmpty() { if id := cast.ToUint(part); id > 0 && id == adminID { return true } } } return false } // filterMonitorMenu 递归过滤掉服务监控菜单 func (r *MenuController) filterMonitorMenu(menus []models.Menu) []models.Menu { var filteredMenus []models.Menu for _, menu := range menus { // 如果当前菜单不是服务监控菜单,则保留 if menu.Slug != "monitor" { // 递归过滤子菜单 if len(menu.Children) > 0 { menu.Children = r.filterMonitorMenu(menu.Children) } filteredMenus = append(filteredMenus, menu) } } return filteredMenus } // filterDevMenu 递归过滤掉开发工具菜单 func (r *MenuController) filterDevMenu(menus []models.Menu) []models.Menu { var filteredMenus []models.Menu for _, menu := range menus { // 如果当前菜单不是开发工具菜单,则保留 if menu.Slug != "dev" { // 递归过滤子菜单 if len(menu.Children) > 0 { menu.Children = r.filterDevMenu(menu.Children) } filteredMenus = append(filteredMenus, menu) } } return filteredMenus }