Files
junhong_cmp_fiber/internal/service/enterprise_device/service.go
huang 03a0960c4d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m2s
refactor: 数据权限过滤从 GORM Callback 改为 Store 层显式调用
- 移除 RegisterDataPermissionCallback 和 SkipDataPermission 机制
- 在 Auth 中间件预计算 SubordinateShopIDs 并注入 Context
- 新增 ApplyShopFilter/ApplyEnterpriseFilter/ApplyOwnerShopFilter 等 Helper 函数
- 所有 Store 层查询方法显式调用数据权限过滤函数
- 权限检查函数 CanManageShop/CanManageEnterprise 改为从 Context 获取数据

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 16:38:52 +08:00

614 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package enterprise_device
import (
"context"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"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"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Service struct {
db *gorm.DB
enterpriseStore *postgres.EnterpriseStore
deviceStore *postgres.DeviceStore
deviceSimBindingStore *postgres.DeviceSimBindingStore
enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore
enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore
logger *zap.Logger
}
func New(
db *gorm.DB,
enterpriseStore *postgres.EnterpriseStore,
deviceStore *postgres.DeviceStore,
deviceSimBindingStore *postgres.DeviceSimBindingStore,
enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore,
enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
enterpriseStore: enterpriseStore,
deviceStore: deviceStore,
deviceSimBindingStore: deviceSimBindingStore,
enterpriseDeviceAuthStore: enterpriseDeviceAuthStore,
enterpriseCardAuthStore: enterpriseCardAuthStore,
logger: logger,
}
}
// AllocateDevices 授权设备给企业
func (s *Service) AllocateDevices(ctx context.Context, enterpriseID uint, req *dto.AllocateDevicesReq) (*dto.AllocateDevicesResp, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
// 验证企业存在
_, err := s.enterpriseStore.GetByID(ctx, enterpriseID)
if err != nil {
return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
// 查询所有设备
var devices []model.Device
if err := s.db.WithContext(ctx).Where("device_no IN ?", req.DeviceNos).Find(&devices).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败")
}
deviceMap := make(map[string]*model.Device)
deviceIDs := make([]uint, 0, len(devices))
for i := range devices {
deviceMap[devices[i].DeviceNo] = &devices[i]
deviceIDs = append(deviceIDs, devices[i].ID)
}
// 获取当前用户的店铺ID用于验证设备所有权
currentShopID := middleware.GetShopIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
// 检查已授权的设备
existingAuths, err := s.enterpriseDeviceAuthStore.GetActiveAuthsByDeviceIDs(ctx, enterpriseID, deviceIDs)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询已有授权失败")
}
resp := &dto.AllocateDevicesResp{
FailedItems: make([]dto.FailedDeviceItem, 0),
AuthorizedDevices: make([]dto.AuthorizedDeviceItem, 0),
}
devicesToAllocate := make([]*model.Device, 0)
for _, deviceNo := range req.DeviceNos {
device, exists := deviceMap[deviceNo]
if !exists {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "设备不存在",
})
continue
}
// 验证设备状态(必须是"已分销"状态)
if device.Status != 2 {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "设备状态不正确,必须是已分销状态",
})
continue
}
// 验证设备所有权(除非是超级管理员或平台用户)
if userType == constants.UserTypeAgent {
if device.ShopID == nil || *device.ShopID != currentShopID {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "无权操作此设备",
})
continue
}
}
// 检查是否已授权
if existingAuths[device.ID] {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "设备已授权给此企业",
})
continue
}
devicesToAllocate = append(devicesToAllocate, device)
}
// 在事务中处理授权
if len(devicesToAllocate) > 0 {
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
now := time.Now()
authorizerType := userType
// 1. 创建设备授权记录
deviceAuths := make([]*model.EnterpriseDeviceAuthorization, 0, len(devicesToAllocate))
for _, device := range devicesToAllocate {
deviceAuths = append(deviceAuths, &model.EnterpriseDeviceAuthorization{
EnterpriseID: enterpriseID,
DeviceID: device.ID,
AuthorizedBy: currentUserID,
AuthorizedAt: now,
AuthorizerType: authorizerType,
Remark: req.Remark,
})
}
if err := tx.Create(deviceAuths).Error; err != nil {
return errors.Wrap(errors.CodeInternalError, err, "创建设备授权记录失败")
}
// 构建设备ID到授权ID的映射
deviceAuthIDMap := make(map[uint]uint)
for _, auth := range deviceAuths {
deviceAuthIDMap[auth.DeviceID] = auth.ID
}
// 2. 查询所有设备绑定的卡
deviceIDsToQuery := make([]uint, 0, len(devicesToAllocate))
for _, device := range devicesToAllocate {
deviceIDsToQuery = append(deviceIDsToQuery, device.ID)
}
var bindings []model.DeviceSimBinding
if err := tx.Where("device_id IN ? AND bind_status = 1", deviceIDsToQuery).Find(&bindings).Error; err != nil {
return errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败")
}
// 3. 为每张绑定的卡创建授权记录
if len(bindings) > 0 {
cardAuths := make([]*model.EnterpriseCardAuthorization, 0, len(bindings))
for _, binding := range bindings {
deviceAuthID := deviceAuthIDMap[binding.DeviceID]
cardAuths = append(cardAuths, &model.EnterpriseCardAuthorization{
EnterpriseID: enterpriseID,
CardID: binding.IotCardID,
DeviceAuthID: &deviceAuthID,
AuthorizedBy: currentUserID,
AuthorizedAt: now,
AuthorizerType: authorizerType,
Remark: req.Remark,
})
}
if err := tx.Create(cardAuths).Error; err != nil {
return errors.Wrap(errors.CodeInternalError, err, "创建卡授权记录失败")
}
}
// 4. 统计每个设备的绑定卡数量
deviceCardCount := make(map[uint]int)
for _, binding := range bindings {
deviceCardCount[binding.DeviceID]++
}
// 5. 构建响应
for _, device := range devicesToAllocate {
resp.AuthorizedDevices = append(resp.AuthorizedDevices, dto.AuthorizedDeviceItem{
DeviceID: device.ID,
DeviceNo: device.DeviceNo,
CardCount: deviceCardCount[device.ID],
})
}
return nil
})
if err != nil {
return nil, err
}
}
resp.SuccessCount = len(devicesToAllocate)
resp.FailCount = len(resp.FailedItems)
return resp, nil
}
// RecallDevices 撤销设备授权
func (s *Service) RecallDevices(ctx context.Context, enterpriseID uint, req *dto.RecallDevicesReq) (*dto.RecallDevicesResp, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
// 验证企业存在
_, err := s.enterpriseStore.GetByID(ctx, enterpriseID)
if err != nil {
return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
// 查询设备
var devices []model.Device
if err := s.db.WithContext(ctx).Where("device_no IN ?", req.DeviceNos).Find(&devices).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败")
}
deviceMap := make(map[string]*model.Device)
deviceIDs := make([]uint, 0, len(devices))
for i := range devices {
deviceMap[devices[i].DeviceNo] = &devices[i]
deviceIDs = append(deviceIDs, devices[i].ID)
}
// 检查授权状态
existingAuths, err := s.enterpriseDeviceAuthStore.GetActiveAuthsByDeviceIDs(ctx, enterpriseID, deviceIDs)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权状态失败")
}
resp := &dto.RecallDevicesResp{
FailedItems: make([]dto.FailedDeviceItem, 0),
}
deviceAuthsToRevoke := make([]uint, 0)
for _, deviceNo := range req.DeviceNos {
device, exists := deviceMap[deviceNo]
if !exists {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "设备不存在",
})
continue
}
if !existingAuths[device.ID] {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "设备未授权给此企业",
})
continue
}
// 获取授权记录ID
auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, device.ID)
if err != nil || auth.EnterpriseID != enterpriseID {
resp.FailedItems = append(resp.FailedItems, dto.FailedDeviceItem{
DeviceNo: deviceNo,
Reason: "授权记录不存在",
})
continue
}
deviceAuthsToRevoke = append(deviceAuthsToRevoke, auth.ID)
}
// 在事务中处理撤销
if len(deviceAuthsToRevoke) > 0 {
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 1. 撤销设备授权
if err := s.enterpriseDeviceAuthStore.RevokeByIDs(ctx, deviceAuthsToRevoke, currentUserID); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "撤销设备授权失败")
}
// 2. 级联撤销卡授权
for _, authID := range deviceAuthsToRevoke {
if err := s.enterpriseCardAuthStore.RevokeByDeviceAuthID(ctx, authID, currentUserID); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "撤销卡授权失败")
}
}
return nil
})
if err != nil {
return nil, err
}
}
resp.SuccessCount = len(deviceAuthsToRevoke)
resp.FailCount = len(resp.FailedItems)
return resp, nil
}
// ListDevices 查询企业授权设备列表(后台管理)
func (s *Service) ListDevices(ctx context.Context, enterpriseID uint, req *dto.EnterpriseDeviceListReq) (*dto.EnterpriseDeviceListResp, error) {
// 验证企业存在
_, err := s.enterpriseStore.GetByID(ctx, enterpriseID)
if err != nil {
return nil, errors.New(errors.CodeEnterpriseNotFound, "企业不存在")
}
// 查询授权记录
opts := postgres.DeviceAuthListOptions{
EnterpriseID: &enterpriseID,
IncludeRevoked: false,
Page: req.Page,
PageSize: req.PageSize,
}
auths, total, err := s.enterpriseDeviceAuthStore.ListByEnterprise(ctx, opts)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权记录失败")
}
if len(auths) == 0 {
return &dto.EnterpriseDeviceListResp{
List: make([]dto.EnterpriseDeviceItem, 0),
Total: 0,
}, nil
}
// 收集设备ID
deviceIDs := make([]uint, 0, len(auths))
authMap := make(map[uint]*model.EnterpriseDeviceAuthorization)
for _, auth := range auths {
deviceIDs = append(deviceIDs, auth.DeviceID)
authMap[auth.DeviceID] = auth
}
// 查询设备信息
var devices []model.Device
query := s.db.WithContext(ctx).Where("id IN ?", deviceIDs)
if req.DeviceNo != "" {
query = query.Where("device_no LIKE ?", "%"+req.DeviceNo+"%")
}
if err := query.Find(&devices).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败")
}
// 统计每个设备的绑定卡数量
var bindings []model.DeviceSimBinding
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, "查询设备绑定卡失败")
}
cardCountMap := make(map[uint]int)
for _, binding := range bindings {
cardCountMap[binding.DeviceID]++
}
// 构建响应
items := make([]dto.EnterpriseDeviceItem, 0, len(devices))
for _, device := range devices {
auth := authMap[device.ID]
items = append(items, dto.EnterpriseDeviceItem{
DeviceID: device.ID,
DeviceNo: device.DeviceNo,
DeviceName: device.DeviceName,
DeviceModel: device.DeviceModel,
CardCount: cardCountMap[device.ID],
AuthorizedAt: auth.AuthorizedAt,
})
}
return &dto.EnterpriseDeviceListResp{
List: items,
Total: total,
}, nil
}
// ListDevicesForEnterprise 查询企业授权设备列表H5企业用户
func (s *Service) ListDevicesForEnterprise(ctx context.Context, req *dto.EnterpriseDeviceListReq) (*dto.EnterpriseDeviceListResp, error) {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
opts := postgres.DeviceAuthListOptions{
EnterpriseID: &enterpriseID,
IncludeRevoked: false,
Page: req.Page,
PageSize: req.PageSize,
}
auths, total, err := s.enterpriseDeviceAuthStore.ListByEnterprise(ctx, opts)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询授权记录失败")
}
if len(auths) == 0 {
return &dto.EnterpriseDeviceListResp{
List: make([]dto.EnterpriseDeviceItem, 0),
Total: 0,
}, nil
}
deviceIDs := make([]uint, 0, len(auths))
authMap := make(map[uint]*model.EnterpriseDeviceAuthorization)
for _, auth := range auths {
deviceIDs = append(deviceIDs, auth.DeviceID)
authMap[auth.DeviceID] = auth
}
var devices []model.Device
query := s.db.WithContext(ctx).Where("id IN ?", deviceIDs)
if req.DeviceNo != "" {
query = query.Where("device_no LIKE ?", "%"+req.DeviceNo+"%")
}
if err := query.Find(&devices).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备信息失败")
}
var bindings []model.DeviceSimBinding
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, "查询设备绑定卡失败")
}
cardCountMap := make(map[uint]int)
for _, binding := range bindings {
cardCountMap[binding.DeviceID]++
}
items := make([]dto.EnterpriseDeviceItem, 0, len(devices))
for _, device := range devices {
auth := authMap[device.ID]
items = append(items, dto.EnterpriseDeviceItem{
DeviceID: device.ID,
DeviceNo: device.DeviceNo,
DeviceName: device.DeviceName,
DeviceModel: device.DeviceModel,
CardCount: cardCountMap[device.ID],
AuthorizedAt: auth.AuthorizedAt,
})
}
return &dto.EnterpriseDeviceListResp{
List: items,
Total: total,
}, nil
}
// GetDeviceDetail 获取设备详情H5企业用户
func (s *Service) GetDeviceDetail(ctx context.Context, deviceID uint) (*dto.EnterpriseDeviceDetailResp, error) {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, deviceID)
if err != nil || auth.EnterpriseID != enterpriseID || auth.RevokedAt != nil {
return nil, errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业")
}
var device model.Device
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(ctx).
Where("device_id = ? AND bind_status = 1", deviceID).
Find(&bindings).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询设备绑定卡失败")
}
cardIDs := make([]uint, 0, len(bindings))
for _, binding := range bindings {
cardIDs = append(cardIDs, binding.IotCardID)
}
var cards []model.IotCard
cardInfos := make([]dto.DeviceCardInfo, 0)
if len(cardIDs) > 0 {
if err := s.db.WithContext(ctx).Where("id IN ?", cardIDs).Find(&cards).Error; err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败")
}
carrierIDs := make([]uint, 0, len(cards))
for _, card := range cards {
carrierIDs = append(carrierIDs, card.CarrierID)
}
var carriers []model.Carrier
carrierMap := make(map[uint]string)
if len(carrierIDs) > 0 {
if err := s.db.WithContext(ctx).Where("id IN ?", carrierIDs).Find(&carriers).Error; err == nil {
for _, carrier := range carriers {
carrierMap[carrier.ID] = carrier.CarrierName
}
}
}
for _, card := range cards {
cardInfos = append(cardInfos, dto.DeviceCardInfo{
CardID: card.ID,
ICCID: card.ICCID,
MSISDN: card.MSISDN,
CarrierName: carrierMap[card.CarrierID],
NetworkStatus: card.NetworkStatus,
NetworkStatusName: getNetworkStatusName(card.NetworkStatus),
})
}
}
return &dto.EnterpriseDeviceDetailResp{
Device: dto.EnterpriseDeviceInfo{
DeviceID: device.ID,
DeviceNo: device.DeviceNo,
DeviceName: device.DeviceName,
DeviceModel: device.DeviceModel,
DeviceType: device.DeviceType,
AuthorizedAt: auth.AuthorizedAt,
},
Cards: cardInfos,
}, nil
}
func (s *Service) SuspendCard(ctx context.Context, deviceID, cardID uint, req *dto.DeviceCardOperationReq) (*dto.DeviceCardOperationResp, error) {
if err := s.validateCardOperation(ctx, deviceID, cardID); err != nil {
return nil, err
}
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, "停机操作失败")
}
return &dto.DeviceCardOperationResp{
Success: true,
Message: "停机成功",
}, nil
}
func (s *Service) ResumeCard(ctx context.Context, deviceID, cardID uint, req *dto.DeviceCardOperationReq) (*dto.DeviceCardOperationResp, error) {
if err := s.validateCardOperation(ctx, deviceID, cardID); err != nil {
return nil, err
}
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, "复机操作失败")
}
return &dto.DeviceCardOperationResp{
Success: true,
Message: "复机成功",
}, nil
}
func (s *Service) validateCardOperation(ctx context.Context, deviceID, cardID uint) error {
enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
if enterpriseID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
auth, err := s.enterpriseDeviceAuthStore.GetByDeviceID(ctx, deviceID)
if err != nil || auth.EnterpriseID != enterpriseID || auth.RevokedAt != nil {
return errors.New(errors.CodeDeviceNotAuthorized, "设备未授权给此企业")
}
var binding model.DeviceSimBinding
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(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, "无权操作此卡")
}
return nil
}
func getNetworkStatusName(status int) string {
if status == 1 {
return "开机"
}
return "停机"
}