package repository import ( "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/security" "gorm.io/gorm" ) var defaultPermissions = []model.Permission{ {Name: "总览查看", Code: "overview:view", Kind: "menu"}, {Name: "用户查看", Code: "user:view", Kind: "menu"}, {Name: "用户创建", Code: "user:create", Kind: "button"}, {Name: "用户更新", Code: "user:update", Kind: "button"}, {Name: "用户状态", Code: "user:status", Kind: "button"}, {Name: "用户重置密码", Code: "user:reset-password", Kind: "button"}, {Name: "用户导出", Code: "user:export", Kind: "button"}, {Name: "App 用户查看", Code: "app-user:view", Kind: "menu"}, {Name: "App 用户更新", Code: "app-user:update", Kind: "button"}, {Name: "App 用户状态", Code: "app-user:status", Kind: "button"}, {Name: "App 用户设置密码", Code: "app-user:password", Kind: "button"}, {Name: "房间查看", Code: "room:view", Kind: "menu"}, {Name: "房间更新", Code: "room:update", Kind: "button"}, {Name: "房间删除", Code: "room:delete", Kind: "button"}, {Name: "APP 配置查看", Code: "app-config:view", Kind: "menu"}, {Name: "APP 配置更新", Code: "app-config:update", Kind: "button"}, {Name: "资源查看", Code: "resource:view", Kind: "menu"}, {Name: "资源创建", Code: "resource:create", Kind: "button"}, {Name: "资源更新", Code: "resource:update", Kind: "button"}, {Name: "资源组查看", Code: "resource-group:view", Kind: "menu"}, {Name: "资源组创建", Code: "resource-group:create", Kind: "button"}, {Name: "资源组更新", Code: "resource-group:update", Kind: "button"}, {Name: "资源赠送查看", Code: "resource-grant:view", Kind: "menu"}, {Name: "资源赠送创建", Code: "resource-grant:create", Kind: "button"}, {Name: "礼物查看", Code: "gift:view", Kind: "menu"}, {Name: "礼物创建", Code: "gift:create", Kind: "button"}, {Name: "礼物更新", Code: "gift:update", Kind: "button"}, {Name: "礼物状态", Code: "gift:status", Kind: "button"}, {Name: "国家查看", Code: "country:view", Kind: "menu"}, {Name: "国家创建", Code: "country:create", Kind: "button"}, {Name: "国家更新", Code: "country:update", Kind: "button"}, {Name: "国家状态", Code: "country:status", Kind: "button"}, {Name: "区域查看", Code: "region:view", Kind: "menu"}, {Name: "区域创建", Code: "region:create", Kind: "button"}, {Name: "区域更新", Code: "region:update", Kind: "button"}, {Name: "区域状态", Code: "region:status", Kind: "button"}, {Name: "主播查看", Code: "host:view", Kind: "menu"}, {Name: "Agency 查看", Code: "agency:view", Kind: "menu"}, {Name: "Agency 创建", Code: "agency:create", Kind: "button"}, {Name: "Agency 状态", Code: "agency:status", Kind: "button"}, {Name: "BD 查看", Code: "bd:view", Kind: "menu"}, {Name: "BD 创建", Code: "bd:create", Kind: "button"}, {Name: "BD 更新", Code: "bd:update", Kind: "button"}, {Name: "币商查看", Code: "coin-seller:view", Kind: "menu"}, {Name: "币商创建", Code: "coin-seller:create", Kind: "button"}, {Name: "币商更新", Code: "coin-seller:update", Kind: "button"}, {Name: "角色查看", Code: "role:view", Kind: "menu"}, {Name: "角色创建", Code: "role:create", Kind: "button"}, {Name: "角色更新", Code: "role:update", Kind: "button"}, {Name: "角色删除", Code: "role:delete", Kind: "button"}, {Name: "角色授权", Code: "role:permission", Kind: "button"}, {Name: "角色数据权限", Code: "role:data-scope", Kind: "button"}, {Name: "角色管理", Code: "role:manage", Kind: "button", Description: "兼容旧版本的角色管理权限"}, {Name: "权限查看", Code: "permission:view", Kind: "menu"}, {Name: "权限创建", Code: "permission:create", Kind: "button"}, {Name: "权限更新", Code: "permission:update", Kind: "button"}, {Name: "权限删除", Code: "permission:delete", Kind: "button"}, {Name: "权限同步", Code: "permission:sync", Kind: "button"}, {Name: "菜单查看", Code: "menu:view", Kind: "menu"}, {Name: "菜单创建", Code: "menu:create", Kind: "button"}, {Name: "菜单更新", Code: "menu:update", Kind: "button"}, {Name: "菜单删除", Code: "menu:delete", Kind: "button"}, {Name: "菜单排序", Code: "menu:sort", Kind: "button"}, {Name: "菜单显隐", Code: "menu:visible", Kind: "button"}, {Name: "日志查看", Code: "log:view", Kind: "menu"}, {Name: "日志导出", Code: "log:export", Kind: "button"}, {Name: "通知查看", Code: "notification:view", Kind: "menu"}, {Name: "通知已读", Code: "notification:read", Kind: "button"}, {Name: "通知全部已读", Code: "notification:read-all", Kind: "button"}, {Name: "通知删除", Code: "notification:delete", Kind: "button"}, {Name: "任务查看", Code: "job:view", Kind: "menu"}, {Name: "任务取消", Code: "job:cancel", Kind: "button"}, {Name: "导出任务创建", Code: "export:create", Kind: "button"}, {Name: "文件上传", Code: "upload:create", Kind: "button"}, } var deprecatedPermissionCodes = []string{"service:view", "service:create", "service:update", "service:operate"} var deprecatedMenuCodes = []string{"services"} func (s *Store) seedPermissions() error { return s.syncDefaultPermissions(s.db) } func (s *Store) seedMenus() error { systemID := uint(0) logID := uint(0) geoID := uint(0) hostOrgID := uint(0) appUsersID := uint(0) appConfigID := uint(0) resourceID := uint(0) roomsID := uint(0) menus := []model.Menu{ {Title: "总览", Code: "overview", Path: "/overview", Icon: "dashboard", PermissionCode: "overview:view", Sort: 10, Visible: true}, {Title: "系统管理", Code: "system", Path: "", Icon: "settings", PermissionCode: "", Sort: 30, Visible: true}, {Title: "日志审计", Code: "logs", Path: "", Icon: "history", PermissionCode: "", Sort: 40, Visible: true}, {Title: "通知中心", Code: "notifications", Path: "/notifications", Icon: "notifications", PermissionCode: "notification:view", Sort: 50, Visible: true}, {Title: "用户管理", Code: "app-users", Path: "", Icon: "users", PermissionCode: "", Sort: 60, Visible: true}, {Title: "房间管理", Code: "rooms", Path: "", Icon: "room", PermissionCode: "", Sort: 65, Visible: true}, {Title: "APP配置", Code: "app-config", Path: "", Icon: "settings", PermissionCode: "", Sort: 66, Visible: true}, {Title: "资源管理", Code: "resources", Path: "", Icon: "inventory", PermissionCode: "", Sort: 67, Visible: true}, {Title: "地区管理", Code: "geo", Path: "", Icon: "map", PermissionCode: "", Sort: 70, Visible: true}, {Title: "团队管理", Code: "host-org", Path: "", Icon: "network", PermissionCode: "", Sort: 80, Visible: true}, {Title: "任务中心", Code: "jobs", Path: "/jobs", Icon: "task", PermissionCode: "job:view", Sort: 90, Visible: true}, } for _, menu := range menus { syncedMenu, err := s.syncDefaultMenu(s.db, menu) if err != nil { return err } if syncedMenu.Code == "system" { systemID = syncedMenu.ID } if syncedMenu.Code == "logs" { logID = syncedMenu.ID } if syncedMenu.Code == "geo" { geoID = syncedMenu.ID } if syncedMenu.Code == "host-org" { hostOrgID = syncedMenu.ID } if syncedMenu.Code == "app-users" { appUsersID = syncedMenu.ID } if syncedMenu.Code == "app-config" { appConfigID = syncedMenu.ID } if syncedMenu.Code == "resources" { resourceID = syncedMenu.ID } if syncedMenu.Code == "rooms" { roomsID = syncedMenu.ID } } children := []model.Menu{ {ParentID: &systemID, Title: "用户管理", Code: "system-users", Path: "/system/users", Icon: "users", PermissionCode: "user:view", Sort: 31, Visible: true}, {ParentID: &systemID, Title: "角色配置", Code: "system-roles", Path: "/system/roles", Icon: "shield", PermissionCode: "role:view", Sort: 32, Visible: true}, {ParentID: &systemID, Title: "菜单权限", Code: "system-menus", Path: "/system/menus", Icon: "menu", PermissionCode: "menu:view", Sort: 33, Visible: true}, {ParentID: &logID, Title: "登录日志", Code: "logs-login", Path: "/logs/login", Icon: "login", PermissionCode: "log:view", Sort: 41, Visible: true}, {ParentID: &logID, Title: "操作日志", Code: "logs-operations", Path: "/logs/operations", Icon: "receipt", PermissionCode: "log:view", Sort: 42, Visible: true}, {ParentID: &appUsersID, Title: "用户列表", Code: "app-user-list", Path: "/app/users", Icon: "users", PermissionCode: "app-user:view", Sort: 60, Visible: true}, {ParentID: &roomsID, Title: "房间列表", Code: "room-list", Path: "/rooms", Icon: "room", PermissionCode: "room:view", Sort: 65, Visible: true}, {ParentID: &appConfigID, Title: "H5配置", Code: "app-config-h5", Path: "/app-config/h5", Icon: "settings", PermissionCode: "app-config:view", Sort: 66, Visible: true}, {ParentID: &resourceID, Title: "资源列表", Code: "resource-list", Path: "/resources", Icon: "inventory", PermissionCode: "resource:view", Sort: 67, Visible: true}, {ParentID: &resourceID, Title: "资源组列表", Code: "resource-group-list", Path: "/resource-groups", Icon: "category", PermissionCode: "resource-group:view", Sort: 68, Visible: true}, {ParentID: &resourceID, Title: "礼物列表", Code: "gift-list", Path: "/gifts", Icon: "gift", PermissionCode: "gift:view", Sort: 69, Visible: true}, {ParentID: &resourceID, Title: "资源赠送", Code: "resource-grant-list", Path: "/resource-grants", Icon: "send", PermissionCode: "resource-grant:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true}, {ParentID: &hostOrgID, Title: "Agency 管理", Code: "host-org-agencies", Path: "/host/agencies", Icon: "apartment", PermissionCode: "agency:view", Sort: 80, Visible: true}, {ParentID: &hostOrgID, Title: "BD Leader 管理", Code: "host-org-bd-leaders", Path: "/host/bd-leaders", Icon: "shield", PermissionCode: "bd:view", Sort: 81, Visible: true}, {ParentID: &hostOrgID, Title: "BD 管理", Code: "host-org-bds", Path: "/host/bds", Icon: "team", PermissionCode: "bd:view", Sort: 82, Visible: true}, {ParentID: &hostOrgID, Title: "主播管理", Code: "host-org-hosts", Path: "/host/hosts", Icon: "users", PermissionCode: "host:view", Sort: 83, Visible: true}, {ParentID: &hostOrgID, Title: "币商管理", Code: "host-org-coin-sellers", Path: "/host/coin-sellers", Icon: "wallet", PermissionCode: "coin-seller:view", Sort: 84, Visible: true}, } for _, menu := range children { if _, err := s.syncDefaultMenu(s.db, menu); err != nil { return err } } return nil } func (s *Store) seedRoles() error { var permissions []model.Permission if err := s.db.Find(&permissions).Error; err != nil { return err } roles := []model.Role{ {Name: "平台管理员", Code: "platform-admin", Description: "拥有管理后台全部权限"}, {Name: "运维管理员", Code: "ops-admin", Description: "管理用户状态和后台操作"}, {Name: "审计员", Code: "auditor", Description: "查看日志和审计记录"}, {Name: "只读用户", Code: "readonly", Description: "只读查看后台数据"}, } for _, role := range roles { if err := s.db.Where("code = ?", role.Code).FirstOrCreate(&role).Error; err != nil { return err } if role.Code == "platform-admin" { if err := s.db.Model(&role).Association("Permissions").Replace(permissions); err != nil { return err } continue } // 预置角色只在没有任何权限时写入默认模板,避免后续人工调整被启动种子覆盖。 if s.db.Model(&role).Association("Permissions").Count() == 0 { if err := s.replaceRolePermissionsByCode(&role, defaultRolePermissionCodes(role.Code)); err != nil { return err } continue } if err := s.appendRolePermissionsByCode(s.db, &role, defaultRolePermissionMigrationCodes(role.Code)); err != nil { return err } } return nil } func (s *Store) seedUsers(cfg config.Config) error { var count int64 if err := s.db.Model(&model.User{}).Count(&count).Error; err != nil { return err } if count > 0 { return nil } if cfg.Bootstrap.Password == "" { return nil } hash, err := security.HashPassword(cfg.Bootstrap.Password) if err != nil { return err } var role model.Role if err := s.db.Where("code = ?", "platform-admin").First(&role).Error; err != nil { return err } user := model.User{ Username: cfg.Bootstrap.Username, Name: cfg.Bootstrap.Name, PasswordHash: hash, Team: cfg.Bootstrap.Team, Status: model.UserStatusActive, MFAEnabled: false, } if err := s.db.Create(&user).Error; err != nil { return err } return s.db.Model(&user).Association("Roles").Replace([]model.Role{role}) } func (s *Store) seedNotifications() error { var count int64 if err := s.db.Model(&model.Notification{}).Count(&count).Error; err != nil { return err } if count > 0 { return nil } items := []model.Notification{ {Title: "权限种子已同步", Content: "默认权限和菜单已写入后台库", Level: "info"}, {Title: "审计日志已启用", Content: "写操作会记录操作日志", Level: "info"}, {Title: "导出任务已启用", Content: "任务中心会处理后台导出任务", Level: "info"}, } return s.db.Create(&items).Error } func (s *Store) pruneDeprecatedDefaults() error { return s.db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("code IN ? OR permission_code IN ?", deprecatedMenuCodes, deprecatedPermissionCodes).Delete(&model.Menu{}).Error; err != nil { return err } var permissions []model.Permission if err := tx.Where("code IN ?", deprecatedPermissionCodes).Find(&permissions).Error; err != nil { return err } if len(permissions) == 0 { return nil } permissionIDs := make([]uint, 0, len(permissions)) for _, permission := range permissions { permissionIDs = append(permissionIDs, permission.ID) } if err := tx.Table("admin_role_permissions").Where("permission_id IN ?", permissionIDs).Delete(nil).Error; err != nil { return err } return tx.Where("id IN ?", permissionIDs).Delete(&model.Permission{}).Error }) } func (s *Store) syncDefaultPermissions(tx *gorm.DB) error { for _, definition := range defaultPermissions { permission := model.Permission{} // 权限种子是前后端契约来源;已存在时同步名称、类型和说明,避免按钮文案和授权页长期漂移。 result := tx.Where("code = ?", definition.Code).Limit(1).Find(&permission) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { if err := tx.Create(&definition).Error; err != nil { return err } continue } if err := tx.Model(&permission).Updates(map[string]interface{}{ "name": definition.Name, "kind": definition.Kind, "description": definition.Description, }).Error; err != nil { return err } } return nil } func (s *Store) syncDefaultMenu(tx *gorm.DB, definition model.Menu) (model.Menu, error) { menu := model.Menu{} // 菜单种子同样是前后端契约来源;已存在时同步标题、路径和权限,避免后台菜单文案长期漂移。 result := tx.Where("code = ?", definition.Code).Limit(1).Find(&menu) if result.Error != nil { return model.Menu{}, result.Error } if result.RowsAffected == 0 { if err := tx.Create(&definition).Error; err != nil { return model.Menu{}, err } return definition, nil } if err := tx.Model(&menu).Updates(map[string]interface{}{ "parent_id": definition.ParentID, "title": definition.Title, "path": definition.Path, "icon": definition.Icon, "permission_code": definition.PermissionCode, "sort": definition.Sort, "visible": definition.Visible, }).Error; err != nil { return model.Menu{}, err } return menu, nil } func (s *Store) replaceRolePermissionsByCode(role *model.Role, codes []string) error { var permissions []model.Permission if len(codes) > 0 { if err := s.db.Where("code IN ?", codes).Find(&permissions).Error; err != nil { return err } } return s.db.Model(role).Association("Permissions").Replace(permissions) } func (s *Store) appendRolePermissionsByCode(tx *gorm.DB, role *model.Role, codes []string) error { if len(codes) == 0 { return nil } var current model.Role if err := tx.Preload("Permissions").First(¤t, role.ID).Error; err != nil { return err } existing := make(map[string]struct{}, len(current.Permissions)) for _, permission := range current.Permissions { existing[permission.Code] = struct{}{} } missingCodes := make([]string, 0, len(codes)) for _, code := range codes { if _, ok := existing[code]; !ok { missingCodes = append(missingCodes, code) } } if len(missingCodes) == 0 { return nil } var permissions []model.Permission if err := tx.Where("code IN ?", missingCodes).Find(&permissions).Error; err != nil { return err } if len(permissions) == 0 { return nil } return tx.Model(role).Association("Permissions").Append(permissions) } func defaultRolePermissionCodes(code string) []string { switch code { case "ops-admin": return []string{ "overview:view", "user:view", "user:status", "app-user:view", "app-user:update", "app-user:status", "app-user:password", "room:view", "room:update", "room:delete", "app-config:view", "app-config:update", "resource:view", "resource:create", "resource:update", "resource-group:view", "resource-group:create", "resource-group:update", "resource-grant:view", "resource-grant:create", "gift:view", "gift:create", "gift:update", "gift:status", "country:view", "country:create", "country:update", "country:status", "region:view", "region:create", "region:update", "region:status", "host:view", "agency:view", "agency:create", "agency:status", "bd:view", "bd:create", "bd:update", "coin-seller:view", "coin-seller:create", "coin-seller:update", "log:view", "notification:view", "notification:read", "job:view", "job:cancel", "export:create", "upload:create", } case "auditor": return []string{"overview:view", "log:view", "user:view", "app-user:view", "room:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "role:view", "permission:view", "job:view"} case "readonly": return []string{ "overview:view", "user:view", "app-user:view", "room:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "country:view", "region:view", "host:view", "agency:view", "bd:view", "coin-seller:view", "role:view", "permission:view", "menu:view", "log:view", "notification:view", "job:view", } default: return nil } } func defaultRolePermissionMigrationCodes(code string) []string { switch code { case "ops-admin": return []string{ "app-user:update", "app-user:status", "app-user:password", "room:view", "room:update", "room:delete", "app-config:view", "app-config:update", "resource:view", "resource:create", "resource:update", "resource-group:view", "resource-group:create", "resource-group:update", "resource-grant:view", "resource-grant:create", "gift:view", "gift:create", "gift:update", "gift:status", "upload:create", "country:view", "country:create", "country:update", "country:status", "region:view", "region:create", "region:update", "region:status", "coin-seller:view", "coin-seller:create", "coin-seller:update", } case "auditor", "readonly": return []string{"room:view", "app-config:view", "resource:view", "resource-group:view", "resource-grant:view", "gift:view", "coin-seller:view"} default: return nil } }