// Package asset_audit 提供资产操作审计日志服务。 package asset_audit 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/logger" "go.uber.org/zap" ) // AssetOperationLogStore 资产操作日志存储接口。 type AssetOperationLogStore interface { Create(ctx context.Context, log *model.AssetOperationLog) error ListByAssetPaged( ctx context.Context, assetType string, assetID uint, page int, pageSize int, operationType string, resultStatus string, ) ([]*model.AssetOperationLog, int64, error) } // OperationLogger 资产审计记录接口。 type OperationLogger interface { LogOperation(ctx context.Context, log *model.AssetOperationLog) } // Service 资产审计服务。 type Service struct { store AssetOperationLogStore } // ListByAssetParams 按资产查询日志参数。 type ListByAssetParams struct { AssetType string AssetID uint Page int PageSize int OperationType string ResultStatus string } // NewService 创建资产审计服务实例。 func NewService(store AssetOperationLogStore) *Service { return &Service{store: store} } // LogOperation 记录资产操作日志(异步写入,不阻塞主流程)。 func (s *Service) LogOperation(ctx context.Context, log *model.AssetOperationLog) { if s == nil || s.store == nil || log == nil { return } go func() { if err := s.store.Create(context.Background(), log); err != nil { logger.GetAppLogger().Error("写入资产操作日志失败", zap.String("asset_type", log.AssetType), zap.Uint("asset_id", log.AssetID), zap.String("operation_type", log.OperationType), zap.String("result_status", log.ResultStatus), zap.Error(err)) } }() } // ListByAsset 按资产分页查询操作日志。 func (s *Service) ListByAsset(ctx context.Context, params ListByAssetParams) (*dto.AssetOperationLogListResponse, error) { if s == nil || s.store == nil { return &dto.AssetOperationLogListResponse{ Total: 0, Page: 1, PageSize: constants.DefaultPageSize, Items: []*dto.AssetOperationLogItem{}, }, nil } page := params.Page pageSize := params.PageSize if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = constants.DefaultPageSize } if pageSize > 100 { pageSize = 100 } logs, total, err := s.store.ListByAssetPaged( ctx, NormalizeAssetType(params.AssetType), params.AssetID, page, pageSize, params.OperationType, params.ResultStatus, ) if err != nil { return nil, err } items := make([]*dto.AssetOperationLogItem, 0, len(logs)) for _, item := range logs { items = append(items, toAssetOperationLogItem(item)) } return &dto.AssetOperationLogListResponse{ Total: total, Page: page, PageSize: pageSize, Items: items, }, nil } func toAssetOperationLogItem(log *model.AssetOperationLog) *dto.AssetOperationLogItem { if log == nil { return nil } operationBefore, operationAfter := normalizeOperationContent( log.OperationType, map[string]any(log.BeforeData), map[string]any(log.AfterData), ) return &dto.AssetOperationLogItem{ ID: log.ID, CreatedAt: log.CreatedAt, OperatorID: log.OperatorID, OperatorType: log.OperatorType, OperatorName: log.OperatorName, AssetType: log.AssetType, AssetID: log.AssetID, AssetIdentifier: log.AssetIdentifier, OperationType: log.OperationType, OperationDesc: log.OperationDesc, OperationContentBefore: operationBefore, OperationContentAfter: operationAfter, OperationFieldsDesc: getOperationFieldDesc(log.OperationType), BeforeData: map[string]any(log.BeforeData), AfterData: map[string]any(log.AfterData), RequestID: log.RequestID, IPAddress: log.IPAddress, UserAgent: log.UserAgent, RequestPath: log.RequestPath, RequestMethod: log.RequestMethod, ResultStatus: log.ResultStatus, ErrorCode: log.ErrorCode, ErrorMsg: log.ErrorMsg, BatchTotal: log.BatchTotal, SuccessCount: log.SuccessCount, FailCount: log.FailCount, } }