✨ 新增几大中心功能
This commit is contained in:
223
server/source/system/menu_repair.go
Normal file
223
server/source/system/menu_repair.go
Normal file
@@ -0,0 +1,223 @@
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user