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
}