All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m5s
495 lines
11 KiB
Go
495 lines
11 KiB
Go
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),
|
|
RealNameStatus: card.RealNameStatus,
|
|
RealNameStatusName: constants.GetRealNameStatusName(card.RealNameStatus),
|
|
BindTime: binding.BindTime,
|
|
IsCurrent: binding.IsCurrent,
|
|
RealnamePolicy: card.RealnamePolicy,
|
|
NetworkStatus: card.NetworkStatus,
|
|
GatewayExtend: card.GatewayExtend,
|
|
}
|
|
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
|
|
}
|
|
|
|
var cardAudit map[string]any
|
|
if card, cardErr := s.iotCardStore.GetByID(ctx, binding.IotCardID); cardErr == nil {
|
|
cardAudit = map[string]any{
|
|
"id": card.ID,
|
|
"iccid": card.ICCID,
|
|
"status": card.Status,
|
|
}
|
|
}
|
|
|
|
beforeAuditData := map[string]any{
|
|
"device": deviceSnapshot(device),
|
|
"binding_id": binding.ID,
|
|
"iot_card_id": binding.IotCardID,
|
|
}
|
|
if cardAudit != nil {
|
|
beforeAuditData["card"] = cardAudit
|
|
}
|
|
|
|
if err := s.deviceSimBindingStore.Unbind(ctx, binding.ID); err != nil {
|
|
s.logDeviceOperation(
|
|
ctx,
|
|
constants.AssetAuditOpDeviceUnbindCard,
|
|
"设备解绑卡失败",
|
|
constants.AssetAuditResultFailed,
|
|
device,
|
|
beforeAuditData,
|
|
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),
|
|
)
|
|
}
|
|
|
|
afterAuditData := map[string]any{
|
|
"iot_card_id": cardID,
|
|
"unbind": true,
|
|
}
|
|
if cardAudit != nil {
|
|
afterAuditData["card"] = cardAudit
|
|
}
|
|
|
|
s.logDeviceOperation(
|
|
ctx,
|
|
constants.AssetAuditOpDeviceUnbindCard,
|
|
"设备解绑卡",
|
|
constants.AssetAuditResultSuccess,
|
|
device,
|
|
beforeAuditData,
|
|
afterAuditData,
|
|
1,
|
|
1,
|
|
0,
|
|
nil,
|
|
)
|
|
|
|
return &dto.UnbindCardFromDeviceResponse{
|
|
Message: "解绑成功",
|
|
}, nil
|
|
}
|