package customer_binding import ( "context" "strconv" "gorm.io/gorm" accessauditapp "github.com/break/junhong_cmp_fiber/internal/application/accessaudit" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/auditcontext" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // SetAccessAudit 注入个人客户资产关系的统一审计接缝。 func (s *Service) SetAccessAudit(writer accessauditapp.Writer) { s.accessAudit = writer } func (s *Service) writeBindingAudit( ctx context.Context, tx *gorm.DB, actionCode, summary string, customerID uint, personalDevices []accessauditapp.PersonalCustomerDeviceChange, personalICCIDs []accessauditapp.PersonalCustomerICCIDChange, cards []accessauditapp.IotCardChange, devices []accessauditapp.DeviceChange, ) error { if s.accessAudit == nil { return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置") } var customer model.PersonalCustomer if err := tx.WithContext(ctx).First(&customer, customerID).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询个人客户审计快照失败") } value := auditcontext.From(ctx) operatorID := customerID actorKind := constants.AuditActorPersonalCustomer actorName := customer.Nickname source := constants.AuditSourcePersonalAPI scopeType := constants.AuditScopePersonalCustomer visibility := constants.AuditSubjectDetail subjectData := map[string]any(nil) if actionCode == constants.AuditActionPersonalCustomerAssetBound { assetType, assetID := bindingAssetReference(cards, devices) subjectData = map[string]any{"asset_type": assetType, "asset_id": assetID} cards = nil devices = nil } else { operatorID = middleware.GetUserIDFromContext(ctx) if parsed, err := strconv.ParseUint(value.ActorID, 10, 64); err == nil && parsed > 0 { operatorID = uint(parsed) } actorKind = value.ActorKind actorName = value.ActorName source = value.Source scopeType = constants.AuditScopePlatform visibility = constants.AuditSubjectResult } if operatorID == 0 { return errors.New(errors.CodeInvalidStatus, "个人客户资产审计操作者不完整") } return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{ ActionCode: actionCode, Summary: summary, Result: constants.AuditResultSuccess, OperatorID: operatorID, ActorKind: actorKind, ActorName: actorName, Source: source, ScopeType: scopeType, PersonalCustomer: &customer, PersonalDevices: personalDevices, PersonalICCIDs: personalICCIDs, Cards: cards, Devices: devices, SubjectVisibility: visibility, SubjectSummary: summary, SubjectData: subjectData, }) } func bindingAssetReference(cards []accessauditapp.IotCardChange, devices []accessauditapp.DeviceChange) (string, uint) { if len(cards) > 0 && cards[0].Card != nil { return constants.AuditResourceIotCard, cards[0].Card.ID } if len(devices) > 0 && devices[0].Device != nil { return constants.AuditResourceDevice, devices[0].Device.ID } return "", 0 } func cardAuditChange(card *model.IotCard, relation, role string, beforeData, afterData map[string]any) accessauditapp.IotCardChange { return accessauditapp.IotCardChange{ Card: card, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData, SubjectSummary: "个人客户资产关系已更新", } } func deviceAuditChange(device *model.Device, relation, role string, beforeData, afterData map[string]any) accessauditapp.DeviceChange { return accessauditapp.DeviceChange{ Device: device, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData, SubjectSummary: "个人客户资产关系已更新", } } func (s *Service) loadAuditAssets(ctx context.Context, tx *gorm.DB, oldType string, oldID uint, newType string, newID uint) ([]accessauditapp.IotCardChange, []accessauditapp.DeviceChange, error) { cards := make([]accessauditapp.IotCardChange, 0, 2) devices := make([]accessauditapp.DeviceChange, 0, 2) appendAsset := func(assetType string, assetID uint, role string) error { switch normalizeAssetType(assetType) { case assetTypeIotCard: card, err := s.readCard(ctx, tx, assetID) if err != nil { return err } cards = append(cards, cardAuditChange(card, constants.AuditResourceRelationAffected, role, nil, nil)) case assetTypeDevice: device, err := s.readDevice(ctx, tx, assetID) if err != nil { return err } devices = append(devices, deviceAuditChange(device, constants.AuditResourceRelationAffected, role, nil, nil)) default: return errors.New(errors.CodeInvalidParam, "无效的资产类型") } return nil } if err := appendAsset(oldType, oldID, constants.AuditResourceRolePersonalCustomerOldAsset); err != nil { return nil, nil, err } if err := appendAsset(newType, newID, constants.AuditResourceRolePersonalCustomerNewAsset); err != nil { return nil, nil, err } return cards, devices, nil } func (s *Service) writeMigrationAudit( ctx context.Context, tx *gorm.DB, customerID uint, oldType string, oldID uint, newType string, newID uint, personalDevices []accessauditapp.PersonalCustomerDeviceChange, personalICCIDs []accessauditapp.PersonalCustomerICCIDChange, ) error { cards, devices, err := s.loadAuditAssets(ctx, tx, oldType, oldID, newType, newID) if err != nil { return err } return s.writeBindingAudit( ctx, tx, constants.AuditActionPersonalCustomerAssetBindingMigrated, "换货迁移个人客户资产绑定", customerID, personalDevices, personalICCIDs, cards, devices, ) } // UnbindByVirtualNo 按现有换货重置语义删除设备号绑定,并在同一事务记录实际删除关系。 func (s *Service) UnbindByVirtualNo(ctx context.Context, tx *gorm.DB, assetType string, assetID uint, virtualNo string) error { if s.accessAudit == nil { return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置") } if tx == nil { tx = s.db } records, err := s.makePCD(tx).GetByDeviceNo(ctx, virtualNo) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询个人客户资产绑定失败") } if err := tx.WithContext(ctx).Where("virtual_no = ?", virtualNo).Delete(&model.PersonalCustomerDevice{}).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "清理个人客户绑定失败") } for _, record := range records { if record == nil { continue } cards := []accessauditapp.IotCardChange(nil) devices := []accessauditapp.DeviceChange(nil) switch normalizeAssetType(assetType) { case assetTypeIotCard: card, loadErr := s.readCard(ctx, tx, assetID) if loadErr != nil { return loadErr } cards = append(cards, cardAuditChange(card, constants.AuditResourceRelationAffected, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil)) case assetTypeDevice: device, loadErr := s.readDevice(ctx, tx, assetID) if loadErr != nil { return loadErr } devices = append(devices, deviceAuditChange(device, constants.AuditResourceRelationAffected, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil)) default: return errors.New(errors.CodeInvalidParam, "无效的资产类型") } if err := s.writeBindingAudit( ctx, tx, constants.AuditActionPersonalCustomerAssetUnbound, "解除个人客户资产绑定", record.CustomerID, []accessauditapp.PersonalCustomerDeviceChange{{ Binding: record, Role: constants.AuditResourceRolePersonalCustomerAssetBinding, BeforeData: map[string]any{"virtual_no": record.VirtualNo, "status": record.Status}, AfterData: map[string]any{"deleted": true}, }}, nil, cards, devices, ); err != nil { return err } } return nil }