using BaseFunction; using NSAnalysis.DAL; using NSAnalysis.Model; using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.Text; namespace NSAnalysis { /// /// CSV解析结果,替代全局静态变量 ConfigDfn.strEquipNo 等,消除多线程竞争 /// public class CsvParseResult { public string CarID { get; set; } public string CarModel { get; set; } public string Position { get; set; } public string GroupName { get; set; } public string MeasureTime { get; set; } public bool Success { get; set; } } public class FileSorter { private sealed class TaskRule { public string ModelName { get; set; } public string ModelCode { get; set; } public string Position { get; set; } public string SourceDir { get; set; } public string TargetDir { get; set; } public string MatchToken { get; set; } } public CjlrDAL _dal = new CjlrDAL(); //定义读取的位置 public int readRowIndex = 2; // 默认读取第3行(从0开始计数) public int readColIndex = 1; // 默认读取第2列(从0开始计数) public event Action OnLog; // 日志事件 public event Action OnFileParsed; // 解析完成后通知:车号、位置、测量时间 // 关键流程节点日志事件 public event Action OnProcessStep; // 处理步骤日志事件 // 封装 OnProcessStep 事件 private void emitProcessStep(string message) { OnProcessStep?.Invoke(message); } public FileSorter() { } // 主逻辑处理 public void ProcessFiles() { var tasks = GetTaskRecords(); if (tasks == null || tasks.Rows.Count == 0) { Trace("没有找到任何任务记录,处理终止。"); return; } Trace($"[ProcessFiles] 共查询到 {tasks.Rows.Count} 个分发任务,开始逐一执行。"); var rulesBySource = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (DataRow task in tasks.Rows) { var rule = new TaskRule { ModelName = task["modelsName"].ToString(), ModelCode = task["modelsCode"].ToString(), Position = task["position"].ToString(), SourceDir = task["sourceFile"].ToString(), TargetDir = task["targetFile"].ToString() }; rule.MatchToken = NormalizeMatchToken($"{rule.ModelCode}_{rule.Position}"); if (!rulesBySource.ContainsKey(rule.SourceDir)) { rulesBySource[rule.SourceDir] = new List(); } rulesBySource[rule.SourceDir].Add(rule); } foreach (var item in rulesBySource) { string sourceDir = item.Key; List rules = item.Value; Trace($"[ProcessFiles] 开始扫描源路径: {sourceDir},共 {rules.Count} 条匹配规则。"); foreach (var rule in rules) { Trace($"[ProcessFiles] 已加载分发任务 - 源路径: {rule.SourceDir}, 目标路径: {rule.TargetDir}, 匹配字符: {rule.ModelCode} 位置:{rule.Position}, 标准化特征符: {rule.MatchToken}"); } if (!Directory.Exists(sourceDir)) { Trace($"[ProcessFiles] 源文件地址不存在或错误: {sourceDir}"); continue; } ProcessDirectory(sourceDir, rules); } } // 获取任务记录 private DataTable GetTaskRecords() { DataTable dt = _dal.SelectTaskByCondition("", "", "start"); if (dt == null || dt.Rows.Count == 0) { Trace("未发现移动任务."); return null; } return dt; } // 处理目录中的文件 private void ProcessDirectory(string sourceDir, List rules) { foreach (string file in Directory.GetFiles(sourceDir, "*.csv")) { Trace($"正在处理文件 : {file}"); if (!ConfigDfn.iEnableSort) { return; } if (_dal.IsFileProcessed(file)) { Trace($"文件已处理过,跳过后续匹配和解析: {file}"); emitProcessStep($"---> 2、文件已处理过,跳过后续匹配和解析: {file}"); continue; } string rawMatchValue; string normalizedMatchValue; if (!TryReadMatchToken(file, readRowIndex, readColIndex, out rawMatchValue, out normalizedMatchValue)) { Trace($"无法读取匹配特征,跳过文件: {file}"); emitProcessStep($"---> 2、无法读取匹配特征,跳过文件: {file}"); continue; } TaskRule matchedRule = null; foreach (var rule in rules) { if (string.Equals(rule.MatchToken, normalizedMatchValue, StringComparison.OrdinalIgnoreCase)) { matchedRule = rule; break; } } if (matchedRule == null) { Trace($"未匹配到文件: {file},原始特征符: {rawMatchValue},标准化后: {normalizedMatchValue}"); emitProcessStep($"---> 5、未匹配到文件: {Path.GetFileName(file)},特征符: {normalizedMatchValue}"); continue; } EnsureTargetDirectory(matchedRule.TargetDir); Trace($"匹配成功,准备移动文件: {file} -> {matchedRule.TargetDir},原始特征符: {rawMatchValue},标准化后: {normalizedMatchValue}"); string destFile = MoveMatchedFile(file, matchedRule.TargetDir); Trace($"移动完成,: {file} -> {destFile}"); emitProcessStep($"---> 5、文件移动完成: -> {destFile}"); InsertTaskDetail(matchedRule, file, destFile, 1, "文件移动成功"); AnalysisNxsCSV(destFile); } } /// /// 检查CSV文件中指定行列的字符串是否匹配目标值 /// public static bool MatchCsvValue(string filePath, string targetValue, int rowIndex, int colIndex) { MyBase.TraceWriteLine($"[MatchCsvValue] 检查文件: {filePath}, 行索引: {rowIndex}, 列索引: {colIndex}, 目标值: {targetValue}"); try { string rawValue; if (!TryReadCsvCellValue(filePath, rowIndex, colIndex, out rawValue)) return false; string normalizedSource = NormalizeMatchToken(rawValue); string normalizedTarget = NormalizeMatchToken(targetValue); MyBase.TraceWriteLine($"[MatchCsvValue] 原始值: {rawValue}, 标准化原始值: {normalizedSource}, 标准化目标值: {normalizedTarget}"); return string.Equals(normalizedSource, normalizedTarget, StringComparison.OrdinalIgnoreCase); } catch (Exception ex) { MyBase.TraceWriteLine($"处理CSV文件时出错: {ex.Message}"); return false; } } private static bool TryReadMatchToken(string filePath, int rowIndex, int colIndex, out string rawValue, out string normalizedValue) { rawValue = string.Empty; normalizedValue = string.Empty; if (!TryReadCsvCellValue(filePath, rowIndex, colIndex, out rawValue)) { return false; } normalizedValue = NormalizeMatchToken(rawValue); MyBase.TraceWriteLine($"[MatchCsvValue] 检查文件: {filePath}, 行索引: {rowIndex}, 列索引: {colIndex}, 原始值: {rawValue}, 标准化值: {normalizedValue}"); return true; } private static bool TryReadCsvCellValue(string filePath, int rowIndex, int colIndex, out string cellValue) { cellValue = string.Empty; string[] lines = File.ReadAllLines(filePath); if (rowIndex < 0 || rowIndex >= lines.Length) { return false; } string[] columns = lines[rowIndex].Split(','); if (colIndex < 0 || colIndex >= columns.Length) { return false; } cellValue = columns[colIndex].Trim().Trim('"'); return !string.IsNullOrEmpty(cellValue); } private static string NormalizeMatchToken(string rawValue) { if (string.IsNullOrWhiteSpace(rawValue)) { return string.Empty; } string cleaned = rawValue.Trim().Trim('"'); string[] parts = cleaned.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) { return cleaned.ToUpperInvariant(); } if (parts.Length >= 3 && IsSideToken(parts[parts.Length - 1])) { string prefix = string.Join("_", parts, 0, parts.Length - 1).ToUpperInvariant(); return $"{prefix}_{char.ToUpperInvariant(parts[parts.Length - 1][0])}"; } if (parts.Length >= 2 && IsSideToken(parts[1])) { return $"{parts[0].ToUpperInvariant()}_{char.ToUpperInvariant(parts[1][0])}"; } return cleaned.ToUpperInvariant(); } private static bool IsSideToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return false; } char side = char.ToUpperInvariant(token.Trim()[0]); return side == 'L' || side == 'R'; } private static void EnsureTargetDirectory(string targetDir) { if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); MyBase.TraceWriteLine($"创建目标文件夹: {targetDir}"); } } private static string MoveMatchedFile(string sourceFile, string targetDir) { string destFile = Path.Combine(targetDir, Path.GetFileName(sourceFile)); if (File.Exists(destFile)) { string backupFile = destFile + ".bak_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); File.Move(destFile, backupFile); MyBase.TraceWriteLine($"目标文件已存在,已重命名为备份文件: {backupFile}"); } File.Move(sourceFile, destFile); return destFile; } private void InsertTaskDetail(TaskRule rule, string sourceFile, string targetFile, int taskStatus, string taskDetail) { var detailModel = new CjlrTaskReleaseDetailModel { ModelsName = rule.ModelName, ModelsCode = rule.ModelCode, Position = rule.Position, SourceFile = sourceFile, TargetFile = targetFile, TaskFileName = Path.GetFileName(sourceFile), TaskStatus = taskStatus, TaskDetail = taskDetail, CreateDate = DateTime.Now }; _dal.InsertTaskDetail(detailModel); } // 生成单侧统计信息的方法(使用局部参数,不再依赖全局变量) private void GenerateSingleSideStatistics(string carID, string carModel, string groupName, string position, string measureTime) { #region 统计信息显示 DataTable sampleData = _dal.SelectMeasureResultByCarID(carID, groupName); if (sampleData == null || sampleData.Rows.Count == 0) { MyBase.TraceWriteLine("没有测量数据,无法生成统计信息。"); emitProcessStep(position + " 没有测量数据,无法生成统计信息。"); return; } AnalysisResult analysis = AnalysisResult.AnalyzeMeasureData(sampleData); int dtRowCount = analysis.TotalCount; double OutCount = analysis.OutCount; double OKCount = analysis.OKCount; double RejectedCount = analysis.RejectedCount; double FPYPercent = analysis.FPYPercent; TMeasureResultModel tmrm = new TMeasureResultModel(); FPYPercent = OKCount / (OKCount + OutCount); if (FPYPercent >= ConfigDfn.dFPY) { tmrm.Result = 1; } else if (FPYPercent >= ConfigDfn.dFPY2 && FPYPercent < ConfigDfn.dFPY) { tmrm.Result = 1; } else { tmrm.Result = 2; } tmrm.CarID = carID; tmrm.CarType = carModel; tmrm.SumMeasureItems = dtRowCount; tmrm.GoodMeasureItems = (int)OKCount; tmrm.NoGoodMeasureItems = (int)OutCount; tmrm.RejectMeasureItems = (int)RejectedCount; tmrm.FPY = FPYPercent.ToString("F4"); tmrm.Remark = position; tmrm.MeasureDate = measureTime; _dal.InsertTMeasureResult(tmrm); MyBase.TraceWriteLine("将总结果插入数据库完毕。"); emitProcessStep($"---> 3、统计信息已生成: 位置: {position}, 总测量项: {dtRowCount}, 合格项: {OKCount}, 不合格项: {OutCount}, 异常项: {RejectedCount}, FPY: {FPYPercent:F4}"); #endregion 统计信息显示 } // 导入CSV文件到数据库,返回解析结果(不再写全局变量) public CsvParseResult ImportCsv2Sql(string filePath) { var result = new CsvParseResult { Success = false }; var records = new List(); var lineNo = 0; var groupName = string.Empty; var position = string.Empty; emitProcessStep($"正在处理: {filePath}"); // 判断文件是否已经处理过 if (_dal.IsFileProcessed(filePath)) { MyBase.TraceWriteLine($"文件已处理过,跳过: {filePath}"); emitProcessStep($"---> 2、文件已处理过,跳过: {filePath}"); return result; } // 备份原始文件 try { string backupDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Backup"); if (!Directory.Exists(backupDir)) { Directory.CreateDirectory(backupDir); } string backupFilePath = Path.Combine(backupDir, Path.GetFileName(filePath)); File.Copy(filePath, backupFilePath, true); MyBase.TraceWriteLine("已备份文件到: " + backupFilePath); } catch (Exception ex) { MyBase.TraceWriteLine("备份文件时发生错误:" + ex.Message); } try { using (var reader = new StreamReader(filePath)) { reader.ReadLine(); // Skip header lineNo = 1; while (!reader.EndOfStream) { lineNo++; var line = reader.ReadLine(); var values = line.Split(','); if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#") || line.StartsWith("//") || line.StartsWith("MeasPoint.Name")) { continue; } if (values.Length < 22) { MyBase.TraceWriteLine("CSV行数据不完整,跳过该行:" + line); continue; } if (string.IsNullOrEmpty(values[2])) { MyBase.TraceWriteLine($"第 {lineNo} 行数据为0,跳过该行:" + line); continue; } var record = new CJLR_MeaDataModel { PointName = values[0], GroupName = values[1], ProductNum = values[2], Model = values[3], Station = values[4], Method = values[5], Standard = values[6], DimensionName = values[7], DimensionValue = values[8], DimensionUnit = values[9], IsManual = bool.Parse(values[10]), Classification = values[11], ToleranceName0 = values[12], ToleranceLower0 = values[13], ToleranceUpper0 = values[14], ToleranceName1 = values[15], ToleranceLower1 = values[16], ToleranceUpper1 = values[17], NominalValue = values[18], MeasureDate = DateTime.ParseExact(values[19], "yyyyMMdd", CultureInfo.InvariantCulture), MeasureTime = TimeSpan.ParseExact(values[20], "hhmmss", CultureInfo.InvariantCulture), SequenceNum = int.Parse(values[21]) }; records.Add(record); } } } catch (Exception ex) { MyBase.TraceWriteLine("导入CSV文件时发生错误:" + ex.Message); emitProcessStep($"---> 2、导入CSV文件时发生错误: {ex.Message}"); string errorDir = Path.Combine(Path.GetDirectoryName(filePath), "Error"); if (!Directory.Exists(errorDir)) { Directory.CreateDirectory(errorDir); } string errorFilePath = Path.Combine(errorDir, Path.GetFileName(filePath)); File.Move(filePath, errorFilePath); MyBase.TraceWriteLine("已将文件移动到错误文件夹: " + errorFilePath); return result; } try { if (records.Count > 0) { var firstRecord = records[0]; // 所有解析结果存入局部变量,不写全局 ConfigDfn result.MeasureTime = firstRecord.MeasureDate.ToString("yyyy-MM-dd") + " " + firstRecord.MeasureTime.ToString(@"hh\:mm\:ss"); result.CarID = firstRecord.ProductNum; result.CarModel = firstRecord.Model; groupName = firstRecord.GroupName; result.GroupName = groupName; MyBase.TraceWriteLine("测量时间:" + result.MeasureTime); MyBase.TraceWriteLine("--------------------------------------------------------"); MyBase.TraceWriteLine("车号:" + result.CarID); MyBase.TraceWriteLine("车型:" + result.CarModel); MyBase.TraceWriteLine("组名:" + groupName); if (string.IsNullOrEmpty(firstRecord.GroupName)) { MyBase.TraceWriteLine("组名为空,使用默认位置。"); result.Position = "Default"; } else { var parts2 = firstRecord.GroupName.Split('_'); if (parts2.Length > 1 && !string.IsNullOrEmpty(parts2[1])) { result.Position = parts2[1].Substring(0, 1); } else { result.Position = firstRecord.GroupName; } } MyBase.TraceWriteLine("位置:" + result.Position); MyBase.TraceWriteLine("测量时间:" + result.MeasureTime); emitProcessStep($"---> 1、解析到, 车号: {result.CarID} 车型: {result.CarModel} 位置: {result.Position} 测量时间: {result.MeasureTime}"); } else { MyBase.TraceWriteLine("没有找到有效的测量记录,无法设置测量时间。CSV文件导入到数据库失败!"); emitProcessStep($"---> 2、没有找到有效的测量记录,无法设置测量时间。CSV文件导入到数据库失败!"); string errorDir = Path.Combine(Path.GetDirectoryName(filePath), "Error"); if (!Directory.Exists(errorDir)) { Directory.CreateDirectory(errorDir); } string errorFilePath = Path.Combine(errorDir, Path.GetFileName(filePath)); File.Move(filePath, errorFilePath); MyBase.TraceWriteLine("已将文件移动到错误文件夹: " + errorFilePath); return result; } // 逐条插入数据到数据库 foreach (var record in records) { _dal.InsertOrUpdateCJLRMeaData(record); } // 记录处理文件到数据库 _dal.InsertProcessedFile(filePath); MyBase.TraceWriteLine("CSV文件导入到数据库成功!"); emitProcessStep($"---> 2、CSV文件导入到数据库成功"); result.Success = true; #region 处理单侧统计信息 GenerateSingleSideStatistics(result.CarID, result.CarModel, groupName, result.Position, result.MeasureTime); #endregion 处理单侧统计信息 MyBase.TraceWriteLine("--------------------------------------------------------"); } catch (Exception ex) { MyBase.TraceWriteLine("导入CSV文件时发生错误:" + ex.Message); emitProcessStep($"---> 2、导入CSV文件时发生错误: {ex.Message}"); } return result; } // 解析CSV文件,返回解析结果(不再依赖/写入全局变量) public CsvParseResult AnalysisNxsCSV(string strCSVName) { var emptyResult = new CsvParseResult { Success = false }; if (!File.Exists(strCSVName)) { MyBase.TraceWriteLine("文件不存在:" + strCSVName); return emptyResult; } try { // 解析 CSV 文件并导入到数据库,获取局部结果 CsvParseResult parseResult = ImportCsv2Sql(strCSVName); if (!parseResult.Success || string.IsNullOrEmpty(parseResult.CarID)) { return parseResult; } // 解析完成后触发事件,传递局部结果(不依赖全局变量) Trace($"触发事件,车号: {parseResult.CarID} 位置: {parseResult.Position}"); OnFileParsed?.Invoke(parseResult.CarID, parseResult.Position, parseResult.MeasureTime); // 检查是否有双侧测量结果(防御性检查) bool isMeasureComplete = _dal.HasBothSidesMeasureResult(parseResult.CarID); if (isMeasureComplete) { MyBase.TraceWriteLine("双侧测量结果已完成,开始生成客户报告。"); emitProcessStep($"---> 4、双侧测量结果已完成,开始生成客户报告,车号: {parseResult.CarID}"); if (parseResult.CarModel != null && parseResult.CarModel.Contains("MY")) { MyBase.TraceWriteLine("车型为 551_MY21,跳过客户报告生成。"); emitProcessStep($"---> 4、车型为 551_MY21,跳过客户报告生成,车号: {parseResult.CarID}"); return parseResult; } GenCustomerReport(parseResult.CarID, parseResult.CarModel); } else { MyBase.TraceWriteLine("双侧测量结果未完成,跳过客户报告生成。"); emitProcessStep($"---> 4、双侧测量结果未完成,跳过客户报告生成,车号: {parseResult.CarID}"); } return parseResult; } catch (Exception ex) { MyBase.TraceWriteLine("解析 CSV 文件时发生错误:" + ex.Message); return emptyResult; } } // 生成客户报告函数(使用参数传入,不依赖全局变量) private void GenCustomerReport(string strCarID, string carModel) { DataTable dtCSVContent = _dal.SelectPointDimensionByCarID(strCarID); if (dtCSVContent == null || dtCSVContent.Rows.Count == 0) { MyBase.TraceWriteLine("没有找到测量数据,无法生成客户报告。"); return; } string fileName = strCarID + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".csv"; if (!Directory.Exists(ConfigDfn.strReportPath)) { Directory.CreateDirectory(ConfigDfn.strReportPath); MyBase.TraceWriteLine($"创建报告目录: {ConfigDfn.strReportPath}"); } string backupDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReportBackup"); if (!Directory.Exists(backupDir)) { Directory.CreateDirectory(backupDir); } string savePath = Path.Combine(backupDir, fileName); string target = Path.Combine(ConfigDfn.strReportPath, fileName); GenerateCsvReport(strCarID, carModel, dtCSVContent, ConfigDfn.strCSVReportTemplatePath, savePath); MyBase.TraceWriteLine($"客户报告已生成: {savePath}"); emitProcessStep($"---> 5、客户报告已生成: {savePath}"); try { File.Copy(savePath, target, true); MyBase.TraceWriteLine("已拷贝客户报告到目标路径: " + target); emitProcessStep($"---> 6、已拷贝客户报告到目标路径: {target}"); } catch { MyBase.TraceWriteLine("拷贝客户报告到目标路径失败: " + target); emitProcessStep($"---> 6、拷贝客户报告到目标路径失败: {target}"); } } // 生成CSV报告函数(carModel 作为参数传入,不依赖全局变量) private void GenerateCsvReport(string strCarID, string carModel, DataTable measureData, string templatePath, string savePath) { var templateLines = File.ReadAllLines(templatePath); StringBuilder sb = new StringBuilder(); foreach (var line in templateLines) { string processedLine = line; if (processedLine.Contains("{datetime}")) { processedLine = processedLine.Replace("{datetime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); } if (processedLine.Contains("{Part_ident}")) { processedLine = processedLine.Replace("{Part_ident}", strCarID); } if (processedLine.Contains("{model}")) { processedLine = processedLine.Replace("{model}", carModel); } if (processedLine.Contains("{Part_code}")) { if (carModel == "E03") processedLine = processedLine.Replace("{Part_code}", "E03_5000000FAonline"); else if (carModel == "E0Y") processedLine = processedLine.Replace("{Part_code}", "E0Y_5000000FAonline"); else processedLine = processedLine.Replace("{Part_code}", "UnknownModel_5000000FAonline"); } processedLine = processedLine.Trim('"'); Console.WriteLine($"处理行: {processedLine}"); sb.AppendLine(processedLine); } sb.AppendLine(); sb.AppendLine(); sb.AppendLine(); sb.AppendLine(); sb.AppendLine("Characteristic,Extension,Measured_Value"); foreach (DataRow row in measureData.Rows) { var dimensionValue = row["DimensionValue"]?.ToString(); if (dimensionValue != null && dimensionValue.Contains("1.#R")) { dimensionValue = ""; } sb.AppendFormat("{0},{1},{2}\n", row["PointName"], row["DimensionName"], dimensionValue); } File.WriteAllText(savePath, sb.ToString(), Encoding.UTF8); } // 日志记录方法 private void Trace(string msg) { OnLog?.Invoke(msg); MyBase.TraceWriteLine(msg); } // 打印 DataTable 对象的方法 public static void PrintDataTable(DataTable dt) { if (dt == null || dt.Rows.Count == 0) { Console.WriteLine("[PrintDataTable] DataTable is empty or null."); return; } foreach (DataColumn column in dt.Columns) { Console.WriteLine($"{column.ColumnName}\t"); } Console.WriteLine(""); foreach (DataRow row in dt.Rows) { foreach (var item in row.ItemArray) { Console.WriteLine($"{item}\t"); } Console.WriteLine(""); } } // 测试方法 public void test() { SQLHelper.connStr = DatabaseDfn.SqlConnectStr(); GenCustomerReport("K0902906", "E03"); } } }