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);
}
}