using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrpaonVision.Core.Production; using OrpaonVision.Core.Results; using OrpaonVision.Core.Common; using OrpaonVision.Model.Production; using OrpaonVision.ConfigApp.Infrastructure.Options; using OrpaonVision.ConfigApp.Infrastructure.Persistence.Options; using System.Collections.Concurrent; namespace OrpaonVision.ConfigApp.Infrastructure.Services; /// /// 产品会话管理服务实现。 /// public sealed class ProductionSessionManagerService : IProductionSessionManagerService { private readonly ILogger _logger; private readonly PersistenceOptions _persistenceOptions; private readonly ConcurrentDictionary _activeSessions = new(); private readonly ConcurrentDictionary> _stationSessions = new(); private readonly ConcurrentDictionary> _sessionEvents = new(); private readonly object _lock = new(); /// /// 构造函数。 /// public ProductionSessionManagerService( ILogger logger, IOptions persistenceOptions) { _logger = logger; _persistenceOptions = persistenceOptions.Value; } /// public async Task> CreateSessionOnArrivalAsync(CreateSessionOnArrivalRequest request, CancellationToken cancellationToken = default) { try { _logger.LogInformation("进站建会话开始: 产品类型={ProductType}, 工位={Station}, 操作员={Operator}", request.ProductTypeCode, request.StationId, request.OperatorId); lock (_lock) { // 检查工位是否已有活动会话 var existingSession = GetCurrentActiveSessionInternal(request.StationId); if (existingSession != null) { return Result.Fail("ACTIVE_SESSION_EXISTS", $"工位 {request.StationId} 已存在活动会话 {existingSession.SessionId}"); } // 创建新会话 var session = new ProductionSessionModel { SessionId = Guid.NewGuid(), ProductTypeCode = request.ProductTypeCode, ProductTypeName = request.ProductTypeName, StationId = request.StationId, StationName = request.StationName, OperatorId = request.OperatorId, OperatorName = request.OperatorName, ShiftId = request.ShiftId, ShiftName = request.ShiftName, StartedAtUtc = request.ArrivalTimeUtc, Status = ProductionSessionStatus.InProgress, Result = ProductionSessionResult.Pending, CurrentLayer = 0, TotalLayers = request.TotalLayers, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, CreatedBy = request.CreatedBy, UpdatedBy = request.CreatedBy, Remark = request.Remark }; // 添加到内存存储 _activeSessions.TryAdd(session.SessionId, session); var stationSessions = _stationSessions.GetOrAdd(request.StationId, _ => new List()); stationSessions.Add(session); // 记录会话事件 RecordSessionEvent(session.SessionId, ProductionSessionEventType.SessionCreated, "进站建会话", request.CreatedBy); _logger.LogInformation("进站建会话成功: 会话ID={SessionId}, 产品类型={ProductType}", session.SessionId, request.ProductTypeCode); return Result.Success(session); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "进站建会话失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "CREATE_SESSION_FAILED", "进站建会话失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task ArchiveSessionOnDepartureAsync(Guid sessionId, ProductionSessionResult result, string? ngReason = null, string? remark = null, CancellationToken cancellationToken = default) { try { _logger.LogInformation("离站归档会话开始: 会话ID={SessionId}, 结果={Result}", sessionId, result); lock (_lock) { if (!_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在"); } // 更新会话状态 session.EndedAtUtc = DateTime.UtcNow; session.Result = result; session.Status = result == ProductionSessionResult.Ok ? ProductionSessionStatus.CompletedOk : ProductionSessionStatus.CompletedNg; session.NgReason = ngReason; session.Remark = remark; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; // 从活动会话中移除 _activeSessions.TryRemove(sessionId, out _); if (_stationSessions.TryGetValue(session.StationId, out var stationSessions)) { stationSessions.Remove(session); } // 记录会话事件 RecordSessionEvent(sessionId, ProductionSessionEventType.SessionArchived, $"离站归档: 结果={result}, NG原因={ngReason}", "System"); // 模拟持久化到数据库 PersistSessionToDatabase(session); _logger.LogInformation("离站归档会话成功: 会话ID={SessionId}", sessionId); return Result.Success(); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "离站归档会话失败。TraceId: {TraceId}", traceId); var errorResult = Result.FromException(ex, "ARCHIVE_SESSION_FAILED", "离站归档会话失败。", traceId); return Result.FailWithTrace(errorResult.Code, errorResult.Message, errorResult.TraceId ?? traceId, errorResult.Errors.ToArray()); } } /// public async Task UpdateSessionProgressAsync(Guid sessionId, int currentLayer, ProductionSessionStatus layerStatus, CancellationToken cancellationToken = default) { try { _logger.LogDebug("更新会话进度: 会话ID={SessionId}, 当前层级={CurrentLayer}, 状态={Status}", sessionId, currentLayer, layerStatus); lock (_lock) { if (!_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在"); } session.CurrentLayer = currentLayer; session.Status = layerStatus; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; // 记录会话事件 RecordSessionEvent(sessionId, ProductionSessionEventType.ProgressUpdated, $"进度更新: 层级={currentLayer}, 状态={layerStatus}", "System"); return Result.Success(); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "更新会话进度失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "UPDATE_PROGRESS_FAILED", "更新会话进度失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task PauseSessionAsync(Guid sessionId, string reason, CancellationToken cancellationToken = default) { try { _logger.LogInformation("暂停会话: 会话ID={SessionId}, 原因={Reason}", sessionId, reason); lock (_lock) { if (!_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在"); } session.Status = ProductionSessionStatus.Paused; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; session.Remark = $"暂停: {reason}"; // 记录会话事件 RecordSessionEvent(sessionId, ProductionSessionEventType.SessionPaused, reason, "System"); return Result.Success(); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "暂停会话失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "PAUSE_SESSION_FAILED", "暂停会话失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task ResumeSessionAsync(Guid sessionId, string reason, CancellationToken cancellationToken = default) { try { _logger.LogInformation("恢复会话: 会话ID={SessionId}, 原因={Reason}", sessionId, reason); lock (_lock) { if (!_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在"); } session.Status = ProductionSessionStatus.InProgress; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; session.Remark = $"恢复: {reason}"; // 记录会话事件 RecordSessionEvent(sessionId, ProductionSessionEventType.SessionResumed, reason, "System"); return Result.Success(); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "恢复会话失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "RESUME_SESSION_FAILED", "恢复会话失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task CancelSessionAsync(Guid sessionId, string reason, CancellationToken cancellationToken = default) { try { _logger.LogInformation("取消会话: 会话ID={SessionId}, 原因={Reason}", sessionId, reason); lock (_lock) { if (!_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在"); } session.Status = ProductionSessionStatus.Cancelled; session.EndedAtUtc = DateTime.UtcNow; session.Result = ProductionSessionResult.Ng; session.NgReason = $"取消: {reason}"; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; // 从活动会话中移除 _activeSessions.TryRemove(sessionId, out _); if (_stationSessions.TryGetValue(session.StationId, out var stationSessions)) { stationSessions.Remove(session); } // 记录会话事件 RecordSessionEvent(sessionId, ProductionSessionEventType.SessionCancelled, reason, "System"); // 模拟持久化到数据库 PersistSessionToDatabase(session); return Result.Success(); } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "取消会话失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "CANCEL_SESSION_FAILED", "取消会话失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> GetCurrentActiveSessionAsync(string stationId, CancellationToken cancellationToken = default) { try { var session = GetCurrentActiveSessionInternal(stationId); return Result.Success(session); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取当前活动会话失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_CURRENT_SESSION_FAILED", "获取当前活动会话失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> GetSessionAsync(Guid sessionId, CancellationToken cancellationToken = default) { try { if (_activeSessions.TryGetValue(sessionId, out var session)) { return Result.Success(session); } // 如果活动会话中没有,从数据库查询 var dbSession = await GetSessionFromDatabaseAsync(sessionId, cancellationToken); return Result.Success(dbSession); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取会话详情失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_SESSION_FAILED", "获取会话详情失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task>> GetSessionHistoryAsync(GetSessionHistoryRequest request, CancellationToken cancellationToken = default) { try { // 模拟从数据库查询历史记录 var allSessions = await GetAllSessionsFromDatabaseAsync(cancellationToken); // 应用过滤条件 var filteredSessions = allSessions.AsEnumerable(); if (!string.IsNullOrWhiteSpace(request.ProductTypeCode)) { filteredSessions = filteredSessions.Where(s => s.ProductTypeCode == request.ProductTypeCode); } if (!string.IsNullOrWhiteSpace(request.StationId)) { filteredSessions = filteredSessions.Where(s => s.StationId == request.StationId); } if (!string.IsNullOrWhiteSpace(request.OperatorId)) { filteredSessions = filteredSessions.Where(s => s.OperatorId == request.OperatorId); } if (request.Status.HasValue) { filteredSessions = filteredSessions.Where(s => s.Status == request.Status.Value); } if (request.Result.HasValue) { filteredSessions = filteredSessions.Where(s => s.Result == request.Result.Value); } if (request.StartTimeUtc.HasValue) { filteredSessions = filteredSessions.Where(s => s.StartedAtUtc >= request.StartTimeUtc.Value); } if (request.EndTimeUtc.HasValue) { filteredSessions = filteredSessions.Where(s => s.EndedAtUtc <= request.EndTimeUtc.Value); } // 应用排序 filteredSessions = request.SortField switch { OrpaonVision.Core.Production.SessionSortField.StartTimeUtc => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.StartedAtUtc) : filteredSessions.OrderByDescending(s => s.StartedAtUtc), OrpaonVision.Core.Production.SessionSortField.EndTimeUtc => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.EndedAtUtc) : filteredSessions.OrderByDescending(s => s.EndedAtUtc), OrpaonVision.Core.Production.SessionSortField.ProductTypeCode => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.ProductTypeCode) : filteredSessions.OrderByDescending(s => s.ProductTypeCode), OrpaonVision.Core.Production.SessionSortField.StationId => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.StationId) : filteredSessions.OrderByDescending(s => s.StationId), OrpaonVision.Core.Production.SessionSortField.OperatorName => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.OperatorName) : filteredSessions.OrderByDescending(s => s.OperatorName), OrpaonVision.Core.Production.SessionSortField.CurrentLayer => request.SortDirection == OrpaonVision.Core.Production.SortDirection.Ascending ? filteredSessions.OrderBy(s => s.CurrentLayer) : filteredSessions.OrderByDescending(s => s.CurrentLayer), _ => filteredSessions.OrderByDescending(s => s.StartedAtUtc) }; // 应用分页 var totalCount = filteredSessions.Count(); var pagedSessions = filteredSessions .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .ToList(); var result = new PagedResult { Items = pagedSessions, TotalCount = totalCount, PageIndex = request.PageIndex, PageSize = request.PageSize }; return Result>.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取会话历史记录失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_SESSION_HISTORY_FAILED", "获取会话历史记录失败。", traceId); return Result>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> GetSessionStatisticsAsync(GetSessionStatisticsRequest request, CancellationToken cancellationToken = default) { try { // 模拟从数据库查询统计数据 var allSessions = await GetAllSessionsFromDatabaseAsync(cancellationToken); // 应用过滤条件 var filteredSessions = allSessions.AsEnumerable(); if (!string.IsNullOrWhiteSpace(request.ProductTypeCode)) { filteredSessions = filteredSessions.Where(s => s.ProductTypeCode == request.ProductTypeCode); } if (!string.IsNullOrWhiteSpace(request.StationId)) { filteredSessions = filteredSessions.Where(s => s.StationId == request.StationId); } if (!string.IsNullOrWhiteSpace(request.OperatorId)) { filteredSessions = filteredSessions.Where(s => s.OperatorId == request.OperatorId); } filteredSessions = filteredSessions.Where(s => s.StartedAtUtc >= request.StartTimeUtc && s.StartedAtUtc <= request.EndTimeUtc); // 计算平均处理时间 var completedSessions = filteredSessions.Where(s => s.EndedAtUtc.HasValue).ToList(); double averageProcessingTimeSeconds = 0; double throughputPerHour = 0; if (completedSessions.Any()) { averageProcessingTimeSeconds = completedSessions.Average(s => (s.EndedAtUtc!.Value - s.StartedAtUtc).TotalSeconds); throughputPerHour = completedSessions.Count / completedSessions.Average(s => (s.EndedAtUtc!.Value - s.StartedAtUtc).TotalHours); } // 按维度分组统计 var byProductType = new List(); var byStation = new List(); var byOperator = new List(); var byDate = new List(); if (request.Dimensions.Contains(OrpaonVision.Core.Common.StatisticsDimension.ByProductType)) { byProductType = CalculateProductTypeStatistics(filteredSessions); } if (request.Dimensions.Contains(OrpaonVision.Core.Common.StatisticsDimension.ByStation)) { byStation = CalculateStationStatistics(filteredSessions); } if (request.Dimensions.Contains(OrpaonVision.Core.Common.StatisticsDimension.ByOperator)) { byOperator = CalculateOperatorStatistics(filteredSessions); } if (request.Dimensions.Contains(OrpaonVision.Core.Common.StatisticsDimension.ByDate)) { byDate = CalculateDailyStatistics(filteredSessions); } // 计算基础统计 var successRate = filteredSessions.Count() > 0 ? (double)filteredSessions.Count(s => s.Result == ProductionSessionResult.Ok) / filteredSessions.Count() * 100 : 0; var statistics = new ProductionSessionStatistics { TotalSessions = filteredSessions.Count(), OkSessions = filteredSessions.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = filteredSessions.Count(s => s.Result == ProductionSessionResult.Ng), InProgressSessions = filteredSessions.Count(s => s.Status == ProductionSessionStatus.InProgress), CancelledSessions = filteredSessions.Count(s => s.Status == ProductionSessionStatus.Cancelled), PausedSessions = filteredSessions.Count(s => s.Status == ProductionSessionStatus.Paused), AverageProcessingTimeSeconds = averageProcessingTimeSeconds, ThroughputPerHour = throughputPerHour, SuccessRate = successRate, ByProductType = byProductType, ByStation = byStation, ByOperator = byOperator, ByDate = byDate }; return Result.Success(statistics); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取会话统计信息失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace("GET_STATISTICS_FAILED", "获取会话统计信息失败。", traceId, ex.Message); } } /// public async Task> ExportSessionsAsync(ExportSessionsRequest request, CancellationToken cancellationToken = default) { try { _logger.LogInformation("导出会话记录开始: 格式={Format}", request.Format); // 获取要导出的会话数据 var historyRequest = new GetSessionHistoryRequest { ProductTypeCode = request.ProductTypeCode, StationId = request.StationId, OperatorId = request.OperatorId, Status = request.Status, Result = request.Result, StartTimeUtc = request.StartTimeUtc, EndTimeUtc = request.EndTimeUtc, PageIndex = 1, PageSize = int.MaxValue // 导出所有数据 }; var historyResult = await GetSessionHistoryAsync(historyRequest, cancellationToken); if (!historyResult.Succeeded) { return Result.Fail(historyResult.Code, historyResult.Message, historyResult.Errors.ToArray()); } // 根据格式导出数据 var exportData = request.Format switch { ExportFormat.Excel => await ExportToExcelAsync(historyResult.Data.Items, request, cancellationToken), ExportFormat.Csv => await ExportToCsvAsync(historyResult.Data.Items, request, cancellationToken), ExportFormat.Json => await ExportToJsonAsync(historyResult.Data.Items, request, cancellationToken), _ => throw new NotSupportedException($"不支持的导出格式: {request.Format}") }; _logger.LogInformation("导出会话记录成功: 记录数={Count}, 格式={Format}", historyResult.Data.Items.Count, request.Format); return Result.Success(exportData); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "导出会话记录失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace("EXPORT_SESSIONS_FAILED", "导出会话记录失败。", traceId, ex.Message); } } /// public async Task> BatchArchiveSessionsAsync(IReadOnlyList sessionIds, CancellationToken cancellationToken = default) { try { _logger.LogInformation("批量归档会话开始: 会话数量={Count}", sessionIds.Count); var successCount = 0; var failureCount = 0; var failedSessionIds = new List(); var errors = new List(); foreach (var sessionId in sessionIds) { try { // 检查会话是否存在且为活动状态 if (_activeSessions.TryGetValue(sessionId, out var session)) { // 归档会话 var archiveResult = await ArchiveSessionOnDepartureAsync(sessionId, ProductionSessionResult.Ok, null, "批量归档", cancellationToken); if (archiveResult.Succeeded) { successCount++; } else { failureCount++; failedSessionIds.Add(sessionId); errors.Add($"会话 {sessionId} 归档失败: {archiveResult.Message}"); } } else { failureCount++; failedSessionIds.Add(sessionId); errors.Add($"会话 {sessionId} 不存在或已归档"); } } catch (Exception ex) { failureCount++; failedSessionIds.Add(sessionId); errors.Add($"会话 {sessionId} 归档异常: {ex.Message}"); } } _logger.LogInformation("批量归档会话完成: 成功={SuccessCount}, 失败={FailureCount}", successCount, failureCount); var result = new BatchArchiveResult { TotalCount = sessionIds.Count, SuccessCount = successCount, FailureCount = failureCount, FailedSessionIds = failedSessionIds, Errors = errors }; return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "批量归档会话失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace("BATCH_ARCHIVE_FAILED", "批量归档会话失败。", traceId, ex.Message); } } /// public async Task> CleanupExpiredSessionsAsync(int retentionDays, CancellationToken cancellationToken = default) { try { _logger.LogInformation("清理过期会话开始: 保留天数={RetentionDays}", retentionDays); var startTime = DateTime.UtcNow; var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays); // 获取所有已完成的会话 var allSessions = await GetAllSessionsFromDatabaseAsync(cancellationToken); var expiredSessions = allSessions.Where(s => s.EndedAtUtc.HasValue && s.EndedAtUtc.Value < cutoffDate).ToList(); var cleanedCount = 0; var freedSpace = 0L; foreach (var session in expiredSessions) { try { // 模拟删除会话和相关数据 await DeleteSessionFromDatabaseAsync(session.SessionId, cancellationToken); cleanedCount++; // 模拟释放存储空间 freedSpace += 1024; // 假设每个会话占用1KB } catch (Exception ex) { _logger.LogWarning(ex, "清理过期会话失败: 会话ID={SessionId}", session.SessionId); } } var elapsed = DateTime.UtcNow - startTime; var result = new CleanupResult { CleanedCount = cleanedCount, FreedSpaceBytes = freedSpace, ElapsedMilliseconds = (long)elapsed.TotalMilliseconds }; _logger.LogInformation("清理过期会话完成: 清理数量={CleanedCount}, 释放空间={FreedSpace}KB, 耗时={ElapsedMs}ms", cleanedCount, freedSpace / 1024, result.ElapsedMilliseconds); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "清理过期会话失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace("CLEANUP_FAILED", "清理过期会话失败。", traceId, ex.Message); } } #region 私有方法 /// /// 获取当前活动会话(内部方法)。 /// private ProductionSessionModel? GetCurrentActiveSessionInternal(string stationId) { if (_stationSessions.TryGetValue(stationId, out var sessions)) { return sessions.LastOrDefault(s => s.Status == ProductionSessionStatus.InProgress || s.Status == ProductionSessionStatus.Paused); } return null; } /// /// 记录会话事件。 /// private void RecordSessionEvent(Guid sessionId, ProductionSessionEventType eventType, string description, string operatorName) { var events = _sessionEvents.GetOrAdd(sessionId, _ => new List()); var sessionEvent = new ProductionSessionEvent { EventId = Guid.NewGuid(), SessionId = sessionId, EventType = eventType, Description = description, OperatorName = operatorName, EventTimeUtc = DateTime.UtcNow }; events.Add(sessionEvent); } /// /// 持久化会话到数据库(模拟)。 /// private void PersistSessionToDatabase(ProductionSessionModel session) { // 模拟数据库持久化操作 _logger.LogDebug("持久化会话到数据库: 会话ID={SessionId}", session.SessionId); } /// /// 从数据库获取会话(模拟)。 /// private async Task GetSessionFromDatabaseAsync(Guid sessionId, CancellationToken cancellationToken = default) { // 模拟数据库查询 await Task.Delay(10, cancellationToken); return null; // 简化实现,返回null表示未找到 } /// /// 从数据库获取所有会话(模拟)。 /// private async Task> GetAllSessionsFromDatabaseAsync(CancellationToken cancellationToken = default) { // 模拟数据库查询,返回一些示例数据 await Task.Delay(10, cancellationToken); return new List { new ProductionSessionModel { SessionId = Guid.NewGuid(), ProductTypeCode = "VF-A100", ProductTypeName = "变频器A100", StationId = "ST-001", StationName = "装配工位1", OperatorId = "OP-001", OperatorName = "张三", ShiftId = "SH-001", ShiftName = "白班", StartedAtUtc = DateTime.UtcNow.AddHours(-2), EndedAtUtc = DateTime.UtcNow.AddHours(-1), Status = ProductionSessionStatus.CompletedOk, Result = ProductionSessionResult.Ok, CurrentLayer = 3, TotalLayers = 3, CreatedAtUtc = DateTime.UtcNow.AddDays(-1), UpdatedAtUtc = DateTime.UtcNow.AddHours(-1), CreatedBy = "System", UpdatedBy = "System" } }; } /// /// 从数据库删除会话(模拟)。 /// private async Task DeleteSessionFromDatabaseAsync(Guid sessionId, CancellationToken cancellationToken = default) { // 模拟数据库删除操作 await Task.Delay(5, cancellationToken); _logger.LogDebug("从数据库删除会话: 会话ID={SessionId}", sessionId); } /// /// 计算产品类型统计。 /// private List CalculateProductTypeStatistics(IEnumerable sessions) { return sessions .GroupBy(s => s.ProductTypeCode) .Select(g => new ProductTypeStatistics { ProductTypeCode = g.Key, ProductTypeName = g.First().ProductTypeName, TotalSessions = g.Count(), OkSessions = g.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = g.Count(s => s.Result == ProductionSessionResult.Ng), PassRate = g.Count() > 0 ? (double)g.Count(s => s.Result == ProductionSessionResult.Ok) / g.Count() * 100 : 0 }) .ToList(); } /// /// 计算工位统计。 /// private List CalculateStationStatistics(IEnumerable sessions) { return sessions .GroupBy(s => s.StationId) .Select(g => new StationStatistics { StationId = g.Key, StationName = g.First().StationName, TotalSessions = g.Count(), OkSessions = g.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = g.Count(s => s.Result == ProductionSessionResult.Ng), PassRate = g.Count() > 0 ? (double)g.Count(s => s.Result == ProductionSessionResult.Ok) / g.Count() * 100 : 0, ThroughputPerHour = CalculateThroughputPerHour(g) }) .ToList(); } /// /// 计算操作员统计。 /// private List CalculateOperatorStatistics(IEnumerable sessions) { return sessions .GroupBy(s => s.OperatorId) .Select(g => new OperatorStatistics { OperatorId = g.Key, OperatorName = g.First().OperatorName, TotalSessions = g.Count(), OkSessions = g.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = g.Count(s => s.Result == ProductionSessionResult.Ng), PassRate = g.Count() > 0 ? (double)g.Count(s => s.Result == ProductionSessionResult.Ok) / g.Count() * 100 : 0, AverageProcessingTimeSeconds = CalculateAverageProcessingTime(g) }) .ToList(); } /// /// 计算日期统计。 /// private List CalculateDailyStatistics(IEnumerable sessions) { return sessions .GroupBy(s => s.StartedAtUtc.Date) .Select(g => new DailyStatistics { DateUtc = g.Key, TotalSessions = g.Count(), OkSessions = g.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = g.Count(s => s.Result == ProductionSessionResult.Ng), PassRate = g.Count() > 0 ? (double)g.Count(s => s.Result == ProductionSessionResult.Ok) / g.Count() * 100 : 0, ThroughputPerHour = CalculateThroughputPerHour(g) }) .ToList(); } /// /// 计算每小时处理量。 /// private double CalculateThroughputPerHour(IEnumerable sessions) { var completedSessions = sessions.Where(s => s.EndedAtUtc.HasValue).ToList(); if (!completedSessions.Any()) return 0; var totalHours = completedSessions.Sum(s => (s.EndedAtUtc!.Value - s.StartedAtUtc).TotalHours); return totalHours > 0 ? completedSessions.Count / totalHours : 0; } /// /// 计算平均处理时间。 /// private double CalculateAverageProcessingTime(IEnumerable sessions) { var completedSessions = sessions.Where(s => s.EndedAtUtc.HasValue).ToList(); if (!completedSessions.Any()) return 0; return completedSessions.Average(s => (s.EndedAtUtc!.Value - s.StartedAtUtc).TotalSeconds); } /// /// 导出为Excel格式(模拟)。 /// private async Task ExportToExcelAsync(IReadOnlyList sessions, ExportSessionsRequest request, CancellationToken cancellationToken = default) { await Task.Delay(100, cancellationToken); // 模拟Excel文件内容 var csvContent = "SessionId,ProductTypeCode,StationId,OperatorName,StartedAtUtc,EndedAtUtc,Status,Result\n"; foreach (var session in sessions) { csvContent += $"{session.SessionId},{session.ProductTypeCode},{session.StationId},{session.OperatorName},{session.StartedAtUtc:yyyy-MM-dd HH:mm:ss},{session.EndedAtUtc:yyyy-MM-dd HH:mm:ss},{session.Status},{session.Result}\n"; } return System.Text.Encoding.UTF8.GetBytes(csvContent); } /// /// 导出为CSV格式。 /// private async Task ExportToCsvAsync(IReadOnlyList sessions, ExportSessionsRequest request, CancellationToken cancellationToken = default) { await Task.Delay(50, cancellationToken); var csvContent = "SessionId,ProductTypeCode,StationId,OperatorName,StartedAtUtc,EndedAtUtc,Status,Result\n"; foreach (var session in sessions) { csvContent += $"{session.SessionId},{session.ProductTypeCode},{session.StationId},{session.OperatorName},{session.StartedAtUtc:yyyy-MM-dd HH:mm:ss},{session.EndedAtUtc:yyyy-MM-dd HH:mm:ss},{session.Status},{session.Result}\n"; } return System.Text.Encoding.UTF8.GetBytes(csvContent); } /// /// 导出为JSON格式。 /// private async Task ExportToJsonAsync(IReadOnlyList sessions, ExportSessionsRequest request, CancellationToken cancellationToken = default) { await Task.Delay(50, cancellationToken); var json = System.Text.Json.JsonSerializer.Serialize(sessions, new System.Text.Json.JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase }); return System.Text.Encoding.UTF8.GetBytes(json); } #endregion } /// /// 产品会话事件模型。 /// public sealed class ProductionSessionEvent { /// /// 事件ID。 /// public Guid EventId { get; set; } /// /// 会话ID。 /// public Guid SessionId { get; set; } /// /// 事件类型。 /// public ProductionSessionEventType EventType { get; set; } /// /// 事件描述。 /// public string Description { get; set; } = string.Empty; /// /// 操作员姓名。 /// public string OperatorName { get; set; } = string.Empty; /// /// 事件时间(UTC)。 /// public DateTime EventTimeUtc { get; set; } /// /// 扩展属性。 /// public Dictionary ExtendedProperties { get; set; } = new(); } /// /// 产品会话事件类型。 /// public enum ProductionSessionEventType { /// /// 会话创建。 /// SessionCreated = 0, /// /// 会话归档。 /// SessionArchived = 1, /// /// 进度更新。 /// ProgressUpdated = 2, /// /// 会话暂停。 /// SessionPaused = 3, /// /// 会话恢复。 /// SessionResumed = 4, /// /// 会话取消。 /// SessionCancelled = 5, /// /// 层级开始。 /// LayerStarted = 6, /// /// 层级完成。 /// LayerCompleted = 7, /// /// 检测到NG。 /// NgDetected = 8, /// /// 人工干预。 /// ManualIntervention = 9 }