1907 lines
84 KiB
C#
1907 lines
84 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Common;
|
||
using OrpaonVision.Core.HistoryTrace;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 历史追溯服务实现。
|
||
/// </summary>
|
||
#if DISABLED_LEGACY_HISTORY_TRACE_SERVICE
|
||
public sealed class HistoryTraceService : IHistoryTraceService
|
||
{
|
||
private readonly ILogger<HistoryTraceService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<string, object> _tracebackCache = new();
|
||
private readonly object _lock = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public HistoryTraceService(ILogger<HistoryTraceService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_logger.LogInformation("历史追溯服务已初始化");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<ProductSessionHistory>>> GetProductSessionHistoryAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, SessionStatus? sessionStatus = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取产品会话历史:开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode},会话状态={SessionStatus}",
|
||
startTime, endTime, productTypeCode, sessionStatus);
|
||
|
||
var cacheKey = $"session_history_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}_{sessionStatus}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取产品会话历史");
|
||
return Result.Success((IReadOnlyList<ProductSessionHistory>)cached);
|
||
}
|
||
|
||
var sessionHistory = await GenerateProductSessionHistoryAsync(startTime, endTime, productTypeCode, sessionStatus, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, sessionHistory);
|
||
}
|
||
|
||
_logger.LogInformation("产品会话历史获取完成:会话数量={SessionCount}", sessionHistory.Count);
|
||
return Result.Success(sessionHistory);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取产品会话历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_PRODUCT_SESSION_HISTORY_FAILED", "获取产品会话历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<ProductSessionHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ProductSessionDetail>> GetSessionDetailAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取会话详细信息:会话ID={SessionId}", sessionId);
|
||
|
||
var cacheKey = $"session_detail_{sessionId}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取会话详细信息");
|
||
return Result.Success((ProductSessionDetail)cached);
|
||
}
|
||
|
||
var sessionDetail = await GenerateSessionDetailAsync(sessionId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, sessionDetail);
|
||
}
|
||
|
||
_logger.LogInformation("会话详细信息获取完成:会话ID={SessionId},层数={LayerCount},截图数={ScreenshotCount}",
|
||
sessionId, sessionDetail.LayerHistories.Count, sessionDetail.ScreenshotHistories.Count);
|
||
|
||
return Result.Success(sessionDetail);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取会话详细信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SESSION_DETAIL_FAILED", "获取会话详细信息失败", traceId);
|
||
return Result.FailWithTrace<ProductSessionDetail>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<LayerProcessingHistory>>> GetLayerProcessingHistoryAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取层处理历史:会话ID={SessionId}", sessionId);
|
||
|
||
var cacheKey = $"layer_history_{sessionId}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取层处理历史");
|
||
return Result.Success((IReadOnlyList<LayerProcessingHistory>)cached);
|
||
}
|
||
|
||
var layerHistory = await GenerateLayerProcessingHistoryAsync(sessionId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, layerHistory);
|
||
}
|
||
|
||
_logger.LogInformation("层处理历史获取完成:会话ID={SessionId},层数={LayerCount}", sessionId, layerHistory.Count);
|
||
return Result.Success(layerHistory);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层处理历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_LAYER_PROCESSING_HISTORY_FAILED", "获取层处理历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<LayerProcessingHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerProcessingDetail>> GetLayerDetailAsync(Guid layerId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取层详细信息:层ID={LayerId}", layerId);
|
||
|
||
var cacheKey = $"layer_detail_{layerId}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取层详细信息");
|
||
return Result.Success((LayerProcessingDetail)cached);
|
||
}
|
||
|
||
var layerDetail = await GenerateLayerDetailAsync(layerId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, layerDetail);
|
||
}
|
||
|
||
_logger.LogInformation("层详细信息获取完成:层ID={LayerId},检测数={DetectionCount},截图数={ScreenshotCount}",
|
||
layerId, layerDetail.PartDetectionResults.Count, layerDetail.ScreenshotHistories.Count);
|
||
|
||
return Result.Success(layerDetail);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层详细信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_LAYER_DETAIL_FAILED", "获取层详细信息失败", traceId);
|
||
return Result.FailWithTrace<LayerProcessingDetail>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<ScreenshotHistory>>> GetScreenshotHistoryAsync(Guid? sessionId = null, Guid? layerId = null, DateTime? startTime = null, DateTime? endTime = null, ScreenshotType? screenshotType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取截图历史:会话ID={SessionId},层ID={LayerId},开始时间={StartTime},结束时间={EndTime},截图类型={ScreenshotType}",
|
||
sessionId, layerId, startTime, endTime, screenshotType);
|
||
|
||
var cacheKey = $"screenshot_history_{sessionId}_{layerId}_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{screenshotType}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取截图历史");
|
||
return Result.Success((IReadOnlyList<ScreenshotHistory>)cached);
|
||
}
|
||
|
||
var screenshotHistory = await GenerateScreenshotHistoryAsync(sessionId, layerId, startTime, endTime, screenshotType, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, screenshotHistory);
|
||
}
|
||
|
||
_logger.LogInformation("截图历史获取完成:截图数量={ScreenshotCount}", screenshotHistory.Count);
|
||
return Result.Success(screenshotHistory);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取截图历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SCREENSHOT_HISTORY_FAILED", "获取截图历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<ScreenshotHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ScreenshotDetail>> GetScreenshotDetailAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取截图详细信息:截图ID={ScreenshotId}", screenshotId);
|
||
|
||
var cacheKey = $"screenshot_detail_{screenshotId}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取截图详细信息");
|
||
return Result.Success((ScreenshotDetail)cached);
|
||
}
|
||
|
||
var screenshotDetail = await GenerateScreenshotDetailAsync(screenshotId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, screenshotDetail);
|
||
}
|
||
|
||
_logger.LogInformation("截图详细信息获取完成:截图ID={ScreenshotId},检测数={DetectionCount},缺陷数={DefectCount},标注数={AnnotationCount}",
|
||
screenshotId, screenshotDetail.AssociatedDetections.Count, screenshotDetail.AssociatedDefects.Count, screenshotDetail.Annotations.Count);
|
||
|
||
return Result.Success(screenshotDetail);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取截图详细信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SCREENSHOT_DETAIL_FAILED", "获取截图详细信息失败", traceId);
|
||
return Result.FailWithTrace<ScreenshotDetail>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<byte[]>> GetScreenshotContentAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取截图文件内容:截图ID={ScreenshotId}", screenshotId);
|
||
|
||
var content = await GenerateScreenshotContentAsync(screenshotId, cancellationToken);
|
||
|
||
_logger.LogInformation("截图文件内容获取完成:截图ID={ScreenshotId},文件大小={FileSizeBytes}字节",
|
||
screenshotId, content.Length);
|
||
|
||
return Result.Success(content);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取截图文件内容失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_SCREENSHOT_CONTENT_FAILED", "获取截图文件内容失败", traceId);
|
||
return Result.FailWithTrace<byte[]>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<EvidenceChain>> GetEvidenceChainAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取证据链信息:会话ID={SessionId}", sessionId);
|
||
|
||
var cacheKey = $"evidence_chain_{sessionId}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取证据链信息");
|
||
return Result.Success((EvidenceChain)cached);
|
||
}
|
||
|
||
var evidenceChain = await GenerateEvidenceChainAsync(sessionId, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, evidenceChain);
|
||
}
|
||
|
||
_logger.LogInformation("证据链信息获取完成:会话ID={SessionId},节点数={NodeCount},完整性={Completeness:F2},可信度={Credibility:F2}",
|
||
sessionId, evidenceChain.EvidenceNodes.Count, evidenceChain.CompletenessScore, evidenceChain.CredibilityScore);
|
||
|
||
return Result.Success(evidenceChain);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取证据链信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_EVIDENCE_CHAIN_FAILED", "获取证据链信息失败", traceId);
|
||
return Result.FailWithTrace<EvidenceChain>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerEvidenceTraceback>> GetLayerEvidenceTracebackAsync(Guid sessionId, int? layerSequence = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取层级证据回溯:会话ID={SessionId},层序号={LayerSequence}", sessionId, layerSequence);
|
||
|
||
var cacheKey = $"layer_traceback_{sessionId}_{layerSequence}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取层级证据回溯");
|
||
return Result.Success((LayerEvidenceTraceback)cached);
|
||
}
|
||
|
||
var layerTraceback = await GenerateLayerEvidenceTracebackAsync(sessionId, layerSequence, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, layerTraceback);
|
||
}
|
||
|
||
_logger.LogInformation("层级证据回溯获取完成:会话ID={SessionId},链路数={LinkCount},完整性={Completeness:F2}",
|
||
sessionId, layerTraceback.LayerEvidenceLinks.Count, layerTraceback.Summary.TracebackCompletenessScore);
|
||
|
||
return Result.Success(layerTraceback);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级证据回溯失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_LAYER_EVIDENCE_TRACEBACK_FAILED", "获取层级证据回溯失败", traceId);
|
||
return Result.FailWithTrace<LayerEvidenceTraceback>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<DefectTraceback>> GetDefectTracebackAsync(Guid sessionId, DefectType? defectType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取缺陷追溯信息:会话ID={SessionId},缺陷类型={DefectType}", sessionId, defectType);
|
||
|
||
var cacheKey = $"defect_traceback_{sessionId}_{defectType}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取缺陷追溯信息");
|
||
return Result.Success((DefectTraceback)cached);
|
||
}
|
||
|
||
var defectTraceback = await GenerateDefectTracebackAsync(sessionId, defectType, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, defectTraceback);
|
||
}
|
||
|
||
_logger.LogInformation("缺陷追溯信息获取完成:会话ID={SessionId},链路数={LinkCount},缺陷数={DefectCount}",
|
||
sessionId, defectTraceback.DefectTracebackLinks.Count, defectTraceback.DefectStatistics.TotalDefectCount);
|
||
|
||
return Result.Success(defectTraceback);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取缺陷追溯信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_DEFECT_TRACEBACK_FAILED", "获取缺陷追溯信息失败", traceId);
|
||
return Result.FailWithTrace<DefectTraceback>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<OperationHistory>>> GetOperationHistoryAsync(Guid? sessionId = null, string? operatorId = null, DateTime? startTime = null, DateTime? endTime = null, OperationType? operationType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取操作历史记录:会话ID={SessionId},操作员ID={OperatorId},开始时间={StartTime},结束时间={EndTime},操作类型={OperationType}",
|
||
sessionId, operatorId, startTime, endTime, operationType);
|
||
|
||
var cacheKey = $"operation_history_{sessionId}_{operatorId}_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{operationType}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取操作历史记录");
|
||
return Result.Success((IReadOnlyList<OperationHistory>)cached);
|
||
}
|
||
|
||
var operationHistory = await GenerateOperationHistoryAsync(sessionId, operatorId, startTime, endTime, operationType, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, operationHistory);
|
||
}
|
||
|
||
_logger.LogInformation("操作历史记录获取完成:操作数量={OperationCount}", operationHistory.Count);
|
||
return Result.Success(operationHistory);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取操作历史记录失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_OPERATION_HISTORY_FAILED", "获取操作历史记录失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<OperationHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ComprehensiveTracebackReport>> GetComprehensiveTracebackReportAsync(Guid sessionId, OrpaonVision.Core.HistoryTrace.TracebackReportType reportType, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取综合追溯报告:会话ID={SessionId},报告类型={ReportType}", sessionId, reportType);
|
||
|
||
var report = await GenerateComprehensiveTracebackReportAsync(sessionId, reportType, cancellationToken);
|
||
|
||
_logger.LogInformation("综合追溯报告获取完成:报告ID={ReportId},报告类型={ReportType},评分={OverallScore:F2}",
|
||
report.ReportId, report.ReportType, report.ExecutiveSummary.OverallScore);
|
||
|
||
return Result.Success(report);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取综合追溯报告失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_COMPREHENSIVE_TRACEBACK_REPORT_FAILED", "获取综合追溯报告失败", traceId);
|
||
return Result.FailWithTrace<ComprehensiveTracebackReport>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<TracebackStatistics>> GetTracebackStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("获取追溯统计数据:开始时间={StartTime},结束时间={EndTime},产品类型={ProductTypeCode}",
|
||
startTime, endTime, productTypeCode);
|
||
|
||
var cacheKey = $"traceback_statistics_{startTime:yyyyMMddHHmm}_{endTime:yyyyMMddHHmm}_{productTypeCode}";
|
||
|
||
if (_options.EnableStatisticsCache && _tracebackCache.TryGetValue(cacheKey, out var cached))
|
||
{
|
||
_logger.LogDebug("从缓存获取追溯统计数据");
|
||
return Result.Success((TracebackStatistics)cached);
|
||
}
|
||
|
||
var statistics = await GenerateTracebackStatisticsAsync(startTime, endTime, productTypeCode, cancellationToken);
|
||
|
||
if (_options.EnableStatisticsCache)
|
||
{
|
||
_tracebackCache.TryAdd(cacheKey, statistics);
|
||
}
|
||
|
||
_logger.LogInformation("追溯统计数据获取完成:总会话数={TotalSession},完成率={CompletionRate:F2}%,平均时长={AvgDuration:F2}分钟",
|
||
statistics.TotalSessionCount, statistics.SessionCompletionRate, statistics.AverageSessionDurationMinutes);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取追溯统计数据失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_TRACEBACK_STATISTICS_FAILED", "获取追溯统计数据失败", traceId);
|
||
return Result.FailWithTrace<TracebackStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<HistorySearchResult>> SearchHistoryAsync(HistorySearchRequest searchRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("搜索历史记录:搜索ID={SearchId},关键词={Keyword},搜索类型={SearchType}",
|
||
searchRequest.SearchId, searchRequest.SearchKeyword, searchRequest.SearchType);
|
||
|
||
var searchResult = await GenerateHistorySearchResultAsync(searchRequest, cancellationToken);
|
||
|
||
_logger.LogInformation("历史记录搜索完成:搜索ID={SearchId},总结果数={TotalCount},返回数={ReturnedCount},耗时={ElapsedMs}ms",
|
||
searchRequest.SearchId, searchResult.TotalResultCount, searchResult.ReturnedResultCount, searchResult.SearchElapsedMs);
|
||
|
||
return Result.Success(searchResult);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "搜索历史记录失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "SEARCH_HISTORY_FAILED", "搜索历史记录失败", traceId);
|
||
return Result.FailWithTrace<HistorySearchResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<TracebackExportResult>> ExportTracebackDataAsync(TracebackExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("导出追溯数据:导出ID={ExportId},导出类型={ExportType},格式={ExportFormat}",
|
||
exportRequest.ExportId, exportRequest.ExportType, exportRequest.ExportFormat);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var result = await GenerateTracebackExportAsync(exportRequest, cancellationToken);
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
var exportResult = new TracebackExportResult
|
||
{
|
||
ExportId = exportRequest.ExportId,
|
||
IsSuccess = true,
|
||
FilePath = result.FilePath,
|
||
FileSizeBytes = result.FileSizeBytes,
|
||
RecordCount = result.RecordCount,
|
||
ExportElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
ExportTimeUtc = DateTime.UtcNow,
|
||
ResultDescription = $"导出完成:{result.RecordCount}条记录,文件大小{result.FileSizeBytes}字节",
|
||
ExportStatistics = result.ExportStatistics,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["export_type"] = exportRequest.ExportType,
|
||
["export_format"] = exportRequest.ExportFormat,
|
||
["request_user"] = exportRequest.RequestUser
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("追溯数据导出完成:文件路径={FilePath},记录数量={RecordCount},耗时={ElapsedMs}ms",
|
||
exportResult.FilePath, exportResult.RecordCount, exportResult.ExportElapsedMs);
|
||
|
||
return Result.Success(exportResult);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "导出追溯数据失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "EXPORT_TRACEBACK_DATA_FAILED", "导出追溯数据失败", traceId);
|
||
return Result.FailWithTrace<TracebackExportResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 生成产品会话历史。
|
||
/// </summary>
|
||
private async Task<IReadOnlyList<ProductSessionHistory>> GenerateProductSessionHistoryAsync(DateTime startTime, DateTime endTime, string? productTypeCode, SessionStatus? sessionStatus, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var sessionHistory = new List<ProductSessionHistory>();
|
||
var current = startTime;
|
||
|
||
while (current <= endTime)
|
||
{
|
||
var sessionCount = random.Next(5, 15);
|
||
|
||
for (int i = 0; i < sessionCount; i++)
|
||
{
|
||
var status = sessionStatus ?? (SessionStatus)random.Next(0, 6);
|
||
var sessionStartTime = current.AddHours(random.Next(0, 24)).AddMinutes(random.Next(0, 60));
|
||
var sessionEndTime = status == SessionStatus.Completed ? sessionStartTime.AddMinutes(random.Next(30, 120)) : null;
|
||
|
||
sessionHistory.Add(new ProductSessionHistory
|
||
{
|
||
SessionId = Guid.NewGuid(),
|
||
ProductSerialNumber = $"SN{random.Next(100000, 999999)}",
|
||
ProductTypeCode = productTypeCode ?? $"PT{random.Next(1, 10)}",
|
||
SessionStatus = status,
|
||
SessionStartTimeUtc = sessionStartTime,
|
||
SessionEndTimeUtc = sessionEndTime,
|
||
OperatorId = $"OP{random.Next(1, 10):D3}",
|
||
OperatorName = $"操作员{random.Next(1, 10)}",
|
||
StationId = $"ST{random.Next(1, 5):D3}",
|
||
StationName = $"工位{random.Next(1, 5)}",
|
||
TotalLayerCount = random.Next(3, 8),
|
||
CompletedLayerCount = random.Next(0, 8),
|
||
FinalJudgmentResult = status == SessionStatus.Completed ? (FinalJudgmentResult)random.Next(0, 4) : null,
|
||
DefectCount = random.Next(0, 10),
|
||
AlarmCount = random.Next(0, 5),
|
||
ScreenshotCount = random.Next(10, 50),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 20,
|
||
["data_source"] = "simulated"
|
||
}
|
||
});
|
||
}
|
||
|
||
current = current.AddDays(1);
|
||
}
|
||
|
||
return sessionHistory.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成会话详细信息。
|
||
/// </summary>
|
||
private async Task<ProductSessionDetail> GenerateSessionDetailAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var sessionHistory = new ProductSessionHistory
|
||
{
|
||
SessionId = sessionId,
|
||
ProductSerialNumber = $"SN{random.Next(100000, 999999)}",
|
||
ProductTypeCode = $"PT{random.Next(1, 10)}",
|
||
SessionStatus = SessionStatus.Completed,
|
||
SessionStartTimeUtc = DateTime.UtcNow.AddHours(-random.Next(1, 24)),
|
||
SessionEndTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
OperatorId = $"OP{random.Next(1, 10):D3}",
|
||
OperatorName = $"操作员{random.Next(1, 10)}",
|
||
StationId = $"ST{random.Next(1, 5):D3}",
|
||
StationName = $"工位{random.Next(1, 5)}",
|
||
TotalLayerCount = random.Next(3, 8),
|
||
CompletedLayerCount = random.Next(3, 8),
|
||
FinalJudgmentResult = (FinalJudgmentResult)random.Next(0, 4),
|
||
DefectCount = random.Next(0, 10),
|
||
AlarmCount = random.Next(0, 5),
|
||
ScreenshotCount = random.Next(10, 50)
|
||
};
|
||
|
||
var layerHistories = await GenerateLayerProcessingHistoryAsync(sessionId, cancellationToken);
|
||
var screenshotHistories = await GenerateScreenshotHistoryAsync(sessionId, null, null, null, null, cancellationToken);
|
||
var operationHistories = await GenerateOperationHistoryAsync(sessionId, null, null, null, null, cancellationToken);
|
||
var evidenceChain = await GenerateEvidenceChainAsync(sessionId, cancellationToken);
|
||
var defectTraceback = await GenerateDefectTracebackAsync(sessionId, null, cancellationToken);
|
||
|
||
return new ProductSessionDetail
|
||
{
|
||
SessionHistory = sessionHistory,
|
||
LayerHistories = layerHistories,
|
||
ScreenshotHistories = screenshotHistories,
|
||
OperationHistories = operationHistories,
|
||
EvidenceChain = evidenceChain,
|
||
DefectTraceback = defectTraceback,
|
||
PerformanceMetrics = GenerateSessionPerformanceMetrics(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 50,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层处理历史。
|
||
/// </summary>
|
||
private async Task<IReadOnlyList<LayerProcessingHistory>> GenerateLayerProcessingHistoryAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var layerHistory = new List<LayerProcessingHistory>();
|
||
var layerCount = random.Next(3, 8);
|
||
|
||
for (int i = 1; i <= layerCount; i++)
|
||
{
|
||
var layerStartTime = DateTime.UtcNow.AddHours(-random.Next(1, 24)).AddMinutes(i * 10);
|
||
var layerEndTime = layerStartTime.AddSeconds(random.Next(30, 120));
|
||
|
||
layerHistory.Add(new LayerProcessingHistory
|
||
{
|
||
LayerId = Guid.NewGuid(),
|
||
SessionId = sessionId,
|
||
LayerSequence = i,
|
||
LayerName = $"第{i}层",
|
||
LayerType = $"LayerType{random.Next(1, 5)}",
|
||
LayerStatus = LayerStatus.Completed,
|
||
LayerStartTimeUtc = layerStartTime,
|
||
LayerEndTimeUtc = layerEndTime,
|
||
DetectedPartCount = random.Next(5, 20),
|
||
QualifiedPartCount = random.Next(4, 20),
|
||
DefectivePartCount = random.Next(0, 5),
|
||
LayerJudgmentResult = (LayerJudgmentResult)random.Next(0, 4),
|
||
Defects = GenerateLayerDefects(random, i),
|
||
ScreenshotCount = random.Next(3, 10),
|
||
AlarmCount = random.Next(0, 3),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 15,
|
||
["data_source"] = "simulated"
|
||
}
|
||
});
|
||
}
|
||
|
||
return layerHistory.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层详细信息。
|
||
/// </summary>
|
||
private async Task<LayerProcessingDetail> GenerateLayerDetailAsync(Guid layerId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var layerHistory = new LayerProcessingHistory
|
||
{
|
||
LayerId = layerId,
|
||
SessionId = Guid.NewGuid(),
|
||
LayerSequence = random.Next(1, 8),
|
||
LayerName = $"第{random.Next(1, 8)}层",
|
||
LayerType = $"LayerType{random.Next(1, 5)}",
|
||
LayerStatus = LayerStatus.Completed,
|
||
LayerStartTimeUtc = DateTime.UtcNow.AddHours(-random.Next(1, 24)),
|
||
LayerEndTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
DetectedPartCount = random.Next(5, 20),
|
||
QualifiedPartCount = random.Next(4, 20),
|
||
DefectivePartCount = random.Next(0, 5),
|
||
LayerJudgmentResult = (LayerJudgmentResult)random.Next(0, 4),
|
||
Defects = GenerateLayerDefects(random, random.Next(1, 8)),
|
||
ScreenshotCount = random.Next(3, 10),
|
||
AlarmCount = random.Next(0, 3)
|
||
};
|
||
|
||
var screenshotHistories = await GenerateScreenshotHistoryAsync(layerHistory.SessionId, layerId, null, null, null, cancellationToken);
|
||
var operationHistories = await GenerateOperationHistoryAsync(layerHistory.SessionId, null, null, null, null, cancellationToken);
|
||
var partDetectionResults = GeneratePartDetectionResults(random, layerId);
|
||
var ruleExecutionResults = GenerateRuleExecutionResults(random, layerId);
|
||
|
||
return new LayerProcessingDetail
|
||
{
|
||
LayerHistory = layerHistory,
|
||
ScreenshotHistories = screenshotHistories,
|
||
OperationHistories = operationHistories,
|
||
PartDetectionResults = partDetectionResults,
|
||
RuleExecutionResults = ruleExecutionResults,
|
||
PerformanceMetrics = GenerateLayerPerformanceMetrics(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 25,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成截图历史。
|
||
/// </summary>
|
||
private async Task<IReadOnlyList<ScreenshotHistory>> GenerateScreenshotHistoryAsync(Guid? sessionId, Guid? layerId, DateTime? startTime, DateTime? endTime, ScreenshotType? screenshotType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var screenshotHistory = new List<ScreenshotHistory>();
|
||
var screenshotCount = random.Next(5, 20);
|
||
|
||
for (int i = 0; i < screenshotCount; i++)
|
||
{
|
||
var screenshotTime = startTime?.AddMinutes(random.Next(0, (int)(endTime - startTime).Value.TotalMinutes)) ?? DateTime.UtcNow.AddMinutes(-random.Next(10, 120));
|
||
var type = screenshotType ?? (ScreenshotType)random.Next(0, 8);
|
||
|
||
screenshotHistory.Add(new ScreenshotHistory
|
||
{
|
||
ScreenshotId = Guid.NewGuid(),
|
||
SessionId = sessionId ?? Guid.NewGuid(),
|
||
LayerId = layerId,
|
||
ScreenshotType = type,
|
||
ScreenshotTimeUtc = screenshotTime,
|
||
FilePath = $"/screenshots/{Guid.NewGuid():N}.jpg",
|
||
FileSizeBytes = random.Next(100000, 5000000),
|
||
FileFormat = "JPEG",
|
||
ImageWidth = random.Next(1920, 4096),
|
||
ImageHeight = random.Next(1080, 2160),
|
||
ImageDescription = $"{type}截图",
|
||
AssociatedDetectionCount = random.Next(0, 20),
|
||
AssociatedDefectCount = random.Next(0, 5),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 10,
|
||
["data_source"] = "simulated"
|
||
}
|
||
});
|
||
}
|
||
|
||
return screenshotHistory.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成截图详细信息。
|
||
/// </summary>
|
||
private async Task<ScreenshotDetail> GenerateScreenshotDetailAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var screenshotHistory = new ScreenshotHistory
|
||
{
|
||
ScreenshotId = screenshotId,
|
||
SessionId = Guid.NewGuid(),
|
||
LayerId = Guid.NewGuid(),
|
||
ScreenshotType = (ScreenshotType)random.Next(0, 8),
|
||
ScreenshotTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 120)),
|
||
FilePath = $"/screenshots/{screenshotId:N}.jpg",
|
||
FileSizeBytes = random.Next(100000, 5000000),
|
||
FileFormat = "JPEG",
|
||
ImageWidth = random.Next(1920, 4096),
|
||
ImageHeight = random.Next(1080, 2160),
|
||
ImageDescription = "检测截图",
|
||
AssociatedDetectionCount = random.Next(0, 20),
|
||
AssociatedDefectCount = random.Next(0, 5)
|
||
};
|
||
|
||
var imageMetadata = GenerateImageMetadata(random);
|
||
var associatedDetections = GeneratePartDetectionResults(random, Guid.NewGuid());
|
||
var associatedDefects = GenerateLayerDefects(random, random.Next(1, 8));
|
||
var annotations = GenerateImageAnnotations(random);
|
||
|
||
return new ScreenshotDetail
|
||
{
|
||
ScreenshotHistory = screenshotHistory,
|
||
ImageMetadata = imageMetadata,
|
||
AssociatedDetections = associatedDetections,
|
||
AssociatedDefects = associatedDefects,
|
||
Annotations = annotations,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 20,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成截图文件内容。
|
||
/// </summary>
|
||
private async Task<byte[]> GenerateScreenshotContentAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
// 简化处理:生成模拟的JPEG文件头
|
||
var random = new Random();
|
||
var fileSize = random.Next(100000, 5000000);
|
||
var content = new byte[fileSize];
|
||
|
||
// JPEG文件头
|
||
var jpegHeader = new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 };
|
||
Array.Copy(jpegHeader, content, jpegHeader.Length);
|
||
|
||
// 填充随机数据
|
||
for (int i = jpegHeader.Length; i < fileSize - 2; i++)
|
||
{
|
||
content[i] = (byte)random.Next(0, 256);
|
||
}
|
||
|
||
// JPEG文件尾
|
||
content[fileSize - 2] = 0xFF;
|
||
content[fileSize - 1] = 0xD9;
|
||
|
||
return content;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成证据链。
|
||
/// </summary>
|
||
private async Task<EvidenceChain> GenerateEvidenceChainAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var evidenceNodes = new List<EvidenceNode>();
|
||
var nodeCount = random.Next(10, 30);
|
||
|
||
for (int i = 0; i < nodeCount; i++)
|
||
{
|
||
evidenceNodes.Add(new EvidenceNode
|
||
{
|
||
NodeId = Guid.NewGuid(),
|
||
NodeType = (EvidenceNodeType)random.Next(0, 10),
|
||
NodeWeight = random.NextDouble(),
|
||
NodeTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 120)),
|
||
NodeDescription = $"证据节点{i + 1}",
|
||
AssociatedDataId = Guid.NewGuid(),
|
||
AssociatedDataType = $"DataType{random.Next(1, 5)}",
|
||
ChildNodes = new List<EvidenceNode>(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["node_index"] = i,
|
||
["generation_time_ms"] = 5
|
||
}
|
||
});
|
||
}
|
||
|
||
return new EvidenceChain
|
||
{
|
||
EvidenceChainId = Guid.NewGuid(),
|
||
SessionId = sessionId,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
CompletenessScore = 0.8 + random.NextDouble() * 0.2,
|
||
CredibilityScore = 0.85 + random.NextDouble() * 0.15,
|
||
EvidenceNodes = evidenceNodes.AsReadOnly(),
|
||
Summary = GenerateEvidenceChainSummary(random, evidenceNodes.Count),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 30,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层级证据回溯。
|
||
/// </summary>
|
||
private async Task<LayerEvidenceTraceback> GenerateLayerEvidenceTracebackAsync(Guid sessionId, int? layerSequence, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var layerEvidenceLinks = new List<LayerEvidenceLink>();
|
||
var linkCount = random.Next(3, 8);
|
||
|
||
for (int i = 1; i <= linkCount; i++)
|
||
{
|
||
if (layerSequence.HasValue && i != layerSequence.Value)
|
||
continue;
|
||
|
||
var keyScreenshots = await GenerateScreenshotHistoryAsync(sessionId, Guid.NewGuid(), null, null, null, cancellationToken);
|
||
var keyDefects = GenerateLayerDefects(random, i);
|
||
var keyOperations = await GenerateOperationHistoryAsync(sessionId, null, null, null, null, cancellationToken);
|
||
|
||
layerEvidenceLinks.Add(new LayerEvidenceLink
|
||
{
|
||
LinkId = Guid.NewGuid(),
|
||
LayerSequence = i,
|
||
LayerName = $"第{i}层",
|
||
LayerProcessingTimeUtc = DateTime.UtcNow.AddHours(-random.Next(1, 24)).AddMinutes(i * 10),
|
||
KeyScreenshots = keyScreenshots.Take(random.Next(1, 4)).ToList(),
|
||
KeyDefects = keyDefects,
|
||
KeyOperations = keyOperations.Take(random.Next(1, 3)).ToList(),
|
||
LinkCompleteness = 0.7 + random.NextDouble() * 0.3,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 15
|
||
}
|
||
});
|
||
}
|
||
|
||
return new LayerEvidenceTraceback
|
||
{
|
||
SessionId = sessionId,
|
||
TracebackTimeUtc = DateTime.UtcNow,
|
||
LayerEvidenceLinks = layerEvidenceLinks.AsReadOnly(),
|
||
Summary = GenerateLayerTracebackSummary(random, layerEvidenceLinks.Count),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 40,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缺陷追溯。
|
||
/// </summary>
|
||
private async Task<DefectTraceback> GenerateDefectTracebackAsync(Guid sessionId, DefectType? defectType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var defectTracebackLinks = new List<DefectTracebackLink>();
|
||
var linkCount = random.Next(0, 10);
|
||
|
||
for (int i = 0; i < linkCount; i++)
|
||
{
|
||
var type = defectType ?? (DefectType)random.Next(0, 8);
|
||
var associatedScreenshot = (await GenerateScreenshotHistoryAsync(sessionId, null, null, null, null, cancellationToken)).FirstOrDefault();
|
||
var associatedOperations = (await GenerateOperationHistoryAsync(sessionId, null, null, null, null, cancellationToken)).Take(random.Next(1, 3)).ToList();
|
||
|
||
defectTracebackLinks.Add(new DefectTracebackLink
|
||
{
|
||
LinkId = Guid.NewGuid(),
|
||
DefectId = Guid.NewGuid(),
|
||
DefectType = type,
|
||
DefectSeverity = (DefectSeverity)random.Next(0, 4),
|
||
DiscoveryTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 120)),
|
||
LayerSequence = random.Next(1, 8),
|
||
LayerName = $"第{random.Next(1, 8)}层",
|
||
RootCauseAnalysis = $"根本原因分析{i + 1}",
|
||
AssociatedScreenshot = associatedScreenshot,
|
||
AssociatedOperations = associatedOperations,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 12
|
||
}
|
||
});
|
||
}
|
||
|
||
return new DefectTraceback
|
||
{
|
||
SessionId = sessionId,
|
||
TracebackTimeUtc = DateTime.UtcNow,
|
||
DefectTracebackLinks = defectTracebackLinks.AsReadOnly(),
|
||
DefectStatistics = GenerateDefectStatisticsAnalysis(random, defectTracebackLinks.Count),
|
||
PatternAnalysis = GenerateDefectPatternAnalysis(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 35,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成操作历史。
|
||
/// </summary>
|
||
private async Task<IReadOnlyList<OperationHistory>> GenerateOperationHistoryAsync(Guid? sessionId, string? operatorId, DateTime? startTime, DateTime? endTime, OperationType? operationType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var operationHistory = new List<OperationHistory>();
|
||
var operationCount = random.Next(5, 20);
|
||
|
||
for (int i = 0; i < operationCount; i++)
|
||
{
|
||
var operationTime = startTime?.AddMinutes(random.Next(0, (int)(endTime - startTime).Value.TotalMinutes)) ?? DateTime.UtcNow.AddMinutes(-random.Next(10, 120));
|
||
var type = operationType ?? (OperationType)random.Next(0, 14);
|
||
|
||
operationHistory.Add(new OperationHistory
|
||
{
|
||
OperationId = Guid.NewGuid(),
|
||
SessionId = sessionId ?? Guid.NewGuid(),
|
||
LayerId = Guid.NewGuid(),
|
||
OperationType = type,
|
||
OperationTimeUtc = operationTime,
|
||
OperatorId = operatorId ?? $"OP{random.Next(1, 10):D3}",
|
||
OperatorName = $"操作员{random.Next(1, 10)}",
|
||
OperationDescription = $"{type}操作{i + 1}",
|
||
OperationResult = (OperationResult)random.Next(0, 6),
|
||
OperationParameters = new Dictionary<string, object>
|
||
{
|
||
["param1"] = $"value{random.Next(1, 100)}",
|
||
["param2"] = random.NextDouble()
|
||
},
|
||
OperationDurationMs = random.Next(100, 5000),
|
||
AssociatedScreenshotId = Guid.NewGuid(),
|
||
ErrorMessage = random.NextDouble() > 0.8 ? $"错误信息{random.Next(1, 10)}" : null,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 8,
|
||
["data_source"] = "simulated"
|
||
}
|
||
});
|
||
}
|
||
|
||
return operationHistory.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成综合追溯报告。
|
||
/// </summary>
|
||
private async Task<ComprehensiveTracebackReport> GenerateComprehensiveTracebackReportAsync(Guid sessionId, OrpaonVision.Core.HistoryTrace.TracebackReportType reportType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var sessionDetail = await GenerateSessionDetailAsync(sessionId, cancellationToken);
|
||
|
||
return new ComprehensiveTracebackReport
|
||
{
|
||
ReportId = Guid.NewGuid(),
|
||
ReportType = reportType,
|
||
SessionId = sessionId,
|
||
GeneratedAtUtc = DateTime.UtcNow,
|
||
ReportTitle = $"{reportType}追溯报告",
|
||
ExecutiveSummary = GenerateReportExecutiveSummary(random),
|
||
SessionOverview = sessionDetail,
|
||
LayerAnalyses = GenerateLayerAnalysisReports(random, sessionDetail.LayerHistories.Count),
|
||
DefectAnalysis = GenerateDefectAnalysisReport(random),
|
||
PerformanceAnalysis = GeneratePerformanceAnalysisReport(random),
|
||
EvidenceChainAnalysis = GenerateEvidenceChainAnalysis(random),
|
||
ImprovementSuggestions = GenerateTracebackImprovementSuggestions(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 100,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成追溯统计数据。
|
||
/// </summary>
|
||
private async Task<TracebackStatistics> GenerateTracebackStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var timeRange = new TimeRange { StartTimeUtc = startTime, EndTimeUtc = endTime };
|
||
var totalSessionCount = random.Next(50, 200);
|
||
var completedSessionCount = (int)(totalSessionCount * (0.8 + random.NextDouble() * 0.15));
|
||
|
||
return new TracebackStatistics
|
||
{
|
||
TimeRange = timeRange,
|
||
ProductTypeCode = productTypeCode ?? "default",
|
||
TotalSessionCount = totalSessionCount,
|
||
CompletedSessionCount = completedSessionCount,
|
||
TotalLayerCount = totalSessionCount * random.Next(3, 8),
|
||
TotalScreenshotCount = totalSessionCount * random.Next(10, 50),
|
||
TotalDefectCount = random.Next(0, totalSessionCount * 5),
|
||
TotalOperationCount = totalSessionCount * random.Next(20, 100),
|
||
AverageSessionDurationMinutes = 45 + random.NextDouble() * 30,
|
||
AverageLayerProcessingTimeSeconds = 30 + random.NextDouble() * 20,
|
||
ByDate = GenerateDailyTracebackStatistics(startTime, endTime, random),
|
||
ByProductType = GenerateProductTypeTracebackStatistics(random),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 60,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成历史搜索结果。
|
||
/// </summary>
|
||
private async Task<HistorySearchResult> GenerateHistorySearchResultAsync(HistorySearchRequest searchRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
var sessionResults = await GenerateProductSessionHistoryAsync(
|
||
searchRequest.TimeRange?.StartTimeUtc ?? DateTime.UtcNow.AddDays(-7),
|
||
searchRequest.TimeRange?.EndTimeUtc ?? DateTime.UtcNow,
|
||
searchRequest.ProductTypeCode,
|
||
null,
|
||
cancellationToken);
|
||
|
||
var layerResults = sessionResults.SelectMany(s => Enumerable.Range(1, random.Next(3, 8))
|
||
.Select(i => new LayerProcessingHistory
|
||
{
|
||
LayerId = Guid.NewGuid(),
|
||
SessionId = s.SessionId,
|
||
LayerSequence = i,
|
||
LayerName = $"第{i}层",
|
||
LayerType = $"LayerType{random.Next(1, 5)}",
|
||
LayerStatus = LayerStatus.Completed,
|
||
LayerStartTimeUtc = s.SessionStartTimeUtc.AddMinutes(i * 10),
|
||
LayerEndTimeUtc = s.SessionStartTimeUtc.AddMinutes(i * 10 + random.Next(30, 120)),
|
||
DetectedPartCount = random.Next(5, 20),
|
||
QualifiedPartCount = random.Next(4, 20),
|
||
DefectivePartCount = random.Next(0, 5),
|
||
LayerJudgmentResult = (LayerJudgmentResult)random.Next(0, 4),
|
||
Defects = GenerateLayerDefects(random, i),
|
||
ScreenshotCount = random.Next(3, 10),
|
||
AlarmCount = random.Next(0, 3)
|
||
})).ToList();
|
||
|
||
var screenshotResults = sessionResults.SelectMany(s => Enumerable.Range(0, random.Next(5, 15))
|
||
.Select(i => new ScreenshotHistory
|
||
{
|
||
ScreenshotId = Guid.NewGuid(),
|
||
SessionId = s.SessionId,
|
||
ScreenshotType = (ScreenshotType)random.Next(0, 8),
|
||
ScreenshotTimeUtc = s.SessionStartTimeUtc.AddMinutes(random.Next(0, 120)),
|
||
FilePath = $"/screenshots/{Guid.NewGuid():N}.jpg",
|
||
FileSizeBytes = random.Next(100000, 5000000),
|
||
FileFormat = "JPEG",
|
||
ImageWidth = random.Next(1920, 4096),
|
||
ImageHeight = random.Next(1080, 2160),
|
||
ImageDescription = "检测截图",
|
||
AssociatedDetectionCount = random.Next(0, 20),
|
||
AssociatedDefectCount = random.Next(0, 5)
|
||
})).ToList();
|
||
|
||
var defectResults = layerResults.SelectMany(l => l.Defects).ToList();
|
||
var operationResults = sessionResults.SelectMany(s => Enumerable.Range(0, random.Next(10, 30))
|
||
.Select(i => new OperationHistory
|
||
{
|
||
OperationId = Guid.NewGuid(),
|
||
SessionId = s.SessionId,
|
||
OperationType = (OperationType)random.Next(0, 14),
|
||
OperationTimeUtc = s.SessionStartTimeUtc.AddMinutes(random.Next(0, 120)),
|
||
OperatorId = $"OP{random.Next(1, 10):D3}",
|
||
OperatorName = $"操作员{random.Next(1, 10)}",
|
||
OperationDescription = "操作记录",
|
||
OperationResult = (OperationResult)random.Next(0, 6),
|
||
OperationDurationMs = random.Next(100, 5000)
|
||
})).ToList();
|
||
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
return new HistorySearchResult
|
||
{
|
||
SearchId = searchRequest.SearchId,
|
||
SearchTimeUtc = searchRequest.SearchTimeUtc,
|
||
TotalResultCount = sessionResults.Count + layerResults.Count + screenshotResults.Count + defectResults.Count + operationResults.Count,
|
||
ReturnedResultCount = Math.Min(searchRequest.MaxResultCount, sessionResults.Count + layerResults.Count + screenshotResults.Count + defectResults.Count + operationResults.Count),
|
||
SearchElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
SessionResults = sessionResults.Take(searchRequest.MaxResultCount / 5).ToList(),
|
||
LayerResults = layerResults.Take(searchRequest.MaxResultCount / 5).ToList(),
|
||
ScreenshotResults = screenshotResults.Take(searchRequest.MaxResultCount / 5).ToList(),
|
||
DefectResults = defectResults.Take(searchRequest.MaxResultCount / 5).ToList(),
|
||
OperationResults = operationResults.Take(searchRequest.MaxResultCount / 5).ToList(),
|
||
SearchSuggestions = new[] { "建议1", "建议2", "建议3" },
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 80,
|
||
["data_source"] = "simulated"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成追溯数据导出。
|
||
/// </summary>
|
||
private async Task<(string FilePath, long FileSizeBytes, int RecordCount, ExportStatistics ExportStatistics)> GenerateTracebackExportAsync(TracebackExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var random = new Random();
|
||
var fileName = $"traceback_export_{exportRequest.ExportId:N}.{exportRequest.ExportFormat.ToString().ToLower()}";
|
||
var filePath = Path.Combine(Path.GetTempPath(), fileName);
|
||
|
||
// 简化处理:生成模拟文件
|
||
var recordCount = random.Next(100, 1000);
|
||
var fileContent = GenerateExportFileContent(exportRequest, recordCount);
|
||
await File.WriteAllTextAsync(filePath, fileContent, cancellationToken);
|
||
|
||
var fileInfo = new FileInfo(filePath);
|
||
|
||
var exportStatistics = new ExportStatistics
|
||
{
|
||
SessionCount = exportRequest.SessionIds.Count > 0 ? exportRequest.SessionIds.Count : random.Next(10, 50),
|
||
LayerCount = random.Next(30, 200),
|
||
ScreenshotCount = random.Next(100, 500),
|
||
DefectCount = random.Next(0, 50),
|
||
OperationCount = random.Next(200, 1000),
|
||
EvidenceChainCount = exportRequest.IncludeEvidenceChain ? random.Next(10, 50) : 0
|
||
};
|
||
|
||
return (filePath, fileInfo.Length, recordCount, exportStatistics);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 生成层缺陷列表。
|
||
/// </summary>
|
||
private IReadOnlyList<LayerDefect> GenerateLayerDefects(Random random, int layerSequence)
|
||
{
|
||
var defects = new List<LayerDefect>();
|
||
var defectCount = random.Next(0, 5);
|
||
|
||
for (int i = 0; i < defectCount; i++)
|
||
{
|
||
defects.Add(new LayerDefect
|
||
{
|
||
DefectId = Guid.NewGuid(),
|
||
DefectType = (DefectType)random.Next(0, 8),
|
||
DefectSeverity = (DefectSeverity)random.Next(0, 4),
|
||
DefectDescription = $"缺陷{i + 1}",
|
||
PartName = $"部件{random.Next(1, 20)}",
|
||
DefectPositionX = random.NextDouble() * 1000,
|
||
DefectPositionY = random.NextDouble() * 1000,
|
||
DefectConfidence = 0.7 + random.NextDouble() * 0.3,
|
||
DetectionTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
AssociatedScreenshotId = Guid.NewGuid(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["layer_sequence"] = layerSequence
|
||
}
|
||
});
|
||
}
|
||
|
||
return defects.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成部件检测结果。
|
||
/// </summary>
|
||
private IReadOnlyList<PartDetectionResult> GeneratePartDetectionResults(Random random, Guid layerId)
|
||
{
|
||
var results = new List<PartDetectionResult>();
|
||
var detectionCount = random.Next(5, 20);
|
||
|
||
for (int i = 0; i < detectionCount; i++)
|
||
{
|
||
results.Add(new PartDetectionResult
|
||
{
|
||
DetectionId = Guid.NewGuid(),
|
||
PartName = $"部件{i + 1}",
|
||
PartType = $"PartType{random.Next(1, 10)}",
|
||
DetectionStatus = (PartDetectionStatus)random.Next(0, 6),
|
||
Confidence = 0.6 + random.NextDouble() * 0.4,
|
||
BoundingBox = new BoundingBox
|
||
{
|
||
X = random.NextDouble() * 1000,
|
||
Y = random.NextDouble() * 1000,
|
||
Width = 50 + random.NextDouble() * 100,
|
||
Height = 50 + random.NextDouble() * 100
|
||
},
|
||
DetectionTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
AssociatedScreenshotId = Guid.NewGuid(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["layer_id"] = layerId
|
||
}
|
||
});
|
||
}
|
||
|
||
return results.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成规则执行结果。
|
||
/// </summary>
|
||
private IReadOnlyList<RuleExecutionResult> GenerateRuleExecutionResults(Random random, Guid layerId)
|
||
{
|
||
var results = new List<RuleExecutionResult>();
|
||
var ruleCount = random.Next(5, 15);
|
||
|
||
for (int i = 0; i < ruleCount; i++)
|
||
{
|
||
results.Add(new RuleExecutionResult
|
||
{
|
||
ExecutionId = Guid.NewGuid(),
|
||
RuleName = $"规则{i + 1}",
|
||
RuleType = $"RuleType{random.Next(1, 5)}",
|
||
ExecutionStatus = (RuleExecutionStatus)random.Next(0, 5),
|
||
ExecutionResult = random.NextDouble() > 0.2,
|
||
ExecutionTimeMs = random.Next(10, 1000),
|
||
ExecutionTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
ErrorMessage = random.NextDouble() > 0.8 ? $"规则执行错误{i + 1}" : null,
|
||
ExecutionDetails = $"规则{i + 1}执行详情",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["layer_id"] = layerId
|
||
}
|
||
});
|
||
}
|
||
|
||
return results.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成图像元数据。
|
||
/// </summary>
|
||
private ImageMetadata GenerateImageMetadata(Random random)
|
||
{
|
||
return new ImageMetadata
|
||
{
|
||
CaptureTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 120)),
|
||
CameraId = $"CAM{random.Next(1, 5):D3}",
|
||
CameraName = $"相机{random.Next(1, 5)}",
|
||
ExposureTimeMs = 0.001 + random.NextDouble() * 0.1,
|
||
ISO = random.Next(100, 3200),
|
||
Aperture = 1.4 + random.NextDouble() * 8,
|
||
FocalLength = 24 + random.NextDouble() * 100,
|
||
FlashUsed = random.NextDouble() > 0.7,
|
||
ImageQualityScore = 0.7 + random.NextDouble() * 0.3,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 5
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成图像标注。
|
||
/// </summary>
|
||
private IReadOnlyList<ImageAnnotation> GenerateImageAnnotations(Random random)
|
||
{
|
||
var annotations = new List<ImageAnnotation>();
|
||
var annotationCount = random.Next(0, 10);
|
||
|
||
for (int i = 0; i < annotationCount; i++)
|
||
{
|
||
annotations.Add(new ImageAnnotation
|
||
{
|
||
AnnotationId = Guid.NewGuid(),
|
||
AnnotationType = (AnnotationType)random.Next(0, 8),
|
||
AnnotationContent = $"标注{i + 1}",
|
||
AnnotationPosition = new BoundingBox
|
||
{
|
||
X = random.NextDouble() * 1000,
|
||
Y = random.NextDouble() * 1000,
|
||
Width = 50 + random.NextDouble() * 100,
|
||
Height = 50 + random.NextDouble() * 100
|
||
},
|
||
AnnotationColor = $"#{random.Next(0x1000000):X6}",
|
||
AnnotationTimeUtc = DateTime.UtcNow.AddMinutes(-random.Next(10, 60)),
|
||
Annotator = $"标注员{random.Next(1, 5)}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["annotation_index"] = i
|
||
}
|
||
});
|
||
}
|
||
|
||
return annotations.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成证据链摘要。
|
||
/// </summary>
|
||
private EvidenceChainSummary GenerateEvidenceChainSummary(Random random, int nodeCount)
|
||
{
|
||
return new EvidenceChainSummary
|
||
{
|
||
TotalNodeCount = nodeCount,
|
||
CriticalNodeCount = (int)(nodeCount * (0.2 + random.NextDouble() * 0.2)),
|
||
ScreenshotNodeCount = (int)(nodeCount * (0.3 + random.NextDouble() * 0.2)),
|
||
DefectNodeCount = (int)(nodeCount * (0.1 + random.NextDouble() * 0.1)),
|
||
AlarmNodeCount = (int)(nodeCount * (0.05 + random.NextDouble() * 0.05)),
|
||
TimeSpanMinutes = 60 + random.NextDouble() * 60,
|
||
QualityRating = (EvidenceChainQuality)random.Next(0, 5),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 10
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层级回溯摘要。
|
||
/// </summary>
|
||
private LayerTracebackSummary GenerateLayerTracebackSummary(Random random, int linkCount)
|
||
{
|
||
return new LayerTracebackSummary
|
||
{
|
||
TotalLayerCount = linkCount,
|
||
CompleteLinkCount = (int)(linkCount * (0.7 + random.NextDouble() * 0.2)),
|
||
PartialLinkCount = (int)(linkCount * (0.1 + random.NextDouble() * 0.1)),
|
||
MissingLinkCount = linkCount - (int)(linkCount * (0.7 + random.NextDouble() * 0.2)) - (int)(linkCount * (0.1 + random.NextDouble() * 0.1)),
|
||
TotalScreenshotCount = linkCount * random.Next(3, 10),
|
||
TotalDefectCount = random.Next(0, linkCount * 2),
|
||
TracebackCompletenessScore = 0.7 + random.NextDouble() * 0.25,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 8
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缺陷统计分析。
|
||
/// </summary>
|
||
private DefectStatisticsAnalysis GenerateDefectStatisticsAnalysis(Random random, int defectCount)
|
||
{
|
||
var defectsByType = new Dictionary<DefectType, int>();
|
||
var defectsBySeverity = new Dictionary<DefectSeverity, int>();
|
||
var defectsByLayer = new Dictionary<int, int>();
|
||
|
||
for (int i = 0; i < defectCount; i++)
|
||
{
|
||
var type = (DefectType)random.Next(0, 8);
|
||
var severity = (DefectSeverity)random.Next(0, 4);
|
||
var layer = random.Next(1, 8);
|
||
|
||
defectsByType[type] = defectsByType.GetValueOrDefault(type, 0) + 1;
|
||
defectsBySeverity[severity] = defectsBySeverity.GetValueOrDefault(severity, 0) + 1;
|
||
defectsByLayer[layer] = defectsByLayer.GetValueOrDefault(layer, 0) + 1;
|
||
}
|
||
|
||
return new DefectStatisticsAnalysis
|
||
{
|
||
TotalDefectCount = defectCount,
|
||
DefectsByType = defectsByType,
|
||
DefectsBySeverity = defectsBySeverity,
|
||
DefectsByLayer = defectsByLayer,
|
||
DefectDensity = defectCount > 0 ? random.NextDouble() * 10 : 0.0,
|
||
DefectRate = defectCount > 0 ? random.NextDouble() * 5 : 0.0,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 12
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缺陷模式分析。
|
||
/// </summary>
|
||
private DefectPatternAnalysis GenerateDefectPatternAnalysis(Random random)
|
||
{
|
||
var recurringPatterns = new List<DefectPattern>();
|
||
var anomalousPatterns = new List<DefectPattern>();
|
||
|
||
for (int i = 0; i < random.Next(0, 3); i++)
|
||
{
|
||
recurringPatterns.Add(new DefectPattern
|
||
{
|
||
PatternId = Guid.NewGuid(),
|
||
PatternName = $"重复模式{i + 1}",
|
||
PatternDescription = $"重复出现的缺陷模式{i + 1}",
|
||
OccurrenceFrequency = random.NextDouble(),
|
||
PatternConfidence = 0.6 + random.NextDouble() * 0.4,
|
||
RelatedDefectIds = Enumerable.Range(0, random.Next(1, 5)).Select(_ => Guid.NewGuid()).ToList(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["pattern_type"] = "recurring"
|
||
}
|
||
});
|
||
}
|
||
|
||
for (int i = 0; i < random.Next(0, 2); i++)
|
||
{
|
||
anomalousPatterns.Add(new DefectPattern
|
||
{
|
||
PatternId = Guid.NewGuid(),
|
||
PatternName = $"异常模式{i + 1}",
|
||
PatternDescription = $"异常出现的缺陷模式{i + 1}",
|
||
OccurrenceFrequency = random.NextDouble() * 0.1,
|
||
PatternConfidence = 0.5 + random.NextDouble() * 0.3,
|
||
RelatedDefectIds = Enumerable.Range(0, random.Next(1, 3)).Select(_ => Guid.NewGuid()).ToList(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["pattern_type"] = "anomalous"
|
||
}
|
||
});
|
||
}
|
||
|
||
return new DefectPatternAnalysis
|
||
{
|
||
RecurringPatterns = recurringPatterns.AsReadOnly(),
|
||
AnomalousPatterns = anomalousPatterns.AsReadOnly(),
|
||
TrendAnalysis = new DefectTrendAnalysis
|
||
{
|
||
TrendDirection = (TrendDirection)random.Next(0, 4),
|
||
ChangeRate = random.NextDouble() * 10 - 5,
|
||
TrendDescription = "缺陷趋势分析",
|
||
PredictedNextDefectCount = random.Next(0, 10),
|
||
Confidence = 0.6 + random.NextDouble() * 0.3,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 8
|
||
}
|
||
},
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 15
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成会话性能指标。
|
||
/// </summary>
|
||
private SessionPerformanceMetrics GenerateSessionPerformanceMetrics(Random random)
|
||
{
|
||
return new SessionPerformanceMetrics
|
||
{
|
||
TotalProcessingTimeMinutes = 30 + random.NextDouble() * 60,
|
||
AverageLayerProcessingTimeSeconds = 30 + random.NextDouble() * 30,
|
||
SystemAvailability = 0.95 + random.NextDouble() * 0.04,
|
||
ProcessingEfficiency = 0.8 + random.NextDouble() * 0.2,
|
||
ResourceUtilization = 0.7 + random.NextDouble() * 0.25,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 6
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层性能指标。
|
||
/// </summary>
|
||
private LayerPerformanceMetrics GenerateLayerPerformanceMetrics(Random random)
|
||
{
|
||
return new LayerPerformanceMetrics
|
||
{
|
||
LayerProcessingTimeSeconds = 30 + random.NextDouble() * 30,
|
||
DetectionProcessingTimeMs = random.Next(100, 2000),
|
||
RuleExecutionTimeMs = random.Next(50, 1000),
|
||
ScreenshotProcessingTimeMs = random.Next(200, 3000),
|
||
MemoryUsageMb = 50 + random.NextDouble() * 100,
|
||
CpuUsagePercentage = 20 + random.NextDouble() * 60,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 5
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报告执行摘要。
|
||
/// </summary>
|
||
private ReportExecutiveSummary GenerateReportExecutiveSummary(Random random)
|
||
{
|
||
return new ReportExecutiveSummary
|
||
{
|
||
OverallScore = 70 + random.NextDouble() * 25,
|
||
KeyFindings = new[] { "关键发现1", "关键发现2", "关键发现3" },
|
||
MajorIssues = new[] { "主要问题1", "主要问题2" },
|
||
ImprovementOpportunities = new[] { "改进机会1", "改进机会2", "改进机会3" },
|
||
SummaryDescription = "综合追溯报告执行摘要",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 10
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成层级分析报告。
|
||
/// </summary>
|
||
private IReadOnlyList<LayerAnalysisReport> GenerateLayerAnalysisReports(Random random, int layerCount)
|
||
{
|
||
var reports = new List<LayerAnalysisReport>();
|
||
|
||
for (int i = 1; i <= layerCount; i++)
|
||
{
|
||
reports.Add(new LayerAnalysisReport
|
||
{
|
||
LayerSequence = i,
|
||
LayerName = $"第{i}层",
|
||
LayerPerformanceScore = 70 + random.NextDouble() * 25,
|
||
LayerQualityScore = 75 + random.NextDouble() * 20,
|
||
LayerEfficiencyScore = 65 + random.NextDouble() * 30,
|
||
KeyMetrics = new Dictionary<string, double>
|
||
{
|
||
["处理时间"] = 30 + random.NextDouble() * 30,
|
||
["检测数量"] = random.Next(5, 20),
|
||
["缺陷数量"] = random.Next(0, 5)
|
||
},
|
||
IdentifiedIssues = new[] { $"问题{i}.1", $"问题{i}.2" },
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 8
|
||
}
|
||
});
|
||
}
|
||
|
||
return reports.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缺陷分析报告。
|
||
/// </summary>
|
||
private DefectAnalysisReport GenerateDefectAnalysisReport(Random random)
|
||
{
|
||
var defectCount = random.Next(0, 20);
|
||
var defectsByType = new Dictionary<DefectType, int>();
|
||
var defectsBySeverity = new Dictionary<DefectSeverity, int>();
|
||
|
||
for (int i = 0; i < defectCount; i++)
|
||
{
|
||
var type = (DefectType)random.Next(0, 8);
|
||
var severity = (DefectSeverity)random.Next(0, 4);
|
||
|
||
defectsByType[type] = defectsByType.GetValueOrDefault(type, 0) + 1;
|
||
defectsBySeverity[severity] = defectsBySeverity.GetValueOrDefault(severity, 0) + 1;
|
||
}
|
||
|
||
return new DefectAnalysisReport
|
||
{
|
||
TotalDefectCount = defectCount,
|
||
DefectRate = defectCount > 0 ? random.NextDouble() * 5 : 0.0,
|
||
DefectDistribution = defectsByType,
|
||
SeverityDistribution = defectsBySeverity,
|
||
HighFrequencyDefects = new List<DefectPattern>(),
|
||
DefectTrend = new DefectTrendAnalysis
|
||
{
|
||
TrendDirection = (TrendDirection)random.Next(0, 4),
|
||
ChangeRate = random.NextDouble() * 10 - 5,
|
||
TrendDescription = "缺陷趋势分析",
|
||
PredictedNextDefectCount = random.Next(0, 10),
|
||
Confidence = 0.6 + random.NextDouble() * 0.3,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 8
|
||
}
|
||
},
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 12
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成性能分析报告。
|
||
/// </summary>
|
||
private PerformanceAnalysisReport GeneratePerformanceAnalysisReport(Random random)
|
||
{
|
||
return new PerformanceAnalysisReport
|
||
{
|
||
TotalProcessingTimeMinutes = 45 + random.NextDouble() * 30,
|
||
AverageLayerProcessingTimeSeconds = 30 + random.NextDouble() * 30,
|
||
SystemAvailability = 0.95 + random.NextDouble() * 0.04,
|
||
ProcessingEfficiency = 0.8 + random.NextDouble() * 0.2,
|
||
ResourceUtilization = 0.7 + random.NextDouble() * 0.25,
|
||
PerformanceBottlenecks = new[] { "瓶颈1", "瓶颈2" },
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 10
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成证据链分析。
|
||
/// </summary>
|
||
private EvidenceChainAnalysis GenerateEvidenceChainAnalysis(Random random)
|
||
{
|
||
return new EvidenceChainAnalysis
|
||
{
|
||
EvidenceChainCompleteness = 0.8 + random.NextDouble() * 0.2,
|
||
EvidenceChainCredibility = 0.85 + random.NextDouble() * 0.15,
|
||
CriticalEvidenceNodes = new List<EvidenceNode>(),
|
||
EvidenceGaps = new List<EvidenceGap>
|
||
{
|
||
new EvidenceGap
|
||
{
|
||
GapId = Guid.NewGuid(),
|
||
GapType = (EvidenceGapType)random.Next(0, 7),
|
||
GapDescription = "证据缺口",
|
||
GapSeverity = (GapSeverity)random.Next(0, 4),
|
||
RecommendedAction = "建议措施",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 5
|
||
}
|
||
}
|
||
},
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 15
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成追溯改进建议。
|
||
/// </summary>
|
||
private IReadOnlyList<TracebackImprovementSuggestion> GenerateTracebackImprovementSuggestions(Random random)
|
||
{
|
||
var suggestions = new List<TracebackImprovementSuggestion>();
|
||
|
||
for (int i = 0; i < random.Next(3, 8); i++)
|
||
{
|
||
suggestions.Add(new TracebackImprovementSuggestion
|
||
{
|
||
SuggestionId = Guid.NewGuid(),
|
||
SuggestionCategory = (SuggestionCategory)random.Next(0, 8),
|
||
SuggestionTitle = $"改进建议{i + 1}",
|
||
SuggestionDescription = $"改进建议描述{i + 1}",
|
||
Priority = random.Next(1, 5),
|
||
ExpectedImpact = $"预期效果{i + 1}",
|
||
ImplementationDifficulty = (ImplementationDifficulty)random.Next(0, 4),
|
||
RelatedEvidenceIds = Enumerable.Range(0, random.Next(1, 3)).Select(_ => Guid.NewGuid()).ToList(),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["suggestion_index"] = i
|
||
}
|
||
});
|
||
}
|
||
|
||
return suggestions.AsReadOnly();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按日期分组的追溯统计。
|
||
/// </summary>
|
||
private Dictionary<DateTime, DailyTracebackStatistics> GenerateDailyTracebackStatistics(DateTime startTime, DateTime endTime, Random random)
|
||
{
|
||
var dailyStats = new Dictionary<DateTime, DailyTracebackStatistics>();
|
||
var current = startTime.Date;
|
||
|
||
while (current <= endTime.Date)
|
||
{
|
||
dailyStats[current] = new DailyTracebackStatistics
|
||
{
|
||
Date = current,
|
||
SessionCount = random.Next(5, 20),
|
||
CompletedSessionCount = random.Next(4, 20),
|
||
LayerCount = random.Next(15, 100),
|
||
ScreenshotCount = random.Next(50, 300),
|
||
DefectCount = random.Next(0, 20)
|
||
};
|
||
|
||
current = current.AddDays(1);
|
||
}
|
||
|
||
return dailyStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成按产品类型分组的追溯统计。
|
||
/// </summary>
|
||
private Dictionary<string, ProductTypeTracebackStatistics> GenerateProductTypeTracebackStatistics(Random random)
|
||
{
|
||
var productTypeStats = new Dictionary<string, ProductTypeTracebackStatistics>();
|
||
|
||
for (int i = 1; i <= 5; i++)
|
||
{
|
||
productTypeStats[$"PT{i}"] = new ProductTypeTracebackStatistics
|
||
{
|
||
ProductTypeCode = $"PT{i}",
|
||
SessionCount = random.Next(10, 50),
|
||
CompletedSessionCount = random.Next(8, 50),
|
||
LayerCount = random.Next(30, 200),
|
||
ScreenshotCount = random.Next(100, 600),
|
||
DefectCount = random.Next(0, 50)
|
||
};
|
||
}
|
||
|
||
return productTypeStats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成导出文件内容。
|
||
/// </summary>
|
||
private string GenerateExportFileContent(TracebackExportRequest exportRequest, int recordCount)
|
||
{
|
||
var random = new Random();
|
||
var content = new System.Text.StringBuilder();
|
||
|
||
// 简化处理:生成CSV格式内容
|
||
content.AppendLine("ID,时间,类型,状态,描述");
|
||
|
||
for (int i = 0; i < recordCount; i++)
|
||
{
|
||
var timestamp = DateTime.UtcNow.AddMinutes(-random.Next(0, 1440));
|
||
var type = exportRequest.ExportType.ToString();
|
||
var status = "Completed";
|
||
var description = $"记录{i + 1}";
|
||
|
||
content.AppendLine($"{i + 1},{timestamp:yyyy-MM-dd HH:mm:ss},{type},{status},{description}");
|
||
}
|
||
|
||
return content.ToString();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 历史追溯服务实现(最小可编译版本)。
|
||
/// </summary>
|
||
public sealed class HistoryTraceService : IHistoryTraceService
|
||
{
|
||
private readonly ILogger<HistoryTraceService> _logger;
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public HistoryTraceService(ILogger<HistoryTraceService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_ = options;
|
||
_logger.LogInformation("历史追溯服务已初始化(最小实现)");
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<ProductSessionHistory>>> GetProductSessionHistoryAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, OrpaonVision.Core.HistoryTrace.SessionStatus? sessionStatus = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<IReadOnlyList<ProductSessionHistory>>.Success(Array.Empty<ProductSessionHistory>()));
|
||
|
||
public Task<Result<ProductSessionDetail>> GetSessionDetailAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<ProductSessionDetail>.Success(new ProductSessionDetail()));
|
||
|
||
public Task<Result<IReadOnlyList<LayerProcessingHistory>>> GetLayerProcessingHistoryAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<IReadOnlyList<LayerProcessingHistory>>.Success(Array.Empty<LayerProcessingHistory>()));
|
||
|
||
public Task<Result<LayerProcessingDetail>> GetLayerDetailAsync(Guid layerId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<LayerProcessingDetail>.Success(new LayerProcessingDetail()));
|
||
|
||
public Task<Result<IReadOnlyList<ScreenshotHistory>>> GetScreenshotHistoryAsync(Guid? sessionId = null, Guid? layerId = null, DateTime? startTime = null, DateTime? endTime = null, ScreenshotType? screenshotType = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<IReadOnlyList<ScreenshotHistory>>.Success(Array.Empty<ScreenshotHistory>()));
|
||
|
||
public Task<Result<ScreenshotDetail>> GetScreenshotDetailAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<ScreenshotDetail>.Success(new ScreenshotDetail()));
|
||
|
||
public Task<Result<byte[]>> GetScreenshotContentAsync(Guid screenshotId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<byte[]>.Success(Array.Empty<byte>()));
|
||
|
||
public Task<Result<EvidenceChain>> GetEvidenceChainAsync(Guid sessionId, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<EvidenceChain>.Success(new EvidenceChain()));
|
||
|
||
public Task<Result<LayerEvidenceTraceback>> GetLayerEvidenceTracebackAsync(Guid sessionId, int? layerSequence = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<LayerEvidenceTraceback>.Success(new LayerEvidenceTraceback()));
|
||
|
||
public Task<Result<DefectTraceback>> GetDefectTracebackAsync(Guid sessionId, DefectType? defectType = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<DefectTraceback>.Success(new DefectTraceback()));
|
||
|
||
public Task<Result<IReadOnlyList<OperationHistory>>> GetOperationHistoryAsync(Guid? sessionId = null, string? operatorId = null, DateTime? startTime = null, DateTime? endTime = null, OperationType? operationType = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<IReadOnlyList<OperationHistory>>.Success(Array.Empty<OperationHistory>()));
|
||
|
||
public Task<Result<ComprehensiveTracebackReport>> GetComprehensiveTracebackReportAsync(Guid sessionId, OrpaonVision.Core.HistoryTrace.TracebackReportType reportType, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<ComprehensiveTracebackReport>.Success(new ComprehensiveTracebackReport()));
|
||
|
||
public Task<Result<TracebackStatistics>> GetTracebackStatisticsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<TracebackStatistics>.Success(new TracebackStatistics()));
|
||
|
||
public Task<Result<HistorySearchResult>> SearchHistoryAsync(HistorySearchRequest searchRequest, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<HistorySearchResult>.Success(new HistorySearchResult()));
|
||
|
||
public Task<Result<TracebackExportResult>> ExportTracebackDataAsync(TracebackExportRequest exportRequest, CancellationToken cancellationToken = default)
|
||
=> Task.FromResult(Result<TracebackExportResult>.Success(new TracebackExportResult
|
||
{
|
||
ExportId = exportRequest.ExportId,
|
||
IsSuccess = true,
|
||
FilePath = string.Empty,
|
||
FileSizeBytes = 0,
|
||
RecordCount = 0,
|
||
ExportElapsedMs = 0,
|
||
ExportTimeUtc = DateTime.UtcNow,
|
||
ResultDescription = "最小实现返回。"
|
||
}));
|
||
}
|