package system import ( "context" . "git.echol.cn/loser/Go-Web-Template/server/model/system" "git.echol.cn/loser/Go-Web-Template/server/service/system" "github.com/pkg/errors" "gorm.io/gorm" ) // 该 initializer 用于把「菜单 DB 数据」与「前端文件路由」对齐: // - 按 Name 精准匹配,幂等修正 path/parent/component,避免出现“前端显示与实际路由不一致”的别名情况。 // - 若缺少必要父级/子级菜单,则补齐到目标结构。 // // 注意:它不会删除菜单,只做修正/补齐,避免误伤线上已有配置。 const initOrderMenuRepair = initOrderMenu + 1 type menuRepair struct{} func init() { system.RegisterInit(initOrderMenuRepair, &menuRepair{}) } func (m *menuRepair) InitializerName() string { return "sys_base_menus_repair" } func (m *menuRepair) MigrateTable(ctx context.Context) (context.Context, error) { // 仅修复数据,不建表 return ctx, nil } func (m *menuRepair) TableCreated(ctx context.Context) bool { // 仅修复数据,不参与建表逻辑 return true } func (m *menuRepair) DataInserted(ctx context.Context) bool { // 每次都允许运行(幂等修复),因此返回 false 以触发 InitializeData return false } type menuSeed struct { level uint hidden bool parentName string // 空表示根节点 path string name string component string sort int title string icon string } func ensureMenu(tx *gorm.DB, seed menuSeed, parentID uint) (*SysBaseMenu, error) { var menu SysBaseMenu err := tx.Where("name = ?", seed.name).First(&menu).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } if errors.Is(err, gorm.ErrRecordNotFound) { menu = SysBaseMenu{ MenuLevel: seed.level, ParentId: parentID, Path: seed.path, Name: seed.name, Hidden: seed.hidden, Component: seed.component, Sort: seed.sort, Meta: Meta{ Title: seed.title, Icon: seed.icon, }, } if err := tx.Create(&menu).Error; err != nil { return nil, err } return &menu, nil } updates := map[string]any{ "menu_level": seed.level, "parent_id": parentID, "path": seed.path, "component": seed.component, } if err := tx.Model(&menu).Updates(updates).Error; err != nil { return nil, err } // 尽量不覆盖用户自定义标题/图标:仅在为空时补齐 metaUpdates := map[string]any{} if menu.Meta.Title == "" && seed.title != "" { metaUpdates["title"] = seed.title } if menu.Meta.Icon == "" && seed.icon != "" { metaUpdates["icon"] = seed.icon } if len(metaUpdates) > 0 { if err := tx.Model(&menu).Updates(metaUpdates).Error; err != nil { return nil, err } } return &menu, nil } func (m *menuRepair) InitializeData(ctx context.Context) (next context.Context, err error) { db, ok := ctx.Value("db").(*gorm.DB) if !ok { return ctx, system.ErrMissingDBContext } seeds := []menuSeed{ // 父级(若不存在则补齐) { level: 0, hidden: false, path: "userCenter", name: "userCenter", component: "features/discovery/ModuleLandingPage:userCenter", sort: 2, title: "用户中心", icon: "AppstoreOutlined", }, { level: 0, hidden: false, path: "opsConfig", name: "opsConfig", component: "features/discovery/ModuleLandingPage:opsConfig", sort: 6, title: "运营配置", icon: "SettingOutlined", }, { level: 0, hidden: false, path: "common", name: "common", component: "features/discovery/ModuleLandingPage:common", sort: 6, title: "公共能力", icon: "AppstoreOutlined", }, // 子级(目标规范路径) { level: 1, hidden: false, parentName: "userCenter", path: "appLoginLog", name: "appLoginLog", component: "features/appLoginLogs/AppLoginLogsPage", sort: 99, title: "登录日志", icon: "FileTextOutlined", }, { level: 1, hidden: false, parentName: "opsConfig", path: "sysParamChangeLog", name: "sysParamChangeLog", component: "features/params/SysParamChangeLogPage", sort: 99, title: "参数变更日志", icon: "SettingOutlined", }, { level: 1, hidden: false, parentName: "common", path: "upload", name: "upload", component: "features/media/MediaLibraryPage", sort: 1, title: "媒体库(上传下载)", icon: "UploadOutlined", }, } err = db.Transaction(func(tx *gorm.DB) error { byName := map[string]*SysBaseMenu{} // 先确保父级存在(包含 common 也一起修正 component/path) for _, seed := range seeds { if seed.level != 0 { continue } menu, err := ensureMenu(tx, seed, 0) if err != nil { return errors.Wrap(err, "ensure parent menu failed") } byName[seed.name] = menu } // 再确保子级存在并挂载到父级 for _, seed := range seeds { if seed.level == 0 { continue } parent := byName[seed.parentName] if parent == nil { return errors.Errorf("missing parent menu %s for %s", seed.parentName, seed.name) } if _, err := ensureMenu(tx, seed, parent.ID); err != nil { return errors.Wrap(err, "ensure child menu failed") } } return nil }) if err != nil { return ctx, err } return ctx, nil }