package data import ( "context" "errors" "kra/internal/biz" "gorm.io/gorm" ) type menuRepo struct{ data *Data } func NewMenuRepo(data *Data) biz.MenuRepo { return &menuRepo{data: data} } func menuFromPO(po menuPO) *biz.Menu { return &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType} } func menuToPO(v *biz.Menu) menuPO { return menuPO{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.ActiveName, KeepAlive: v.KeepAlive, DefaultMenu: v.DefaultMenu, Title: v.Title, Icon: v.Icon, CloseTab: v.CloseTab, TransitionType: v.TransitionType} } func replaceMenuRelations(tx *gorm.DB, menu *biz.Menu) error { var oldButtonIDs []uint if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menu.ID).Pluck("id", &oldButtonIDs).Error; err != nil { return err } if len(oldButtonIDs) > 0 { if err := tx.Where("sys_base_menu_btn_id IN ?", oldButtonIDs).Delete(&authorityButtonPO{}).Error; err != nil { return err } } if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuButtonPO{}).Error; err != nil { return err } if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuParameterPO{}).Error; err != nil { return err } buttons := make([]menuButtonPO, 0, len(menu.Buttons)) for _, button := range menu.Buttons { buttons = append(buttons, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menu.ID}) } if len(buttons) > 0 { if err := tx.Create(&buttons).Error; err != nil { return err } } parameters := make([]menuParameterPO, 0, len(menu.Parameters)) for _, parameter := range menu.Parameters { parameters = append(parameters, menuParameterPO{MenuID: menu.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } if len(parameters) > 0 { return tx.Create(¶meters).Error } return nil } func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var count int64 if err := tx.Model(&menuPO{}).Where("name = ?", v.Name).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在重复name,请修改name") } if v.ParentID != 0 { var parent menuPO if err := tx.First(&parent, v.ParentID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("父菜单不存在") } return err } if err := tx.Model(&menuPO{}).Where("parent_id = ?", v.ParentID).Count(&count).Error; err != nil { return err } if count == 0 { if err := tx.Model(&authorityPO{}).Where("default_router = ?", parent.Name).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限") } if err := tx.Where("sys_base_menu_id = ?", v.ParentID).Delete(&authorityMenuPO{}).Error; err != nil { return err } } } po := menuToPO(v) if err := tx.Create(&po).Error; err != nil { return err } v.ID = po.ID return replaceMenuRelations(tx, v) }) } func (r *menuRepo) Update(ctx context.Context, v *biz.Menu) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var old menuPO if err := tx.First(&old, v.ID).Error; err != nil { return err } var count int64 if old.Name != v.Name { if err := tx.Model(&menuPO{}).Where("id <> ? AND name = ?", v.ID, v.Name).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在相同name修改失败") } } if err := replaceMenuRelations(tx, v); err != nil { return err } values := map[string]any{"parent_id": v.ParentID, "path": v.Path, "name": v.Name, "hidden": v.Hidden, "component": v.Component, "sort": v.Sort, "active_name": v.ActiveName, "keep_alive": v.KeepAlive, "default_menu": v.DefaultMenu, "title": v.Title, "icon": v.Icon, "close_tab": v.CloseTab, "transition_type": v.TransitionType} return tx.Model(&old).Updates(values).Error }) } func (r *menuRepo) Delete(ctx context.Context, id uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var count int64 if err := tx.Model(&menuPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("此菜单存在子菜单不可删除") } var menu menuPO if err := tx.First(&menu, id).Error; err != nil { return errors.New("记录不存在") } if err := tx.Model(&authorityPO{}).Where("default_router = ?", menu.Name).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("此菜单有角色正在作为首页,不可删除") } var buttonIDs []uint if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", id).Pluck("id", &buttonIDs).Error; err != nil { return err } if err := tx.Where("sys_menu_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil { return err } if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuButtonPO{}).Error; err != nil { return err } if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuParameterPO{}).Error; err != nil { return err } if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { return err } return tx.Delete(&menuPO{}, id).Error }) } func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error { var parameters []menuParameterPO if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(¶meters).Error; err != nil { return err } menu.Parameters = make([]*biz.MenuParameter, 0, len(parameters)) for _, parameter := range parameters { menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } var buttons []menuButtonPO if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(&buttons).Error; err != nil { return err } menu.Buttons = make([]*biz.MenuButton, 0, len(buttons)) for _, button := range buttons { menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: button.MenuID}) } return nil } func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) { var po menuPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } menu := menuFromPO(po) if err := r.loadRelations(ctx, menu); err != nil { return nil, err } return menu, nil } func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) { var pos []menuPO db := r.data.gormDB.WithContext(ctx).Model(&menuPO{}).Order("sort,id") config := r.data.runtime.Admin() if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { var authority authorityPO if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil { return nil, err } if authority.ParentID != nil && *authority.ParentID != 0 { db = db.Where("id IN (?)", r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Select("sys_base_menu_id").Where("sys_authority_authority_id = ?", actor.AuthorityID)) } } if err := db.Find(&pos).Error; err != nil { return nil, err } out := make([]*biz.Menu, 0, len(pos)) for _, po := range pos { out = append(out, menuFromPO(po)) } if len(out) == 0 { return out, nil } byID := make(map[uint]*biz.Menu, len(out)) ids := make([]uint, 0, len(out)) for _, menu := range out { byID[menu.ID] = menu ids = append(ids, menu.ID) menu.Parameters = []*biz.MenuParameter{} menu.Buttons = []*biz.MenuButton{} } var parameters []menuParameterPO if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Order("id").Find(¶meters).Error; err != nil { return nil, err } for _, parameter := range parameters { menu := byID[parameter.MenuID] menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } var buttons []menuButtonPO if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Order("id").Find(&buttons).Error; err != nil { return nil, err } for _, button := range buttons { menu := byID[button.MenuID] menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: button.MenuID}) } return out, nil } func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error { if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, id); err != nil { return err } config := r.data.runtime.Admin() if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { var authority authorityPO if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil { return err } if authority.ParentID != nil && *authority.ParentID != 0 { var allowedIDs []uint if err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", actor.AuthorityID).Pluck("sys_base_menu_id", &allowedIDs).Error; err != nil { return err } allowed := make(map[uint]bool, len(allowedIDs)) for _, menuID := range allowedIDs { allowed[menuID] = true } for _, menuID := range ids { if !allowed[menuID] { return errors.New("添加失败,请勿跨级操作") } } } } return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { return err } links := make([]authorityMenuPO, 0, len(ids)) for _, menuID := range ids { links = append(links, authorityMenuPO{SysAuthorityAuthorityID: id, SysBaseMenuID: menuID}) } if len(links) > 0 { return tx.Create(&links).Error } return nil }) } func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error return ids, err } func (r *menuRepo) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error return ids, err } func (r *menuRepo) DefaultRouterRoleIDs(ctx context.Context, id uint) ([]uint, error) { var menu menuPO if err := r.data.gormDB.WithContext(ctx).First(&menu, id).Error; err != nil { return nil, err } var ids []uint err := r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("default_router = ?", menu.Name).Pluck("authority_id", &ids).Error return ids, err } func (r *menuRepo) SetMenuRoles(ctx context.Context, id uint, ids []uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { return err } links := make([]authorityMenuPO, 0, len(ids)) for _, authorityID := range ids { links = append(links, authorityMenuPO{SysAuthorityAuthorityID: authorityID, SysBaseMenuID: id}) } if len(links) > 0 { return tx.Create(&links).Error } return nil }) }