Files
go-admin/app/admin/models/sys_menu.go
T
zhangwenjian 7fadb4b585 feat✨: give the seeded rows a natural key to be found by
Seeding needs something to look for before it inserts, or a retry writes
a second copy of everything it already wrote. sys_api already had one in
(app_code, path, action). sys_menu had nothing usable: menu_name is
pascalCase(appCode) + pascalCase(code), which is not injective -
"list-all", "listAll" and "list_all" all become "ListAll" - so the
original code cannot be recovered from it. Hence a new column.

seed_code is nullable, against this repository's habit of NOT NULL
DEFAULT '' for a new column, and deliberately. Every row that predates
it has no meaningful value, and under a unique index an empty string
collides with every other empty string while NULL collides with nothing.
The convention exists because deleted_at's nullability broke a unique
index; here nullability is what makes one possible.

The unique index on sys_api cannot simply be created: a live database is
known to hold historical duplicates - the demo site had eighteen. The
migration looks first and refuses while naming the offending rows,
rather than letting CREATE UNIQUE INDEX fail with a constraint error
that names none. Same shape as 1786700003000's refuseOnDuplicates.

Run against SQLite, MySQL 8.0 and PostgreSQL 15, including the CONCAT
duplicate check, which had only ever been executed by SQLite's driver.
2026-09-08 19:26:42 +08:00

70 lines
3.2 KiB
Go

package models
import "go-admin/common/models"
type SysMenu struct {
MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"`
MenuName string `json:"menuName" gorm:"size:128;"`
Title string `json:"title" gorm:"size:128;"`
Icon string `json:"icon" gorm:"size:128;"`
Path string `json:"path" gorm:"size:128;"`
Paths string `json:"paths" gorm:"size:128;"`
MenuType string `json:"menuType" gorm:"size:1;"`
Action string `json:"action" gorm:"size:16;"`
Permission string `json:"permission" gorm:"size:255;"`
ParentId int `json:"parentId" gorm:"size:11;"`
NoCache bool `json:"noCache" gorm:"size:8;"`
Breadcrumb string `json:"breadcrumb" gorm:"size:255;"`
Component string `json:"component" gorm:"size:255;"`
Sort int `json:"sort" gorm:"size:4;"`
Visible string `json:"visible" gorm:"size:1;"`
IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"`
SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"`
Apis []int `json:"apis" gorm:"-"`
DataScope string `json:"dataScope" gorm:"-"`
Params string `json:"params" gorm:"-"`
RoleId int `gorm:"-"`
Children []SysMenu `json:"children,omitempty" gorm:"-"`
IsSelect bool `json:"is_select" gorm:"-"`
// AppCode identifies which application's seed.SeedMenus call wrote this
// row; empty for the host's own built-in menus. NOT NULL DEFAULT '' for
// the same reason sys_migration.app_code is (see contract/models.Migration):
// AutoMigrate adding this column to an existing table leaves every
// pre-existing row reading back as "" rather than NULL.
AppCode string `json:"appCode" gorm:"type:varchar(64);not null;default:'';index:idx_sys_menu_app_code;comment:AppCode"`
// SeedCode is the raw seed.MenuSpec.Code this row was created from, kept
// so seedMenuTree can ask "did I already write this node" without
// relying on MenuName's PascalCase concatenation, which is not
// injective (see design doc §1.6). Nullable, unlike AppCode: every row
// seed.SeedMenus writes sets a real value, but every pre-existing row -
// the host's own hand-placed menus, and every app-seeded row written
// before this column existed - has none, and there is no way to
// backfill one that means anything. NULL is what lets an unbounded
// number of those coexist under the same app_code without tripping the
// unique index below: the database never treats two NULLs as equal, so
// only rows that do carry a real code participate in the uniqueness
// check at all.
SeedCode *string `json:"seedCode" gorm:"size:64;uniqueIndex:uk_sys_menu_app_seed_code_del;comment:raw MenuSpec.Code, null for rows not written through SeedMenus"`
models.ControlBy
models.ModelTime
}
type SysMenuSlice []SysMenu
func (x SysMenuSlice) Len() int { return len(x) }
func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort }
func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (*SysMenu) TableName() string {
return "sys_menu"
}
func (e *SysMenu) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysMenu) GetId() interface{} {
return e.MenuId
}