package device import ( "context" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/logger" "go.uber.org/zap" "gorm.io/gorm" ) func (s *Service) ListBindings(ctx context.Context, deviceID uint) (*dto.ListDeviceCardsResponse, error) { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "设备不存在") } return nil, err } bindings, err := s.deviceSimBindingStore.ListByDeviceID(ctx, device.ID) if err != nil { return nil, err } cardIDs := make([]uint, 0, len(bindings)) for _, binding := range bindings { cardIDs = append(cardIDs, binding.IotCardID) } var cards []*model.IotCard if len(cardIDs) > 0 { cards, err = s.iotCardStore.GetByIDs(ctx, cardIDs) if err != nil { return nil, err } } cardMap := make(map[uint]*model.IotCard) for _, card := range cards { cardMap[card.ID] = card } responses := make([]*dto.DeviceCardBindingResponse, 0, len(bindings)) for _, binding := range bindings { card := cardMap[binding.IotCardID] if card == nil { continue } resp := &dto.DeviceCardBindingResponse{ ID: binding.ID, SlotPosition: binding.SlotPosition, IotCardID: binding.IotCardID, ICCID: card.ICCID, MSISDN: card.MSISDN, CarrierName: card.CarrierName, Status: card.Status, StatusName: constants.GetIotCardStatusName(card.Status), BindTime: binding.BindTime, IsCurrent: binding.IsCurrent, RealnamePolicy: card.RealnamePolicy, NetworkStatus: card.NetworkStatus, } responses = append(responses, resp) } return &dto.ListDeviceCardsResponse{ Bindings: responses, }, nil } func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCardToDeviceRequest) (*dto.BindCardToDeviceResponse, error) { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { if err == gorm.ErrRecordNotFound { appErr := errors.New(errors.CodeNotFound, "设备不存在") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, nil, nil, map[string]any{ "device_id": deviceID, "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, appErr, ) return nil, appErr } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, nil, nil, map[string]any{ "device_id": deviceID, "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, err, ) return nil, err } if req.SlotPosition > device.MaxSimSlots { appErr := errors.New(errors.CodeInvalidParam, "插槽位置超出设备最大插槽数") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡被拒绝", constants.AssetAuditResultDenied, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, appErr, ) return nil, appErr } existingBinding, err := s.deviceSimBindingStore.GetByDeviceAndSlot(ctx, device.ID, req.SlotPosition) if err != nil && err != gorm.ErrRecordNotFound { s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, err, ) return nil, err } if existingBinding != nil { appErr := errors.New(errors.CodeConflict, "该插槽已有绑定的卡") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡被拒绝", constants.AssetAuditResultDenied, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, appErr, ) return nil, appErr } card, err := s.iotCardStore.GetByID(ctx, req.IotCardID) if err != nil { if err == gorm.ErrRecordNotFound { appErr := errors.New(errors.CodeIotCardNotFound) s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, appErr, ) return nil, appErr } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, err, ) return nil, err } activeBinding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID) if err != nil && err != gorm.ErrRecordNotFound { s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, err, ) return nil, err } if activeBinding != nil { appErr := errors.New(errors.CodeIotCardBoundToDevice, "该卡已绑定到其他设备") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡被拒绝", constants.AssetAuditResultDenied, device, map[string]any{ "device": deviceSnapshot(device), "card": map[string]any{ "id": card.ID, "iccid": card.ICCID, "status": card.Status, }, }, map[string]any{ "iot_card_id": req.IotCardID, "slot_position": req.SlotPosition, }, 0, 0, 0, appErr, ) return nil, appErr } binding := &model.DeviceSimBinding{ DeviceID: device.ID, IotCardID: card.ID, SlotPosition: req.SlotPosition, BindStatus: 1, } if err := s.deviceSimBindingStore.Create(ctx, binding); err != nil { s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{ "device": deviceSnapshot(device), "card": map[string]any{ "id": card.ID, "iccid": card.ICCID, "status": card.Status, }, }, map[string]any{ "slot_position": req.SlotPosition, }, 0, 0, 0, err, ) return nil, err } // 异步快照维护:更新卡上的设备虚拟号,失败不阻断主流程 if updateErr := s.iotCardStore.UpdateDeviceVirtualNo(ctx, card.ID, device.VirtualNo); updateErr != nil { logger.GetAppLogger().Warn("更新卡的设备虚拟号快照失败", zap.Uint("card_id", card.ID), zap.Uint("device_id", device.ID), zap.Error(updateErr), ) } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceBindCard, "设备绑卡", constants.AssetAuditResultSuccess, device, map[string]any{ "device": deviceSnapshot(device), "card": map[string]any{ "id": card.ID, "iccid": card.ICCID, "status": card.Status, }, }, map[string]any{ "binding_id": binding.ID, "slot_position": req.SlotPosition, "iot_card_id": card.ID, "iccid": card.ICCID, }, 1, 1, 0, nil, ) return &dto.BindCardToDeviceResponse{ BindingID: binding.ID, Message: "绑定成功", }, nil } func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*dto.UnbindCardFromDeviceResponse, error) { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { if err == gorm.ErrRecordNotFound { appErr := errors.New(errors.CodeNotFound, "设备不存在") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡失败", constants.AssetAuditResultFailed, nil, nil, map[string]any{ "device_id": deviceID, "iot_card_id": cardID, }, 0, 0, 0, appErr, ) return nil, appErr } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡失败", constants.AssetAuditResultFailed, nil, nil, map[string]any{ "device_id": deviceID, "iot_card_id": cardID, }, 0, 0, 0, err, ) return nil, err } binding, err := s.deviceSimBindingStore.GetByDeviceAndCard(ctx, device.ID, cardID) if err != nil { if err == gorm.ErrRecordNotFound { appErr := errors.New(errors.CodeNotFound, "该卡未绑定到此设备") s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡被拒绝", constants.AssetAuditResultDenied, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{"iot_card_id": cardID}, 0, 0, 0, appErr, ) return nil, appErr } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{"device": deviceSnapshot(device)}, map[string]any{"iot_card_id": cardID}, 0, 0, 0, err, ) return nil, err } if err := s.deviceSimBindingStore.Unbind(ctx, binding.ID); err != nil { s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡失败", constants.AssetAuditResultFailed, device, map[string]any{ "device": deviceSnapshot(device), "binding_id": binding.ID, "iot_card_id": binding.IotCardID, }, nil, 0, 0, 0, err, ) return nil, err } // 解绑后清空卡的设备虚拟号快照,失败不阻断主流程 if updateErr := s.iotCardStore.UpdateDeviceVirtualNo(ctx, cardID, ""); updateErr != nil { logger.GetAppLogger().Warn("清空卡的设备虚拟号快照失败", zap.Uint("card_id", cardID), zap.Uint("device_id", deviceID), zap.Error(updateErr), ) } s.logDeviceOperation( ctx, constants.AssetAuditOpDeviceUnbindCard, "设备解绑卡", constants.AssetAuditResultSuccess, device, map[string]any{ "device": deviceSnapshot(device), "binding_id": binding.ID, "iot_card_id": binding.IotCardID, }, map[string]any{ "iot_card_id": cardID, "unbind": true, }, 1, 1, 0, nil, ) return &dto.UnbindCardFromDeviceResponse{ Message: "解绑成功", }, nil }