package device import ( "context" "strconv" "github.com/google/uuid" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit" "github.com/break/junhong_cmp_fiber/pkg/auditcontext" "github.com/break/junhong_cmp_fiber/pkg/auditfailure" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // SetAccessAudit 注入设备身份生命周期的统一审计 Writer。 func (s *Service) SetAccessAudit(writer *audit.Writer) { s.auditWriter = writer } func (s *Service) appendDeviceLifecycleAudit( ctx context.Context, tx *gorm.DB, actionCode, summary, result string, device *model.Device, beforeData, afterData map[string]any, references []audit.ResourceInput, businessErr error, ) error { if s.auditWriter == nil || device == nil || device.ID == 0 { return errors.New(errors.CodeInvalidStatus, "设备统一审计接缝未配置或资源不完整") } resourceID := strconv.FormatUint(uint64(device.ID), 10) errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr) resources := []audit.ResourceInput{{ Type: constants.AuditResourceDevice, ID: &resourceID, Key: audit.DeviceResourceKey(device), DisplayName: device.VirtualNo, Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleDeviceTarget, IdentitySnapshot: audit.DeviceIdentitySnapshot(device), BeforeData: beforeData, AfterData: afterData, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: summary, }} resources = append(resources, references...) return s.auditWriter.Append(ctx, tx, audit.AppendInput{ ActionCode: actionCode, Summary: summary, ScopeType: constants.AuditScopePlatform, Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary, Resources: resources, }) } func (s *Service) recordDeviceLifecycleFailure(ctx context.Context, actionCode, summary, result string, device *model.Device, deviceID uint, businessErr error) { if device == nil { device = &model.Device{} device.ID = deviceID } if s.db == nil || s.auditWriter == nil || device.ID == 0 { recordDeviceAuditSecondaryFailure(ctx, actionCode, deviceID, businessErr, errors.New(errors.CodeInvalidStatus, "设备统一审计接缝未配置")) return } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendDeviceLifecycleAudit(ctx, tx, actionCode, summary, result, device, nil, nil, nil, businessErr) }); err != nil { recordDeviceAuditSecondaryFailure(ctx, actionCode, device.ID, businessErr, err) } } func recordDeviceAuditSecondaryFailure(ctx context.Context, actionCode string, deviceID uint, businessErr, auditErr error) { errorCode, _ := assetAuditSvc.BuildErrorInfo(businessErr) linkage := auditcontext.From(ctx) auditfailure.RecordSecondaryWriteFailure( actionCode, strconv.FormatUint(uint64(deviceID), 10), linkage.RequestID, linkage.CorrelationID, errorCode, auditErr, ) } type deviceAuditOutcome struct { Result string Summary string } type deviceBatchAuditItem struct { Device *model.Device PrimaryRole string Result string Summary string ErrorSummary string BeforeData map[string]any AfterData map[string]any References []audit.ResourceInput } type deviceCardAuditChange struct { ShopID *uint Status int } func deviceAuditOutcomes(devices []*model.Device, result, summary string) map[uint]deviceAuditOutcome { outcomes := make(map[uint]deviceAuditOutcome, len(devices)) for _, device := range devices { if device != nil && device.ID > 0 { outcomes[device.ID] = deviceAuditOutcome{Result: result, Summary: summary} } } return outcomes } func setDeviceAuditOutcomes(outcomes map[uint]deviceAuditOutcome, ids []uint, result, summary string) { for _, id := range ids { if _, ok := outcomes[id]; ok { outcomes[id] = deviceAuditOutcome{Result: result, Summary: summary} } } } func setDeviceAuditFailedItems(outcomes map[uint]deviceAuditOutcome, items []dto.AllocationDeviceFailedItem) { for _, item := range items { if _, ok := outcomes[item.DeviceID]; ok { outcomes[item.DeviceID] = deviceAuditOutcome{Result: constants.AuditResultDenied, Summary: item.Reason} } } } func deviceModelsByIDs(devices []*model.Device, ids []uint) []*model.Device { wanted := make(map[uint]struct{}, len(ids)) for _, id := range ids { wanted[id] = struct{}{} } result := make([]*model.Device, 0, len(ids)) for _, device := range devices { if device != nil { if _, ok := wanted[device.ID]; ok { result = append(result, device) } } } return result } func (s *Service) appendDeviceTransferAudit( ctx context.Context, tx *gorm.DB, rootAction, itemAction, kind, summary, result string, devices []*model.Device, outcomes map[uint]deviceAuditOutcome, records []*model.AssetAllocationRecord, targetShopID *uint, newStatus, batchTotal, successCount, failCount int, cardReferences map[uint][]audit.ResourceInput, businessErr error, ) error { if cardReferences == nil { var err error cardReferences, _, err = loadDeviceCardAuditReferences(ctx, tx, devices, nil) if err != nil { return err } } shops, err := loadDeviceTransferAuditShops(ctx, tx, devices, targetShopID) if err != nil { return err } recordByDeviceID := make(map[uint]*model.AssetAllocationRecord, len(records)) for _, record := range records { if record != nil { recordByDeviceID[record.AssetID] = record } } items := make([]deviceBatchAuditItem, 0, len(devices)) for _, device := range devices { if device == nil || device.ID == 0 { continue } outcome, ok := outcomes[device.ID] if !ok { continue } var afterData map[string]any if outcome.Result == constants.AuditResultSuccess { afterData = map[string]any{"shop_id": targetShopID, "status": newStatus} } references := deviceTransferAuditReferences(device, recordByDeviceID[device.ID], targetShopID, shops) references = append(references, cardReferences[device.ID]...) items = append(items, deviceBatchAuditItem{ Device: device, PrimaryRole: constants.AuditResourceRoleDeviceTransferTarget, Result: outcome.Result, Summary: outcome.Summary, ErrorSummary: outcome.Summary, BeforeData: map[string]any{"shop_id": device.ShopID, "status": device.Status}, AfterData: afterData, References: references, }) } allocationNo := "" if len(records) > 0 && records[0] != nil { allocationNo = records[0].AllocationNo } return s.appendDeviceBatchAudit(ctx, tx, rootAction, itemAction, kind, summary, result, batchTotal, successCount, failCount, items, map[string]any{"allocation_no": allocationNo, "to_shop_id": targetShopID, "new_status": newStatus}, businessErr) } func loadDeviceTransferAuditShops(ctx context.Context, tx *gorm.DB, devices []*model.Device, targetShopID *uint) (map[uint]*model.Shop, error) { shopIDs := make(map[uint]struct{}) if targetShopID != nil && *targetShopID > 0 { shopIDs[*targetShopID] = struct{}{} } for _, device := range devices { if device != nil && device.ShopID != nil && *device.ShopID > 0 { shopIDs[*device.ShopID] = struct{}{} } } ids := make([]uint, 0, len(shopIDs)) for id := range shopIDs { ids = append(ids, id) } var rows []*model.Shop if len(ids) > 0 { if err := tx.WithContext(ctx).Unscoped().Where("id IN ?", ids).Find(&rows).Error; err != nil { return nil, err } } shops := make(map[uint]*model.Shop, len(rows)) for _, shop := range rows { shops[shop.ID] = shop } return shops, nil } func deviceTransferAuditReferences(device *model.Device, record *model.AssetAllocationRecord, targetShopID *uint, shops map[uint]*model.Shop) []audit.ResourceInput { resources := make([]audit.ResourceInput, 0, 3) if record != nil && record.ID > 0 { recordID := strconv.FormatUint(uint64(record.ID), 10) resources = append(resources, audit.ResourceInput{ Type: constants.AuditResourceAssetAllocationRecord, ID: &recordID, Key: recordID, DisplayName: record.AllocationNo, Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleAssetAllocationRecord, IdentitySnapshot: map[string]any{ "id": record.ID, "allocation_no": record.AllocationNo, "asset_type": record.AssetType, "asset_id": record.AssetID, "asset_identifier": record.AssetIdentifier, "from_owner_type": record.FromOwnerType, "from_owner_id": record.FromOwnerID, "to_owner_type": record.ToOwnerType, "to_owner_id": record.ToOwnerID, }, AfterData: map[string]any{"created": true}, SubjectVisibility: constants.AuditSubjectInternalOnly, }) } if device.ShopID != nil && *device.ShopID > 0 { resources = appendDeviceShopAuditReference(resources, shops[*device.ShopID], *device.ShopID, constants.AuditResourceRoleTransferSourceShop) } if targetShopID != nil && *targetShopID > 0 { resources = appendDeviceShopAuditReference(resources, shops[*targetShopID], *targetShopID, constants.AuditResourceRoleTransferTargetShop) } return resources } func appendDeviceShopAuditReference(resources []audit.ResourceInput, shop *model.Shop, shopID uint, role string) []audit.ResourceInput { id := strconv.FormatUint(uint64(shopID), 10) name := id identity := map[string]any{"id": shopID} if shop != nil { name = shop.ShopName identity = map[string]any{"id": shop.ID, "shop_code": shop.ShopCode, "shop_name": shop.ShopName, "parent_id": shop.ParentID, "level": shop.Level} } return append(resources, audit.ResourceInput{ Type: constants.AuditResourceShop, ID: &id, Key: id, DisplayName: name, Relation: constants.AuditResourceRelationReference, Role: role, IdentitySnapshot: identity, SubjectVisibility: constants.AuditSubjectInternalOnly, }) } func (s *Service) appendDeviceBatchAudit( ctx context.Context, tx *gorm.DB, rootAction, itemAction, kind, summary, result string, batchTotal, successCount, failCount int, items []deviceBatchAuditItem, metadata map[string]any, businessErr error, ) error { linkage := auditcontext.From(ctx) batchKey := linkage.RequestID if batchKey == "" { batchKey = linkage.CorrelationID } if s.auditWriter == nil || batchKey == "" { return errors.New(errors.CodeInvalidStatus, "设备批量审计上下文不完整") } errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr) children := make([]audit.AppendInput, 0, len(items)) for _, item := range items { if item.Device == nil || item.Device.ID == 0 { continue } deviceID := strconv.FormatUint(uint64(item.Device.ID), 10) primaryRole := item.PrimaryRole if primaryRole == "" { primaryRole = constants.AuditResourceRoleDeviceTarget } resources := []audit.ResourceInput{{ Type: constants.AuditResourceDevice, ID: &deviceID, Key: audit.DeviceResourceKey(item.Device), DisplayName: item.Device.VirtualNo, Relation: constants.AuditResourceRelationPrimary, Role: primaryRole, IdentitySnapshot: audit.DeviceIdentitySnapshot(item.Device), BeforeData: item.BeforeData, AfterData: item.AfterData, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: item.Summary, }} resources = append(resources, item.References...) childErrorCode, childErrorSummary := "", "" if item.Result == constants.AuditResultFailed || item.Result == constants.AuditResultDenied { childErrorCode = errorCode childErrorSummary = item.ErrorSummary if childErrorSummary == "" { childErrorSummary = errorSummary } } children = append(children, audit.AppendInput{ EventID: stableDeviceBatchEventID(kind+"-"+item.Result+"-device", batchKey+":"+deviceID), ActionCode: itemAction, Summary: item.Summary, ScopeType: constants.AuditScopePlatform, Result: item.Result, ErrorCode: childErrorCode, ErrorSummary: childErrorSummary, Resources: resources, }) } return s.auditWriter.AppendBatch(ctx, tx, audit.BatchInput{ Root: audit.AppendInput{ EventID: stableDeviceBatchEventID(kind+"-"+result, batchKey), ActionCode: rootAction, Summary: summary, ScopeType: constants.AuditScopePlatform, Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary, BatchTotal: batchTotal, SuccessCount: successCount, FailCount: failCount, Metadata: metadata, Resources: []audit.ResourceInput{{ Type: constants.AuditResourceDeviceBatch, Key: batchKey, DisplayName: batchKey, Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleDeviceBatch, IdentitySnapshot: map[string]any{ "request_id": linkage.RequestID, "correlation_id": linkage.CorrelationID, "device_count": len(items), "operation_type": kind, }, SubjectVisibility: constants.AuditSubjectInternalOnly, }}, }, Children: children, }) } func loadDeviceCardAuditReferences( ctx context.Context, tx *gorm.DB, devices []*model.Device, change *deviceCardAuditChange, ) (map[uint][]audit.ResourceInput, []uint, error) { deviceByID := make(map[uint]*model.Device, len(devices)) deviceIDs := make([]uint, 0, len(devices)) for _, device := range devices { if device != nil && device.ID > 0 { deviceByID[device.ID] = device deviceIDs = append(deviceIDs, device.ID) } } result := make(map[uint][]audit.ResourceInput) if len(deviceIDs) == 0 { return result, nil, nil } var bindings []*model.DeviceSimBinding if err := tx.WithContext(ctx).Where("device_id IN ? AND bind_status = ?", deviceIDs, 1).Find(&bindings).Error; err != nil { return nil, nil, err } cardIDs := make([]uint, 0, len(bindings)) seenCards := make(map[uint]struct{}, len(bindings)) for _, binding := range bindings { if _, exists := seenCards[binding.IotCardID]; !exists { seenCards[binding.IotCardID] = struct{}{} cardIDs = append(cardIDs, binding.IotCardID) } } var cards []*model.IotCard if len(cardIDs) > 0 { if err := tx.WithContext(ctx).Unscoped().Where("id IN ?", cardIDs).Find(&cards).Error; err != nil { return nil, nil, err } } cardByID := make(map[uint]*model.IotCard, len(cards)) for _, card := range cards { cardByID[card.ID] = card } for _, binding := range bindings { device := deviceByID[binding.DeviceID] card := cardByID[binding.IotCardID] bindingID := strconv.FormatUint(uint64(binding.ID), 10) cardID := strconv.FormatUint(uint64(binding.IotCardID), 10) deviceVirtualNo, cardICCID, cardVirtualNo := "", "", "" if device != nil { deviceVirtualNo = device.VirtualNo } cardIdentity := map[string]any{"id": binding.IotCardID} cardKey, cardName := cardID, cardID if card != nil { cardICCID, cardVirtualNo = card.ICCID, card.VirtualNo cardKey, cardName = audit.IotCardResourceKey(card), card.ICCID cardIdentity = audit.IotCardIdentitySnapshot(card) } cardRelation := constants.AuditResourceRelationReference var beforeData, afterData map[string]any if change != nil { cardRelation = constants.AuditResourceRelationAffected if card != nil { beforeData = map[string]any{"shop_id": card.ShopID, "status": card.Status} } afterData = map[string]any{"shop_id": change.ShopID, "status": change.Status} } result[binding.DeviceID] = append(result[binding.DeviceID], audit.ResourceInput{ Type: constants.AuditResourceIotCard, ID: &cardID, Key: cardKey, DisplayName: cardName, Relation: cardRelation, Role: constants.AuditResourceRoleDeviceBoundCard, IdentitySnapshot: cardIdentity, BeforeData: beforeData, AfterData: afterData, SubjectVisibility: constants.AuditSubjectInternalOnly, }, audit.ResourceInput{ Type: constants.AuditResourceDeviceSIMBinding, ID: &bindingID, Key: bindingID, DisplayName: deviceVirtualNo, Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleDeviceCardBinding, IdentitySnapshot: map[string]any{ "id": binding.ID, "device_id": binding.DeviceID, "device_virtual_no": deviceVirtualNo, "slot_position": binding.SlotPosition, "iot_card_id": binding.IotCardID, "iccid": cardICCID, "virtual_no": cardVirtualNo, "is_current": binding.IsCurrent, }, SubjectVisibility: constants.AuditSubjectInternalOnly, }, ) } return result, cardIDs, nil } func (s *Service) recordDeviceTransferAuditFailure( ctx context.Context, rootAction, itemAction, kind, summary, result string, devices []*model.Device, outcomes map[uint]deviceAuditOutcome, targetShopID *uint, newStatus, batchTotal, successCount, failCount int, businessErr error, ) { if s.db == nil || s.auditWriter == nil || len(devices) == 0 { recordDeviceAuditSecondaryFailure(ctx, rootAction, 0, businessErr, errors.New(errors.CodeInvalidStatus, "设备批量审计接缝未配置或资源不完整")) return } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendDeviceTransferAudit(ctx, tx, rootAction, itemAction, kind, summary, result, devices, outcomes, nil, targetShopID, newStatus, batchTotal, successCount, failCount, nil, businessErr) }); err != nil { recordDeviceAuditSecondaryFailure(ctx, rootAction, 0, businessErr, err) } } func stableDeviceBatchEventID(kind, key string) string { return "evt_" + uuid.NewSHA1(uuid.NameSpaceOID, []byte("device:"+kind+":"+key)).String() } func (s *Service) appendDeviceSeriesBindingAudit( ctx context.Context, tx *gorm.DB, devices []*model.Device, outcomes map[uint]deviceAuditOutcome, seriesID *uint, result string, batchTotal, successCount, failCount int, metadata map[string]any, businessErr error, ) error { series, err := loadDeviceSeriesAuditResources(ctx, tx, devices, seriesID) if err != nil { return err } cardReferences, _, err := loadDeviceCardAuditReferences(ctx, tx, devices, nil) if err != nil { return err } items := make([]deviceBatchAuditItem, 0, len(devices)) for _, device := range devices { if device == nil || device.ID == 0 { continue } outcome, ok := outcomes[device.ID] if !ok { continue } var afterData map[string]any if outcome.Result == constants.AuditResultSuccess { afterData = map[string]any{"series_id": seriesID} } references := deviceSeriesAuditReferences(device.SeriesID, seriesID, series) references = append(references, cardReferences[device.ID]...) items = append(items, deviceBatchAuditItem{ Device: device, PrimaryRole: constants.AuditResourceRoleDeviceSeriesTarget, Result: outcome.Result, Summary: outcome.Summary, ErrorSummary: outcome.Summary, BeforeData: map[string]any{"series_id": device.SeriesID}, AfterData: afterData, References: references, }) } return s.appendDeviceBatchAudit(ctx, tx, constants.AuditActionDeviceSeriesBindingBatch, constants.AuditActionDeviceSeriesBound, "series-binding", "批量设置设备系列绑定", result, batchTotal, successCount, failCount, items, metadata, businessErr) } func loadDeviceSeriesAuditResources(ctx context.Context, tx *gorm.DB, devices []*model.Device, targetSeriesID *uint) (map[uint]*model.PackageSeries, error) { seriesIDs := make(map[uint]struct{}) if targetSeriesID != nil && *targetSeriesID > 0 { seriesIDs[*targetSeriesID] = struct{}{} } for _, device := range devices { if device != nil && device.SeriesID != nil && *device.SeriesID > 0 { seriesIDs[*device.SeriesID] = struct{}{} } } ids := make([]uint, 0, len(seriesIDs)) for id := range seriesIDs { ids = append(ids, id) } var rows []*model.PackageSeries if len(ids) > 0 { if err := tx.WithContext(ctx).Unscoped().Where("id IN ?", ids).Find(&rows).Error; err != nil { return nil, err } } series := make(map[uint]*model.PackageSeries, len(rows)) for _, item := range rows { series[item.ID] = item } return series, nil } func deviceSeriesAuditReferences(previousID, targetID *uint, series map[uint]*model.PackageSeries) []audit.ResourceInput { resources := make([]audit.ResourceInput, 0, 2) if previousID != nil && *previousID > 0 { resources = appendDevicePackageSeriesAuditReference(resources, series[*previousID], *previousID, constants.AuditResourceRolePreviousPackageSeries) } if targetID != nil && *targetID > 0 { resources = appendDevicePackageSeriesAuditReference(resources, series[*targetID], *targetID, constants.AuditResourceRoleTargetPackageSeries) } return resources } func appendDevicePackageSeriesAuditReference(resources []audit.ResourceInput, series *model.PackageSeries, seriesID uint, role string) []audit.ResourceInput { id := strconv.FormatUint(uint64(seriesID), 10) name := id identity := map[string]any{"id": seriesID} if series != nil { name = series.SeriesName identity = map[string]any{"id": series.ID, "series_code": series.SeriesCode, "series_name": series.SeriesName, "status": series.Status} } return append(resources, audit.ResourceInput{ Type: constants.AuditResourcePackageSeries, ID: &id, Key: id, DisplayName: name, Relation: constants.AuditResourceRelationReference, Role: role, IdentitySnapshot: identity, SubjectVisibility: constants.AuditSubjectInternalOnly, }) } func (s *Service) recordDeviceSeriesBindingAuditFailure( ctx context.Context, devices []*model.Device, outcomes map[uint]deviceAuditOutcome, seriesID *uint, result string, batchTotal, successCount, failCount int, metadata map[string]any, businessErr error, ) { if s.db == nil || s.auditWriter == nil || len(devices) == 0 { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceSeriesBindingBatch, 0, businessErr, errors.New(errors.CodeInvalidStatus, "设备系列绑定审计接缝未配置或资源不完整")) return } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendDeviceSeriesBindingAudit(ctx, tx, devices, outcomes, seriesID, result, batchTotal, successCount, failCount, metadata, businessErr) }); err != nil { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceSeriesBindingBatch, 0, businessErr, err) } } func (s *Service) appendDeviceRealnamePolicyBatchAudit(ctx context.Context, tx *gorm.DB, devices []*model.Device, policy string) error { cardReferences, _, err := loadDeviceCardAuditReferences(ctx, tx, devices, nil) if err != nil { return err } items := make([]deviceBatchAuditItem, 0, len(devices)) for _, device := range devices { if device == nil || device.ID == 0 || device.RealnamePolicy == policy { continue } items = append(items, deviceBatchAuditItem{ Device: device, PrimaryRole: constants.AuditResourceRoleDeviceTarget, Result: constants.AuditResultSuccess, Summary: "更新设备实名策略", BeforeData: map[string]any{"realname_policy": device.RealnamePolicy}, AfterData: map[string]any{"realname_policy": policy}, References: cardReferences[device.ID], }) } return s.appendDeviceBatchAudit(ctx, tx, constants.AuditActionDeviceRealnamePolicyBatchUpdated, constants.AuditActionDeviceRealnamePolicyUpdated, "realname-policy", "批量更新设备实名策略", constants.AuditResultSuccess, len(items), len(items), 0, items, map[string]any{"realname_policy": policy, "requested_count": len(devices)}, nil) } func (s *Service) recordDeviceRealnamePolicyBatchFailure(ctx context.Context, devices []*model.Device, policy, result string, businessErr error) { if s.db == nil || s.auditWriter == nil || len(devices) == 0 { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceRealnamePolicyBatchUpdated, 0, businessErr, errors.New(errors.CodeInvalidStatus, "设备实名策略批量审计接缝未配置或资源不完整")) return } items := make([]deviceBatchAuditItem, 0, len(devices)) for _, device := range devices { if device == nil || device.ID == 0 { continue } items = append(items, deviceBatchAuditItem{ Device: device, PrimaryRole: constants.AuditResourceRoleDeviceTarget, Result: result, Summary: "更新设备实名策略未完成", BeforeData: map[string]any{"realname_policy": device.RealnamePolicy}, AfterData: map[string]any{"requested_realname_policy": policy}, }) } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendDeviceBatchAudit(ctx, tx, constants.AuditActionDeviceRealnamePolicyBatchUpdated, constants.AuditActionDeviceRealnamePolicyUpdated, "realname-policy", "批量更新设备实名策略未完成", result, len(devices), 0, len(devices), items, map[string]any{"realname_policy": policy}, businessErr) }); err != nil { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceRealnamePolicyBatchUpdated, 0, businessErr, err) } } func (s *Service) appendDeviceRealnamePolicyAudit( ctx context.Context, tx *gorm.DB, summary, result string, device *model.Device, beforeData, afterData map[string]any, businessErr error, ) error { if s.auditWriter == nil || device == nil || device.ID == 0 { return errors.New(errors.CodeInvalidStatus, "设备实名策略审计接缝未配置或资源不完整") } cardReferences, _, err := loadDeviceCardAuditReferences(ctx, tx, []*model.Device{device}, nil) if err != nil { return err } deviceID := strconv.FormatUint(uint64(device.ID), 10) errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr) resources := []audit.ResourceInput{{ Type: constants.AuditResourceDevice, ID: &deviceID, Key: audit.DeviceResourceKey(device), DisplayName: device.VirtualNo, Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleDeviceTarget, IdentitySnapshot: audit.DeviceIdentitySnapshot(device), BeforeData: beforeData, AfterData: afterData, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: summary, }} resources = append(resources, cardReferences[device.ID]...) return s.auditWriter.Append(ctx, tx, audit.AppendInput{ ActionCode: constants.AuditActionDeviceRealnamePolicyUpdated, Summary: summary, ScopeType: constants.AuditScopePlatform, Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary, Resources: resources, }) } func (s *Service) recordDeviceRealnamePolicyFailure(ctx context.Context, device *model.Device, deviceID uint, businessErr error) { if device == nil || device.ID == 0 || s.db == nil || s.auditWriter == nil { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceRealnamePolicyUpdated, deviceID, businessErr, errors.New(errors.CodeInvalidStatus, "设备实名策略审计接缝未配置或资源不完整")) return } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendDeviceRealnamePolicyAudit(ctx, tx, "更新设备实名策略失败", constants.AuditResultFailed, device, map[string]any{"realname_policy": device.RealnamePolicy}, nil, businessErr) }); err != nil { recordDeviceAuditSecondaryFailure(ctx, constants.AuditActionDeviceRealnamePolicyUpdated, deviceID, businessErr, err) } }