feat: 添加独立其他设置菜单及有效端参数保存

This commit is contained in:
Yvan
2026-11-04 21:18:31 +08:00
parent c2e8b729be
commit ca9ce99b66
27 changed files with 805 additions and 9 deletions
+46 -1
View File
@@ -1,14 +1,59 @@
package business
import (
"gorm.io/gorm"
databusiness "kra/internal/data/business"
datasystem "kra/internal/data/system"
"kra/internal/modules/surface"
"kra/pkg/database/migration"
"kra/pkg/module"
)
func Definition() module.Definition {
return module.Definition{Name: "business", Migrations: databusiness.Migrations(), Surface: module.Surface{
settingsMenu := module.Menu{Name: "sw_OtherSettings", Path: "OtherSettings", Component: "view/sw/OtherSettings.vue", Title: "其他设置", Icon: databusiness.MenuIcon("sw_OtherSettings"), Sort: 5}
migrations := append(databusiness.Migrations(), migration.Step{ID: "202611040016_business_other_settings_surface", Migrate: func(db *gorm.DB) error {
// First-install menus are created by the normal seed flow.
if !db.Migrator().HasTable("sys_authorities") {
return nil
}
var admins int64
if err := db.Table("sys_authorities").Where("authority_id=888 AND deleted_at IS NULL").Count(&admins).Error; err != nil {
return err
}
if admins == 0 {
return nil
}
return db.Transaction(func(tx *gorm.DB) error {
menu := settingsMenu
var alert struct{ Sort int }
result := tx.Table("sys_base_menus").Select("sort").Where("name=? AND parent_id=0 AND deleted_at IS NULL", "sw_AlertCenter").Limit(1).Find(&alert)
if result.Error != nil {
return result.Error
}
if result.RowsAffected > 0 {
menu.Sort = alert.Sort + 1
}
var collisions int64
if err := tx.Table("sys_base_menus").Where("parent_id=0 AND deleted_at IS NULL AND name<>? AND sort=?", menu.Name, menu.Sort).Count(&collisions).Error; err != nil {
return err
}
if collisions > 0 {
if err := tx.Table("sys_base_menus").Where("parent_id=0 AND deleted_at IS NULL AND name<>? AND sort>=?", menu.Name, menu.Sort).Update("sort", gorm.Expr("sort + 1")).Error; err != nil {
return err
}
}
return datasystem.EnsureAdminSurface(tx, module.Surface{
Menus: []module.Menu{menu},
APIs: []module.API{
{Path: "/sw/settings/effectiveness", Method: "GET", Group: "其他设置", Description: "获取有效端判定参数"},
{Path: "/sw/settings/effectiveness", Method: "PUT", Group: "其他设置", Description: "保存有效端判定参数"},
},
}, 888)
})
}})
return module.Definition{Name: "business", Migrations: migrations, Surface: module.Surface{
Menus: []module.Menu{
settingsMenu,
{Name: "sw_RankingAnalysis", Path: "RankingAnalysis", ParentName: "sw_DataAnalysis", Component: "view/sw/RankingAnalysis.vue", Title: "排行榜", Icon: databusiness.MenuIcon("sw_RankingAnalysis"), Sort: 6},
{Name: "sw_ServerAnalysis", Path: "ServerAnalysis", ParentName: "sw_DataAnalysis", Component: "view/sw/ServerAnalysis.vue", Title: "区服分析", Icon: databusiness.MenuIcon("sw_ServerAnalysis"), Sort: 5},
{Name: "sw_ChannelDataStatistics", Path: "ChannelDataStatistics", ParentName: "sw_DataAnalysis", Component: "view/sw/ChannelDataStatistics.vue", Title: "渠道数据统计", Icon: databusiness.MenuIcon("sw_ChannelDataStatistics"), Sort: 1},
@@ -0,0 +1,66 @@
package business
import (
"path/filepath"
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
datasystem "kra/internal/data/system"
)
func TestOtherSettingsUpgradeAddsStandaloneMenuForSuperAdmin(t *testing.T) {
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "settings.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
pool, _ := db.DB()
t.Cleanup(func() { _ = pool.Close() })
for _, step := range datasystem.Migrations() {
if err := step.Migrate(db); err != nil {
t.Fatal(err)
}
}
if err := db.Exec("INSERT INTO sys_authorities(authority_id,authority_name,parent_id) VALUES(888,'admin',0),(901,'business',888)").Error; err != nil {
t.Fatal(err)
}
if err := db.Exec("INSERT INTO sys_base_menus(name,title,parent_id,sort) VALUES('sw_AlertCenter','预警中心',0,4),('permission','权限管理',0,5)").Error; err != nil {
t.Fatal(err)
}
for attempt := 0; attempt < 2; attempt++ {
for _, step := range Definition().Migrations {
if err := step.Migrate(db); err != nil {
t.Fatal(err)
}
}
}
var menu struct {
ID, ParentID uint
Sort int
Title, Component string
}
if err := db.Table("sys_base_menus").Where("name=?", "sw_OtherSettings").Take(&menu).Error; err != nil {
t.Fatal(err)
}
if menu.ParentID != 0 || menu.Sort != 5 || menu.Title != "其他设置" || menu.Component != "view/sw/OtherSettings.vue" {
t.Fatalf("必须是独立菜单: %+v", menu)
}
var permissionSort int
if err := db.Table("sys_base_menus").Select("sort").Where("name='permission'").Scan(&permissionSort).Error; err != nil || permissionSort != 6 {
t.Fatal("其他设置必须紧接预警中心,后续菜单只应顺延一次")
}
var roles []uint
if err := db.Table("sys_authority_menus").Where("sys_base_menu_id=?", menu.ID).Pluck("sys_authority_authority_id", &roles).Error; err != nil {
t.Fatal(err)
}
if len(roles) != 1 || roles[0] != 888 {
t.Fatalf("只有超管应收到菜单,重复升级不能重复授权: %v", roles)
}
var count int64
if err := db.Table("casbin_rule").Where("ptype='p' AND v1=? AND v0='888'", "/sw/settings/effectiveness").Count(&count).Error; err != nil || count != 2 {
t.Fatal("超管读写 API 授权缺失或重复")
}
if err := db.Table("casbin_rule").Where("ptype='p' AND v1=? AND v0<>'888'", "/sw/settings/effectiveness").Count(&count).Error; err != nil || count != 0 {
t.Fatal("不应向商务账号开放设置 API")
}
}
+4 -4
View File
@@ -25,16 +25,16 @@ func TestCatalogContainsBuiltInModulesInDependencyOrder(t *testing.T) {
t.Fatalf("definition[%d] = %q, want %q", index, got, name)
}
}
if got := catalog.MigrationSteps(); len(got) != 28 {
t.Fatalf("module migrations = %d, want 28", len(got))
if got := catalog.MigrationSteps(); len(got) != 30 {
t.Fatalf("module migrations = %d, want 30", len(got))
}
var menus, apis int
for _, definition := range catalog.Definitions {
menus += len(definition.Surface.Menus)
apis += len(definition.Surface.APIs)
}
if menus != 18 || apis != 69 {
t.Fatalf("admin surface = %d menus/%d APIs, want 18/69", menus, apis)
if menus != 19 || apis != 71 {
t.Fatalf("admin surface = %d menus/%d APIs, want 19/71", menus, apis)
}
if got := catalog.DefaultTimedTasks(); len(got) != 2 {
t.Fatalf("default timed tasks = %d, want 2", len(got))