using Microsoft.Extensions.Logging; using OrpaonVision.Core.Abstractions; using OrpaonVision.Core.Results; using OrpaonVision.Model.Production; using System.Collections.Concurrent; namespace OrpaonVision.Core.Services; /// /// 产品会话记录服务实现。 /// public sealed class ProductionSessionService : IProductionSessionService { private readonly ILogger _logger; private readonly ConcurrentDictionary _sessions = new(); private readonly object _lock = new object(); /// /// 构造函数。 /// public ProductionSessionService(ILogger logger) { _logger = logger; InitializeSampleData(); } /// public Result CreateSession(CreateProductionSessionRequest request) { try { _logger.LogInformation("正在创建产品会话,产品类型: {ProductType}, 工位: {Station}, 操作员: {Operator}", request.ProductTypeCode, request.StationId, request.OperatorId); // 检查当前是否已有活动会话 var currentSession = GetCurrentSession(request.StationId); if (currentSession.Succeeded && currentSession.Data != null) { return Result.Fail("ACTIVE_SESSION_EXISTS", $"工位 {request.StationId} 已存在活动会话。"); } 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 = DateTime.UtcNow, Status = ProductionSessionStatus.InProgress, Result = ProductionSessionResult.Pending, CurrentLayer = 1, TotalLayers = request.TotalLayers, Remark = request.Remark, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, CreatedBy = request.CreatedBy, UpdatedBy = request.CreatedBy }; _sessions.TryAdd(session.SessionId, session); _logger.LogInformation("产品会话创建成功,会话ID: {SessionId}", session.SessionId); return Result.Success(session, message: "产品会话创建成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "创建产品会话失败。TraceId: {TraceId}", traceId); var errorResult = Result.FromException(ex, "CREATE_SESSION_FAILED", "创建产品会话失败。", traceId); return Result.FailWithTrace(errorResult.Code, errorResult.Message, errorResult.TraceId ?? traceId, errorResult.Errors.ToArray()); } } /// public Result UpdateSessionStatus(Guid sessionId, ProductionSessionStatus status, string? reason = null) { try { if (!_sessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在。"); } lock (_lock) { session.Status = status; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; if (status == ProductionSessionStatus.CompletedNg && !string.IsNullOrEmpty(reason)) { session.NgReason = reason; } if (status == ProductionSessionStatus.CompletedOk || status == ProductionSessionStatus.CompletedNg || status == ProductionSessionStatus.Cancelled) { session.EndedAtUtc = DateTime.UtcNow; } } _logger.LogInformation("会话状态更新成功,会话ID: {SessionId}, 状态: {Status}", sessionId, status); return Result.Success("会话状态更新成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "更新会话状态失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "UPDATE_SESSION_STATUS_FAILED", "更新会话状态失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public Result UpdateSessionProgress(Guid sessionId, int currentLayer) { try { if (!_sessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在。"); } lock (_lock) { session.CurrentLayer = currentLayer; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; } _logger.LogDebug("会话进度更新成功,会话ID: {SessionId}, 当前层: {CurrentLayer}", sessionId, currentLayer); return Result.Success("会话进度更新成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "更新会话进度失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "UPDATE_SESSION_PROGRESS_FAILED", "更新会话进度失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public Result CompleteSession(Guid sessionId, ProductionSessionResult result, string? ngReason = null, string? remark = null) { try { if (!_sessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在。"); } lock (_lock) { session.Result = result; session.Status = result == ProductionSessionResult.Ok ? ProductionSessionStatus.CompletedOk : ProductionSessionStatus.CompletedNg; session.EndedAtUtc = DateTime.UtcNow; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; if (result == ProductionSessionResult.Ng && !string.IsNullOrEmpty(ngReason)) { session.NgReason = ngReason; } if (!string.IsNullOrEmpty(remark)) { session.Remark = remark; } } _logger.LogInformation("会话完成,会话ID: {SessionId}, 结果: {Result}", sessionId, result); return Result.Success("会话完成。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "完成会话失败。TraceId: {TraceId}", traceId); var errorResult = Result.FromException(ex, "COMPLETE_SESSION_FAILED", "完成会话失败。", traceId); return Result.FailWithTrace(errorResult.Code, errorResult.Message, errorResult.TraceId ?? traceId, errorResult.Errors.ToArray()); } } /// public Result CancelSession(Guid sessionId, string? reason = null) { try { if (!_sessions.TryGetValue(sessionId, out var session)) { return Result.Fail("SESSION_NOT_FOUND", $"会话 {sessionId} 不存在。"); } lock (_lock) { session.Status = ProductionSessionStatus.Cancelled; session.EndedAtUtc = DateTime.UtcNow; session.UpdatedAtUtc = DateTime.UtcNow; session.UpdatedBy = "System"; if (!string.IsNullOrEmpty(reason)) { session.Remark = reason; } } _logger.LogInformation("会话取消,会话ID: {SessionId}, 原因: {Reason}", sessionId, reason); 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 Result GetSession(Guid sessionId) { try { _sessions.TryGetValue(sessionId, out var session); if (session == null) { _logger.LogWarning("会话不存在,会话ID: {SessionId}", sessionId); return Result.Success(null, message: "会话不存在。"); } _logger.LogDebug("获取会话成功,会话ID: {SessionId}", sessionId); return Result.Success(session, message: "获取会话成功。"); } 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 Result> GetSessions(GetProductionSessionsRequest request) { try { _logger.LogInformation("正在获取会话列表,产品类型: {ProductType}, 工位: {Station}, 页码: {PageIndex}", request.ProductTypeCode, request.StationId, request.PageIndex); var query = _sessions.Values.AsQueryable(); // 应用筛选条件 if (!string.IsNullOrEmpty(request.ProductTypeCode)) { query = query.Where(s => s.ProductTypeCode.Equals(request.ProductTypeCode, StringComparison.OrdinalIgnoreCase)); } if (!string.IsNullOrEmpty(request.StationId)) { query = query.Where(s => s.StationId.Equals(request.StationId, StringComparison.OrdinalIgnoreCase)); } if (!string.IsNullOrEmpty(request.OperatorId)) { query = query.Where(s => s.OperatorId.Equals(request.OperatorId, StringComparison.OrdinalIgnoreCase)); } if (request.Status.HasValue) { query = query.Where(s => s.Status == request.Status.Value); } if (request.Result.HasValue) { query = query.Where(s => s.Result == request.Result.Value); } if (request.StartTimeUtc.HasValue) { query = query.Where(s => s.StartedAtUtc >= request.StartTimeUtc.Value); } if (request.EndTimeUtc.HasValue) { query = query.Where(s => s.EndedAtUtc <= request.EndTimeUtc.Value); } // 排序 query = query.OrderByDescending(s => s.StartedAtUtc); var totalCount = query.Count(); var items = query.Skip((request.PageIndex - 1) * request.PageSize).Take(request.PageSize).ToList(); var pagedResult = new PagedResult { Items = items, TotalCount = totalCount, PageIndex = request.PageIndex, PageSize = request.PageSize }; _logger.LogInformation("获取会话列表成功,总数: {TotalCount}, 当前页: {Count}", totalCount, items.Count); return Result>.Success(pagedResult, message: "获取会话列表成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取会话列表失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_SESSIONS_FAILED", "获取会话列表失败。", traceId); return Result>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public Result GetCurrentSession(string stationId) { try { var currentSession = _sessions.Values .FirstOrDefault(s => s.StationId.Equals(stationId, StringComparison.OrdinalIgnoreCase) && s.Status == ProductionSessionStatus.InProgress); _logger.LogDebug("获取当前会话完成,工位: {StationId}, 会话ID: {SessionId}", stationId, currentSession?.SessionId); return Result.Success(currentSession, message: "获取当前会话成功。"); } 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 Result GetStatistics(GetProductionSessionStatisticsRequest request) { try { _logger.LogInformation("正在获取会话统计信息,时间范围: {StartTime} - {EndTime}", request.StartTimeUtc, request.EndTimeUtc); var query = _sessions.Values.Where(s => s.StartedAtUtc >= request.StartTimeUtc && s.StartedAtUtc <= request.EndTimeUtc); // 应用筛选条件 if (!string.IsNullOrEmpty(request.ProductTypeCode)) { query = query.Where(s => s.ProductTypeCode.Equals(request.ProductTypeCode, StringComparison.OrdinalIgnoreCase)); } if (!string.IsNullOrEmpty(request.StationId)) { query = query.Where(s => s.StationId.Equals(request.StationId, StringComparison.OrdinalIgnoreCase)); } if (!string.IsNullOrEmpty(request.OperatorId)) { query = query.Where(s => s.OperatorId.Equals(request.OperatorId, StringComparison.OrdinalIgnoreCase)); } var sessions = query.ToList(); var statistics = CalculateStatistics(sessions, request.StartTimeUtc, request.EndTimeUtc); _logger.LogInformation("获取会话统计信息成功,总会话数: {TotalSessions}, 合格率: {PassRate:P1}", statistics.TotalSessions, statistics.PassRate); return Result.Success(statistics, message: "获取会话统计信息成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取会话统计信息失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_STATISTICS_FAILED", "获取会话统计信息失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public Result ExportSessions(ExportProductionSessionsRequest request) { try { _logger.LogInformation("正在导出会话记录,格式: {Format}", request.Format); // 获取要导出的会话 var getSessionsRequest = new GetProductionSessionsRequest { 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 sessionsResult = GetSessions(getSessionsRequest); if (!sessionsResult.Succeeded || sessionsResult.Data == null) { return Result.Fail("EXPORT_FAILED", "获取要导出的会话失败。"); } var sessions = sessionsResult.Data.Items.ToList(); var exportData = GenerateExportData(sessions, request.Format); _logger.LogInformation("导出会话记录成功,记录数: {Count}, 格式: {Format}", sessions.Count, request.Format); return Result.Success(exportData, message: "导出会话记录成功。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "导出会话记录失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "EXPORT_SESSIONS_FAILED", "导出会话记录失败。", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } private ProductionSessionStatistics CalculateStatistics(List sessions, DateTime startTime, DateTime endTime) { var statistics = new ProductionSessionStatistics { TotalSessions = sessions.Count, OkSessions = sessions.Count(s => s.Result == ProductionSessionResult.Ok), NgSessions = sessions.Count(s => s.Result == ProductionSessionResult.Ng), InProgressSessions = sessions.Count(s => s.Status == ProductionSessionStatus.InProgress), CancelledSessions = sessions.Count(s => s.Status == ProductionSessionStatus.Cancelled), PausedSessions = sessions.Count(s => s.Status == ProductionSessionStatus.Paused) }; statistics.PassRate = statistics.TotalSessions > 0 ? (double)statistics.OkSessions / statistics.TotalSessions : 0; // 计算平均处理时间 var completedSessions = sessions.Where(s => s.EndedAtUtc.HasValue).ToList(); if (completedSessions.Any()) { statistics.AverageProcessingTimeSeconds = completedSessions .Average(s => (s.EndedAtUtc!.Value - s.StartedAtUtc).TotalSeconds); } // 计算每小时处理量 var totalHours = (endTime - startTime).TotalHours; if (totalHours > 0) { statistics.ThroughputPerHour = statistics.TotalSessions / totalHours; } // 按产品类型分组统计 statistics.ByProductType = sessions .GroupBy(s => new { s.ProductTypeCode, s.ProductTypeName }) .Select(g => new ProductTypeStatistics { ProductTypeCode = g.Key.ProductTypeCode, ProductTypeName = g.Key.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() : 0 }) .ToList(); // 按工位分组统计 statistics.ByStation = sessions .GroupBy(s => new { s.StationId, s.StationName }) .Select(g => new StationStatistics { StationId = g.Key.StationId, StationName = g.Key.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() : 0 }) .ToList(); // 按操作员分组统计 statistics.ByOperator = sessions .GroupBy(s => new { s.OperatorId, s.OperatorName }) .Select(g => new OperatorStatistics { OperatorId = g.Key.OperatorId, OperatorName = g.Key.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() : 0 }) .ToList(); // 按日期分组统计 statistics.ByDate = 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() : 0 }) .OrderBy(d => d.DateUtc) .ToList(); return statistics; } private byte[] GenerateExportData(List sessions, ExportFormat format) { return format switch { ExportFormat.Csv => GenerateCsvExport(sessions), ExportFormat.Json => GenerateJsonExport(sessions), ExportFormat.Excel => GenerateExcelExport(sessions), _ => GenerateCsvExport(sessions) }; } private byte[] GenerateCsvExport(List sessions) { using var ms = new System.IO.MemoryStream(); using var writer = new System.IO.StreamWriter(ms, System.Text.Encoding.UTF8); // 写入CSV头部 writer.WriteLine("会话ID,产品类型,工位,操作员,开始时间,结束时间,状态,结果,当前层,总层数,NG原因,备注"); // 写入数据行 foreach (var session in sessions) { writer.WriteLine($"{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},{session.CurrentLayer},{session.TotalLayers}," + $"{session.NgReason ?? ""},{session.Remark ?? ""}"); } writer.Flush(); return ms.ToArray(); } private byte[] GenerateJsonExport(List sessions) { var json = System.Text.Json.JsonSerializer.Serialize(sessions, new System.Text.Json.JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); return System.Text.Encoding.UTF8.GetBytes(json); } private byte[] GenerateExcelExport(List sessions) { // 简化的Excel导出实现,实际项目中可以使用EPPlus等库 // 这里暂时返回CSV格式 return GenerateCsvExport(sessions); } private void InitializeSampleData() { _logger.LogInformation("正在初始化示例产品会话数据"); var sampleSessions = new List { new() { SessionId = Guid.NewGuid(), ProductTypeCode = "VFD-001", ProductTypeName = "变频器V1.0", StationId = "ST-001", StationName = "装配工位1", OperatorId = "OP-001", OperatorName = "张三", ShiftId = "SH-001", ShiftName = "白班", StartedAtUtc = DateTime.UtcNow.AddHours(-2), EndedAtUtc = DateTime.UtcNow.AddHours(-1.8), Status = ProductionSessionStatus.CompletedOk, Result = ProductionSessionResult.Ok, CurrentLayer = 3, TotalLayers = 3, CreatedAtUtc = DateTime.UtcNow.AddHours(-2), UpdatedAtUtc = DateTime.UtcNow.AddHours(-1.8), CreatedBy = "System", UpdatedBy = "System" }, new() { SessionId = Guid.NewGuid(), ProductTypeCode = "VFD-001", ProductTypeName = "变频器V1.0", StationId = "ST-001", StationName = "装配工位1", OperatorId = "OP-002", OperatorName = "李四", ShiftId = "SH-001", ShiftName = "白班", StartedAtUtc = DateTime.UtcNow.AddHours(-1.5), EndedAtUtc = DateTime.UtcNow.AddHours(-1.2), Status = ProductionSessionStatus.CompletedNg, Result = ProductionSessionResult.Ng, CurrentLayer = 2, TotalLayers = 3, NgReason = "第2层检测到缺陷", CreatedAtUtc = DateTime.UtcNow.AddHours(-1.5), UpdatedAtUtc = DateTime.UtcNow.AddHours(-1.2), CreatedBy = "System", UpdatedBy = "System" }, new() { SessionId = Guid.NewGuid(), ProductTypeCode = "VFD-002", ProductTypeName = "变频器V2.0", StationId = "ST-002", StationName = "装配工位2", OperatorId = "OP-003", OperatorName = "王五", ShiftId = "SH-002", ShiftName = "夜班", StartedAtUtc = DateTime.UtcNow.AddMinutes(-30), Status = ProductionSessionStatus.InProgress, Result = ProductionSessionResult.Pending, CurrentLayer = 1, TotalLayers = 4, CreatedAtUtc = DateTime.UtcNow.AddMinutes(-30), UpdatedAtUtc = DateTime.UtcNow.AddMinutes(-30), CreatedBy = "System", UpdatedBy = "System" } }; foreach (var session in sampleSessions) { _sessions.TryAdd(session.SessionId, session); } _logger.LogInformation("示例产品会话数据初始化完成,共 {Count} 条记录", sampleSessions.Count); } }