fix: 修复并发安全问题导致的数据库崩溃和数据错乱
问题根因:
FileSortTimer 每15秒触发 Task.Run 执行 ProcessFiles(),当P盘文件量大时
上一轮未完成新一轮又启动,多线程并发操作同一数据库连接和全局变量,导致:
1. SQLHelper 静态 SqlConnection/SqlCommand/SqlDataReader 被多线程覆盖,连接状态异常
2. ConfigDfn.strEquipNo 等全局静态变量被并发覆盖,事件触发时使用错误车号
3. 定时器无重入保护,多个 ProcessFiles() 实例并发执行
4. HasBothSidesMeasureResult 查询返回空表时直接访问列导致崩溃
修复内容:
[P0] SQLHelper.cs - 线程安全改造
- 删除静态 conn/cmd/sdr 字段和 GetConn() 方法
- 所有方法(ExecuteQuery/ExecuteNonQuery/ExecuteDs/BulkCopy)改为 using 独立连接模式
- 每次调用创建独立 SqlConnection,彻底消除连接竞争
[P0] FormMain.cs - 防止定时器重入
- 新增 _isProcessing 原子标志位(Interlocked.CompareExchange)
- FileSortTimer_Tick 检测到上一轮仍在执行时跳过本次触发
- finally 块确保标志位一定被释放
[P1] FileSorter.cs - 消除全局变量竞争
- 新增 CsvParseResult 类封装解析结果(CarID/CarModel/Position/MeasureTime)
- ImportCsv2Sql 改为返回 CsvParseResult,不再写入 ConfigDfn 全局变量
- GenerateSingleSideStatistics 改为参数传入(carID/carModel/groupName/position/measureTime)
- GenCustomerReport/GenerateCsvReport 改为参数传入 carModel
- OnFileParsed 事件改为三参数签名(carID, position, measureTime)
[P1] FormMain.cs - UI事件适配
- FileSorter_OnFileParsed 适配新的三参数签名
- DisplayMeasureData 接收 measureTime 参数,不再从全局变量读取
[P2] CjlrDAL.cs - 防御性检查
- HasBothSidesMeasureResult 增加 carId 空值检查
- 增加 dt.Columns.Contains("HasBothSides") 检查,避免空表异常
[P2] FileSorter.cs - 增强日志
- ProcessFiles() 开头打印查询到的任务总数,便于现场确认任务是否全部加载
影响文件:
- Analysis/DAL/SQLHelper.cs
- Analysis/CjlrForm/FileSorter.cs
- Analysis/FormMain.cs
- Analysis/DAL/CjlrDAL.cs
This commit is contained in:
@@ -10,6 +10,19 @@ using System.Text;
|
||||
|
||||
namespace NSAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// CSV解析结果,替代全局静态变量 ConfigDfn.strEquipNo 等,消除多线程竞争
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
public CjlrDAL _dal = new CjlrDAL();
|
||||
@@ -21,6 +34,650 @@ namespace NSAnalysis
|
||||
|
||||
public event Action<string> OnLog; // 日志事件
|
||||
|
||||
public event Action<string, string, string> OnFileParsed; // 解析完成后通知:车号、位置、测量时间
|
||||
|
||||
// 关键流程节点日志事件
|
||||
public event Action<string> 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} 个分发任务,开始逐一执行。");
|
||||
|
||||
foreach (DataRow task in tasks.Rows)
|
||||
{
|
||||
string modelName = task["modelsName"].ToString();
|
||||
string modelCode = task["modelsCode"].ToString();
|
||||
string position = task["position"].ToString();
|
||||
string sourceDir = task["sourceFile"].ToString();
|
||||
string targetDir = task["targetFile"].ToString();
|
||||
|
||||
// 打印信息
|
||||
Trace($"[ProcessFiles] 正在执行分发任务 - 源路径: {sourceDir}, 目标路径: {targetDir}, 匹配字符: {modelCode} 位置:{position}");
|
||||
if (Directory.Exists(sourceDir))
|
||||
{
|
||||
ProcessDirectory(sourceDir, targetDir, modelCode, modelName, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace($"[ProcessFiles] 源文件地址不存在或错误: {sourceDir}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取任务记录
|
||||
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, string targetDir, string modelCode, string modelName, string position)
|
||||
{
|
||||
// 匹配信息
|
||||
string matchStr = $"{modelCode}_{position}";
|
||||
Trace($"匹配文件特征符: {matchStr}");
|
||||
|
||||
// 确保目标目录存在
|
||||
if (!Directory.Exists(targetDir))
|
||||
{
|
||||
Directory.CreateDirectory(targetDir);
|
||||
Trace($"创建目标文件夹: {targetDir}");
|
||||
}
|
||||
|
||||
// 遍历源目录中的所有CSV文件
|
||||
foreach (string file in Directory.GetFiles(sourceDir, "*.csv"))
|
||||
{
|
||||
// 打印正在处理的文件
|
||||
Trace($"正在处理文件 : {file}");
|
||||
|
||||
// 解析入库,返回局部结果,不再写全局变量
|
||||
CsvParseResult parseResult = AnalysisNxsCSV(file);
|
||||
|
||||
// 如果未启用分发功能,直接返回
|
||||
if (!ConfigDfn.iEnableSort)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#region 分发逻辑
|
||||
|
||||
if (MatchCsvValue(file, matchStr, readRowIndex, readColIndex))
|
||||
{
|
||||
// 记录日志,匹配到
|
||||
Trace($"匹配成功,准备移动文件: {file} -> {targetDir}");
|
||||
|
||||
string destFile = Path.Combine(targetDir, Path.GetFileName(file));
|
||||
if (File.Exists(destFile))
|
||||
{
|
||||
string backupFile = destFile + ".bak_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
File.Move(destFile, backupFile);
|
||||
Trace($"目标文件已存在,已重命名为备份文件: {backupFile}");
|
||||
}
|
||||
File.Move(file, destFile);
|
||||
Trace($"移动完成,: {file} -> {destFile}");
|
||||
emitProcessStep($"---> 5、文件移动完成: -> {destFile}");
|
||||
|
||||
//插入分发详情
|
||||
CjlrTaskReleaseDetailModel detailModel = new CjlrTaskReleaseDetailModel
|
||||
{
|
||||
ModelsName = modelName,
|
||||
ModelsCode = modelCode,
|
||||
Position = position,
|
||||
SourceFile = file,
|
||||
TargetFile = destFile,
|
||||
TaskFileName = Path.GetFileName(file),
|
||||
TaskStatus = 1,
|
||||
TaskDetail = "文件移动成功",
|
||||
CreateDate = DateTime.Now
|
||||
};
|
||||
_dal.InsertTaskDetail(detailModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace($"未匹配到文件: {file}");
|
||||
emitProcessStep($"---> 5、未匹配到文件: {file}");
|
||||
|
||||
//记录到数据库
|
||||
CjlrTaskReleaseDetailModel detailModel = new CjlrTaskReleaseDetailModel
|
||||
{
|
||||
ModelsName = modelName,
|
||||
ModelsCode = modelCode,
|
||||
Position = position,
|
||||
SourceFile = file,
|
||||
TargetFile = "",
|
||||
TaskFileName = Path.GetFileName(file),
|
||||
TaskStatus = 2,
|
||||
TaskDetail = "文件未匹配",
|
||||
CreateDate = DateTime.Now
|
||||
};
|
||||
try
|
||||
{
|
||||
_dal.InsertTaskDetail(detailModel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace($"记录错误到数据库失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 分发逻辑
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查CSV文件中指定行列的字符串是否匹配目标值
|
||||
/// </summary>
|
||||
public static bool MatchCsvValue(string filePath, string targetValue, int rowIndex, int colIndex)
|
||||
{
|
||||
MyBase.TraceWriteLine($"[MatchCsvValue] 检查文件: {filePath}, 行索引: {rowIndex}, 列索引: {colIndex}, 目标值: {targetValue}");
|
||||
try
|
||||
{
|
||||
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;
|
||||
|
||||
return columns[colIndex].Trim().Contains(targetValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MyBase.TraceWriteLine($"处理CSV文件时出错: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成单侧统计信息的方法(使用局部参数,不再依赖全局变量)
|
||||
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<CJLR_MeaDataModel>();
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
public CjlrDAL _dal = new CjlrDAL();
|
||||
|
||||
//定义读取的位置
|
||||
public int readRowIndex = 2; // 默认读取第3行(从0开始计数)
|
||||
|
||||
public int readColIndex = 1; // 默认读取第2列(从0开始计数)
|
||||
|
||||
public event Action<string> OnLog; // 日志事件
|
||||
|
||||
public event Action<string, string> OnFileParsed; // 解析完成后通知文件名
|
||||
|
||||
// 关键流程节点日志事件
|
||||
|
||||
Reference in New Issue
Block a user