收口审计治理与套餐任务进展

Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
2026-08-05 14:30:54 +08:00
parent b3499adfca
commit 5e552d99bc
178 changed files with 16797 additions and 5674 deletions

View File

@@ -5,6 +5,7 @@ import (
"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/logger"
@@ -80,105 +81,29 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
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
return nil, 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,
err,
)
return nil, err
}
metadata := map[string]any{"iot_card_id": req.IotCardID, "slot_position": req.SlotPosition}
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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡被拒绝", constants.AuditResultDenied,
device, nil, nil, metadata, 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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡失败", constants.AuditResultFailed,
device, nil, nil, metadata, 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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡被拒绝", constants.AuditResultDenied,
device, nil, nil, metadata, appErr)
return nil, appErr
}
@@ -186,88 +111,30 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡失败", constants.AuditResultFailed,
device, nil, nil, metadata, 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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡失败", constants.AuditResultFailed,
device, nil, nil, metadata, err)
return nil, err
}
item := deviceBindingAuditItem{
Card: card, CardRole: constants.AuditResourceRoleDeviceBindingTargetCard,
CardBefore: map[string]any{"device_id": nil, "slot_position": nil},
CardAfter: map[string]any{"device_id": device.ID, "slot_position": req.SlotPosition},
}
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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡失败", constants.AuditResultFailed,
device, nil, []deviceBindingAuditItem{item}, metadata, 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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡被拒绝", constants.AuditResultDenied,
device, nil, []deviceBindingAuditItem{item}, metadata, appErr)
return nil, appErr
}
@@ -278,29 +145,26 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
BindStatus: 1,
}
if err := s.deviceSimBindingStore.Create(ctx, binding); err != nil {
s.logDeviceOperation(
ctx,
constants.AssetAuditOpDeviceBindCard,
"设备绑卡失败",
constants.AssetAuditResultFailed,
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := postgres.NewDeviceSimBindingStore(tx, nil).Create(ctx, binding); err != nil {
return err
}
item.Binding = binding
item.BindingRole = constants.AuditResourceRoleDeviceCreatedBinding
item.BindingAfter = bindingStateData(binding, constants.BindStatusBound, false)
return s.appendDeviceBindingAudit(ctx, tx, constants.AuditActionDeviceCardBound, "设备绑定 IoT 卡", constants.AuditResultSuccess,
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,
)
map[string]any{"slot_position": req.SlotPosition, "iot_card_id": nil},
map[string]any{"slot_position": req.SlotPosition, "iot_card_id": card.ID},
[]deviceBindingAuditItem{item}, metadata, nil)
})
if err != nil {
result := constants.AuditResultFailed
if appErr, ok := err.(*errors.AppError); ok && (appErr.Code == errors.CodeConflict || appErr.Code == errors.CodeIotCardBoundToDevice) {
result = constants.AuditResultDenied
}
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardBound, "设备绑卡失败", result,
device, nil, []deviceBindingAuditItem{{Card: card, CardRole: constants.AuditResourceRoleDeviceBindingTargetCard}}, metadata, err)
return nil, err
}
@@ -313,32 +177,6 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
)
}
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: "绑定成功",
@@ -349,111 +187,54 @@ func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*
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
return nil, 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,
err,
)
return nil, err
}
metadata := map[string]any{"iot_card_id": cardID}
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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardUnbound, "设备解绑卡被拒绝", constants.AuditResultDenied,
device, nil, nil, metadata, 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,
)
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardUnbound, "设备解绑卡失败", constants.AuditResultFailed,
device, nil, nil, metadata, 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,
card, cardErr := s.iotCardStore.GetByID(ctx, binding.IotCardID)
if cardErr != nil {
card = &model.IotCard{}
card.ID = binding.IotCardID
}
item := deviceBindingAuditItem{
Card: card, Binding: binding,
CardRole: constants.AuditResourceRoleDeviceBindingTargetCard, BindingRole: constants.AuditResourceRoleDeviceRemovedBinding,
CardBefore: map[string]any{"device_id": device.ID, "slot_position": binding.SlotPosition},
CardAfter: map[string]any{"device_id": nil, "slot_position": nil},
BindingBefore: bindingStateData(binding, constants.BindStatusBound, binding.IsCurrent),
BindingAfter: bindingStateData(binding, constants.BindStatusUnbound, false),
}
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := postgres.NewDeviceSimBindingStore(tx, nil).Unbind(ctx, binding.ID); err != nil {
return err
}
}
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,
if err := tx.WithContext(ctx).Model(&model.DeviceSimBinding{}).Where("id = ?", binding.ID).Update("is_current", false).Error; err != nil {
return err
}
return s.appendDeviceBindingAudit(ctx, tx, constants.AuditActionDeviceCardUnbound, "设备解绑 IoT 卡", constants.AuditResultSuccess,
device,
beforeAuditData,
nil,
0,
0,
0,
err,
)
map[string]any{"slot_position": binding.SlotPosition, "iot_card_id": binding.IotCardID},
map[string]any{"slot_position": binding.SlotPosition, "iot_card_id": nil},
[]deviceBindingAuditItem{item}, metadata, nil)
})
if err != nil {
s.recordDeviceBindingAuditFailure(ctx, constants.AuditActionDeviceCardUnbound, "设备解绑卡失败", constants.AuditResultFailed,
device, nil, []deviceBindingAuditItem{item}, metadata, err)
return nil, err
}
@@ -466,28 +247,6 @@ func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*
)
}
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