refactor platform account and access models
This commit is contained in:
@@ -50,7 +50,7 @@ func InitPlatformAccess(database *gorm.DB) error {
|
|||||||
if err := database.Where("menu_code = ?", menu.MenuCode).FirstOrCreate(&menu).Error; err != nil {
|
if err := database.Where("menu_code = ?", menu.MenuCode).FirstOrCreate(&menu).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
relation := models.PlatformRoleMenuRelation{PlatformRoleID: rootRole.ID, PlatformMenuID: menu.ID}
|
relation := models.PlatformRoleMenu{PlatformRoleID: rootRole.ID, PlatformMenuID: menu.ID}
|
||||||
if err := database.Where("platform_role_id = ? AND platform_menu_id = ?", rootRole.ID, menu.ID).FirstOrCreate(&relation).Error; err != nil {
|
if err := database.Where("platform_role_id = ? AND platform_menu_id = ?", rootRole.ID, menu.ID).FirstOrCreate(&relation).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func InitPlatformAccess(database *gorm.DB) error {
|
|||||||
|
|
||||||
// InitPlatformRoot 幂等创建平台总后台 root 账号。
|
// InitPlatformRoot 幂等创建平台总后台 root 账号。
|
||||||
func InitPlatformRoot(database *gorm.DB) error {
|
func InitPlatformRoot(database *gorm.DB) error {
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
err := database.Where("username = ?", PlatformRootUsername).First(&account).Error
|
err := database.Where("username = ?", PlatformRootUsername).First(&account).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -74,7 +74,7 @@ func InitPlatformRoot(database *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
account = models.PlatfromAccount{
|
account = models.PlatformAccount{
|
||||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled"},
|
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled"},
|
||||||
Username: PlatformRootUsername,
|
Username: PlatformRootUsername,
|
||||||
DisplayName: "平台根管理员",
|
DisplayName: "平台根管理员",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestInitPlatformAccessSeedsEveryProtectedFrontendDomain(t *testing.T) {
|
|||||||
WithArgs(domain, 1).
|
WithArgs(domain, 1).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "status", "version", "parent_id", "menu_code", "name", "icon", "path", "sort_no"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "status", "version", "parent_id", "menu_code", "name", "icon", "path", "sort_no"}).
|
||||||
AddRow(menuID, domain+"-menu", "enabled", 1, uint64(0), domain, domain, "", "/"+domain, index))
|
AddRow(menuID, domain+"-menu", "enabled", 1, uint64(0), domain, domain, "", "/"+domain, index))
|
||||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "platform_role_menu_relation" WHERE platform_role_id = $1 AND platform_menu_id = $2 ORDER BY "platform_role_menu_relation"."id" LIMIT $3`)).
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "platform_role_menu" WHERE platform_role_id = $1 AND platform_menu_id = $2 ORDER BY "platform_role_menu"."id" LIMIT $3`)).
|
||||||
WithArgs(uint64(1), menuID, 1).
|
WithArgs(uint64(1), menuID, 1).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "platform_role_id", "platform_menu_id"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "platform_role_id", "platform_menu_id"}).
|
||||||
AddRow(uint64(index+100), uint64(1), menuID))
|
AddRow(uint64(index+100), uint64(1), menuID))
|
||||||
|
|||||||
41
backend/api/internal/logic/common/platform_access.go
Normal file
41
backend/api/internal/logic/common/platform_access.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequirePlatformRoot restricts sensitive platform configuration operations to the root role.
|
||||||
|
func RequirePlatformRoot(ctx *gin.Context) bool {
|
||||||
|
claims, err := middleware.ParseAuth(ctx)
|
||||||
|
if err == nil && claims.Role == "root" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPlatformMenus returns the menus granted to one platform role.
|
||||||
|
func LoadPlatformMenus(roleCode string) ([]models.PlatformMenu, error) {
|
||||||
|
var menus []models.PlatformMenu
|
||||||
|
if roleCode == "root" {
|
||||||
|
err := impl.DBService.Order("sort_no asc, id asc").Find(&menus).Error
|
||||||
|
return menus, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var role models.PlatformRole
|
||||||
|
if err := impl.DBService.Where("role_code = ? AND status = ?", roleCode, "enabled").First(&role).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err := impl.DBService.
|
||||||
|
Select("platform_menu.*").
|
||||||
|
Joins("JOIN platform_role_menu ON platform_role_menu.platform_menu_id = platform_menu.id").
|
||||||
|
Where("platform_role_menu.platform_role_id = ? AND platform_menu.status = ?", role.ID, "enabled").
|
||||||
|
Order("sort_no asc, id asc").
|
||||||
|
Find(&menus).Error
|
||||||
|
return menus, err
|
||||||
|
}
|
||||||
@@ -133,7 +133,7 @@ func isCreatedResponseField(key string) bool {
|
|||||||
func ProtectPreciseLocation(ctx *gin.Context, model, value any) any {
|
func ProtectPreciseLocation(ctx *gin.Context, model, value any) any {
|
||||||
maskPersonalName := reflect.TypeOf(model) == reflect.TypeOf(&models.UserAccount{}) ||
|
maskPersonalName := reflect.TypeOf(model) == reflect.TypeOf(&models.UserAccount{}) ||
|
||||||
reflect.TypeOf(model) == reflect.TypeOf(&models.StaffAccount{})
|
reflect.TypeOf(model) == reflect.TypeOf(&models.StaffAccount{})
|
||||||
maskDisplayName := reflect.TypeOf(model) == reflect.TypeOf(&models.PlatfromAccount{})
|
maskDisplayName := reflect.TypeOf(model) == reflect.TypeOf(&models.PlatformAccount{})
|
||||||
ProtectPublicFields(value, maskPersonalName, maskDisplayName, HasPreciseLocationScope(ctx))
|
ProtectPublicFields(value, maskPersonalName, maskDisplayName, HasPreciseLocationScope(ctx))
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"git.apinb.com/bsm-sdk/core/infra"
|
"git.apinb.com/bsm-sdk/core/infra"
|
||||||
"git.apinb.com/bsm-sdk/core/middleware"
|
"git.apinb.com/bsm-sdk/core/middleware"
|
||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||||||
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -38,7 +39,7 @@ func Login(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
err := impl.DBService.Where("username = ?", strings.TrimSpace(request.Username)).First(&account).Error
|
err := impl.DBService.Where("username = ?", strings.TrimSpace(request.Username)).First(&account).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
@@ -85,12 +86,12 @@ func CurrentProfile(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
menus, err := loadPlatformMenus(account.PlatformRoleCode)
|
menus, err := common.LoadPlatformMenus(account.PlatformRoleCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
||||||
return
|
return
|
||||||
@@ -133,7 +134,7 @@ func ChangePassword(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package platform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.apinb.com/bsm-sdk/core/errcode"
|
"git.apinb.com/bsm-sdk/core/errcode"
|
||||||
"git.apinb.com/bsm-sdk/core/infra"
|
"git.apinb.com/bsm-sdk/core/infra"
|
||||||
@@ -10,11 +11,83 @@ import (
|
|||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errSystemPlatformRole = errors.New("system platform roles cannot be modified")
|
var errSystemPlatformRole = errors.New("system platform roles cannot be modified")
|
||||||
|
|
||||||
|
const platformMenusContextKey = "platform_authorized_menus"
|
||||||
|
|
||||||
|
func platformMenuAllowsPath(menus []models.PlatformMenu, requestPath string) bool {
|
||||||
|
marker := "/platform/v1/"
|
||||||
|
index := strings.Index(requestPath, marker)
|
||||||
|
if index < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
relative := strings.Trim(requestPath[index+len(marker):], "/")
|
||||||
|
resource := strings.Split(relative, "/")[0]
|
||||||
|
domain := platformRouteDomain(resource)
|
||||||
|
for _, menu := range menus {
|
||||||
|
if menu.MenuCode == domain {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
menuPath := strings.Trim(menu.Path, "/")
|
||||||
|
if menuPath != "" && strings.Split(menuPath, "/")[0] == domain {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRouteDomain(resource string) string {
|
||||||
|
prefix := strings.Split(resource, "_")[0]
|
||||||
|
switch prefix {
|
||||||
|
case "product":
|
||||||
|
return "device"
|
||||||
|
case "gasorder":
|
||||||
|
return "delivery"
|
||||||
|
case "fin":
|
||||||
|
return "finance"
|
||||||
|
case "cms":
|
||||||
|
return "content"
|
||||||
|
case "cs":
|
||||||
|
return "customer_service"
|
||||||
|
case "platform":
|
||||||
|
return "platform"
|
||||||
|
default:
|
||||||
|
return prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirePlatformMenuAccess enforces role-menu authorization after JWT authentication.
|
||||||
|
func RequirePlatformMenuAccess() gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
if strings.Contains(ctx.Request.URL.Path, "/platform/v1/auth/") {
|
||||||
|
ctx.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims, err := middleware.ParseAuth(ctx)
|
||||||
|
if err != nil {
|
||||||
|
infra.Response.Error(ctx, err)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if claims.Role == "root" {
|
||||||
|
ctx.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menus, err := common.LoadPlatformMenus(claims.Role)
|
||||||
|
if err != nil || !platformMenuAllowsPath(menus, ctx.Request.URL.Path) {
|
||||||
|
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Set(platformMenusContextKey, menus)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ListPlatformRole 查询平台角色分页列表。
|
// ListPlatformRole 查询平台角色分页列表。
|
||||||
func ListPlatformRole(ctx *gin.Context) { common.ListPage[models.PlatformRole](ctx) }
|
func ListPlatformRole(ctx *gin.Context) { common.ListPage[models.PlatformRole](ctx) }
|
||||||
|
|
||||||
@@ -23,7 +96,7 @@ func GetPlatformRole(ctx *gin.Context) { common.GetByIdentity[models.PlatformRol
|
|||||||
|
|
||||||
// CreatePlatformRole 创建非内置平台角色。
|
// CreatePlatformRole 创建非内置平台角色。
|
||||||
func CreatePlatformRole(ctx *gin.Context) {
|
func CreatePlatformRole(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request models.PlatformRole
|
var request models.PlatformRole
|
||||||
@@ -45,7 +118,7 @@ func CreatePlatformRole(ctx *gin.Context) {
|
|||||||
|
|
||||||
// UpdatePlatformRole 更新非内置平台角色。
|
// UpdatePlatformRole 更新非内置平台角色。
|
||||||
func UpdatePlatformRole(ctx *gin.Context) {
|
func UpdatePlatformRole(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request struct {
|
var request struct {
|
||||||
@@ -118,7 +191,7 @@ func GetPlatformMenu(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlatformMenu(ctx *gin.Context) {
|
func CreatePlatformMenu(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request platformMenuRequest
|
var request platformMenuRequest
|
||||||
@@ -140,7 +213,7 @@ func CreatePlatformMenu(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePlatformMenu(ctx *gin.Context) {
|
func UpdatePlatformMenu(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request platformMenuRequest
|
var request platformMenuRequest
|
||||||
@@ -157,14 +230,14 @@ func UpdatePlatformMenu(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePlatformMenuStatus(ctx *gin.Context) {
|
func UpdatePlatformMenuStatus(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.UpdateRecordStatus(ctx, &models.PlatformMenu{})
|
common.UpdateRecordStatus(ctx, &models.PlatformMenu{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArchivePlatformMenu(ctx *gin.Context) {
|
func ArchivePlatformMenu(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.ArchiveRecord(ctx, &models.PlatformMenu{})
|
common.ArchiveRecord(ctx, &models.PlatformMenu{})
|
||||||
@@ -176,7 +249,7 @@ type platformRoleMenusRequest struct {
|
|||||||
|
|
||||||
// ReplacePlatformRoleMenus replaces every menu assignment for a role atomically.
|
// ReplacePlatformRoleMenus replaces every menu assignment for a role atomically.
|
||||||
func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request platformRoleMenusRequest
|
var request platformRoleMenusRequest
|
||||||
@@ -201,11 +274,11 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
|||||||
return gorm.ErrRecordNotFound
|
return gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := transaction.Where("platform_role_id = ?", role.ID).Delete(&models.PlatformRoleMenuRelation{}).Error; err != nil {
|
if err := transaction.Where("platform_role_id = ?", role.ID).Delete(&models.PlatformRoleMenu{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, menu := range menus {
|
for _, menu := range menus {
|
||||||
relation := models.PlatformRoleMenuRelation{PlatformRoleID: role.ID, PlatformMenuID: menu.ID}
|
relation := models.PlatformRoleMenu{PlatformRoleID: role.ID, PlatformMenuID: menu.ID}
|
||||||
if err := transaction.Create(&relation).Error; err != nil {
|
if err := transaction.Create(&relation).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -228,7 +301,7 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
|||||||
|
|
||||||
// ListPlatformRoleMenuIdentities returns the current assignment for the role editor.
|
// ListPlatformRoleMenuIdentities returns the current assignment for the role editor.
|
||||||
func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var role models.PlatformRole
|
var role models.PlatformRole
|
||||||
@@ -238,8 +311,8 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
var identities []string
|
var identities []string
|
||||||
if err := impl.DBService.Model(&models.PlatformMenu{}).
|
if err := impl.DBService.Model(&models.PlatformMenu{}).
|
||||||
Joins("JOIN platform_role_menu_relation ON platform_role_menu_relation.platform_menu_id = platform_menu.id").
|
Joins("JOIN platform_role_menu ON platform_role_menu.platform_menu_id = platform_menu.id").
|
||||||
Where("platform_role_menu_relation.platform_role_id = ?", role.ID).
|
Where("platform_role_menu.platform_role_id = ?", role.ID).
|
||||||
Order("platform_menu.sort_no asc, platform_menu.id asc").
|
Order("platform_menu.sort_no asc, platform_menu.id asc").
|
||||||
Pluck("platform_menu.identity", &identities).Error; err != nil {
|
Pluck("platform_menu.identity", &identities).Error; err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
@@ -250,7 +323,7 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
|||||||
|
|
||||||
// UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。
|
// UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。
|
||||||
func UpdatePlatformRoleStatus(ctx *gin.Context) {
|
func UpdatePlatformRoleStatus(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request struct {
|
var request struct {
|
||||||
@@ -274,7 +347,7 @@ func UpdatePlatformRoleStatus(ctx *gin.Context) {
|
|||||||
|
|
||||||
// ArchivePlatformRole 归档非内置平台角色,系统角色始终受保护。
|
// ArchivePlatformRole 归档非内置平台角色,系统角色始终受保护。
|
||||||
func ArchivePlatformRole(ctx *gin.Context) {
|
func ArchivePlatformRole(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var role models.PlatformRole
|
var role models.PlatformRole
|
||||||
@@ -286,7 +359,7 @@ func ArchivePlatformRole(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.UpdateAllowedByIdentity(ctx, &models.PlatformRole{}, archiveValues(), []string{"status"})
|
common.UpdateAllowedByIdentity(ctx, &models.PlatformRole{}, gin.H{"status": "archived"}, []string{"status"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。
|
// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。
|
||||||
@@ -296,7 +369,7 @@ func ListPlatformMenu(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list, err := loadPlatformMenus(claims.Role)
|
list, err := common.LoadPlatformMenus(claims.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
@@ -304,12 +377,12 @@ func ListPlatformMenu(ctx *gin.Context) {
|
|||||||
infra.Response.Success(ctx, gin.H{"total": len(list), "list": platformMenuViews(list)})
|
infra.Response.Success(ctx, gin.H{"total": len(list), "list": platformMenuViews(list)})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。
|
// ListPlatformAccount 查询平台账号列表,手机号在展示层脱敏。
|
||||||
func ListPlatfromAccount(ctx *gin.Context) {
|
func ListPlatformAccount(ctx *gin.Context) {
|
||||||
page, size := common.PageSize(ctx)
|
page, size := common.PageSize(ctx)
|
||||||
var list []models.PlatfromAccount
|
var list []models.PlatformAccount
|
||||||
var total int64
|
var total int64
|
||||||
query := common.ApplyKeywordFilter(ctx, impl.DBService.Model(&models.PlatfromAccount{}), &models.PlatfromAccount{})
|
query := common.ApplyKeywordFilter(ctx, impl.DBService.Model(&models.PlatformAccount{}), &models.PlatformAccount{})
|
||||||
if err := query.Count(&total).Error; err != nil {
|
if err := query.Count(&total).Error; err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
@@ -321,13 +394,13 @@ func ListPlatfromAccount(ctx *gin.Context) {
|
|||||||
views := make([]map[string]any, 0, len(list))
|
views := make([]map[string]any, 0, len(list))
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
view := platformAccountView(item)
|
view := platformAccountView(item)
|
||||||
common.ProtectPreciseLocation(ctx, &models.PlatfromAccount{}, view)
|
common.ProtectPreciseLocation(ctx, &models.PlatformAccount{}, view)
|
||||||
views = append(views, view)
|
views = append(views, view)
|
||||||
}
|
}
|
||||||
infra.Response.Success(ctx, gin.H{"total": total, "list": views})
|
infra.Response.Success(ctx, gin.H{"total": total, "list": views})
|
||||||
}
|
}
|
||||||
|
|
||||||
type platfromAccountRequest struct {
|
type platformAccountRequest struct {
|
||||||
Username string `json:"username" binding:"required,max=64"`
|
Username string `json:"username" binding:"required,max=64"`
|
||||||
Password string `json:"password" binding:"required,min=8,max=128"`
|
Password string `json:"password" binding:"required,min=8,max=128"`
|
||||||
DisplayName string `json:"display_name" binding:"max=64"`
|
DisplayName string `json:"display_name" binding:"max=64"`
|
||||||
@@ -336,7 +409,7 @@ type platfromAccountRequest struct {
|
|||||||
Phone string `json:"phone" binding:"max=32"`
|
Phone string `json:"phone" binding:"max=32"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func platformAccountView(account models.PlatfromAccount) map[string]any {
|
func platformAccountView(account models.PlatformAccount) map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"identity": account.Identity, "username": account.Username,
|
"identity": account.Identity, "username": account.Username,
|
||||||
"display_name": account.DisplayName, "avatar": account.Avatar, "phone": account.Phone,
|
"display_name": account.DisplayName, "avatar": account.Avatar, "phone": account.Phone,
|
||||||
@@ -344,21 +417,21 @@ func platformAccountView(account models.PlatfromAccount) map[string]any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlatfromAccount(ctx *gin.Context) {
|
func GetPlatformAccount(ctx *gin.Context) {
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&account).Error; err != nil {
|
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&account).Error; err != nil {
|
||||||
common.RespondRecordError(ctx, err)
|
common.RespondRecordError(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := platformAccountView(account)
|
view := platformAccountView(account)
|
||||||
infra.Response.Success(ctx, common.ProtectPreciseLocation(ctx, &models.PlatfromAccount{}, view))
|
infra.Response.Success(ctx, common.ProtectPreciseLocation(ctx, &models.PlatformAccount{}, view))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlatfromAccount(ctx *gin.Context) {
|
func CreatePlatformAccount(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request platfromAccountRequest
|
var request platformAccountRequest
|
||||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||||
return
|
return
|
||||||
@@ -367,21 +440,21 @@ func CreatePlatfromAccount(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hash, err := passwordHash(request.Password)
|
hash, err := platformPasswordHash(request.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account := models.PlatfromAccount{Entity: common.NewEntity("enabled"), Username: request.Username, DisplayName: request.DisplayName, Avatar: request.Avatar, PasswordHash: hash, PlatformRoleCode: request.PlatformRoleCode, Phone: request.Phone}
|
account := models.PlatformAccount{Entity: common.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 {
|
if err := impl.DBService.Create(&account).Error; err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := platformAccountView(account)
|
view := platformAccountView(account)
|
||||||
infra.Response.Success(ctx, common.ProtectPreciseLocation(ctx, &models.PlatfromAccount{}, view))
|
infra.Response.Success(ctx, common.ProtectPreciseLocation(ctx, &models.PlatformAccount{}, view))
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePlatfromAccount(ctx *gin.Context) {
|
func UpdatePlatformAccount(ctx *gin.Context) {
|
||||||
var request struct {
|
var request struct {
|
||||||
DisplayName string `json:"display_name" binding:"max=64"`
|
DisplayName string `json:"display_name" binding:"max=64"`
|
||||||
Avatar string `json:"avatar" binding:"max=512"`
|
Avatar string `json:"avatar" binding:"max=512"`
|
||||||
@@ -394,7 +467,7 @@ func UpdatePlatfromAccount(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
values := gin.H{"display_name": request.DisplayName, "avatar": request.Avatar, "phone": request.Phone}
|
values := gin.H{"display_name": request.DisplayName, "avatar": request.Avatar, "phone": request.Phone}
|
||||||
if request.PlatformRoleCode != nil {
|
if request.PlatformRoleCode != nil {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isAssignablePlatformRole(*request.PlatformRoleCode) {
|
if !isAssignablePlatformRole(*request.PlatformRoleCode) {
|
||||||
@@ -403,7 +476,28 @@ func UpdatePlatfromAccount(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
values["platform_role_code"] = *request.PlatformRoleCode
|
values["platform_role_code"] = *request.PlatformRoleCode
|
||||||
}
|
}
|
||||||
common.UpdateAllowedByIdentity(ctx, &models.PlatfromAccount{}, values, []string{"display_name", "avatar", "platform_role_code", "phone"})
|
common.UpdateAllowedByIdentity(ctx, &models.PlatformAccount{}, values, []string{"display_name", "avatar", "platform_role_code", "phone"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlatformAccountStatus updates a platform account lifecycle state.
|
||||||
|
func UpdatePlatformAccountStatus(ctx *gin.Context) {
|
||||||
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.UpdateRecordStatus(ctx, &models.PlatformAccount{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchivePlatformAccount archives a platform account without deleting audit history.
|
||||||
|
func ArchivePlatformAccount(ctx *gin.Context) {
|
||||||
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ArchiveRecord(ctx, &models.PlatformAccount{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformPasswordHash(password string) (string, error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
return string(hash), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAssignablePlatformRole(roleCode string) bool {
|
func isAssignablePlatformRole(roleCode string) bool {
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package platform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const platformMenusContextKey = "platform_authorized_menus"
|
|
||||||
|
|
||||||
func loadPlatformMenus(roleCode string) ([]models.PlatformMenu, error) {
|
|
||||||
var menus []models.PlatformMenu
|
|
||||||
if roleCode == "root" {
|
|
||||||
err := impl.DBService.Order("sort_no asc, id asc").Find(&menus).Error
|
|
||||||
return menus, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var role models.PlatformRole
|
|
||||||
if err := impl.DBService.Where("role_code = ? AND status = ?", roleCode, "enabled").First(&role).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err := impl.DBService.
|
|
||||||
Select("platform_menu.*").
|
|
||||||
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 = ? AND platform_menu.status = ?", role.ID, "enabled").
|
|
||||||
Order("sort_no asc, id asc").
|
|
||||||
Find(&menus).Error
|
|
||||||
return menus, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func platformMenuAllowsPath(menus []models.PlatformMenu, requestPath string) bool {
|
|
||||||
marker := "/platform/v1/"
|
|
||||||
index := strings.Index(requestPath, marker)
|
|
||||||
if index < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
relative := strings.Trim(requestPath[index+len(marker):], "/")
|
|
||||||
resource := strings.Split(relative, "/")[0]
|
|
||||||
domain := platformRouteDomain(resource)
|
|
||||||
for _, menu := range menus {
|
|
||||||
if menu.MenuCode == domain {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
menuPath := strings.Trim(menu.Path, "/")
|
|
||||||
if menuPath != "" && strings.Split(menuPath, "/")[0] == domain {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func platformRouteDomain(resource string) string {
|
|
||||||
prefix := strings.Split(resource, "_")[0]
|
|
||||||
switch prefix {
|
|
||||||
case "product":
|
|
||||||
return "device"
|
|
||||||
case "gasorder":
|
|
||||||
return "delivery"
|
|
||||||
case "fin":
|
|
||||||
return "finance"
|
|
||||||
case "cms":
|
|
||||||
return "content"
|
|
||||||
case "cs":
|
|
||||||
return "customer_service"
|
|
||||||
case "platfrom", "platform":
|
|
||||||
return "platform"
|
|
||||||
default:
|
|
||||||
return prefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequirePlatformMenuAccess enforces role-menu authorization after JWT authentication.
|
|
||||||
func RequirePlatformMenuAccess() gin.HandlerFunc {
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
if strings.Contains(ctx.Request.URL.Path, "/platform/v1/auth/") {
|
|
||||||
ctx.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
claims, err := middleware.ParseAuth(ctx)
|
|
||||||
if err != nil {
|
|
||||||
infra.Response.Error(ctx, err)
|
|
||||||
ctx.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if claims.Role == "root" {
|
|
||||||
ctx.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
menus, err := loadPlatformMenus(claims.Role)
|
|
||||||
if err != nil || !platformMenuAllowsPath(menus, ctx.Request.URL.Path) {
|
|
||||||
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
|
||||||
ctx.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Set(platformMenusContextKey, menus)
|
|
||||||
ctx.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requirePlatformRoot(ctx *gin.Context) bool {
|
|
||||||
claims, err := middleware.ParseAuth(ctx)
|
|
||||||
if err == nil && claims.Role == "root" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -354,7 +354,7 @@ func productOperator(ctx *gin.Context) (string, string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
if err := impl.DBService.Select("display_name").Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
if err := impl.DBService.Select("display_name").Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||||
return claims.Identity, ""
|
return claims.Identity, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func ExpectedResources() []ResourceContract {
|
|||||||
resourceContract("gasorder", "gasorder_track", ReadOnly, "list"), resourceContract("gasorder", "gasorder_track_point", ReadOnly, "list"), resourceContract("gasorder", "gasorder_confirm", ReadOnly, "list"), resourceContract("gasorder", "gasorder_payment", ReadOnly, "list"),
|
resourceContract("gasorder", "gasorder_track", ReadOnly, "list"), resourceContract("gasorder", "gasorder_track_point", ReadOnly, "list"), resourceContract("gasorder", "gasorder_confirm", ReadOnly, "list"), resourceContract("gasorder", "gasorder_payment", ReadOnly, "list"),
|
||||||
resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"),
|
resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"),
|
||||||
resourceContract("content", "cms_content", Writable, "list"), resourceContract("customer_service", "cs_ticket", Writable, "list"),
|
resourceContract("content", "cms_content", Writable, "list"), resourceContract("customer_service", "cs_ticket", Writable, "list"),
|
||||||
resourceContract("platform", "platfrom_account", Writable, "list"), resourceContract("platform", "platform_role", Writable, "list"), resourceContract("platform", "platform_menu", Writable, "tree"),
|
resourceContract("platform", "platform_account", Writable, "list"), resourceContract("platform", "platform_role", Writable, "list"), resourceContract("platform", "platform_menu", Writable, "tree"),
|
||||||
resourceContract("wallet", "wallet_basic", ReadOnly, "list"), resourceContract("wallet", "wallet_bank", ReadOnly, "list"), resourceContract("wallet", "wallet_payment", ReadOnly, "list"), resourceContract("wallet", "wallet_record", ReadOnly, "list"), resourceContract("wallet", "wallet_refund", ReadOnly, "list"), resourceContract("wallet", "wallet_apply_cash", ReadOnly, "list"),
|
resourceContract("wallet", "wallet_basic", ReadOnly, "list"), resourceContract("wallet", "wallet_bank", ReadOnly, "list"), resourceContract("wallet", "wallet_payment", ReadOnly, "list"), resourceContract("wallet", "wallet_record", ReadOnly, "list"), resourceContract("wallet", "wallet_refund", ReadOnly, "list"), resourceContract("wallet", "wallet_apply_cash", ReadOnly, "list"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ func UpdateWalletBasicStatus(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RechargeWalletBasic(ctx *gin.Context) {
|
func RechargeWalletBasic(ctx *gin.Context) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request struct {
|
var request struct {
|
||||||
@@ -259,7 +259,7 @@ func RejectWalletApplyCash(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reviewWalletApplyCash(ctx *gin.Context, targetStatus string) {
|
func reviewWalletApplyCash(ctx *gin.Context, targetStatus string) {
|
||||||
if !requirePlatformRoot(ctx) {
|
if !common.RequirePlatformRoot(ctx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request struct {
|
var request struct {
|
||||||
@@ -311,7 +311,7 @@ func walletOperator(ctx *gin.Context) (string, string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
var account models.PlatfromAccount
|
var account models.PlatformAccount
|
||||||
if err := impl.DBService.Select("display_name").Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
if err := impl.DBService.Select("display_name").Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||||
return claims.Identity, ""
|
return claims.Identity, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package models
|
|||||||
|
|
||||||
import "git.apinb.com/bsm-sdk/core/database"
|
import "git.apinb.com/bsm-sdk/core/database"
|
||||||
|
|
||||||
// PlatfromAccount 对应 platfrom_account,表示平台总后台登录账号。
|
// PlatformAccount 对应 platform_account,表示平台总后台登录账号。
|
||||||
type PlatfromAccount struct {
|
type PlatformAccount struct {
|
||||||
Entity // 公共实体字段
|
Entity // 公共实体字段
|
||||||
Username string `gorm:"column:username;type:varchar(64);uniqueIndex;not null" json:"username"` // 登录用户名
|
Username string `gorm:"column:username;type:varchar(64);uniqueIndex;not null" json:"username"` // 登录用户名
|
||||||
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称
|
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称
|
||||||
@@ -13,7 +13,7 @@ type PlatfromAccount struct {
|
|||||||
Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null;default:''" json:"phone"` // 手机号
|
Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null;default:''" json:"phone"` // 手机号
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&PlatfromAccount{}) }
|
func init() { database.AppendMigrate(&PlatformAccount{}) }
|
||||||
|
|
||||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||||
func (table *PlatfromAccount) TableName() string { return "platfrom_account" }
|
func (table *PlatformAccount) TableName() string { return "platform_account" }
|
||||||
23
backend/api/internal/models/platform_models_test.go
Normal file
23
backend/api/internal/models/platform_models_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPlatformModelTableNames(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
table interface{ TableName() string }
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "account", table: &PlatformAccount{}, want: "platform_account"},
|
||||||
|
{name: "menu", table: &PlatformMenu{}, want: "platform_menu"},
|
||||||
|
{name: "role", table: &PlatformRole{}, want: "platform_role"},
|
||||||
|
{name: "role menu", table: &PlatformRoleMenu{}, want: "platform_role_menu"},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if got := test.table.TableName(); got != test.want {
|
||||||
|
t.Fatalf("TableName() = %q, want %q", got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
"git.apinb.com/bsm-sdk/core/database"
|
"git.apinb.com/bsm-sdk/core/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlatformRoleMenuRelation 对应 platform_role_menu_relation,记录角色拥有的菜单权限。
|
// PlatformRoleMenu 对应 platform_role_menu,记录角色拥有的菜单权限。
|
||||||
type PlatformRoleMenuRelation struct {
|
type PlatformRoleMenu struct {
|
||||||
ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 数据库自增主键
|
ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 数据库自增主键
|
||||||
PlatformRoleID uint64 `gorm:"column:platform_role_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_role_id"` // 角色自增主键
|
PlatformRoleID uint64 `gorm:"column:platform_role_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_role_id"` // 角色自增主键
|
||||||
PlatformMenuID uint64 `gorm:"column:platform_menu_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_menu_id"` // 菜单自增主键
|
PlatformMenuID uint64 `gorm:"column:platform_menu_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_menu_id"` // 菜单自增主键
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null" json:"created_at"` // 创建时间
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null" json:"created_at"` // 创建时间
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { database.AppendMigrate(&PlatformRoleMenuRelation{}) }
|
func init() { database.AppendMigrate(&PlatformRoleMenu{}) }
|
||||||
|
|
||||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||||
func (table *PlatformRoleMenuRelation) TableName() string { return "platform_role_menu_relation" }
|
func (table *PlatformRoleMenu) TableName() string { return "platform_role_menu" }
|
||||||
@@ -28,11 +28,11 @@ func GetDashboardOverview() (DashboardOverview, error) {
|
|||||||
return overview, nil
|
return overview, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPlatfromAccount 返回平台账号分页列表,敏感字段由接口展示层脱敏。
|
// ListPlatformAccount 返回平台账号分页列表,敏感字段由接口展示层脱敏。
|
||||||
func ListPlatfromAccount(page, size int) ([]PlatfromAccount, int64, error) {
|
func ListPlatformAccount(page, size int) ([]PlatformAccount, int64, error) {
|
||||||
var list []PlatfromAccount
|
var list []PlatformAccount
|
||||||
var total int64
|
var total int64
|
||||||
databaseQuery := impl.DBService.Model(&PlatfromAccount{})
|
databaseQuery := impl.DBService.Model(&PlatformAccount{})
|
||||||
if err := databaseQuery.Count(&total).Error; err != nil {
|
if err := databaseQuery.Count(&total).Error; err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,13 @@ func registerUserRoute(group *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerPlatformRoute(group *gin.RouterGroup) {
|
func registerPlatformRoute(group *gin.RouterGroup) {
|
||||||
registerWritableResource(group, "/platfrom_account", platform.ListPlatfromAccount, platform.CreatePlatfromAccount, platform.GetPlatfromAccount, platform.UpdatePlatfromAccount, &models.PlatfromAccount{})
|
account := group.Group("/platform_account")
|
||||||
|
account.GET("", platform.ListPlatformAccount)
|
||||||
|
account.POST("", platform.CreatePlatformAccount)
|
||||||
|
account.GET("/:identity", platform.GetPlatformAccount)
|
||||||
|
account.PUT("/:identity", platform.UpdatePlatformAccount)
|
||||||
|
account.PATCH("/:identity/status", platform.UpdatePlatformAccountStatus)
|
||||||
|
account.DELETE("/:identity", platform.ArchivePlatformAccount)
|
||||||
role := group.Group("/platform_role")
|
role := group.Group("/platform_role")
|
||||||
role.GET("", platform.ListPlatformRole)
|
role.GET("", platform.ListPlatformRole)
|
||||||
role.POST("", platform.CreatePlatformRole)
|
role.POST("", platform.CreatePlatformRole)
|
||||||
@@ -189,7 +195,6 @@ func registerPlatformRoute(group *gin.RouterGroup) {
|
|||||||
role.DELETE("/:identity", platform.ArchivePlatformRole)
|
role.DELETE("/:identity", platform.ArchivePlatformRole)
|
||||||
role.GET("/:identity/menu", platform.ListPlatformRoleMenuIdentities)
|
role.GET("/:identity/menu", platform.ListPlatformRoleMenuIdentities)
|
||||||
role.PUT("/:identity/menu", platform.ReplacePlatformRoleMenus)
|
role.PUT("/:identity/menu", platform.ReplacePlatformRoleMenus)
|
||||||
role.PUT("/:identity/menus", platform.ReplacePlatformRoleMenus)
|
|
||||||
menu := group.Group("/platform_menu")
|
menu := group.Group("/platform_menu")
|
||||||
menu.GET("", platform.ListPlatformMenu)
|
menu.GET("", platform.ListPlatformMenu)
|
||||||
menu.POST("", platform.CreatePlatformMenu)
|
menu.POST("", platform.CreatePlatformMenu)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD(t *testing.T) {
|
|||||||
"/user_account",
|
"/user_account",
|
||||||
"/user_address",
|
"/user_address",
|
||||||
"/user_service_relation",
|
"/user_service_relation",
|
||||||
"/platfrom_account",
|
"/platform_account",
|
||||||
"/platform_role",
|
"/platform_role",
|
||||||
"/platform_menu",
|
"/platform_menu",
|
||||||
} {
|
} {
|
||||||
@@ -95,7 +95,8 @@ func TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menu", http.MethodGet, http.MethodPut)
|
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menu", http.MethodGet, http.MethodPut)
|
||||||
assertRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menus", http.MethodPut)
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platform_role/:identity/menus", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
||||||
|
assertNoRouteMethods(t, routes, "/heqi/platform/v1/platfrom_account", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlatformProductCommerceAndDeliveryRoutesFollowTheirContracts(t *testing.T) {
|
func TestPlatformProductCommerceAndDeliveryRoutesFollowTheirContracts(t *testing.T) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type PlatformMenu = { identity: string; parent_identity?: string; menu_co
|
|||||||
|
|
||||||
export const platformApi = {
|
export const platformApi = {
|
||||||
overview: () => request<Record<string, number>>('/dashboard/overview'),
|
overview: () => request<Record<string, number>>('/dashboard/overview'),
|
||||||
listAccount: () => request<{ total: number; list: Record<string, unknown>[] }>('/platfrom_account'),
|
listAccount: () => request<{ total: number; list: Record<string, unknown>[] }>('/platform_account'),
|
||||||
listRole: () => resourceApi.list<PlatformRole>('/platform_role'),
|
listRole: () => resourceApi.list<PlatformRole>('/platform_role'),
|
||||||
createRole: (data: Record<string, unknown>) => resourceApi.create<PlatformRole>('/platform_role', data),
|
createRole: (data: Record<string, unknown>) => resourceApi.create<PlatformRole>('/platform_role', data),
|
||||||
listMenu: () => request<{ total: number; list: PlatformMenu[] }>('/platform_menu'),
|
listMenu: () => request<{ total: number; list: PlatformMenu[] }>('/platform_menu'),
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ export const resources: ResourceUiDefinition[] = [
|
|||||||
define('fin_reconciliation', '财务对账', 'writable', [f('channel', { required: true }), f('bill_date', { required: true }), f('difference_amount', { required: true })]),
|
define('fin_reconciliation', '财务对账', 'writable', [f('channel', { required: true }), f('bill_date', { required: true }), f('difference_amount', { required: true })]),
|
||||||
define('cms_content', '内容', 'writable', [f('content_type', { required: true }), f('title', { required: true }), f('body', { required: true }), f('version_no'), f('publish_status')]),
|
define('cms_content', '内容', 'writable', [f('content_type', { required: true }), f('title', { required: true }), f('body', { required: true }), f('version_no'), f('publish_status')]),
|
||||||
define('cs_ticket', '客服工单', 'writable', [relation('user_account_identity', '/user_account', true), f('ticket_no', { required: true }), f('category', { required: true }), f('priority', { required: true })]),
|
define('cs_ticket', '客服工单', 'writable', [relation('user_account_identity', '/user_account', true), f('ticket_no', { required: true }), f('category', { required: true }), f('priority', { required: true })]),
|
||||||
define('platfrom_account', '平台账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), f('avatar'), f('platform_role_code', { required: true }), f('phone')]),
|
define('platform_account', '平台账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), f('avatar'), f('platform_role_code', { required: true }), f('phone')]),
|
||||||
define('platform_role', '平台角色', 'writable', [f('role_code', { required: true }), f('name', { required: true }), f('data_scope', { required: true })], 'list', [
|
define('platform_role', '平台角色', 'writable', [f('role_code', { required: true }), f('name', { required: true }), f('data_scope', { required: true })], 'list', [
|
||||||
{ name: '分配菜单', resource: '/platform_role/:identity/menu', method: 'PUT', fields: [f('menu_identities', { type: 'identity-list', relation: '/platform_menu' })] },
|
{ name: '分配菜单', resource: '/platform_role/:identity/menu', method: 'PUT', fields: [f('menu_identities', { type: 'identity-list', relation: '/platform_menu' })] },
|
||||||
]),
|
]),
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -109,7 +109,7 @@ const routes: AppRouteRecordRaw[] = [
|
|||||||
child('customer_service', 'tickets', 'tickets', '客服工单', '/cs_ticket'),
|
child('customer_service', 'tickets', 'tickets', '客服工单', '/cs_ticket'),
|
||||||
]),
|
]),
|
||||||
group('platform', 'platform', '平台管理', 'icon-settings', 120, [
|
group('platform', 'platform', '平台管理', 'icon-settings', 120, [
|
||||||
child('platform', 'accounts', 'accounts', '平台账户', '/platfrom_account'),
|
child('platform', 'accounts', 'accounts', '平台账户', '/platform_account'),
|
||||||
child('platform', 'roles', 'roles', '平台角色', '/platform_role'),
|
child('platform', 'roles', 'roles', '平台角色', '/platform_role'),
|
||||||
child('platform', 'menus', 'menus', '平台菜单', '/platform_menu'),
|
child('platform', 'menus', 'menus', '平台菜单', '/platform_menu'),
|
||||||
]),
|
]),
|
||||||
|
|||||||
Reference in New Issue
Block a user