224 lines
5.4 KiB
Go
224 lines
5.4 KiB
Go
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
|
||
}
|
||
|