优化分发规则

This commit is contained in:
zhengxuan.zhang
2026-05-20 13:36:31 +08:00
parent 172a29f082
commit 4d5449f186
8 changed files with 571 additions and 1173 deletions
File diff suppressed because it is too large Load Diff
+31 -31
View File
@@ -14,40 +14,40 @@ using Telerik.WinControls.UI;
namespace NSAnalysis namespace NSAnalysis
{ {
/// <summary> /// <summary>
///
///
///
/// 2025.08.28
/// ----------------------------------------------
/// 1、当解析文件时,增加备份到根目录
/// 2、解析失败或提取有效内容失败,移动到源文件夹的Error文件夹
/// 3、修复查询结果,显示错误问题
///
/// ///
/// /// 2025.08.31
/// /// --------------------------------------------
/// 2025.08.28 /// 1、新增将客户报告 默认生成到备份文件夹,然后拷贝到目标路径,并记录错误日志
/// ---------------------------------------------- ///
/// 1、当解析文件时,增加备份到根目录 ///
/// 2、解析失败或提取有效内容失败,移动到源文件夹的Error文件夹 ///
/// 3、修复查询结果,显示错误问题 /// 2026.05.20
/// /// --------------------------------------------
/// 2025.08.31 /// 1、修复现场反馈的问题(数据库连接、任务加载情况、定时器重复的问题)
/// -------------------------------------------- ///
/// 1、新增将客户报告 默认生成到备份文件夹,然后拷贝到目标路径,并记录错误日志 ///
/// ///
/// ///
/// ///
/// ///
/// ///
/// ///
/// ///
/// /// </summary>
///
///
///
///
///
///
///
///
/// </summary>
public partial class FormMain : Telerik.WinControls.UI.ShapedForm public partial class FormMain : Telerik.WinControls.UI.ShapedForm
{ {
private Timer fileSortTimer = new Timer(); // 定时器,用于定时分发任务 private System.Windows.Forms.Timer fileSortTimer = new System.Windows.Forms.Timer(); // 定时器,用于定时分发任务
private FileSorter fileSorter = new FileSorter(); private FileSorter fileSorter = new FileSorter();
private int _isProcessing = 0; // 0=空闲, 1=处理中,防止定时器重入 private int _isProcessing = 0; // 0=空闲, 1=处理中,防止定时器重入
+1 -1
View File
@@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NSAnalysis</RootNamespace> <RootNamespace>NSAnalysis</RootNamespace>
<AssemblyName>NSAnalysis</AssemblyName> <AssemblyName>NSAnalysis</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
+9 -9
View File
@@ -1,9 +1,9 @@
2026-04-24 13:45:58.350----软件Program Main函数开始执行-- 2026-05-20 11:40:36.119----软件Program Main函数开始执行--
2026-04-24 13:45:58.352--加载配置文件——>开始 2026-05-20 11:40:36.121--加载配置文件——>开始
2026-04-24 13:45:58.364--数据库连接 SqlServerName127.0.0.1 2026-05-20 11:40:36.125--数据库连接 SqlServerName127.0.0.1
2026-04-24 13:45:58.364--数据库连接 SqlUserNamesa 2026-05-20 11:40:36.126--数据库连接 SqlUserNamesa
2026-04-24 13:45:58.364--数据库连接 SqlPasswordHexagon123 2026-05-20 11:40:36.126--数据库连接 SqlPasswordHexagon123
2026-04-24 13:45:58.364--数据库连接 SqlDbNameCJLR 2026-05-20 11:40:36.127--数据库连接 SqlDbNameCJLR
2026-04-24 13:45:58.366--加载配置文件——>完成 2026-05-20 11:40:36.128--加载配置文件——>完成
2026-04-24 13:45:58.367--Log in using the original authorization mode. 2026-05-20 11:40:36.130--Log in using the original authorization mode.
2026-04-24 13:46:03.487--软件授权失败 2026-05-20 11:40:41.124--软件授权失败
Binary file not shown.
Binary file not shown.
-332
View File
@@ -1,332 +0,0 @@
让我再看一下日志中关于P盘数据的匹配情况以及现场数据中P盘文件的实际内容
现在让我查看FormMain.cs中调用FileSorter的部分以及分发任务的配置
现在让我查看日志的最后部分看看最终状态
现在我已经收集了足够的信息来进行综合分析让我总结一下发现的问题
---
## 问题分析报告
根据日志代码和现场数据的综合分析发现以下**核心问题**
---
### 问题一分发任务配置不完整 只配置了L551_21MY的分发规则
**现象** 在整个63000行日志中`ProcessFiles` 分发定时器每15秒触发一次**只执行了一个分发任务**
```
源路径: P:\, 目标路径: K:\L551R 21MY, 匹配字符: L551_21MY 位置:R
```
**但根据你提供的配置截图数据库中配置了6个分发任务**L551RL551LE03LE03RE0YLE0YR而日志中只出现了第1条任务
**根因分析** 查看 `GetTaskRecords()` 方法它调用 `_dal.SelectTaskByCondition("", "", "start")` 查询状态为"start"的任务日志中只出现了L551R的分发任务说明
- 其他5个任务可能在数据库中的运行状态不是"启动"
- 或者第一个任务处理P盘大量文件时耗时过长P盘有数百个CSV文件导致后续任务在同一轮中未被执行到但代码逻辑是foreach遍历所有任务不应该跳过
**更可能的原因** 分发定时器在后台线程 `Task.Run` 中执行而解析入库逻辑 `AnalysisNxsCSV` 在遍历每个文件时都会执行当P盘有几百个文件时第一个任务L551R的处理时间极长15秒定时器再次触发时又启动了新的 `ProcessFiles()`导致**多个分发任务并发执行同一个P盘目录**产生竞争条件
---
### 问题二数据库连接崩溃 并发导致SQL连接池耗尽
**现象** 日志中出现大量数据库错误
- `ExecuteReader requires an open and available Connection. The connection's current state is connecting.`
- `Internal connection fatal error. Error state: 15, Token : 8`
- `Column 'Id' does not belong to table .`
- `Column 'HasBothSides' does not belong to table .`
- `Column 'CarID' does not belong to table .`
**根因**
1. `FileSortTimer_Tick` 每15秒在 `Task.Run` 中启动新的 `ProcessFiles()`
2. 同时 `CenterControl` 中的 `tmReadNextsenseCSV` 定时器500ms间隔也在调用 `AnalysisNextSenseSelfMeasureCSV()` 解析同一路径的CSV
3. **两套解析逻辑并发操作同一个P盘目录和同一个数据库**没有任何锁或互斥机制
4. SQL连接被多线程共享/竞争导致连接状态异常查询返回空DataTable后续代码访问不存在的列时抛出异常
---
### 问题三`ConfigDfn` 全局静态变量被并发覆盖
**现象** 日志中反复出现
```
触发事件,车号: T1558036 位置: R (重复几十次)
触发事件,车号: T1558437 位置: R (重复几十次)
```
**根因** `FileSorter.AnalysisNxsCSV` 中设置了全局变量
```csharp
ConfigDfn.strEquipNo = firstRecord.ProductNum;
ConfigDfn.strCarModel = firstRecord.Model;
ConfigDfn.strEquipPosition = ...
```
当多个文件并发解析时`ConfigDfn.strEquipNo` 被不同线程反复覆盖`OnFileParsed` 事件触发时使用的是被覆盖后的值导致
- 同一个车号被重复触发事件
- `DisplayMeasureData` 查询时找不到数据因为数据还没入库就被下一个文件覆盖了车号
- `HasBothSidesMeasureResult` 查询的是错误的车号
---
### 问题四文件未被分发 匹配逻辑与实际数据不匹配
**现象** P盘中的E03和E0Y文件全部"未匹配到"
**根因** 分发任务只配置了 `L551_21MY` 的匹配规则匹配特征符为 `L551_21MY_R`但P盘中的文件实际是
- E0Y车型MEASGROUPNAME = `E0Y_R20250929`包含日期后缀
- E03车型MEASGROUPNAME = `E03_R`
匹配逻辑 `MatchCsvValue` 检查CSV第3行第2列是否**包含** `L551_21MY_R`而实际值是 `E0Y_R20250929` `E03_R`自然不匹配
**结论** 现场数据库中E03和E0Y的分发任务虽然在配置界面中显示为"启动"但在日志中从未被执行到说明查询条件可能有问题或者这些任务在软件启动时的数据库状态不是"start"
---
### 问题五`ImportCsv2Sql` 失败后文件未被移走导致重复处理
**现象** 大量文件报 `Column 'Id' does not belong to table .` 错误后文件应该被移到Error目录但后续仍然在处理这些文件
**根因** 并发执行时两个线程同时处理同一个文件
1. 线程A调用 `IsFileProcessed` 返回false开始处理
2. 线程B也调用 `IsFileProcessed` 返回false也开始处理
3. 线程A处理完移动文件到Error
4. 线程B再次尝试处理时文件已不存在或者因为数据库连接异常导致 `InsertProcessedFile` 失败文件未被标记为已处理
---
## 总结根本原因
| 序号 | 问题 | 严重程度 |
|------|------|----------|
| 1 | **并发安全问题**`FileSortTimer`(Task.Run) `tmReadNextsenseCSV`(UI线程) 同时操作P盘和数据库无互斥 | 致命 |
| 2 | **全局变量竞争**`ConfigDfn.strEquipNo` 等静态变量被多线程覆盖 | 致命 |
| 3 | **分发任务未全部执行**日志中只看到L551R任务E03/E0Y任务从未执行 | 严重 |
| 4 | **数据库连接池崩溃**并发导致SQL连接异常查询返回空表 | 严重 |
| 5 | **文件重复处理**并发导致 `IsFileProcessed` 检查失效 | 中等 |
---
需要我针对这些问题提出修复方案吗
在制定方案之前我需要确认几个关键点
现在我已经完全掌握了问题的全貌以下是修复方案
---
## 修复方案
基于分析结果问题的根本原因是**并发安全****数据库连接管理**以下按优先级排列修复项
---
### 修复1致命SQLHelper 线程安全改造
**问题** `SQLHelper` 使用 `private static SqlConnection conn``private static SqlCommand cmd``private static SqlDataReader sdr` 三个静态字段多线程并发时互相覆盖导致连接状态异常
**方案** `ExecuteQuery` 和无参 `ExecuteNonQuery` 改为每次创建独立连接与带参数版本的 `ExecuteNonQuery` 保持一致它已经用了 `using (var conn = new SqlConnection(connStr))`
```csharp
// 改造后的 ExecuteQuery(无参数版)
public static DataTable ExecuteQuery(string cmdText, CommandType ct)
{
DataTable dt = new DataTable();
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var cmd = new SqlCommand(cmdText, conn))
{
cmd.CommandType = ct;
using (var sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
}
}
return dt;
}
// 改造后的 ExecuteQuery(带参数版)
public static DataTable ExecuteQuery(string cmdText, SqlParameter[] paras, CommandType ct)
{
DataTable dt = new DataTable();
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var cmd = new SqlCommand(cmdText, conn))
{
cmd.CommandType = ct;
cmd.Parameters.AddRange(paras);
using (var sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
}
}
return dt;
}
// 改造后的 ExecuteNonQuery(无参数版)
public static int ExecuteNonQuery(string cmdText, CommandType ct)
{
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var cmd = new SqlCommand(cmdText, conn))
{
cmd.CommandType = ct;
return cmd.ExecuteNonQuery();
}
}
}
```
**同时删除类级别的静态字段** `conn``cmd``sdr`以及 `GetConn()` 方法所有使用 `GetConn()` 的地方 `ExecuteDs``TWorkpieceListToSQLServer``SqlBulkCopyInsert`也需要同步改造
---
### 修复2致命FileSorter 消除全局变量竞争
**问题** `FileSorter.ImportCsv2Sql` 中直接写入 `ConfigDfn.strEquipNo``ConfigDfn.strCarModel``ConfigDfn.strEquipPosition``ConfigDfn.strMeasureTime`多线程并发时互相覆盖
**方案** 将解析结果封装为局部变量/返回对象不再写入全局静态变量
```csharp
// 新增解析结果模型
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; }
}
```
`ImportCsv2Sql` 改为返回 `CsvParseResult`内部不再写 `ConfigDfn``AnalysisNxsCSV` 使用返回值传递给后续逻辑只在需要更新UI时通过事件将结果传回UI线程由UI线程统一写入 `ConfigDfn`如果UI仍需要
---
### 修复3致命防止 FileSortTimer 并发重入
**问题** 定时器每15秒触发一次 `Task.Run(() => fileSorter.ProcessFiles())`如果上一轮未完成P盘几百个文件新一轮又启动导致多个线程同时遍历P盘
**方案** 加入重入保护
```csharp
private int _isProcessing = 0; // 0=空闲, 1=处理中
private void FileSortTimer_Tick(object sender, EventArgs e)
{
if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) != 0)
{
// 上一轮还在执行,跳过本次
return;
}
Task.Run(() =>
{
try
{
fileSorter.ProcessFiles();
}
catch (Exception ex)
{
MyBase.TraceWriteLine($"分发任务异常: {ex.Message}");
}
finally
{
Interlocked.Exchange(ref _isProcessing, 0);
}
});
}
```
---
### 修复4严重确认分发任务是否全部加载
**问题** 日志中只出现了 `L551R_C11` 一个分发任务其他5个任务L551LE03LE03RE0YLE0YR从未执行
**方案**
1. `ProcessFiles()` 开头增加日志打印查询到的任务总数和每个任务的详情
2. 检查 `SelectTaskByCondition("", "", "start")` 的SQL实现确认查询条件是否正确匹配所有"启动"状态的任务
3. 确认数据库中任务的 `taskStatus` 字段值是否确实为 "start"注意大小写
```csharp
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)
{
// ... 现有逻辑
}
}
```
---
### 修复5严重分离"解析入库""分发"的职责
**问题** 当前 `ProcessDirectory` 中对每个文件先调用 `AnalysisNxsCSV`解析入库+触发事件+检查双侧再执行分发匹配这导致
- 分发任务被解析入库逻辑严重拖慢
- 解析入库触发的 `OnFileParsed` 事件回调UI线程的 `DisplayMeasureData`而此时数据可能还没入库成功
**方案** 将解析入库和分发逻辑解耦为两个独立步骤
```
步骤1(解析入库):遍历P盘文件 → 解析CSV → 写入数据库 → 标记已处理
步骤2(分发):遍历P盘文件 → 匹配车型 → 移动文件到目标路径
```
或者更简单的方案**先完成所有文件的分发匹配和移动再触发解析入库**因为分发只需要读取CSV第3行第2列做匹配非常快而解析入库涉及数据库操作很慢
---
### 修复6中等`HasBothSidesMeasureResult` 异常处理
**问题** 当数据库连接异常时`ExecuteQuery` 返回空DataTable代码直接访问 `dt.Rows[0]["HasBothSides"]` 抛出 `Column 'HasBothSides' does not belong to table`
**方案** `HasBothSidesMeasureResult` 中增加防御性检查
```csharp
public bool HasBothSidesMeasureResult(string carId)
{
// ... SQL查询 ...
DataTable dt = SQLHelper.ExecuteQuery(sql, paras, CommandType.Text);
if (dt == null || dt.Rows.Count == 0 || !dt.Columns.Contains("HasBothSides"))
{
return false;
}
return Convert.ToInt32(dt.Rows[0]["HasBothSides"]) == 1;
}
```
---
### 实施优先级建议
| 优先级 | 修复项 | 预计影响范围 | 风险 |
|--------|--------|-------------|------|
| P0 | 修复1SQLHelper线程安全 | DAL层全局 | 改为标准using模式 |
| P0 | 修复3防止定时器重入 | FormMain | 加一个标志位 |
| P1 | 修复2消除全局变量竞争 | FileSorter + FormMain | 需要重构接口 |
| P1 | 修复4确认分发任务加载 | 需要现场数据库验证 | |
| P2 | 修复5解耦解析与分发 | FileSorter核心逻辑 | 架构调整 |
| P2 | 修复6防御性检查 | DAL层 | |
---
你希望我从哪个修复项开始实施还是需要我对某个方案做进一步细化
+335
View File
@@ -0,0 +1,335 @@
# 现场问题分析与修复说明
## 1. 项目功能理解
当前项目是一个面向车身测量场景的分析软件,核心职责可以拆成 4 个连续步骤:
1. 读取现场目录中的 NextSense CSV 文件
2. 解析 CSV 内容并写入数据库
3. 根据车型和左右侧规则进行分发移动
4. 当同一车号左右两侧数据齐全后,生成客户报告
代码中的主要入口与职责如下:
- `FormMain.FileSortTimer_Tick`
- 定时触发分发任务
- 文件位置:[FormMain.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/FormMain.cs:679)
- `FileSorter.ProcessFiles`
- 查询启用的分发任务
- 对源目录执行扫描、匹配、移动、解析
- 文件位置:[FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:62)
- `FileSorter.AnalysisNxsCSV`
- 解析 CSV、写入数据库、触发 UI 更新、判断双侧是否完成
- 文件位置:[FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:564)
- `CjlrDAL.HasBothSidesMeasureResult`
- 判断同一 `CarID` 是否同时存在 `L``R` 两侧结果
- 文件位置:[CjlrDAL.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/DAL/CjlrDAL.cs:234)
## 2. 现场分发规则理解
根据现场配置界面,当前数据库中维护了 6 条分发规则:
| 任务名称 | 车型代码 | 位置 | 源路径 | 目标路径 |
| --- | --- | --- | --- | --- |
| `L551R_C11` | `L551_21MY` | `R` | `P:\` | `K:\L551R 21MY` |
| `L551L_C11` | `L551_21MY` | `L` | `O:\` | `L:\L551L 21MY` |
| `E03L_C11` | `E03` | `L` | `O:\` | `L:\E03L` |
| `E03R_C11` | `E03` | `R` | `P:\` | `K:\E03R` |
| `E0YL_C11` | `E0Y` | `L` | `O:\` | `L:\E0YL` |
| `E0YR_C11` | `E0Y` | `R` | `P:\` | `K:\E0YR` |
在代码里,每条规则最终会形成一个匹配特征符:
- `L551_21MY_R`
- `L551_21MY_L`
- `E03_R`
- `E03_L`
- `E0Y_R`
- `E0Y_L`
旧逻辑的匹配方式是:
1. 打开 CSV
2. 读取第 3 行第 2 列,也就是 `MEASGROUPNAME`
3.`Contains` 判断是否命中目标特征符
旧实现位置:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:188)
## 3. 现场样本数据理解
结合 `现场问题/20260520-现场数据` 中的样本文件,现场目录中的实际分布如下:
### 3.1 P 目录
`P` 目录是右侧来源目录,现场样本中实际包含:
- `E03_R`
- `E0Y_R20250929`
示例:
- [T2559507.csv](/d:/2025-01-捷豹路虎改造/现场问题/20260520-现场数据/P/T2559507.csv:3)
- `MEASGROUPNAME = E03_R`
- [T0559698.csv](/d:/2025-01-捷豹路虎改造/现场问题/20260520-现场数据/P/T0559698.csv:3)
- `MEASGROUPNAME = E0Y_R20250929`
### 3.2 O 目录
`O` 目录是左侧来源目录,现场样本中实际包含:
- `E03_L`
- `E0Y_L20250929`
示例:
- [TX559018.csv](/d:/2025-01-捷豹路虎改造/现场问题/20260520-现场数据/O/TX559018.csv:3)
- `MEASGROUPNAME = E0Y_L20250929`
### 3.3 L 目录
`L` 目录样本中主要是:
- `L551_21MY_L`
示例:
- [T0554809.csv](/d:/2025-01-捷豹路虎改造/现场问题/20260520-现场数据/L/T0554809.csv:3)
- `MEASGROUPNAME = L551_21MY_L`
### 3.4 K 目录
`K` 目录样本中主要是:
- `E03_R`
示例:
- [T5557787.csv](/d:/2025-01-捷豹路虎改造/现场问题/20260520-现场数据/K/T5557787.csv:3)
- `MEASGROUPNAME = E03_R`
## 4. 升级后现场日志结论
升级后日志文件:
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:20)
从日志可以确认:
1. 程序确实查到了 6 条任务
2. 但日志里只完整执行到了第一条规则 `L551_21MY_R`
3. 第一条规则对 `P:\` 扫描时,大量文件未命中
4. 同时仍然存在“解析入库成功,但该文件不属于当前规则”的情况
关键日志:
- 查询到 6 条任务
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:20)
- 第一条执行规则
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:21)
- 第一条规则的目标特征符
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:22)
- 后续大量 `未匹配到文件`
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:26)
- 期间分发定时器因防重入被跳过
- [Debug.txt.升级后](/d:/2025-01-捷豹路虎改造/现场问题/Debug.txt.升级后:5323)
## 5. 问题根因总结
### 5.1 原逻辑是“按规则扫目录”,不是“按文件路由”
旧逻辑会这样执行:
1. 第一条规则扫描整个 `P:\`
2. 第二条规则扫描整个 `O:\`
3. 第三条规则再扫描整个 `O:\`
4. 第四条规则再扫描整个 `P:\`
这意味着同一个目录会被重复遍历多次。
### 5.2 旧逻辑是“先解析入库,再做分发匹配”
在旧实现中,每个文件进入循环后会先执行:
- `AnalysisNxsCSV(file)`
然后才判断是否属于当前规则。
这会导致:
- 不属于当前规则的文件也会先入库
- 第一条规则处理时间过长
- 日志里会出现“已导入成功,但未匹配到当前规则”
### 5.3 旧匹配对 `E0Y_R20250929` 这类带日期后缀的值不稳定
现场 `E0Y` 组名带日期后缀:
- `E0Y_R20250929`
- `E0Y_L20250929`
如果规则写的是 `E0Y_R``E0Y_L`,仅依赖简单字符串匹配不够稳,后续维护也容易出问题。
### 5.4 已处理文件仍参与后续匹配判断,日志噪音大
旧逻辑中,即使文件已经在数据库里标记为已处理,仍可能继续参与分发阶段的匹配判断,导致现场反复看到:
- `文件已处理过,跳过`
- `未匹配到文件`
这不利于现场判断真实问题。
### 5.5 双侧报告逻辑依赖左右两侧 `CarID` 一致
当前报告逻辑仍然是:
- 同一 `CarID`
- 同时拥有 `L``R`
- 才视为双侧完成
这一规则当前没有修改。现场很多样本左右车号并不完全对齐,所以即使分发成功,也可能仍然无法生成报告。
## 6. 本次已实施的优化
本次按你的要求,先只落地前 4 点优化,不改“双侧报告匹配规则”。
### 6.1 优化 1:先匹配分发,再解析入库
已调整为:
1. 先读取 CSV 第 3 行第 2 列
2. 判断是否命中某条规则
3. 命中后先移动到目标目录
4. 再对移动后的文件执行 `AnalysisNxsCSV`
这样可以避免:
- 不属于当前规则的文件被提前入库
- 第一条规则把整盘无关文件都先解析一遍
对应实现位置:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:127)
### 6.2 优化 2:一个源目录只扫描一遍
已把原来“每条规则单独扫目录”的方式,改成:
1. 先按 `sourceDir` 对规则分组
2. 对每个源目录只扫描一遍
3. 每个文件只读取一次特征值
4. 再在该目录对应的规则集合中做匹配
这样:
- `P:\` 不会被 `L551R``E03R``E0YR` 分别重复扫描
- `O:\` 也不会被多条左侧规则重复扫描
对应实现位置:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:74)
### 6.3 优化 3:已处理文件直接跳过后续匹配与解析
已在目录扫描阶段增加处理:
- 如果 `_dal.IsFileProcessed(file)``true`
- 直接 `continue`
- 不再参与后续匹配
- 不再重复输出“未匹配到文件”
这样能明显减少现场日志噪音。
对应实现位置:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:139)
### 6.4 优化 4:匹配规则标准化
已新增标准化逻辑 `NormalizeMatchToken`
- `E0Y_R20250929` 标准化为 `E0Y_R`
- `E0Y_L20250929` 标准化为 `E0Y_L`
- `E03_R` 保持为 `E03_R`
- `L551_21MY_R` 保持为 `L551_21MY_R`
- `L551_21MY_L` 保持为 `L551_21MY_L`
这样做的好处:
- 兼容现场 `E0Y` 组名带日期后缀
- 不改变 `L551_21MY` 这类三段模型编码
- 后续如果日期后缀变化,不需要再改规则
对应实现位置:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:242)
## 7. 本次代码调整点
本次主要修改文件:
- [FileSorter.cs](/d:/2025-01-捷豹路虎改造/CJLR-Analysis/Analysis/CjlrForm/FileSorter.cs:26)
新增或调整的核心结构:
- `TaskRule`
- 用于承载单条分发规则
- `rulesBySource`
- 将同源目录规则合并处理
- `TryReadMatchToken`
- 快速读取并标准化匹配特征
- `NormalizeMatchToken`
- 规范化 `MEASGROUPNAME`
- `MoveMatchedFile`
- 统一执行文件移动
- `InsertTaskDetail`
- 统一写入分发详情
## 8. 已验证结果
本次修改后,已完成一次本地编译验证:
- 项目:`NXSAnalysis.csproj`
- 配置:`Debug | x64`
- 结果:`0 error`
说明:
- 构建成功
- 仅存在原项目自身的 Telerik 程序集版本 warning
- 不影响本次功能代码编译通过
## 9. 当前仍未调整的部分
按本次要求,以下逻辑暂未改动:
1. 双侧报告匹配规则
- 仍按同一 `CarID` 同时存在 `L``R` 结果判断
2. `MY` 车型报告跳过规则
- 仍保留原有逻辑
3. `SQLHelper` 的线程安全重构
- 当前文档只记录,未在本轮实现
4. 解析线程与其他 CSV 定时器之间的更大范围解耦
- 当前只优化了 `FileSorter` 主链路
## 10. 后续建议
如果继续往下做,建议优先级如下:
1. 重构 `SQLHelper`,彻底去掉静态连接共享
2. 复核双侧报告的 `CarID` 配对规则是否符合现场
3. 增加“按目录统计命中数/未命中数”的汇总日志
4. 增加“模拟分发”模式,现场可先看命中结果再实际移动
## 11. 总结
这次现场问题的核心,并不是简单的“规则没配好”,而是旧分发流程本身存在几个结构性问题:
- 按规则重复扫描目录
- 先解析再匹配
- 已处理文件仍重复参与匹配
- `MEASGROUPNAME` 标准化不足
本次已经先把这些最影响现场效率和可读性的部分落地优化。
报告生成规则暂未改动,后续如果继续处理“双侧配对失败”的问题,需要结合现场车号规则再单独分析。