package platform import ( "errors" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "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) { 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 } infra.Response.Success(ctx, request) } // UpdatePlatformRole 更新非内置平台角色。 func UpdatePlatformRole(ctx *gin.Context) { 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) { 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 } infra.Response.Success(ctx, menu) } func UpdatePlatformMenu(ctx *gin.Context) { 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"}) } type platformRoleMenusRequest struct { MenuIdentities []string `json:"menu_identities" binding:"required"` } // ReplacePlatformRoleMenus replaces every menu assignment for a role atomically. func ReplacePlatformRoleMenus(ctx *gin.Context) { 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}) } // UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。 func UpdatePlatformRoleStatus(ctx *gin.Context) { 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) { 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) { var list []models.PlatformMenu if err := impl.DBService.Order("sort_no asc, id asc").Find(&list).Error; 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) list, total, err := models.ListPlatfromAccount(page, size) if err != nil { infra.Response.Error(ctx, err) return } views := make([]gin.H, 0, len(list)) for _, item := range list { views = append(views, gin.H{"identity": item.Identity, "username": item.Username, "display_name": item.DisplayName, "avatar": item.Avatar, "phone_masked": maskPhone(item.Phone), "platform_role_code": item.PlatformRoleCode, "status": item.Status}) } 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:"max=64"` Phone string `json:"phone" binding:"max=32"` } type platfromAccountView struct { Identity string `json:"identity"` Username string `json:"username"` DisplayName string `json:"display_name"` Avatar string `json:"avatar"` PhoneMasked string `json:"phone_masked"` PlatformRoleCode string `json:"platform_role_code"` Status string `json:"status"` } func platformAccountView(account models.PlatfromAccount) platfromAccountView { return platfromAccountView{Identity: account.Identity, Username: account.Username, DisplayName: account.DisplayName, Avatar: account.Avatar, PhoneMasked: maskPhone(account.Phone), PlatformRoleCode: 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 } infra.Response.Success(ctx, platformAccountView(account)) } func CreatePlatfromAccount(ctx *gin.Context) { var request platfromAccountRequest if err := ctx.ShouldBindJSON(&request); err != nil { 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 account.PlatformRoleCode == "" { account.PlatformRoleCode = "root" } if err := impl.DBService.Create(&account).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, platformAccountView(account)) } 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:"max=64"` Phone string `json:"phone" binding:"max=32"` } if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } updateAllowedByIdentity(ctx, &models.PlatfromAccount{}, gin.H{"display_name": request.DisplayName, "avatar": request.Avatar, "platform_role_code": request.PlatformRoleCode, "phone": request.Phone}, []string{"display_name", "avatar", "platform_role_code", "phone"}) }