refactor: 数据权限过滤从 GORM Callback 改为 Store 层显式调用
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m2s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m2s
- 移除 RegisterDataPermissionCallback 和 SkipDataPermission 机制 - 在 Auth 中间件预计算 SubordinateShopIDs 并注入 Context - 新增 ApplyShopFilter/ApplyEnterpriseFilter/ApplyOwnerShopFilter 等 Helper 函数 - 所有 Store 层查询方法显式调用数据权限过滤函数 - 权限检查函数 CanManageShop/CanManageEnterprise 改为从 Context 获取数据 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,13 +17,18 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ShopStoreInterface 店铺存储接口(仅用于获取店铺信息)
|
||||
type ShopStoreInterface interface {
|
||||
GetByIDs(ctx context.Context, ids []uint) ([]*model.Shop, error)
|
||||
}
|
||||
|
||||
// Service 账号业务服务
|
||||
type Service struct {
|
||||
accountStore *postgres.AccountStore
|
||||
roleStore *postgres.RoleStore
|
||||
accountRoleStore *postgres.AccountRoleStore
|
||||
shopRoleStore *postgres.ShopRoleStore
|
||||
shopStore middleware.ShopStoreInterface
|
||||
shopStore ShopStoreInterface
|
||||
enterpriseStore middleware.EnterpriseStoreInterface
|
||||
auditService AuditServiceInterface
|
||||
}
|
||||
@@ -38,7 +43,7 @@ func New(
|
||||
roleStore *postgres.RoleStore,
|
||||
accountRoleStore *postgres.AccountRoleStore,
|
||||
shopRoleStore *postgres.ShopRoleStore,
|
||||
shopStore middleware.ShopStoreInterface,
|
||||
shopStore ShopStoreInterface,
|
||||
enterpriseStore middleware.EnterpriseStoreInterface,
|
||||
auditService AuditServiceInterface,
|
||||
) *Service {
|
||||
@@ -79,13 +84,13 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAccountRequest) (*m
|
||||
}
|
||||
|
||||
if req.UserType == constants.UserTypeAgent && req.ShopID != nil {
|
||||
if err := middleware.CanManageShop(ctx, *req.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *req.ShopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if req.UserType == constants.UserTypeEnterprise && req.EnterpriseID != nil {
|
||||
if err := middleware.CanManageEnterprise(ctx, *req.EnterpriseID, s.enterpriseStore, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageEnterprise(ctx, *req.EnterpriseID, s.enterpriseStore); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -190,7 +195,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateAccountReq
|
||||
if account.ShopID == nil {
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限操作该账号")
|
||||
}
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID); err != nil {
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
}
|
||||
@@ -291,7 +296,7 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
if account.ShopID == nil {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该账号")
|
||||
}
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID); err != nil {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
}
|
||||
@@ -407,7 +412,7 @@ func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uin
|
||||
if account.ShopID == nil {
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限操作该账号")
|
||||
}
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID); err != nil {
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
}
|
||||
@@ -558,7 +563,7 @@ func (s *Service) RemoveRole(ctx context.Context, accountID, roleID uint) error
|
||||
if account.ShopID == nil {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该账号")
|
||||
}
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *account.ShopID); err != nil {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
@@ -47,8 +46,6 @@ func New(
|
||||
}
|
||||
|
||||
func (s *Service) Login(ctx context.Context, req *dto.LoginRequest, clientIP string) (*dto.LoginResponse, error) {
|
||||
ctx = pkgGorm.SkipDataPermission(ctx)
|
||||
|
||||
account, err := s.accountStore.GetByUsernameOrPhone(ctx, req.Username)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkggorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
@@ -426,10 +425,8 @@ func (s *Service) ListDevicesForEnterprise(ctx context.Context, req *dto.Enterpr
|
||||
authMap[auth.DeviceID] = auth
|
||||
}
|
||||
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
|
||||
var devices []model.Device
|
||||
query := s.db.WithContext(skipCtx).Where("id IN ?", deviceIDs)
|
||||
query := s.db.WithContext(ctx).Where("id IN ?", deviceIDs)
|
||||
if req.DeviceNo != "" {
|
||||
query = query.Where("device_no LIKE ?", "%"+req.DeviceNo+"%")
|
||||
}
|
||||
@@ -438,7 +435,7 @@ func (s *Service) ListDevicesForEnterprise(ctx context.Context, req *dto.Enterpr
|
||||
}
|
||||
|
||||
var bindings []model.DeviceSimBinding
|
||||
if err := s.db.WithContext(skipCtx).
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("device_id IN ? AND bind_status = 1", deviceIDs).
|
||||
Find(&bindings).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败")
|
||||
@@ -480,15 +477,14 @@ func (s *Service) GetDeviceDetail(ctx context.Context, deviceID uint) (*dto.Ente
|
||||
return nil, errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业")
|
||||
}
|
||||
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
|
||||
var device model.Device
|
||||
if err := s.db.WithContext(skipCtx).Where("id = ?", deviceID).First(&device).Error; err != nil {
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", deviceID).First(&device).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败")
|
||||
}
|
||||
|
||||
var bindings []model.DeviceSimBinding
|
||||
if err := s.db.WithContext(skipCtx).
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("device_id = ? AND bind_status = 1", deviceID).
|
||||
Find(&bindings).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败")
|
||||
@@ -502,7 +498,7 @@ func (s *Service) GetDeviceDetail(ctx context.Context, deviceID uint) (*dto.Ente
|
||||
var cards []model.IotCard
|
||||
cardInfos := make([]dto.DeviceCardInfo, 0)
|
||||
if len(cardIDs) > 0 {
|
||||
if err := s.db.WithContext(skipCtx).Where("id IN ?", cardIDs).Find(&cards).Error; err != nil {
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", cardIDs).Find(&cards).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败")
|
||||
}
|
||||
|
||||
@@ -514,7 +510,7 @@ func (s *Service) GetDeviceDetail(ctx context.Context, deviceID uint) (*dto.Ente
|
||||
var carriers []model.Carrier
|
||||
carrierMap := make(map[uint]string)
|
||||
if len(carrierIDs) > 0 {
|
||||
if err := s.db.WithContext(skipCtx).Where("id IN ?", carrierIDs).Find(&carriers).Error; err == nil {
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", carrierIDs).Find(&carriers).Error; err == nil {
|
||||
for _, carrier := range carriers {
|
||||
carrierMap[carrier.ID] = carrier.CarrierName
|
||||
}
|
||||
@@ -551,8 +547,7 @@ func (s *Service) SuspendCard(ctx context.Context, deviceID, cardID uint, req *d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
if err := s.db.WithContext(skipCtx).Model(&model.IotCard{}).
|
||||
if err := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id = ?", cardID).
|
||||
Update("network_status", 0).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "停机操作失败")
|
||||
@@ -569,8 +564,7 @@ func (s *Service) ResumeCard(ctx context.Context, deviceID, cardID uint, req *dt
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
if err := s.db.WithContext(skipCtx).Model(&model.IotCard{}).
|
||||
if err := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id = ?", cardID).
|
||||
Update("network_status", 1).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "复机操作失败")
|
||||
@@ -593,17 +587,16 @@ func (s *Service) validateCardOperation(ctx context.Context, deviceID, cardID ui
|
||||
return errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业")
|
||||
}
|
||||
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
|
||||
var binding model.DeviceSimBinding
|
||||
if err := s.db.WithContext(skipCtx).
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("device_id = ? AND iot_card_id = ? AND bind_status = 1", deviceID, cardID).
|
||||
First(&binding).Error; err != nil {
|
||||
return errors.New(errors.CodeForbidden, "卡不属于该设备")
|
||||
}
|
||||
|
||||
var cardAuth model.EnterpriseCardAuthorization
|
||||
if err := s.db.WithContext(skipCtx).
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("enterprise_id = ? AND card_id = ? AND device_auth_id IS NOT NULL AND revoked_at IS NULL", enterpriseID, cardID).
|
||||
First(&cardAuth).Error; err != nil {
|
||||
return errors.New(errors.CodeForbidden, "无权操作此卡")
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
type ManualTriggerService struct {
|
||||
logStore *postgres.PollingManualTriggerLogStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
shopStore middleware.ShopStoreInterface
|
||||
redis *redis.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
@@ -28,14 +27,12 @@ type ManualTriggerService struct {
|
||||
func NewManualTriggerService(
|
||||
logStore *postgres.PollingManualTriggerLogStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
shopStore middleware.ShopStoreInterface,
|
||||
redis *redis.Client,
|
||||
logger *zap.Logger,
|
||||
) *ManualTriggerService {
|
||||
return &ManualTriggerService{
|
||||
logStore: logStore,
|
||||
iotCardStore: iotCardStore,
|
||||
shopStore: shopStore,
|
||||
redis: redis,
|
||||
logger: logger,
|
||||
}
|
||||
@@ -386,7 +383,7 @@ func (s *ManualTriggerService) canManageCard(ctx context.Context, cardID uint) e
|
||||
}
|
||||
|
||||
// 检查代理是否有权管理该店铺
|
||||
return middleware.CanManageShop(ctx, *card.ShopID, s.shopStore)
|
||||
return middleware.CanManageShop(ctx, *card.ShopID)
|
||||
}
|
||||
|
||||
// canManageCards 检查用户是否有权管理多张卡
|
||||
@@ -403,18 +400,13 @@ func (s *ManualTriggerService) canManageCards(ctx context.Context, cardIDs []uin
|
||||
return errors.New(errors.CodeForbidden, "企业账号无权限手动触发轮询")
|
||||
}
|
||||
|
||||
// 代理账号只能管理自己店铺及下级店铺的卡
|
||||
currentShopID := middleware.GetShopIDFromContext(ctx)
|
||||
if currentShopID == 0 {
|
||||
// 从 Context 获取预计算的下级店铺 ID 列表
|
||||
subordinateIDs := middleware.GetSubordinateShopIDs(ctx)
|
||||
if subordinateIDs == nil {
|
||||
// 平台用户/超管不受限制,但这里不应该进入(前面已经检查过用户类型)
|
||||
return errors.New(errors.CodeForbidden, "无权限操作")
|
||||
}
|
||||
|
||||
// 获取下级店铺ID列表
|
||||
subordinateIDs, err := s.shopStore.GetSubordinateShopIDs(ctx, currentShopID)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询下级店铺失败")
|
||||
}
|
||||
|
||||
// 构建可管理的店铺ID集合
|
||||
allowedShopIDs := make(map[uint]bool)
|
||||
for _, id := range subordinateIDs {
|
||||
@@ -462,7 +454,7 @@ func (s *ManualTriggerService) applyShopPermissionFilter(ctx context.Context, fi
|
||||
|
||||
// 如果用户指定了 ShopID,验证是否在可管理范围内
|
||||
if filter.ShopID != nil {
|
||||
if err := middleware.CanManageShop(ctx, *filter.ShopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, *filter.ShopID); err != nil {
|
||||
return err
|
||||
}
|
||||
// 已指定有效的 ShopID,无需修改
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Service) AssignRolesToShop(ctx context.Context, shopID uint, roleIDs []uint) ([]*model.ShopRole, error) {
|
||||
if err := middleware.CanManageShop(ctx, shopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, shopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *Service) AssignRolesToShop(ctx context.Context, shopID uint, roleIDs []
|
||||
}
|
||||
|
||||
func (s *Service) GetShopRoles(ctx context.Context, shopID uint) (*dto.ShopRolesResponse, error) {
|
||||
if err := middleware.CanManageShop(ctx, shopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, shopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ func (s *Service) GetShopRoles(ctx context.Context, shopID uint) (*dto.ShopRoles
|
||||
}
|
||||
|
||||
func (s *Service) DeleteShopRole(ctx context.Context, shopID, roleID uint) error {
|
||||
if err := middleware.CanManageShop(ctx, shopID, s.shopStore); err != nil {
|
||||
if err := middleware.CanManageShop(ctx, shopID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkggorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -71,9 +70,8 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
|
||||
}
|
||||
|
||||
// 检查是否已存在分配(跳过数据权限过滤,避免误判)
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
exists, err := s.seriesAllocationStore.ExistsByShopAndSeries(skipCtx, req.ShopID, req.SeriesID)
|
||||
// 检查是否已存在分配
|
||||
exists, err := s.seriesAllocationStore.ExistsByShopAndSeries(ctx, req.ShopID, req.SeriesID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "检查分配记录失败")
|
||||
}
|
||||
@@ -84,7 +82,7 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
|
||||
// 代理用户:检查自己是否有该系列的分配权限,且金额不能超过上级给的上限
|
||||
// 平台用户:无上限限制,可自由设定金额
|
||||
if userType == constants.UserTypeAgent {
|
||||
allocatorAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(skipCtx, allocatorShopID, req.SeriesID)
|
||||
allocatorAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, allocatorShopID, req.SeriesID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeForbidden, "您没有该套餐系列的分配权限")
|
||||
@@ -239,8 +237,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeries
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
skipCtx := pkggorm.SkipDataPermission(ctx)
|
||||
_, err := s.seriesAllocationStore.GetByID(skipCtx, id)
|
||||
_, err := s.seriesAllocationStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "分配记录不存在")
|
||||
@@ -248,7 +245,7 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
|
||||
}
|
||||
|
||||
count, err := s.packageAllocationStore.CountBySeriesAllocationID(skipCtx, id)
|
||||
count, err := s.packageAllocationStore.CountBySeriesAllocationID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "检查关联套餐分配失败")
|
||||
}
|
||||
@@ -256,7 +253,7 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
return errors.New(errors.CodeInvalidParam, "存在关联的套餐分配,无法删除")
|
||||
}
|
||||
|
||||
if err := s.seriesAllocationStore.Delete(skipCtx, id); err != nil {
|
||||
if err := s.seriesAllocationStore.Delete(ctx, id); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "删除分配失败")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user