Files
Go-Web-Template/server/source/system/menu_repair.go
2026-04-23 15:29:07 +08:00

224 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}