Files
junhong_cmp_fiber/internal/service/device/unified_audit.go
break 5e552d99bc 收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
2026-08-05 14:30:54 +08:00

674 lines
26 KiB
Go

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)
}
}