fix: 角色删除前校验分配情况,禁止删除使用中的角色
角色被删除后,其关联的账号-角色记录未清理,导致 CountByAccountID 统计到孤儿记录,使被分配该角色的账号无法重新分配新角色。 修复方案:删除角色前检查 tb_account_role 和 tb_shop_role, 若仍有账号或店铺持有该角色则返回 1028 错误,强制管理员显式 解除所有分配后再删除,保证数据一致性与审计完整性。
This commit is contained in:
@@ -129,12 +129,12 @@ func initServices(s *stores, deps *Dependencies) *services {
|
|||||||
)
|
)
|
||||||
|
|
||||||
stopResumeService := iotCardSvc.NewStopResumeService(deps.Redis, s.IotCard, s.PackageUsage, s.DeviceSimBinding, deps.GatewayClient, deps.Logger)
|
stopResumeService := iotCardSvc.NewStopResumeService(deps.Redis, s.IotCard, s.PackageUsage, s.DeviceSimBinding, deps.GatewayClient, deps.Logger)
|
||||||
device := deviceSvc.New(deps.DB, deps.Redis, s.Device, s.DeviceSimBinding, s.IotCard, s.Shop, s.AssetAllocationRecord, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.PackageSeries, deps.GatewayClient)
|
device := deviceSvc.New(deps.DB, deps.Redis, s.Device, s.DeviceSimBinding, s.IotCard, s.Shop, s.AssetAllocationRecord, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.PackageSeries, deps.GatewayClient, s.AssetIdentifier)
|
||||||
|
|
||||||
return &services{
|
return &services{
|
||||||
Account: account,
|
Account: account,
|
||||||
AccountAudit: accountAudit,
|
AccountAudit: accountAudit,
|
||||||
Role: roleSvc.New(s.Role, s.Permission, s.RolePermission),
|
Role: roleSvc.New(s.Role, s.Permission, s.RolePermission, s.AccountRole, s.ShopRole),
|
||||||
Permission: permissionSvc.New(s.Permission, s.AccountRole, s.RolePermission, account, deps.Redis),
|
Permission: permissionSvc.New(s.Permission, s.AccountRole, s.RolePermission, account, deps.Redis),
|
||||||
PersonalCustomer: personalCustomerSvc.NewService(s.PersonalCustomer, s.PersonalCustomerPhone, deps.Logger),
|
PersonalCustomer: personalCustomerSvc.NewService(s.PersonalCustomer, s.PersonalCustomerPhone, deps.Logger),
|
||||||
ClientAuth: clientAuthSvc.New(
|
ClientAuth: clientAuthSvc.New(
|
||||||
@@ -194,7 +194,7 @@ func initServices(s *stores, deps *Dependencies) *services {
|
|||||||
ShopSeriesGrant: shopSeriesGrantSvc.New(deps.DB, s.ShopSeriesAllocation, s.ShopPackageAllocation, s.ShopPackageAllocationPriceHistory, s.Shop, s.Package, s.PackageSeries, deps.Logger),
|
ShopSeriesGrant: shopSeriesGrantSvc.New(deps.DB, s.ShopSeriesAllocation, s.ShopPackageAllocation, s.ShopPackageAllocationPriceHistory, s.Shop, s.Package, s.PackageSeries, deps.Logger),
|
||||||
CommissionStats: commissionStatsSvc.New(s.ShopSeriesCommissionStats),
|
CommissionStats: commissionStatsSvc.New(s.ShopSeriesCommissionStats),
|
||||||
PurchaseValidation: purchaseValidation,
|
PurchaseValidation: purchaseValidation,
|
||||||
Order: orderSvc.New(deps.DB, deps.Redis, s.Order, s.OrderItem, s.AgentWallet, s.AssetWallet, purchaseValidation, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.IotCard, s.Device, s.PackageSeries, s.PackageUsage, s.Package, wechatConfig, deps.WechatPayment, deps.QueueClient, deps.Logger),
|
Order: orderSvc.New(deps.DB, deps.Redis, s.Order, s.OrderItem, s.AgentWallet, s.AssetWallet, purchaseValidation, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.IotCard, s.Device, s.PackageSeries, s.PackageUsage, s.Package, wechatConfig, deps.WechatPayment, deps.QueueClient, deps.Logger, s.AssetIdentifier),
|
||||||
Exchange: exchangeSvc.New(deps.DB, s.ExchangeOrder, s.IotCard, s.Device, s.AssetWallet, s.AssetWalletTransaction, s.PackageUsage, s.PackageUsageDailyRecord, s.ResourceTag, s.PersonalCustomerDevice, deps.Logger),
|
Exchange: exchangeSvc.New(deps.DB, s.ExchangeOrder, s.IotCard, s.Device, s.AssetWallet, s.AssetWalletTransaction, s.PackageUsage, s.PackageUsageDailyRecord, s.ResourceTag, s.PersonalCustomerDevice, deps.Logger),
|
||||||
Recharge: rechargeSvc.New(deps.DB, s.AssetRecharge, s.AssetWallet, s.AssetWalletTransaction, s.IotCard, s.Device, s.ShopSeriesAllocation, s.PackageSeries, s.CommissionRecord, wechatConfig, deps.Logger, deps.QueueClient),
|
Recharge: rechargeSvc.New(deps.DB, s.AssetRecharge, s.AssetWallet, s.AssetWalletTransaction, s.IotCard, s.Device, s.ShopSeriesAllocation, s.PackageSeries, s.CommissionRecord, wechatConfig, deps.Logger, deps.QueueClient),
|
||||||
PollingConfig: pollingSvc.NewConfigService(s.PollingConfig),
|
PollingConfig: pollingSvc.NewConfigService(s.PollingConfig),
|
||||||
@@ -203,7 +203,7 @@ func initServices(s *stores, deps *Dependencies) *services {
|
|||||||
PollingAlert: pollingSvc.NewAlertService(s.PollingAlertRule, s.PollingAlertHistory, deps.Redis, deps.Logger),
|
PollingAlert: pollingSvc.NewAlertService(s.PollingAlertRule, s.PollingAlertHistory, deps.Redis, deps.Logger),
|
||||||
PollingCleanup: pollingSvc.NewCleanupService(s.DataCleanupConfig, s.DataCleanupLog, deps.Logger),
|
PollingCleanup: pollingSvc.NewCleanupService(s.DataCleanupConfig, s.DataCleanupLog, deps.Logger),
|
||||||
PollingManualTrigger: pollingSvc.NewManualTriggerService(s.PollingManualTriggerLog, s.IotCard, deps.Redis, deps.Logger),
|
PollingManualTrigger: pollingSvc.NewManualTriggerService(s.PollingManualTriggerLog, s.IotCard, deps.Redis, deps.Logger),
|
||||||
Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard, deps.GatewayClient),
|
Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard, deps.GatewayClient, s.AssetIdentifier, s.Order, s.OrderItem, s.ExchangeOrder),
|
||||||
AssetLifecycle: assetSvc.NewLifecycleService(deps.DB, s.IotCard, s.Device),
|
AssetLifecycle: assetSvc.NewLifecycleService(deps.DB, s.IotCard, s.Device),
|
||||||
AssetWallet: assetWalletSvc.New(s.AssetWallet, s.AssetWalletTransaction),
|
AssetWallet: assetWalletSvc.New(s.AssetWallet, s.AssetWalletTransaction),
|
||||||
StopResumeService: stopResumeService,
|
StopResumeService: stopResumeService,
|
||||||
|
|||||||
@@ -23,14 +23,18 @@ type Service struct {
|
|||||||
roleStore *postgres.RoleStore
|
roleStore *postgres.RoleStore
|
||||||
permissionStore *postgres.PermissionStore
|
permissionStore *postgres.PermissionStore
|
||||||
rolePermissionStore *postgres.RolePermissionStore
|
rolePermissionStore *postgres.RolePermissionStore
|
||||||
|
accountRoleStore *postgres.AccountRoleStore
|
||||||
|
shopRoleStore *postgres.ShopRoleStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// New 创建角色服务
|
// New 创建角色服务
|
||||||
func New(roleStore *postgres.RoleStore, permissionStore *postgres.PermissionStore, rolePermissionStore *postgres.RolePermissionStore) *Service {
|
func New(roleStore *postgres.RoleStore, permissionStore *postgres.PermissionStore, rolePermissionStore *postgres.RolePermissionStore, accountRoleStore *postgres.AccountRoleStore, shopRoleStore *postgres.ShopRoleStore) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
roleStore: roleStore,
|
roleStore: roleStore,
|
||||||
permissionStore: permissionStore,
|
permissionStore: permissionStore,
|
||||||
rolePermissionStore: rolePermissionStore,
|
rolePermissionStore: rolePermissionStore,
|
||||||
|
accountRoleStore: accountRoleStore,
|
||||||
|
shopRoleStore: shopRoleStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +130,6 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateRoleReques
|
|||||||
|
|
||||||
// Delete 软删除角色
|
// Delete 软删除角色
|
||||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||||
// 检查角色存在
|
|
||||||
_, err := s.roleStore.GetByID(ctx, id)
|
_, err := s.roleStore.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
@@ -135,6 +138,20 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
|||||||
return errors.Wrap(errors.CodeInternalError, err, "获取角色失败")
|
return errors.Wrap(errors.CodeInternalError, err, "获取角色失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountCount, err := s.accountRoleStore.CountByRoleID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "检查角色分配情况失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
shopCount, err := s.shopRoleStore.CountByRoleID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "检查角色分配情况失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountCount > 0 || shopCount > 0 {
|
||||||
|
return errors.New(errors.CodeRoleInUse, fmt.Sprintf("该角色已分配给 %d 个账号、%d 个店铺,请先移除相关分配后再删除", accountCount, shopCount))
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.roleStore.Delete(ctx, id); err != nil {
|
if err := s.roleStore.Delete(ctx, id); err != nil {
|
||||||
return errors.Wrap(errors.CodeInternalError, err, "删除角色失败")
|
return errors.Wrap(errors.CodeInternalError, err, "删除角色失败")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,3 +121,15 @@ func (s *AccountRoleStore) CountByAccountID(ctx context.Context, accountID uint)
|
|||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountByRoleID 统计持有指定角色的账号数量
|
||||||
|
func (s *AccountRoleStore) CountByRoleID(ctx context.Context, roleID uint) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
if err := s.db.WithContext(ctx).
|
||||||
|
Model(&model.AccountRole{}).
|
||||||
|
Where("role_id = ?", roleID).
|
||||||
|
Count(&count).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ func (s *ShopRoleStore) DeleteByShopID(ctx context.Context, shopID uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountByRoleID 统计持有指定角色的店铺数量
|
||||||
|
func (s *ShopRoleStore) CountByRoleID(ctx context.Context, roleID uint) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
if err := s.db.WithContext(ctx).
|
||||||
|
Model(&model.ShopRole{}).
|
||||||
|
Where("role_id = ?", roleID).
|
||||||
|
Count(&count).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ShopRoleStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopRole, error) {
|
func (s *ShopRoleStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopRole, error) {
|
||||||
var srs []*model.ShopRole
|
var srs []*model.ShopRole
|
||||||
query := s.db.WithContext(ctx).Where("shop_id = ?", shopID)
|
query := s.db.WithContext(ctx).Where("shop_id = ?", shopID)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const (
|
|||||||
CodeInvalidPermCode = 1025 // 权限编码格式不正确
|
CodeInvalidPermCode = 1025 // 权限编码格式不正确
|
||||||
CodeRoleAlreadyAssigned = 1026 // 角色已分配
|
CodeRoleAlreadyAssigned = 1026 // 角色已分配
|
||||||
CodePermAlreadyAssigned = 1027 // 权限已分配
|
CodePermAlreadyAssigned = 1027 // 权限已分配
|
||||||
|
CodeRoleInUse = 1028 // 角色使用中,无法删除
|
||||||
|
|
||||||
// 认证相关错误 (1040-1049)
|
// 认证相关错误 (1040-1049)
|
||||||
CodeInvalidCredentials = 1040 // 用户名或密码错误
|
CodeInvalidCredentials = 1040 // 用户名或密码错误
|
||||||
@@ -201,6 +202,7 @@ var allErrorCodes = []int{
|
|||||||
CodeInvalidPermCode,
|
CodeInvalidPermCode,
|
||||||
CodeRoleAlreadyAssigned,
|
CodeRoleAlreadyAssigned,
|
||||||
CodePermAlreadyAssigned,
|
CodePermAlreadyAssigned,
|
||||||
|
CodeRoleInUse,
|
||||||
CodeShopNotFound,
|
CodeShopNotFound,
|
||||||
CodeShopCodeExists,
|
CodeShopCodeExists,
|
||||||
CodeShopLevelExceeded,
|
CodeShopLevelExceeded,
|
||||||
@@ -340,6 +342,7 @@ var errorMessages = map[int]string{
|
|||||||
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
||||||
CodeRoleAlreadyAssigned: "角色已分配",
|
CodeRoleAlreadyAssigned: "角色已分配",
|
||||||
CodePermAlreadyAssigned: "权限已分配",
|
CodePermAlreadyAssigned: "权限已分配",
|
||||||
|
CodeRoleInUse: "角色使用中,无法删除",
|
||||||
CodeShopNotFound: "店铺不存在",
|
CodeShopNotFound: "店铺不存在",
|
||||||
CodeShopCodeExists: "店铺编号已存在",
|
CodeShopCodeExists: "店铺编号已存在",
|
||||||
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
||||||
|
|||||||
Reference in New Issue
Block a user