refactor platform account and access models

This commit is contained in:
david
2026-07-28 13:35:59 +08:00
parent f808f1c51f
commit bc39021c0d
20 changed files with 234 additions and 181 deletions

View File

@@ -50,7 +50,7 @@ func InitPlatformAccess(database *gorm.DB) error {
if err := database.Where("menu_code = ?", menu.MenuCode).FirstOrCreate(&menu).Error; err != nil {
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 {
return err
}
@@ -60,7 +60,7 @@ func InitPlatformAccess(database *gorm.DB) error {
// InitPlatformRoot 幂等创建平台总后台 root 账号。
func InitPlatformRoot(database *gorm.DB) error {
var account models.PlatfromAccount
var account models.PlatformAccount
err := database.Where("username = ?", PlatformRootUsername).First(&account).Error
if err == nil {
return nil
@@ -74,7 +74,7 @@ func InitPlatformRoot(database *gorm.DB) error {
return err
}
account = models.PlatfromAccount{
account = models.PlatformAccount{
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled"},
Username: PlatformRootUsername,
DisplayName: "平台根管理员",

View File

@@ -35,7 +35,7 @@ func TestInitPlatformAccessSeedsEveryProtectedFrontendDomain(t *testing.T) {
WithArgs(domain, 1).
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))
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).
WillReturnRows(sqlmock.NewRows([]string{"id", "platform_role_id", "platform_menu_id"}).
AddRow(uint64(index+100), uint64(1), menuID))

View 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
}

View File

@@ -133,7 +133,7 @@ func isCreatedResponseField(key string) bool {
func ProtectPreciseLocation(ctx *gin.Context, model, value any) any {
maskPersonalName := reflect.TypeOf(model) == reflect.TypeOf(&models.UserAccount{}) ||
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))
return value
}

View File

@@ -9,6 +9,7 @@ import (
"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/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
@@ -38,7 +39,7 @@ func Login(ctx *gin.Context) {
return
}
var account models.PlatfromAccount
var account models.PlatformAccount
err := impl.DBService.Where("username = ?", strings.TrimSpace(request.Username)).First(&account).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
@@ -85,12 +86,12 @@ func CurrentProfile(ctx *gin.Context) {
infra.Response.Error(ctx, err)
return
}
var account models.PlatfromAccount
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
menus, err := loadPlatformMenus(account.PlatformRoleCode)
menus, err := common.LoadPlatformMenus(account.PlatformRoleCode)
if err != nil {
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
return
@@ -133,7 +134,7 @@ func ChangePassword(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var account models.PlatfromAccount
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return

View File

@@ -2,6 +2,7 @@ package platform
import (
"errors"
"strings"
"git.apinb.com/bsm-sdk/core/errcode"
"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/models"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
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 查询平台角色分页列表。
func ListPlatformRole(ctx *gin.Context) { common.ListPage[models.PlatformRole](ctx) }
@@ -23,7 +96,7 @@ func GetPlatformRole(ctx *gin.Context) { common.GetByIdentity[models.PlatformRol
// CreatePlatformRole 创建非内置平台角色。
func CreatePlatformRole(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request models.PlatformRole
@@ -45,7 +118,7 @@ func CreatePlatformRole(ctx *gin.Context) {
// UpdatePlatformRole 更新非内置平台角色。
func UpdatePlatformRole(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request struct {
@@ -118,7 +191,7 @@ func GetPlatformMenu(ctx *gin.Context) {
}
func CreatePlatformMenu(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request platformMenuRequest
@@ -140,7 +213,7 @@ func CreatePlatformMenu(ctx *gin.Context) {
}
func UpdatePlatformMenu(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request platformMenuRequest
@@ -157,14 +230,14 @@ func UpdatePlatformMenu(ctx *gin.Context) {
}
func UpdatePlatformMenuStatus(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
common.UpdateRecordStatus(ctx, &models.PlatformMenu{})
}
func ArchivePlatformMenu(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
common.ArchiveRecord(ctx, &models.PlatformMenu{})
@@ -176,7 +249,7 @@ type platformRoleMenusRequest struct {
// ReplacePlatformRoleMenus replaces every menu assignment for a role atomically.
func ReplacePlatformRoleMenus(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request platformRoleMenusRequest
@@ -201,11 +274,11 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
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
}
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 {
return err
}
@@ -228,7 +301,7 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
// ListPlatformRoleMenuIdentities returns the current assignment for the role editor.
func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var role models.PlatformRole
@@ -238,8 +311,8 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
}
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).
Joins("JOIN platform_role_menu ON platform_role_menu.platform_menu_id = platform_menu.id").
Where("platform_role_menu.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)
@@ -250,7 +323,7 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
// UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。
func UpdatePlatformRoleStatus(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request struct {
@@ -274,7 +347,7 @@ func UpdatePlatformRoleStatus(ctx *gin.Context) {
// ArchivePlatformRole 归档非内置平台角色,系统角色始终受保护。
func ArchivePlatformRole(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var role models.PlatformRole
@@ -286,7 +359,7 @@ func ArchivePlatformRole(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
common.UpdateAllowedByIdentity(ctx, &models.PlatformRole{}, archiveValues(), []string{"status"})
common.UpdateAllowedByIdentity(ctx, &models.PlatformRole{}, gin.H{"status": "archived"}, []string{"status"})
}
// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。
@@ -296,7 +369,7 @@ func ListPlatformMenu(ctx *gin.Context) {
infra.Response.Error(ctx, err)
return
}
list, err := loadPlatformMenus(claims.Role)
list, err := common.LoadPlatformMenus(claims.Role)
if err != nil {
infra.Response.Error(ctx, err)
return
@@ -304,12 +377,12 @@ func ListPlatformMenu(ctx *gin.Context) {
infra.Response.Success(ctx, gin.H{"total": len(list), "list": platformMenuViews(list)})
}
// ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。
func ListPlatfromAccount(ctx *gin.Context) {
// ListPlatformAccount 查询平台账号列表,手机号在展示层脱敏。
func ListPlatformAccount(ctx *gin.Context) {
page, size := common.PageSize(ctx)
var list []models.PlatfromAccount
var list []models.PlatformAccount
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 {
infra.Response.Error(ctx, err)
return
@@ -321,13 +394,13 @@ func ListPlatfromAccount(ctx *gin.Context) {
views := make([]map[string]any, 0, len(list))
for _, item := range list {
view := platformAccountView(item)
common.ProtectPreciseLocation(ctx, &models.PlatfromAccount{}, view)
common.ProtectPreciseLocation(ctx, &models.PlatformAccount{}, view)
views = append(views, view)
}
infra.Response.Success(ctx, gin.H{"total": total, "list": views})
}
type platfromAccountRequest struct {
type platformAccountRequest 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"`
@@ -336,7 +409,7 @@ type platfromAccountRequest struct {
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{
"identity": account.Identity, "username": account.Username,
"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) {
var account models.PlatfromAccount
func GetPlatformAccount(ctx *gin.Context) {
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&account).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
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) {
if !requirePlatformRoot(ctx) {
func CreatePlatformAccount(ctx *gin.Context) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request platfromAccountRequest
var request platformAccountRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
@@ -367,21 +440,21 @@ func CreatePlatfromAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := passwordHash(request.Password)
hash, err := platformPasswordHash(request.Password)
if err != nil {
infra.Response.Error(ctx, err)
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 {
infra.Response.Error(ctx, err)
return
}
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 {
DisplayName string `json:"display_name" binding:"max=64"`
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}
if request.PlatformRoleCode != nil {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
if !isAssignablePlatformRole(*request.PlatformRoleCode) {
@@ -403,7 +476,28 @@ func UpdatePlatfromAccount(ctx *gin.Context) {
}
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 {

View File

@@ -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
}

View File

@@ -354,7 +354,7 @@ func productOperator(ctx *gin.Context) (string, string) {
if err != nil {
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 {
return claims.Identity, ""
}

View File

@@ -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("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("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"),
}
}

View File

@@ -180,7 +180,7 @@ func UpdateWalletBasicStatus(ctx *gin.Context) {
}
func RechargeWalletBasic(ctx *gin.Context) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request struct {
@@ -259,7 +259,7 @@ func RejectWalletApplyCash(ctx *gin.Context) {
}
func reviewWalletApplyCash(ctx *gin.Context, targetStatus string) {
if !requirePlatformRoot(ctx) {
if !common.RequirePlatformRoot(ctx) {
return
}
var request struct {
@@ -311,7 +311,7 @@ func walletOperator(ctx *gin.Context) (string, string) {
if err != nil {
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 {
return claims.Identity, ""
}

View File

@@ -2,8 +2,8 @@ package models
import "git.apinb.com/bsm-sdk/core/database"
// PlatfromAccount 对应 platfrom_account表示平台总后台登录账号。
type PlatfromAccount struct {
// PlatformAccount 对应 platform_account表示平台总后台登录账号。
type PlatformAccount struct {
Entity // 公共实体字段
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"` // 用户展示名称
@@ -13,7 +13,7 @@ type PlatfromAccount struct {
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 返回与模型、文件名一致的单数数据表名。
func (table *PlatfromAccount) TableName() string { return "platfrom_account" }
func (table *PlatformAccount) TableName() string { return "platform_account" }

View 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)
}
})
}
}

View File

@@ -6,15 +6,15 @@ import (
"git.apinb.com/bsm-sdk/core/database"
)
// PlatformRoleMenuRelation 对应 platform_role_menu_relation,记录角色拥有的菜单权限。
type PlatformRoleMenuRelation struct {
// PlatformRoleMenu 对应 platform_role_menu记录角色拥有的菜单权限。
type PlatformRoleMenu struct {
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"` // 角色自增主键
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"` // 创建时间
}
func init() { database.AppendMigrate(&PlatformRoleMenuRelation{}) }
func init() { database.AppendMigrate(&PlatformRoleMenu{}) }
// TableName 返回与模型、文件名一致的单数数据表名。
func (table *PlatformRoleMenuRelation) TableName() string { return "platform_role_menu_relation" }
func (table *PlatformRoleMenu) TableName() string { return "platform_role_menu" }

View File

@@ -28,11 +28,11 @@ func GetDashboardOverview() (DashboardOverview, error) {
return overview, nil
}
// ListPlatfromAccount 返回平台账号分页列表,敏感字段由接口展示层脱敏。
func ListPlatfromAccount(page, size int) ([]PlatfromAccount, int64, error) {
var list []PlatfromAccount
// ListPlatformAccount 返回平台账号分页列表,敏感字段由接口展示层脱敏。
func ListPlatformAccount(page, size int) ([]PlatformAccount, int64, error) {
var list []PlatformAccount
var total int64
databaseQuery := impl.DBService.Model(&PlatfromAccount{})
databaseQuery := impl.DBService.Model(&PlatformAccount{})
if err := databaseQuery.Count(&total).Error; err != nil {
return nil, 0, err
}

View File

@@ -179,7 +179,13 @@ func registerUserRoute(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.GET("", platform.ListPlatformRole)
role.POST("", platform.CreatePlatformRole)
@@ -189,7 +195,6 @@ func registerPlatformRoute(group *gin.RouterGroup) {
role.DELETE("/:identity", platform.ArchivePlatformRole)
role.GET("/:identity/menu", platform.ListPlatformRoleMenuIdentities)
role.PUT("/:identity/menu", platform.ReplacePlatformRoleMenus)
role.PUT("/:identity/menus", platform.ReplacePlatformRoleMenus)
menu := group.Group("/platform_menu")
menu.GET("", platform.ListPlatformMenu)
menu.POST("", platform.CreatePlatformMenu)

View File

@@ -85,7 +85,7 @@ func TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD(t *testing.T) {
"/user_account",
"/user_address",
"/user_service_relation",
"/platfrom_account",
"/platform_account",
"/platform_role",
"/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/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) {

View File

@@ -7,7 +7,7 @@ export type PlatformMenu = { identity: string; parent_identity?: string; menu_co
export const platformApi = {
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'),
createRole: (data: Record<string, unknown>) => resourceApi.create<PlatformRole>('/platform_role', data),
listMenu: () => request<{ total: number; list: PlatformMenu[] }>('/platform_menu'),

View File

@@ -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('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('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', [
{ 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

View File

@@ -109,7 +109,7 @@ const routes: AppRouteRecordRaw[] = [
child('customer_service', 'tickets', 'tickets', '客服工单', '/cs_ticket'),
]),
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', 'menus', 'menus', '平台菜单', '/platform_menu'),
]),