package platform import ( "errors" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/bsm-sdk/core/middleware" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var errSystemPlatformRole = errors.New("system platform roles cannot be modified") // ListPlatformRole 查询平台角色分页列表。 func ListPlatformRole(ctx *gin.Context) { listPage[models.PlatformRole](ctx) } // GetPlatformRole 查询一个平台角色。 func GetPlatformRole(ctx *gin.Context) { getByIdentity[models.PlatformRole](ctx) } // CreatePlatformRole 创建非内置平台角色。 func CreatePlatformRole(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request models.PlatformRole if err := ctx.ShouldBindJSON(&request); err != nil || request.RoleCode == "" || request.Name == "" || request.RoleCode == "root" { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } request.Entity = newEntity("enabled") request.IsSystem = false if request.DataScope == "" { request.DataScope = "global" } if err := impl.DBService.Create(&request).Error; err != nil { infra.Response.Error(ctx, err) return } respondCreatedResource(ctx, request) } // UpdatePlatformRole 更新非内置平台角色。 func UpdatePlatformRole(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request struct { Name string `json:"name" binding:"required,max=64"` DataScope string `json:"data_scope" binding:"required,max=32"` } if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var role models.PlatformRole if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { respondRecordError(ctx, err) return } if role.IsSystem { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } updateAllowedByIdentity(ctx, &models.PlatformRole{}, gin.H{"name": request.Name, "data_scope": request.DataScope}, []string{"name", "data_scope"}) } type platformMenuRequest struct { ParentIdentity string `json:"parent_identity"` MenuCode string `json:"menu_code" binding:"required,max=64"` Name string `json:"name" binding:"required,max=64"` Icon string `json:"icon" binding:"max=64"` Path string `json:"path" binding:"max=255"` SortNo int `json:"sort_no"` } type platformMenuView struct { Identity string `json:"identity"` ParentIdentity string `json:"parent_identity,omitempty"` MenuCode string `json:"menu_code"` Name string `json:"name"` Icon string `json:"icon"` Path string `json:"path"` SortNo int `json:"sort_no"` Status string `json:"status"` } func platformMenuViews(list []models.PlatformMenu) []platformMenuView { identities := make(map[uint64]string, len(list)) for _, item := range list { identities[item.ID] = item.Identity } views := make([]platformMenuView, 0, len(list)) for _, item := range list { views = append(views, platformMenuView{Identity: item.Identity, ParentIdentity: identities[item.ParentID], MenuCode: item.MenuCode, Name: item.Name, Icon: item.Icon, Path: item.Path, SortNo: item.SortNo, Status: item.Status}) } return views } func GetPlatformMenu(ctx *gin.Context) { var menu models.PlatformMenu if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&menu).Error; err != nil { respondRecordError(ctx, err) return } list := []models.PlatformMenu{menu} if menu.ParentID != 0 { var parent models.PlatformMenu if err := impl.DBService.Select("id", "identity").First(&parent, menu.ParentID).Error; err == nil { list = append(list, parent) } } views := platformMenuViews(list) infra.Response.Success(ctx, views[0]) } func CreatePlatformMenu(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request platformMenuRequest if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } parentID, err := resolveIdentityID(&models.PlatformMenu{}, request.ParentIdentity, false) if err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } menu := models.PlatformMenu{Entity: newEntity("enabled"), ParentID: parentID, MenuCode: request.MenuCode, Name: request.Name, Icon: request.Icon, Path: request.Path, SortNo: request.SortNo} if err := impl.DBService.Create(&menu).Error; err != nil { infra.Response.Error(ctx, err) return } respondCreatedResource(ctx, menu) } func UpdatePlatformMenu(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request platformMenuRequest if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } parentID, err := resolveIdentityID(&models.PlatformMenu{}, request.ParentIdentity, false) if err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } updateAllowedByIdentity(ctx, &models.PlatformMenu{}, gin.H{"parent_id": parentID, "name": request.Name, "icon": request.Icon, "path": request.Path, "sort_no": request.SortNo}, []string{"parent_id", "name", "icon", "path", "sort_no"}) } func UpdatePlatformMenuStatus(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } UpdateRecordStatus(ctx, &models.PlatformMenu{}) } func ArchivePlatformMenu(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } ArchiveRecord(ctx, &models.PlatformMenu{}) } type platformRoleMenusRequest struct { MenuIdentities []string `json:"menu_identities"` } // ReplacePlatformRoleMenus replaces every menu assignment for a role atomically. func ReplacePlatformRoleMenus(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request platformRoleMenusRequest if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } if err := impl.DBService.Transaction(func(transaction *gorm.DB) error { var role models.PlatformRole if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { return err } if role.IsSystem { return errSystemPlatformRole } var menus []models.PlatformMenu if len(request.MenuIdentities) > 0 { if err := transaction.Where("identity IN ?", request.MenuIdentities).Find(&menus).Error; err != nil { return err } if len(menus) != len(request.MenuIdentities) { return gorm.ErrRecordNotFound } } if err := transaction.Where("platform_role_id = ?", role.ID).Delete(&models.PlatformRoleMenuRelation{}).Error; err != nil { return err } for _, menu := range menus { relation := models.PlatformRoleMenuRelation{PlatformRoleID: role.ID, PlatformMenuID: menu.ID} if err := transaction.Create(&relation).Error; err != nil { return err } } return nil }); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { infra.Response.Error(ctx, errcode.ErrRecordNotFound) return } if errors.Is(err, errSystemPlatformRole) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"updated": true}) } // ListPlatformRoleMenuIdentities returns the current assignment for the role editor. func ListPlatformRoleMenuIdentities(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var role models.PlatformRole if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { respondRecordError(ctx, err) return } var identities []string if err := impl.DBService.Model(&models.PlatformMenu{}). Joins("JOIN platform_role_menu_relation ON platform_role_menu_relation.platform_menu_id = platform_menu.id"). Where("platform_role_menu_relation.platform_role_id = ?", role.ID). Order("platform_menu.sort_no asc, platform_menu.id asc"). Pluck("platform_menu.identity", &identities).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"menu_identities": identities}) } // UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。 func UpdatePlatformRoleStatus(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request struct { Status string `json:"status" binding:"required,max=32"` } if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var role models.PlatformRole if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { respondRecordError(ctx, err) return } if role.IsSystem { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } updateAllowedByIdentity(ctx, &models.PlatformRole{}, gin.H{"status": request.Status}, []string{"status"}) } // ArchivePlatformRole 归档非内置平台角色,系统角色始终受保护。 func ArchivePlatformRole(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var role models.PlatformRole if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { respondRecordError(ctx, err) return } if role.IsSystem { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } updateAllowedByIdentity(ctx, &models.PlatformRole{}, archiveValues(), []string{"status"}) } // ListPlatformMenu 返回菜单树构建所需的有序菜单列表。 func ListPlatformMenu(ctx *gin.Context) { claims, err := middleware.ParseAuth(ctx) if err != nil { infra.Response.Error(ctx, err) return } list, err := loadPlatformMenus(claims.Role) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"total": len(list), "list": platformMenuViews(list)}) } // ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。 func ListPlatfromAccount(ctx *gin.Context) { page, size := pageSize(ctx) var list []models.PlatfromAccount var total int64 query := applyKeywordFilter(ctx, impl.DBService.Model(&models.PlatfromAccount{}), &models.PlatfromAccount{}) if err := query.Count(&total).Error; err != nil { infra.Response.Error(ctx, err) return } if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { infra.Response.Error(ctx, err) return } views := make([]map[string]any, 0, len(list)) for _, item := range list { view := platformAccountView(item) protectPreciseLocation(ctx, &models.PlatfromAccount{}, view) views = append(views, view) } infra.Response.Success(ctx, gin.H{"total": total, "list": views}) } type platfromAccountRequest struct { Username string `json:"username" binding:"required,max=64"` Password string `json:"password" binding:"required,min=8,max=128"` DisplayName string `json:"display_name" binding:"max=64"` Avatar string `json:"avatar" binding:"max=512"` PlatformRoleCode string `json:"platform_role_code" binding:"required,max=64"` Phone string `json:"phone" binding:"max=32"` } func platformAccountView(account models.PlatfromAccount) map[string]any { return map[string]any{ "identity": account.Identity, "username": account.Username, "display_name": account.DisplayName, "avatar": account.Avatar, "phone": account.Phone, "platform_role_code": account.PlatformRoleCode, "status": account.Status, } } func GetPlatfromAccount(ctx *gin.Context) { var account models.PlatfromAccount if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&account).Error; err != nil { respondRecordError(ctx, err) return } view := platformAccountView(account) infra.Response.Success(ctx, protectPreciseLocation(ctx, &models.PlatfromAccount{}, view)) } func CreatePlatfromAccount(ctx *gin.Context) { if !requirePlatformRoot(ctx) { return } var request platfromAccountRequest if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } if !isAssignablePlatformRole(request.PlatformRoleCode) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } hash, err := passwordHash(request.Password) if err != nil { infra.Response.Error(ctx, err) return } account := models.PlatfromAccount{Entity: newEntity("enabled"), Username: request.Username, DisplayName: request.DisplayName, Avatar: request.Avatar, PasswordHash: hash, PlatformRoleCode: request.PlatformRoleCode, Phone: request.Phone} if err := impl.DBService.Create(&account).Error; err != nil { infra.Response.Error(ctx, err) return } view := platformAccountView(account) infra.Response.Success(ctx, protectPreciseLocation(ctx, &models.PlatfromAccount{}, view)) } func UpdatePlatfromAccount(ctx *gin.Context) { var request struct { DisplayName string `json:"display_name" binding:"max=64"` Avatar string `json:"avatar" binding:"max=512"` PlatformRoleCode *string `json:"platform_role_code" binding:"omitempty,max=64"` Phone string `json:"phone" binding:"max=32"` } if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } values := gin.H{"display_name": request.DisplayName, "avatar": request.Avatar, "phone": request.Phone} if request.PlatformRoleCode != nil { if !requirePlatformRoot(ctx) { return } if !isAssignablePlatformRole(*request.PlatformRoleCode) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } values["platform_role_code"] = *request.PlatformRoleCode } updateAllowedByIdentity(ctx, &models.PlatfromAccount{}, values, []string{"display_name", "avatar", "platform_role_code", "phone"}) } func isAssignablePlatformRole(roleCode string) bool { if roleCode == "" || roleCode == "root" { return false } var role models.PlatformRole return impl.DBService.Where("role_code = ? AND status = ? AND is_system = ?", roleCode, "enabled", false).First(&role).Error == nil }