✨ feat(*): 重构PCB放行流程并接入双扫码校验
- 将流程改为 OMRON FINS + TCP 双扫码 + SFTP 文件校验 - 新增文件校验与扫码结果汇总实现,补充对应单测 - 同步更新流程设计文档、配置和系统设置页面文案
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
# Axi Omron PCB Check CSV 记录持久化设计
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
本设计用于为 `AxiOmron.PcbCheck` 增加“扫码任务与处理结果记录持久化”能力,并满足以下目标:
|
||||
|
||||
- 所有处理记录仅使用 CSV 持久化,不引入数据库。
|
||||
- CSV 按天分文件,文件名固定为 `yyyy-MM-dd.csv`。
|
||||
- 程序启动时自动从“当天 CSV”回读恢复首页记录与统计数据。
|
||||
- 首页列表只显示最近 100 条记录。
|
||||
- 统计区域在每次任务完成后实时更新,且统计口径基于“当天全部有效记录”。
|
||||
- CSV 读写异常不能影响主流程,不允许因为持久化失败而阻断扫码、SFTP、PLC 放行等核心业务。
|
||||
|
||||
## 2. 当前状态
|
||||
|
||||
当前系统在 `WorkflowHostedService` 完成单板流程后,会创建 `BoardProcessRecord` 并通过 `IAppStateStore.RecordAdded` 推送到界面。
|
||||
|
||||
当前实现存在以下限制:
|
||||
|
||||
- 记录仅保存在内存中,程序重启后丢失。
|
||||
- 首页统计值通过 `RecentBoards` 重新计算,而 `RecentBoards` 仅保留 UI 最近记录,统计口径不等于“当天总量”。
|
||||
- 跨 0 点后没有自动切换到新一天数据视图。
|
||||
- 记录读写与统计未形成独立边界,后续扩展导出、恢复和容错能力较弱。
|
||||
|
||||
## 3. 设计结论
|
||||
|
||||
采用“独立记录仓储服务 + CSV 作为唯一事实源 + UI 只展示最近 100 条”的方案。
|
||||
|
||||
核心结论如下:
|
||||
|
||||
- 新增记录仓储服务,负责 CSV 追加写入、当天 CSV 回读和当天统计维护。
|
||||
- `WorkflowHostedService` 仍负责生成 `BoardProcessRecord`,但不再自行承担记录持久化逻辑。
|
||||
- `MainWindowViewModel` 只负责展示最近 100 条和当前统计结果,不再以 UI 列表作为统计真值来源。
|
||||
- CSV 是唯一持久化介质;内存集合只是当前界面投影。
|
||||
- 任意 CSV 异常均只记日志、尽力恢复,不改变主流程状态机,不触发 `Faulted`。
|
||||
|
||||
## 4. 范围与非范围
|
||||
|
||||
### 4.1 本次范围
|
||||
|
||||
- 新增 CSV 记录仓储服务及其接口。
|
||||
- 启动时回读当天 CSV。
|
||||
- 首页最近处理记录列表限制为最近 100 条。
|
||||
- 首页统计区按当天全量有效记录实时计算。
|
||||
- 跨 0 点自动切换到新一天视图与统计。
|
||||
- 为 CSV 读写、坏行容错和跨天行为补充自动化测试。
|
||||
|
||||
### 4.2 非本次范围
|
||||
|
||||
- 不新增数据库、LiteDB、SQLite 或远程存储。
|
||||
- 不新增“查看历史任意日期记录”的完整追溯页面。
|
||||
- 不新增 CSV 手工编辑、补录、删除和回写功能。
|
||||
- 不改变现有主流程业务判定规则。
|
||||
|
||||
## 5. 总体架构
|
||||
|
||||
### 5.1 新增角色
|
||||
|
||||
新增接口与实现:
|
||||
|
||||
- `IBoardRecordRepository`
|
||||
- `CsvBoardRecordRepository`
|
||||
|
||||
该服务职责明确且独立:
|
||||
|
||||
1. 追加写入一条处理记录到目标日期 CSV。
|
||||
2. 启动时加载当天 CSV 的全部有效记录。
|
||||
3. 基于当天全部有效记录提供统计结果。
|
||||
4. 在检测到跨天时切换当前工作日并重新加载新一天数据。
|
||||
|
||||
### 5.2 与现有组件协作关系
|
||||
|
||||
- `WorkflowHostedService`
|
||||
- 生成 `BoardProcessRecord`
|
||||
- 调用仓储服务追加持久化
|
||||
- 在持久化成功或失败后继续通知 UI 更新
|
||||
- `IAppStateStore`
|
||||
- 继续承担运行时 UI 事件分发
|
||||
- 新增“初始化记录”和“更新统计”的承载职责
|
||||
- 不再作为记录真值来源
|
||||
- `MainWindowViewModel`
|
||||
- 绑定最近 100 条记录
|
||||
- 绑定今日统计
|
||||
- 不再从 `RecentBoards` 推导当天真实总量
|
||||
|
||||
## 6. 数据模型设计
|
||||
|
||||
### 6.1 沿用的记录模型
|
||||
|
||||
沿用现有 `BoardProcessRecord` 作为单板结果对象,字段如下:
|
||||
|
||||
- `StartedAt`
|
||||
- `CompletedAt`
|
||||
- `Barcode`
|
||||
- `ScanTryCount`
|
||||
- `SftpTryCount`
|
||||
- `ResultCode`
|
||||
- `ResultDescription`
|
||||
- `ReleaseSent`
|
||||
- `AlarmRaised`
|
||||
- `ExceptionSummary`
|
||||
|
||||
### 6.2 新增统计模型
|
||||
|
||||
新增轻量统计对象,例如 `BoardRecordStatistics`,用于承载当前显示日的聚合结果:
|
||||
|
||||
- `StatisticDate`
|
||||
- `TotalCount`
|
||||
- `OkCount`
|
||||
- `NgCount`
|
||||
- `LastUpdatedAt`
|
||||
|
||||
统计定义如下:
|
||||
|
||||
- `ResultCode == (ushort)WorkflowResultCode.Passed` 计为 OK。
|
||||
- `ResultCode != Passed` 且 `ResultCode != Processing` 且 `ResultCode != None` 计为 NG。
|
||||
- 坏行、空行、解析失败行不参与统计。
|
||||
|
||||
## 7. CSV 设计
|
||||
|
||||
### 7.1 存储目录
|
||||
|
||||
CSV 文件统一存放于应用运行目录下:
|
||||
|
||||
`Data\Records\`
|
||||
|
||||
例如:
|
||||
|
||||
`Data\Records\2026-04-19.csv`
|
||||
|
||||
### 7.2 文件命名规则
|
||||
|
||||
- 每天一个文件。
|
||||
- 文件名固定为 `yyyy-MM-dd.csv`。
|
||||
- 日期以本地时间为准。
|
||||
|
||||
### 7.3 行归属规则
|
||||
|
||||
每条记录归属哪个文件,由 `CompletedAt` 的本地日期决定:
|
||||
|
||||
- `CompletedAt = 2026-04-19 23:59:59` 写入 `2026-04-19.csv`
|
||||
- `CompletedAt = 2026-04-20 00:00:01` 写入 `2026-04-20.csv`
|
||||
|
||||
若一块板在前一天启动、后一天完成,则归属“完成当天”。
|
||||
|
||||
### 7.4 编码与表头
|
||||
|
||||
- 文件编码使用 `UTF-8 with BOM`。
|
||||
- 文件首行写入固定表头。
|
||||
|
||||
固定列顺序如下:
|
||||
|
||||
`StartedAt,CompletedAt,Barcode,ScanTryCount,SftpTryCount,ResultCode,ResultDescription,ReleaseSent,AlarmRaised,ExceptionSummary`
|
||||
|
||||
### 7.5 字段格式
|
||||
|
||||
- 时间字段格式:`yyyy-MM-dd HH:mm:ss.fff`
|
||||
- 布尔字段输出为 `True` / `False`
|
||||
- 数值字段使用十进制字符串
|
||||
- 文本字段按标准 CSV 规则转义:
|
||||
- 包含逗号、双引号、换行时加双引号包裹
|
||||
- 内部双引号转义为 `""`
|
||||
|
||||
## 8. 启动恢复流程
|
||||
|
||||
应用启动后,在主窗口显示前完成以下步骤:
|
||||
|
||||
1. 计算当前本地日期。
|
||||
2. 定位当天 CSV 文件。
|
||||
3. 若文件不存在,则视为当天暂无记录。
|
||||
4. 若文件存在,则读取全部数据行并逐行解析。
|
||||
5. 跳过坏行,保留有效记录。
|
||||
6. 按 `CompletedAt` 升序恢复有效记录,再取最后 100 条,并在首页按完成时间倒序展示。
|
||||
7. 基于当天全部有效记录计算统计结果。
|
||||
8. 将“最近 100 条 + 当天统计”写入 `IAppStateStore`,供首页绑定。
|
||||
|
||||
若启动恢复失败:
|
||||
|
||||
- 记录错误日志。
|
||||
- 首页记录区显示空列表。
|
||||
- 统计区显示 0。
|
||||
- 主流程继续启动,不阻断应用运行。
|
||||
|
||||
## 9. 运行时写入与实时更新流程
|
||||
|
||||
每次单板流程完成后执行以下步骤:
|
||||
|
||||
1. `WorkflowHostedService` 构造 `BoardProcessRecord`。
|
||||
2. 调用 `IBoardRecordRepository.AppendAsync(record, cancellationToken)`。
|
||||
3. 仓储服务将记录追加到目标日期 CSV。
|
||||
4. 若追加成功:
|
||||
- 更新仓储内部当天统计缓存。
|
||||
- 更新仓储内部最近记录缓存。
|
||||
5. 若追加成功,将最新记录与统计通过 `IAppStateStore` 推送给 UI。
|
||||
6. 若追加失败,则保留上一份“已成功持久化”的历史表与统计,不把失败记录混入历史区和统计区,但当前流程结果仍通过现有运行状态区正常显示。
|
||||
|
||||
运行时界面行为约束:
|
||||
|
||||
- 首页表格永远只保留最近 100 条。
|
||||
- 首页统计区始终显示当前本地日期对应的全量统计结果。
|
||||
- 每次成功持久化的任务完成后统计立即刷新,不依赖手动点击“刷新”。
|
||||
|
||||
## 10. 跨 0 点处理流程
|
||||
|
||||
### 10.1 切天规则
|
||||
|
||||
系统以本地日期为准,在程序不重启的情况下,跨 0 点后自动切换到新一天视图。
|
||||
|
||||
### 10.2 切天行为
|
||||
|
||||
当检测到当前本地日期已变化时,仓储服务执行:
|
||||
|
||||
1. 更新当前工作日。
|
||||
2. 加载新一天的 `yyyy-MM-dd.csv`。
|
||||
3. 若新一天文件不存在,则初始化为空列表和 0 统计。
|
||||
4. 将新一天数据重新推送到 `IAppStateStore`。
|
||||
|
||||
### 10.3 检查时机
|
||||
|
||||
至少在以下时机检查是否跨天:
|
||||
|
||||
- 应用启动时
|
||||
- 每次追加记录前
|
||||
- 每次追加记录后
|
||||
- 应用内增加一个固定周期的轻量日切检查定时器,建议每 30 秒检查一次,避免长时间空闲时界面不切天
|
||||
|
||||
### 10.4 跨天中的在制任务
|
||||
|
||||
若一块板跨天完成:
|
||||
|
||||
- 列表与统计归属其 `CompletedAt` 当天。
|
||||
- 例如 `23:59:50` 开始、`00:00:05` 完成,则写入次日 CSV,并计入次日统计。
|
||||
|
||||
## 11. 健壮性与故障隔离
|
||||
|
||||
### 11.1 总原则
|
||||
|
||||
CSV 是记录与追溯系统,不是生产流程控制系统。
|
||||
|
||||
因此:
|
||||
|
||||
- CSV 读写失败不能阻断主流程。
|
||||
- CSV 异常不能触发流程 `Faulted`。
|
||||
- CSV 异常只能以日志和界面状态提示形式暴露。
|
||||
|
||||
### 11.2 启动读取容错
|
||||
|
||||
- 文件不存在:按空数据处理。
|
||||
- 文件为空:按空数据处理。
|
||||
- 表头不匹配:记录 error,放弃恢复该文件并返回空结果,不做复杂兼容解析。
|
||||
- 某一行解析失败:跳过该行,记录 warning,继续处理剩余行。
|
||||
- 文件占用、权限不足、目录不存在:记录 error,返回空结果。
|
||||
|
||||
### 11.3 运行写入容错
|
||||
|
||||
- 目录不存在时自动创建目录。
|
||||
- 单次追加失败:
|
||||
- 记录 error
|
||||
- 不影响本次单板主流程完成
|
||||
- 不进入 `Faulted`
|
||||
- UI 可更新一条轻量状态提示,例如“今日记录写入失败,请检查磁盘或权限”
|
||||
- 历史记录表与统计区维持在最近一次成功持久化后的状态,避免界面展示与 CSV 真值不一致
|
||||
|
||||
### 11.4 坏行处理策略
|
||||
|
||||
坏行定义包括但不限于:
|
||||
|
||||
- 列数不匹配
|
||||
- 时间无法解析
|
||||
- 数值字段无法解析
|
||||
- 布尔字段无法解析
|
||||
|
||||
处理方式统一为:
|
||||
|
||||
- 跳过坏行
|
||||
- 写 warning 日志
|
||||
- 继续处理后续行
|
||||
- 不影响其余记录恢复
|
||||
|
||||
## 12. 并发与线程安全
|
||||
|
||||
记录仓储服务需保证多线程安全。
|
||||
|
||||
设计约束如下:
|
||||
|
||||
- 对 CSV 文件读写使用独立的串行锁,例如 `SemaphoreSlim`。
|
||||
- 同一时刻仅允许一个追加写入执行,避免并发写入造成文件交错。
|
||||
- 启动恢复与运行时追加不可并发破坏内部缓存。
|
||||
- 对当天缓存的最近记录和统计更新应保持原子性,避免 UI 看到一半旧数据、一半新数据。
|
||||
|
||||
## 13. UI 与状态存储改造
|
||||
|
||||
### 13.1 列表展示
|
||||
|
||||
首页“处理记录”区只展示最近 100 条。
|
||||
|
||||
展示内容保持现有交互风格,不新增复杂筛选器。本次可沿用现有列:
|
||||
|
||||
- 完成时间
|
||||
- 条码
|
||||
- 结果
|
||||
|
||||
### 13.2 统计展示
|
||||
|
||||
首页统计区每次任务完成后都自动更新,展示:
|
||||
|
||||
- 今日处理数
|
||||
- OK 数
|
||||
- NG 数
|
||||
|
||||
建议增加“当前统计日期”展示,例如:
|
||||
|
||||
`当前统计日期:2026-04-19`
|
||||
|
||||
这样在跨 0 点后,操作员能明确看到界面已切换到新一天。
|
||||
|
||||
### 13.3 AppStateStore 调整
|
||||
|
||||
`IAppStateStore` 需要具备以下能力之一:
|
||||
|
||||
- 支持初始化一批历史记录和统计对象
|
||||
- 或支持仓储服务在启动恢复后逐条推送记录并单独更新统计
|
||||
|
||||
推荐增加明确方法,而不是让 `MainWindowViewModel` 自行去猜测“当前是恢复模式还是实时模式”。
|
||||
|
||||
## 14. 测试设计
|
||||
|
||||
### 14.1 仓储服务测试
|
||||
|
||||
至少覆盖以下场景:
|
||||
|
||||
1. 首次写入时自动创建目录和文件,并写入表头。
|
||||
2. 同一天多条记录追加后,CSV 内容顺序正确。
|
||||
3. 启动回读时能正确恢复当天记录与统计。
|
||||
4. 坏行存在时,仅跳过坏行,不影响其他行恢复。
|
||||
5. 跨 0 点后切换到新一天文件,列表与统计归零或切换为新一天数据。
|
||||
6. 跨天完成记录写入完成日文件,而不是开始日文件。
|
||||
|
||||
### 14.2 流程服务测试
|
||||
|
||||
至少覆盖以下场景:
|
||||
|
||||
1. 流程完成后会调用仓储服务进行持久化。
|
||||
2. 仓储写入失败时,主流程仍能完成且不进入故障态。
|
||||
3. 仓储写入成功后,`IAppStateStore` 会收到最新记录和统计。
|
||||
|
||||
### 14.3 ViewModel 测试
|
||||
|
||||
至少覆盖以下场景:
|
||||
|
||||
1. 启动恢复后,首页只显示最近 100 条。
|
||||
2. 当天统计值来自全量记录,而不是仅来自最近 100 条。
|
||||
3. 跨天刷新后,列表和统计能切到新一天。
|
||||
|
||||
## 15. 实施顺序建议
|
||||
|
||||
建议按以下顺序实施:
|
||||
|
||||
1. 新增 CSV 仓储接口、实现与统计模型。
|
||||
2. 为仓储补齐 CSV 读写和坏行容错测试。
|
||||
3. 将 `WorkflowHostedService` 接入仓储服务。
|
||||
4. 扩展 `IAppStateStore` 支持初始化历史记录与统计。
|
||||
5. 调整 `MainWindowViewModel`,改为展示仓储恢复结果和实时统计。
|
||||
6. 补充跨 0 点与异常隔离测试。
|
||||
7. 运行构建与测试验证。
|
||||
|
||||
## 16. 最终结论
|
||||
|
||||
本次改造采用“CSV 仓储服务下沉”的方式实现记录持久化,能够在不引入数据库的前提下满足以下诉求:
|
||||
|
||||
- 所有处理记录按天持久化到 CSV。
|
||||
- 程序重启后自动从当天 CSV 恢复首页数据。
|
||||
- 首页仅显示最近 100 条记录。
|
||||
- 统计区按当天全量有效记录实时计算。
|
||||
- 跨 0 点自动切换到新一天。
|
||||
- CSV 异常与主流程完全隔离,不影响扫码、SFTP、PLC 放行等核心业务。
|
||||
|
||||
该方案边界清晰、可测试、可维护,并为后续增加追溯页面、日期切换和导出能力保留了扩展空间。
|
||||
Reference in New Issue
Block a user