docs: update extractor-start-button-flow.md for database architecture changes
- Update version to 1.2 - Add database factory pattern documentation - Add IDatabaseService interface documentation - Update architecture diagram with Database Layer (MySQL/SQL Server) - Update sequence diagram with DatabaseFactory and IDatabaseService - Fix Mermaid syntax (replace <br/> with multiline strings) - Update error handling flowchart for dual database support - Add DB_TYPE environment variable documentation Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# 数据提取界面 - 开始按钮工作流程详解
|
# 数据提取界面 - 开始按钮工作流程详解
|
||||||
|
|
||||||
> **文档版本**: 1.1
|
> **文档版本**: 1.2
|
||||||
> **更新日期**: 2026-03-03
|
> **更新日期**: 2026-03-03
|
||||||
> **适用范围**: ERPAuto v1.0+
|
> **适用范围**: ERPAuto v1.0+
|
||||||
> **相关文件**:
|
> **相关文件**:
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
> - `src/main/services/erp/extractor.ts` (业务逻辑层)
|
> - `src/main/services/erp/extractor.ts` (业务逻辑层)
|
||||||
> - `src/main/services/erp/order-resolver.ts` (订单号解析服务)
|
> - `src/main/services/erp/order-resolver.ts` (订单号解析服务)
|
||||||
> - `src/main/services/erp/erp-auth.ts` (ERP认证服务)
|
> - `src/main/services/erp/erp-auth.ts` (ERP认证服务)
|
||||||
|
> - `src/main/services/database/index.ts` (数据库工厂)
|
||||||
|
> - `src/main/types/database.types.ts` (数据库类型定义)
|
||||||
> - `src/main/types/extractor.types.ts` (类型定义)
|
> - `src/main/types/extractor.types.ts` (类型定义)
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
@@ -47,8 +49,16 @@ graph TB
|
|||||||
J[ExtractorService]
|
J[ExtractorService]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
subgraph "Database Layer (数据库层)"
|
||||||
|
N["DatabaseFactory
|
||||||
|
create()"]
|
||||||
|
O["IDatabaseService
|
||||||
|
统一接口"]
|
||||||
|
P1[(MySQL)]
|
||||||
|
P2[(SQL Server)]
|
||||||
|
end
|
||||||
|
|
||||||
subgraph "External Services (外部服务)"
|
subgraph "External Services (外部服务)"
|
||||||
K[(MySQL Database)]
|
|
||||||
L[ERP Web System]
|
L[ERP Web System]
|
||||||
M[Playwright Browser]
|
M[Playwright Browser]
|
||||||
end
|
end
|
||||||
@@ -58,7 +68,10 @@ graph TB
|
|||||||
E -->|IPC通信| F
|
E -->|IPC通信| F
|
||||||
F -->|接收请求| G
|
F -->|接收请求| G
|
||||||
G -->|解析订单号| H
|
G -->|解析订单号| H
|
||||||
H -->|查询数据| K
|
H -->|创建服务| N
|
||||||
|
N -->|返回实例| O
|
||||||
|
O -->|查询数据| P1
|
||||||
|
O -->|查询数据| P2
|
||||||
G -->|登录认证| I
|
G -->|登录认证| I
|
||||||
I -->|自动化操作| M
|
I -->|自动化操作| M
|
||||||
M -->|访问页面| L
|
M -->|访问页面| L
|
||||||
@@ -71,7 +84,8 @@ graph TB
|
|||||||
|
|
||||||
style A fill:#e1f5ff
|
style A fill:#e1f5ff
|
||||||
style G fill:#fff4e1
|
style G fill:#fff4e1
|
||||||
style K fill:#e8f5e9
|
style N fill:#e8f5e9
|
||||||
|
style O fill:#e8f5e9
|
||||||
style L fill:#f3e5f5
|
style L fill:#f3e5f5
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,7 +94,11 @@ graph TB
|
|||||||
- **Renderer Process**: 负责UI展示和用户交互,使用React管理状态
|
- **Renderer Process**: 负责UI展示和用户交互,使用React管理状态
|
||||||
- **IPC Bridge**: 安全的进程间通信桥梁,通过preload脚本暴露
|
- **IPC Bridge**: 安全的进程间通信桥梁,通过preload脚本暴露
|
||||||
- **Main Process**: 处理业务逻辑、数据库操作、浏览器自动化
|
- **Main Process**: 处理业务逻辑、数据库操作、浏览器自动化
|
||||||
- **External Services**: MySQL数据库和ERP Web系统
|
- **Database Layer**: 数据库抽象层,通过工厂模式创建服务实例
|
||||||
|
- 支持 MySQL 和 SQL Server 双数据库
|
||||||
|
- 通过 `IDatabaseService` 统一接口操作
|
||||||
|
- 由 `DB_TYPE` 环境变量决定使用哪种数据库
|
||||||
|
- **External Services**: ERP Web 系统
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -94,7 +112,8 @@ sequenceDiagram
|
|||||||
participant IPC as electron API
|
participant IPC as electron API
|
||||||
participant Handler as extractor-handler.ts
|
participant Handler as extractor-handler.ts
|
||||||
participant Resolver as OrderNumberResolver
|
participant Resolver as OrderNumberResolver
|
||||||
participant MySQL as MySQL Database
|
participant DBFactory as DatabaseFactory
|
||||||
|
participant Database as IDatabaseService
|
||||||
participant Auth as ErpAuthService
|
participant Auth as ErpAuthService
|
||||||
participant Extractor as ExtractorService
|
participant Extractor as ExtractorService
|
||||||
participant Browser as Playwright Browser
|
participant Browser as Playwright Browser
|
||||||
@@ -122,17 +141,27 @@ sequenceDiagram
|
|||||||
UI->>UI: 设置isRunning=false
|
UI->>UI: 设置isRunning=false
|
||||||
end
|
end
|
||||||
|
|
||||||
Note over Handler,MySQL: 订单号解析阶段
|
Note over Handler,Database: 数据库连接阶段
|
||||||
Handler->>MySQL: 11. 连接MySQL数据库
|
Handler->>DBFactory: 11. 调用 create()
|
||||||
alt MySQL连接失败
|
DBFactory->>DBFactory: 12. 读取 DB_TYPE 环境变量
|
||||||
|
alt DB_TYPE = mysql
|
||||||
|
DBFactory->>Database: 创建 MySqlService 实例
|
||||||
|
else DB_TYPE = sqlserver
|
||||||
|
DBFactory->>Database: 创建 SqlServerService 实例
|
||||||
|
end
|
||||||
|
Database->>Database: 13. connect()
|
||||||
|
alt 数据库连接失败
|
||||||
|
Database-->>Handler: 抛出异常
|
||||||
Handler-->>UI: 返回DatabaseQueryError
|
Handler-->>UI: 返回DatabaseQueryError
|
||||||
end
|
end
|
||||||
|
DBFactory-->>Handler: 14. 返回 IDatabaseService 实例
|
||||||
|
|
||||||
Handler->>Resolver: 12. 创建OrderNumberResolver
|
Note over Handler,Database: 订单号解析阶段
|
||||||
Handler->>Resolver: 13. 调用resolve(orderNumbers)
|
Handler->>Resolver: 15. 创建OrderNumberResolver(dbService)
|
||||||
Resolver->>MySQL: 14. 查询生产订单号映射
|
Handler->>Resolver: 16. 调用resolve(orderNumbers)
|
||||||
MySQL-->>Resolver: 15. 返回映射结果
|
Resolver->>Database: 17. 查询生产订单号映射
|
||||||
Resolver-->>Handler: 16. 返回映射结果<br/>(包含有效订单号和警告)
|
Database-->>Resolver: 18. 返回映射结果
|
||||||
|
Resolver-->>Handler: 19. 返回映射结果<br/>(包含有效订单号和警告)
|
||||||
|
|
||||||
alt 没有有效订单号
|
alt 没有有效订单号
|
||||||
Handler-->>UI: 返回ValidationError
|
Handler-->>UI: 返回ValidationError
|
||||||
@@ -140,62 +169,62 @@ sequenceDiagram
|
|||||||
end
|
end
|
||||||
|
|
||||||
Note over Handler,Browser: ERP认证阶段
|
Note over Handler,Browser: ERP认证阶段
|
||||||
Handler->>Auth: 17. 创建ErpAuthService
|
Handler->>Auth: 20. 创建ErpAuthService
|
||||||
Handler->>Auth: 18. 调用login()
|
Handler->>Auth: 21. 调用login()
|
||||||
Auth->>Browser: 19. 启动Playwright浏览器
|
Auth->>Browser: 22. 启动Playwright浏览器
|
||||||
Browser->>ERP: 20. 访问ERP登录页面
|
Browser->>ERP: 23. 访问ERP登录页面
|
||||||
Browser->>ERP: 21. 填写用户名密码
|
Browser->>ERP: 24. 填写用户名密码
|
||||||
Browser->>ERP: 22. 点击登录按钮
|
Browser->>ERP: 25. 点击登录按钮
|
||||||
ERP-->>Browser: 23. 登录成功
|
ERP-->>Browser: 26. 登录成功
|
||||||
Browser-->>Auth: 24. 返回session对象
|
Browser-->>Auth: 27. 返回session对象
|
||||||
Auth-->>Handler: 25. 登录成功
|
Auth-->>Handler: 28. 登录成功
|
||||||
alt 登录失败
|
alt 登录失败
|
||||||
Auth-->>Handler: 抛出异常
|
Auth-->>Handler: 抛出异常
|
||||||
Handler-->>UI: 返回ErpConnectionError
|
Handler-->>UI: 返回ErpConnectionError
|
||||||
end
|
end
|
||||||
|
|
||||||
Note over Extractor,ERP: 数据提取阶段
|
Note over Extractor,ERP: 数据提取阶段
|
||||||
Handler->>Extractor: 26. 创建ExtractorService
|
Handler->>Extractor: 29. 创建ExtractorService
|
||||||
Handler->>Extractor: 27. 调用extract()<br/>传入有效订单号
|
Handler->>Extractor: 30. 调用extract()<br/>传入有效订单号
|
||||||
Extractor->>Browser: 28. 使用已有session
|
Extractor->>Browser: 31. 使用已有session
|
||||||
Extractor->>ERP: 29. 导航到离散备料计划维护页面
|
Extractor->>ERP: 32. 导航到离散备料计划维护页面
|
||||||
Extractor->>ERP: 30. 设置查询界面<br/>(订单号查询, 全部标签, 限制5000)
|
Extractor->>ERP: 33. 设置查询界面<br/>(订单号查询, 全部标签, 限制5000)
|
||||||
|
|
||||||
loop 批处理循环 (每批最多100个订单)
|
loop 批处理循环 (每批最多100个订单)
|
||||||
Extractor->>Extractor: 31. 创建批次<br/>(按batchSize分组)
|
Extractor->>Extractor: 34. 创建批次<br/>(按batchSize分组)
|
||||||
Note over Extractor: onProgress回调存在但<br/>无法通过IPC传递(函数不可序列化)
|
Note over Extractor: onProgress回调存在但<br/>无法通过IPC传递(函数不可序列化)
|
||||||
|
|
||||||
Extractor->>ERP: 32. 填充订单号到搜索框
|
Extractor->>ERP: 35. 填充订单号到搜索框
|
||||||
Extractor->>ERP: 33. 点击搜索按钮
|
Extractor->>ERP: 36. 点击搜索按钮
|
||||||
Extractor->>ERP: 34. 等待加载完成
|
Extractor->>ERP: 37. 等待加载完成
|
||||||
Extractor->>ERP: 35. 点击第一行复选框
|
Extractor->>ERP: 38. 点击第一行复选框
|
||||||
Extractor->>ERP: 36. 悬停并点击"更多"
|
Extractor->>ERP: 39. 悬停并点击"更多"
|
||||||
Extractor->>ERP: 37. 点击"输出"
|
Extractor->>ERP: 40. 点击"输出"
|
||||||
Extractor->>ERP: 38. 设置行数阈值为300000
|
Extractor->>ERP: 41. 设置行数阈值为300000
|
||||||
Extractor->>ERP: 39. 点击"确定(Y)"
|
Extractor->>ERP: 42. 点击"确定(Y)"
|
||||||
|
|
||||||
Browser->>Browser: 40. 监听下载事件
|
Browser->>Browser: 43. 监听下载事件
|
||||||
ERP->>Browser: 41. 触发文件下载
|
ERP->>Browser: 44. 触发文件下载
|
||||||
Browser->>Browser: 42. 保存文件到downloads目录
|
Browser->>Browser: 45. 保存文件到downloads目录
|
||||||
Browser-->>Extractor: 43. 返回文件路径
|
Browser-->>Extractor: 46. 返回文件路径
|
||||||
Extractor->>Extractor: 44. 记录下载文件路径
|
Extractor->>Extractor: 47. 记录下载文件路径
|
||||||
end
|
end
|
||||||
|
|
||||||
Extractor->>Extractor: 45. 汇总结果<br/>(文件列表, 记录数, 错误)
|
Extractor->>Extractor: 48. 汇总结果<br/>(文件列表, 记录数, 错误)
|
||||||
Extractor-->>Handler: 46. 返回ExtractorResult
|
Extractor-->>Handler: 49. 返回ExtractorResult
|
||||||
Handler->>Handler: 47. 添加解析警告到错误列表
|
Handler->>Handler: 50. 添加解析警告到错误列表
|
||||||
|
|
||||||
Note over Handler,IPC: 清理阶段
|
Note over Handler,IPC: 清理阶段
|
||||||
Handler->>Browser: 48. 关闭浏览器
|
Handler->>Browser: 51. 关闭浏览器
|
||||||
Handler->>MySQL: 49. 断开数据库连接
|
Handler->>Database: 52. 断开数据库连接<br/>(dbService.disconnect())
|
||||||
|
|
||||||
Note over Handler,UI: 响应阶段
|
Note over Handler,UI: 响应阶段
|
||||||
Handler-->>IPC: 50. 返回IPC响应<br/>(success: true, data: result)
|
Handler-->>IPC: 53. 返回IPC响应<br/>(success: true, data: result)
|
||||||
IPC-->>UI: 51. 返回response
|
IPC-->>UI: 54. 返回response
|
||||||
UI->>UI: 52. 设置result状态
|
UI->>UI: 55. 设置result状态
|
||||||
UI->>UI: 53. 设置isRunning=false
|
UI->>UI: 56. 设置isRunning=false
|
||||||
UI->>UI: 54. 清空进度状态
|
UI->>UI: 57. 清空进度状态
|
||||||
UI->>User: 55. 显示提取结果<br/>(文件数, 记录数, 错误数)
|
UI->>User: 58. 显示提取结果<br/>(文件数, 记录数, 错误数)
|
||||||
|
|
||||||
alt 发生任何错误
|
alt 发生任何错误
|
||||||
Handler-->>UI: 返回error响应
|
Handler-->>UI: 返回error响应
|
||||||
@@ -288,23 +317,29 @@ stateDiagram-v2
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
Start([用户点击开始]) --> Validate{前端验证}
|
Start([用户点击开始]) --> Validate{前端验证}
|
||||||
Validate -->|订单号为空| ShowEmptyError[显示错误:<br/>请输入至少一个订单号]
|
Validate -->|订单号为空| ShowEmptyError["显示错误:
|
||||||
|
请输入至少一个订单号"]
|
||||||
Validate -->|验证通过| CallIPC[调用IPC API]
|
Validate -->|验证通过| CallIPC[调用IPC API]
|
||||||
|
|
||||||
CallIPC --> ConfigCheck{环境配置检查}
|
CallIPC --> ConfigCheck{环境配置检查}
|
||||||
ConfigCheck -->|配置不完整| ConfigError[返回ValidationError:<br/>ERP配置不完整]
|
ConfigCheck -->|配置不完整| ConfigError["返回ValidationError:
|
||||||
ConfigCheck -->|配置完整| ConnectMySQL[连接MySQL]
|
ERP配置不完整"]
|
||||||
|
ConfigCheck -->|配置完整| ConnectDB["连接数据库
|
||||||
|
(MySQL或SQL Server)"]
|
||||||
|
|
||||||
ConnectMySQL --> MySQLCheck{连接成功?}
|
ConnectDB --> DBCheck{连接成功?}
|
||||||
MySQLCheck -->|失败| MySQLError[返回DatabaseQueryError:<br/>MySQL连接失败]
|
DBCheck -->|失败| DBError["返回DatabaseQueryError:
|
||||||
MySQLCheck -->|成功| ResolveOrders[解析订单号]
|
数据库连接失败"]
|
||||||
|
DBCheck -->|成功| ResolveOrders[解析订单号]
|
||||||
|
|
||||||
ResolveOrders --> ValidOrders{有有效订单号?}
|
ResolveOrders --> ValidOrders{有有效订单号?}
|
||||||
ValidOrders -->|无| NoOrdersError[返回ValidationError:<br/>没有有效的生产订单号]
|
ValidOrders -->|无| NoOrdersError["返回ValidationError:
|
||||||
|
没有有效的生产订单号"]
|
||||||
ValidOrders -->|有| LoginERP[ERP登录]
|
ValidOrders -->|有| LoginERP[ERP登录]
|
||||||
|
|
||||||
LoginERP --> LoginCheck{登录成功?}
|
LoginERP --> LoginCheck{登录成功?}
|
||||||
LoginCheck -->|失败| LoginError[返回ErpConnectionError:<br/>ERP登录失败]
|
LoginCheck -->|失败| LoginError["返回ErpConnectionError:
|
||||||
|
ERP登录失败"]
|
||||||
LoginCheck -->|成功| ExtractData[执行数据提取]
|
LoginCheck -->|成功| ExtractData[执行数据提取]
|
||||||
|
|
||||||
ExtractData --> BatchLoop[批处理循环]
|
ExtractData --> BatchLoop[批处理循环]
|
||||||
@@ -324,7 +359,7 @@ flowchart TD
|
|||||||
|
|
||||||
ShowEmptyError --> ResetState1[设置isRunning=false]
|
ShowEmptyError --> ResetState1[设置isRunning=false]
|
||||||
ConfigError --> ResetState2[设置isRunning=false]
|
ConfigError --> ResetState2[设置isRunning=false]
|
||||||
MySQLError --> ResetState3[设置isRunning=false]
|
DBError --> ResetState3[设置isRunning=false]
|
||||||
NoOrdersError --> ResetState4[设置isRunning=false]
|
NoOrdersError --> ResetState4[设置isRunning=false]
|
||||||
LoginError --> ResetState5[设置isRunning=false]
|
LoginError --> ResetState5[设置isRunning=false]
|
||||||
|
|
||||||
@@ -337,7 +372,7 @@ flowchart TD
|
|||||||
|
|
||||||
style ShowEmptyError fill:#ffcccc
|
style ShowEmptyError fill:#ffcccc
|
||||||
style ConfigError fill:#ffcccc
|
style ConfigError fill:#ffcccc
|
||||||
style MySQLError fill:#ffcccc
|
style DBError fill:#ffcccc
|
||||||
style NoOrdersError fill:#ffcccc
|
style NoOrdersError fill:#ffcccc
|
||||||
style LoginError fill:#ffcccc
|
style LoginError fill:#ffcccc
|
||||||
style RecordError fill:#fff4cc
|
style RecordError fill:#fff4cc
|
||||||
@@ -349,7 +384,7 @@ flowchart TD
|
|||||||
| 错误类型 | 触发条件 | 用户反馈 | 恢复策略 |
|
| 错误类型 | 触发条件 | 用户反馈 | 恢复策略 |
|
||||||
|---------|---------|---------|---------|
|
|---------|---------|---------|---------|
|
||||||
| `ValidationError` | 订单号为空、配置不完整、无有效订单号 | 显示红色错误消息 | 修正输入后重试 |
|
| `ValidationError` | 订单号为空、配置不完整、无有效订单号 | 显示红色错误消息 | 修正输入后重试 |
|
||||||
| `DatabaseQueryError` | MySQL连接失败 | 显示数据库连接错误 | 检查数据库配置 |
|
| `DatabaseQueryError` | 数据库连接失败 (MySQL/SQL Server) | 显示数据库连接错误 | 检查数据库配置 |
|
||||||
| `ErpConnectionError` | ERP登录失败 | 显示ERP登录错误 | 检查ERP凭据 |
|
| `ErpConnectionError` | ERP登录失败 | 显示ERP登录错误 | 检查ERP凭据 |
|
||||||
| `BatchError` | 单个批次处理失败 | 记录到错误列表,继续处理 | 查看错误详情 |
|
| `BatchError` | 单个批次处理失败 | 记录到错误列表,继续处理 | 查看错误详情 |
|
||||||
| `SystemError` | 未知系统错误 | 显示通用错误消息 | 查看日志 |
|
| `SystemError` | 未知系统错误 | 显示通用错误消息 | 查看日志 |
|
||||||
@@ -361,42 +396,61 @@ flowchart TD
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph "Input (用户输入)"
|
subgraph "Input (用户输入)"
|
||||||
A1[原始输入<br/>订单号列表]
|
A1["原始输入
|
||||||
A2[批次大小<br/>batchSize=100]
|
订单号列表"]
|
||||||
|
A2["批次大小
|
||||||
|
batchSize=100"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Transformation (数据转换)"
|
subgraph "Transformation (数据转换)"
|
||||||
B1[行解析<br/>按换行符分割]
|
B1["行解析
|
||||||
B2[去空白<br/>trim每行]
|
按换行符分割"]
|
||||||
B3[过滤空行<br/>移除空字符串]
|
B2["去空白
|
||||||
B4[存储共享状态<br/>Production IDs]
|
trim每行"]
|
||||||
|
B3["过滤空行
|
||||||
|
移除空字符串"]
|
||||||
|
B4["存储共享状态
|
||||||
|
Production IDs"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Resolution (订单号解析)"
|
subgraph "Resolution (订单号解析)"
|
||||||
C1[查询MySQL<br/>查找映射关系]
|
C0["DatabaseFactory
|
||||||
C2[提取生产订单号<br/>获取有效值]
|
create()"]
|
||||||
C3[收集警告<br/>记录未映射项]
|
C1["查询数据库
|
||||||
|
查找映射关系"]
|
||||||
|
C2["提取生产订单号
|
||||||
|
获取有效值"]
|
||||||
|
C3["收集警告
|
||||||
|
记录未映射项"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Processing (批量处理)"
|
subgraph "Processing (批量处理)"
|
||||||
D1[批次分组<br/>按batchSize切分]
|
D1["批次分组
|
||||||
D2[批次迭代<br/>逐批处理]
|
按batchSize切分"]
|
||||||
D3[订单拼接<br/>逗号连接]
|
D2["批次迭代
|
||||||
|
逐批处理"]
|
||||||
|
D3["订单拼接
|
||||||
|
逗号连接"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Output (结果输出)"
|
subgraph "Output (结果输出)"
|
||||||
E1[下载文件列表<br/>downloadedFiles数组]
|
E1["下载文件列表
|
||||||
E2[合并文件<br/>mergedFile TODO]
|
downloadedFiles数组"]
|
||||||
E3[记录总数<br/>recordCount]
|
E2["合并文件
|
||||||
E4[错误列表<br/>errors数组]
|
mergedFile TODO"]
|
||||||
|
E3["记录总数
|
||||||
|
recordCount"]
|
||||||
|
E4["错误列表
|
||||||
|
errors数组"]
|
||||||
end
|
end
|
||||||
|
|
||||||
A1 --> B1
|
A1 --> B1
|
||||||
B1 --> B2
|
B1 --> B2
|
||||||
B2 --> B3
|
B2 --> B3
|
||||||
B3 --> B4
|
B3 --> B4
|
||||||
B4 --> C1
|
B4 --> C0
|
||||||
A2 --> D1
|
A2 --> D1
|
||||||
|
C0 --> C1
|
||||||
C1 --> C2
|
C1 --> C2
|
||||||
C2 --> D1
|
C2 --> D1
|
||||||
C3 --> E4
|
C3 --> E4
|
||||||
@@ -407,6 +461,7 @@ flowchart LR
|
|||||||
|
|
||||||
style A1 fill:#e3f2fd
|
style A1 fill:#e3f2fd
|
||||||
style A2 fill:#e3f2fd
|
style A2 fill:#e3f2fd
|
||||||
|
style C0 fill:#e8f5e9
|
||||||
style E1 fill:#e8f5e9
|
style E1 fill:#e8f5e9
|
||||||
style E2 fill:#e8f5e9
|
style E2 fill:#e8f5e9
|
||||||
style E3 fill:#e8f5e9
|
style E3 fill:#e8f5e9
|
||||||
@@ -532,7 +587,7 @@ useEffect(() => {
|
|||||||
>
|
>
|
||||||
> 这确保了即使用户在Cleaner页面刷新,数据也已同步。
|
> 这确保了即使用户在Cleaner页面刷新,数据也已同步。
|
||||||
|
|
||||||
### 2. IPC处理器核心逻辑 (extractor-handler.ts:17-154)
|
### 2. IPC处理器核心逻辑 (extractor-handler.ts:17-145)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
@@ -540,7 +595,7 @@ ipcMain.handle(
|
|||||||
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||||
return withErrorHandling(async () => {
|
return withErrorHandling(async () => {
|
||||||
let authService: ErpAuthService | null = null
|
let authService: ErpAuthService | null = null
|
||||||
let mysqlService: MySqlService | null = null
|
let dbService: IDatabaseService | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 环境配置检查
|
// 1. 环境配置检查
|
||||||
@@ -552,12 +607,15 @@ ipcMain.handle(
|
|||||||
throw new ValidationError('ERP 配置不完整')
|
throw new ValidationError('ERP 配置不完整')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 连接MySQL并解析订单号
|
// 2. 使用数据库工厂创建服务实例 (支持 MySQL 和 SQL Server)
|
||||||
const mysqlConfig = { /* ... */ }
|
try {
|
||||||
mysqlService = new MySqlService(mysqlConfig)
|
dbService = await create() // 工厂方法,根据 DB_TYPE 自动选择数据库
|
||||||
await mysqlService.connect()
|
} catch (error) {
|
||||||
|
throw new DatabaseQueryError('数据库连接失败', 'DB_CONNECTION_FAILED', error)
|
||||||
|
}
|
||||||
|
|
||||||
const resolver = new OrderNumberResolver(mysqlService)
|
// 3. 解析订单号
|
||||||
|
const resolver = new OrderNumberResolver(dbService)
|
||||||
const mappings = await resolver.resolve(input.orderNumbers)
|
const mappings = await resolver.resolve(input.orderNumbers)
|
||||||
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||||
const warnings = resolver.getWarnings(mappings)
|
const warnings = resolver.getWarnings(mappings)
|
||||||
@@ -566,33 +624,105 @@ ipcMain.handle(
|
|||||||
throw new ValidationError('没有有效的生产订单号可处理')
|
throw new ValidationError('没有有效的生产订单号可处理')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. ERP登录
|
// 4. ERP登录
|
||||||
authService = new ErpAuthService({ url, username, password, headless: true })
|
authService = new ErpAuthService({ url, username, password, headless: true })
|
||||||
await authService.login()
|
await authService.login()
|
||||||
|
|
||||||
// 4. 执行提取
|
// 5. 执行提取
|
||||||
const extractor = new ExtractorService(authService)
|
const extractor = new ExtractorService(authService)
|
||||||
const result = await extractor.extract({
|
const result = await extractor.extract({
|
||||||
...input,
|
...input,
|
||||||
orderNumbers: validOrderNumbers
|
orderNumbers: validOrderNumbers
|
||||||
})
|
})
|
||||||
|
|
||||||
// 5. 添加警告到结果
|
// 6. 添加警告到结果
|
||||||
if (warnings.length > 0) {
|
if (warnings.length > 0) {
|
||||||
result.errors = [...warnings, ...result.errors]
|
result.errors = [...warnings, ...result.errors]
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} finally {
|
} finally {
|
||||||
// 6. 资源清理
|
// 7. 资源清理
|
||||||
if (authService) await authService.close()
|
if (authService) await authService.close()
|
||||||
if (mysqlService) await mysqlService.disconnect()
|
if (dbService) await dbService.disconnect()
|
||||||
}
|
}
|
||||||
}, 'extractor:run')
|
}, 'extractor:run')
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 2.1 数据库工厂模式 (database/index.ts)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* 数据库工厂 - 创建数据库服务实例
|
||||||
|
* 支持 MySQL 和 SQL Server 双数据库
|
||||||
|
*/
|
||||||
|
export async function create(type?: DatabaseType): Promise<IDatabaseService> {
|
||||||
|
const dbType = type || getDatabaseType() // 从 DB_TYPE 环境变量读取
|
||||||
|
|
||||||
|
// 返回缓存的实例(单例模式)
|
||||||
|
const cached = instances.get(dbType)
|
||||||
|
if (cached && cached.isConnected()) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
let service: IDatabaseService
|
||||||
|
|
||||||
|
if (dbType === 'sqlserver') {
|
||||||
|
service = new SqlServerService(createSqlServerConfig())
|
||||||
|
} else {
|
||||||
|
service = new MySqlService(createMySqlConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
await service.connect()
|
||||||
|
instances.set(dbType, service) // 缓存实例
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库类型判断
|
||||||
|
*/
|
||||||
|
export function getDatabaseType(): DatabaseType {
|
||||||
|
const dbType = process.env.DB_TYPE?.toLowerCase()
|
||||||
|
if (dbType === 'sqlserver' || dbType === 'mssql') {
|
||||||
|
return 'sqlserver'
|
||||||
|
}
|
||||||
|
return 'mysql'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 数据库服务接口 (types/database.types.ts)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* 数据库服务统一接口
|
||||||
|
*/
|
||||||
|
export interface IDatabaseService {
|
||||||
|
/** 数据库类型标识 */
|
||||||
|
readonly type: DatabaseType
|
||||||
|
|
||||||
|
/** 连接数据库 */
|
||||||
|
connect(): Promise<void>
|
||||||
|
|
||||||
|
/** 断开连接 */
|
||||||
|
disconnect(): Promise<void>
|
||||||
|
|
||||||
|
/** 检查连接状态 */
|
||||||
|
isConnected(): boolean
|
||||||
|
|
||||||
|
/** 执行查询 */
|
||||||
|
query(sql: string, params?: any[]): Promise<QueryResult>
|
||||||
|
|
||||||
|
/** 事务执行 */
|
||||||
|
transaction(queries: { sql: string; params?: any[] }[]): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DatabaseType = 'mysql' | 'sqlserver'
|
||||||
|
```
|
||||||
|
|
||||||
### 3. 提取服务批处理逻辑 (extractor.ts:29-77)
|
### 3. 提取服务批处理逻辑 (extractor.ts:29-77)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -725,21 +855,27 @@ export interface ExtractorResult {
|
|||||||
1. **三层验证机制**:
|
1. **三层验证机制**:
|
||||||
- 前端验证: 非空检查
|
- 前端验证: 非空检查
|
||||||
- 配置验证: 环境变量完整性
|
- 配置验证: 环境变量完整性
|
||||||
- 数据验证: 订单号有效性(通过MySQL查询)
|
- 数据验证: 订单号有效性(通过数据库查询)
|
||||||
|
|
||||||
2. **资源管理策略**:
|
2. **数据库架构 (v1.2 更新)**:
|
||||||
|
- 使用工厂模式 (`create()`) 创建数据库服务实例
|
||||||
|
- 支持 MySQL 和 SQL Server 双数据库,通过 `DB_TYPE` 环境变量切换
|
||||||
|
- 通过 `IDatabaseService` 统一接口实现数据库无关操作
|
||||||
|
- 单例缓存机制,避免重复创建连接
|
||||||
|
|
||||||
|
3. **资源管理策略**:
|
||||||
- 使用 try-finally 确保资源清理
|
- 使用 try-finally 确保资源清理
|
||||||
- 浏览器在使用后立即关闭
|
- 浏览器在使用后立即关闭
|
||||||
- 数据库连接在使用后断开
|
- 数据库连接在使用后断开
|
||||||
- 清理操作在 finally 块中独立 try-catch,避免清理失败影响结果返回
|
- 清理操作在 finally 块中独立 try-catch,避免清理失败影响结果返回
|
||||||
|
|
||||||
3. **错误容错设计**:
|
4. **错误容错设计**:
|
||||||
- 单个批次失败不影响其他批次
|
- 单个批次失败不影响其他批次
|
||||||
- 警告信息独立收集,不影响主流程
|
- 警告信息独立收集,不影响主流程
|
||||||
- 详细错误信息返回给前端展示
|
- 详细错误信息返回给前端展示
|
||||||
- 使用自定义错误类型 (`ValidationError`, `DatabaseQueryError`, `ErpConnectionError`)
|
- 使用自定义错误类型 (`ValidationError`, `DatabaseQueryError`, `ErpConnectionError`)
|
||||||
|
|
||||||
4. **用户体验优化**:
|
5. **用户体验优化**:
|
||||||
- sessionStorage 持久化用户输入(`orderNumbers`, `batchSize`)
|
- sessionStorage 持久化用户输入(`orderNumbers`, `batchSize`)
|
||||||
- 订单号实时同步到共享状态(供 Cleaner 模块使用)
|
- 订单号实时同步到共享状态(供 Cleaner 模块使用)
|
||||||
- 详细的日志记录
|
- 详细的日志记录
|
||||||
@@ -761,9 +897,11 @@ export interface ExtractorResult {
|
|||||||
- **批处理**: 默认每批100个订单,平衡性能与稳定性
|
- **批处理**: 默认每批100个订单,平衡性能与稳定性
|
||||||
- **异步并发**: 使用 async/await 处理异步操作
|
- **异步并发**: 使用 async/await 处理异步操作
|
||||||
- **下载监听**: 使用 Playwright 事件监听处理文件下载
|
- **下载监听**: 使用 Playwright 事件监听处理文件下载
|
||||||
|
- **数据库连接池**: 工厂模式缓存实例,复用连接
|
||||||
|
|
||||||
### 扩展性
|
### 扩展性
|
||||||
|
|
||||||
|
- **数据库可切换**: 通过 `DB_TYPE` 环境变量切换 MySQL/SQL Server
|
||||||
- **配置化**: batchSize 可配置
|
- **配置化**: batchSize 可配置
|
||||||
- **模块化**: 服务独立,易于测试和维护
|
- **模块化**: 服务独立,易于测试和维护
|
||||||
- **错误类型化**: 使用自定义错误类型便于精确处理
|
- **错误类型化**: 使用自定义错误类型便于精确处理
|
||||||
|
|||||||
Reference in New Issue
Block a user