新增按需转码功能;支持批量查询和批量添加
This commit is contained in:
+792
@@ -0,0 +1,792 @@
|
||||
# Uft30ChangeCode 详细设计文档
|
||||
|
||||
| 文档信息 | 内容 |
|
||||
| --- | --- |
|
||||
| 项目名称 | Uft30ChangeCode(UF20 → UFT3 转码工具) |
|
||||
| 文档版本 | V1.6.0 |
|
||||
| 更新日期 | 2026-09-14 |
|
||||
| 技术栈 | Qt 6.6.2(MinGW 64-bit)/ C++ / ElaWidgetTools / SQLite |
|
||||
|
||||
---
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 编写目的
|
||||
|
||||
本文档面向开发与维护人员,描述 Uft30ChangeCode 转码工具的总体架构、模块划分、核心流程与关键设计决策,作为后续开发、测试与维护的依据。
|
||||
|
||||
### 1.2 系统定位
|
||||
|
||||
Uft30ChangeCode 是一款桌面端 **UF20 → UFT3 代码转码工具**,提供:
|
||||
|
||||
- **元数据更新**:后台解析 UF20/UFT3 源码,构建功能元数据索引并落库(SQLite 双缓存库);
|
||||
- **业务转码**:**全量转码**(LS_ 功能清单式整体转码,清单存 `cust.json`)与**按需转码**(非 LS 功能,单文件/递归两种模式,清单存 `cust_nonls.json`,转码范围不含核心文件、module 模块文件),转码动作由外部引擎 `bin/uf2touft3/uf2touft3.exe` 执行;
|
||||
- **代码检索**:功能**调用链路查询**(向上调用方、向下被调),双模式(转码业务/非转码业务),支持导出调用文件与链路文本;
|
||||
- **功能查询**:检索 UF20 / UFT3 功能元数据,支持**多关键词批量查询**(逗号/分号/空格分隔,OR 并集);UF20 查询结果**双击/右键一键分流**——可多选,LS_ 功能加入全量转码、其它功能加入按需转码,加入结果合并为单次提示;UFT3 仅用于查询核对;
|
||||
- **数据映射**:维护 UF20 → UFT3 的表替换、函数替换对照表(存 `cust.json`,转码引擎按映射替换代码中的表名/函数名);
|
||||
- **系统设置**:项目环境路径与主题配置(元数据更新与转码引擎依赖这些路径)。
|
||||
|
||||
### 1.3 术语约定
|
||||
|
||||
| 术语 | 说明 |
|
||||
| --- | --- |
|
||||
| UF20 / UFT3 | 新旧两代系统版本,转码即将 UF20 风格代码转换为 UFT3 风格 |
|
||||
| LS 功能 | "内存交易"改造范围的功能类别;**全量转码仅支持 LS_ 开头功能**,按需转码仅针对非 LS 功能 |
|
||||
| 递归标志 | `cust_nonls.json` 中 `convert.dir` 的取值:`0`=否(不递归),`-1`=是(递归) |
|
||||
| 元数据 | 从源码解析出的功能/函数索引信息,存于 SQLite 缓存库 |
|
||||
| 调用链路 | 某功能的向上调用方(谁调用了它)与向下被调(它调用了谁)构成的关系树 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 总体设计
|
||||
|
||||
### 2.1 技术选型
|
||||
|
||||
| 类别 | 选型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GUI 框架 | Qt 6.6.2(MinGW 64-bit) | 跨平台 C++ 桌面框架 |
|
||||
| UI 组件库 | ElaWidgetTools | Fluent 风格控件(ElaWindow / ElaPivot / ElaTableView / ElaComboBox 等) |
|
||||
| 本地存储 | Qt SQL(QSqlDatabase) | SQLite 双缓存库 `uf20_cache.db` / `uft3_cache.db`,由 `DataCache` 单例统一管理 |
|
||||
| 并发 | QtConcurrent + 信号槽 | 元数据后台解析、代码索引后台构建,避免阻塞 UI |
|
||||
| 进程调用 | QProcess(`PythonRunner` 封装) | 调用外部转码引擎(打包 exe,兼容 Python 脚本模式) |
|
||||
| 文件监听 | QFileSystemWatcher(+ QTimer 防抖) | 监听外部修改 `cust.json`(数据映射/全量转码)与 `cust_nonls.json`(按需转码) |
|
||||
| 日志 | LogManager(单例) | 统一 info/warning/error 日志输出到 exe 目录 `logs\` |
|
||||
|
||||
### 2.2 系统分层架构
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph SHELL["主窗口(src/mainwindow)"]
|
||||
M1["MainWindow(ElaWindow)<br/>页面注册 / 任务调度互斥 / 引擎调用"]
|
||||
end
|
||||
|
||||
subgraph UI["UI 层(src/pages)"]
|
||||
A2[元数据更新 MetadataUpdatePage]
|
||||
A3a["业务转码容器 BusinessConvertPage<br/>(ElaPivot:全量/按需)"]
|
||||
A3[全量转码 BatchConvertPage]
|
||||
A4[按需转码 NonLsConvertPage]
|
||||
A5["功能查询 FunctionSearchPage<br/>(UF20 / UFT3 两个子页)"]
|
||||
A6[代码检索 CodeSearchPage]
|
||||
A7["数据映射 TableMappingPage<br/>FunctionMappingPage"]
|
||||
A8[设置 SettingsPage]
|
||||
A9[关于 AboutDialog]
|
||||
end
|
||||
|
||||
subgraph CORE["业务核心层"]
|
||||
B1["业务策略(src/utils)<br/>BusinessStrategy + Factory<br/>UF20 / UFT3 实现"]
|
||||
B2["配置读取器(src/utils)<br/>ConfigReaderBase + Traits<br/>UF2ConfigReader / UFT3ConfigReader"]
|
||||
B3["元数据解析器组(src/metadataupdate)<br/>MetaDataProcessor / FileDB<br/>+ uf2* / uft3* 解析器"]
|
||||
end
|
||||
|
||||
subgraph UTIL["公共支撑层(src/utils)"]
|
||||
C1[DataCache 缓存读写]
|
||||
C2[CallChainAnalyzer 调用链分析]
|
||||
C3[ConfigManager 配置管理]
|
||||
C4[LogManager 日志]
|
||||
C5[Constants 常量]
|
||||
C6[VersionChecker 更新检查]
|
||||
end
|
||||
|
||||
subgraph EXT["引擎与外部资源"]
|
||||
D1[PythonRunner<br/>QProcess 封装]
|
||||
D2[("bin/uf2touft3/uf2touft3.exe<br/>转码引擎")]
|
||||
D3[("bin/uf2touft3/cache/<br/>uf20_cache.db / uft3_cache.db")]
|
||||
D4[("bin/uf2touft3/config/<br/>cust.json / cust_nonls.json")]
|
||||
D5[("bin/uf2touft3/change_code/<br/>默认代码检索根目录")]
|
||||
D6[UF20/UFT3 源码目录]
|
||||
end
|
||||
|
||||
M1 --> UI
|
||||
M1 --> D1
|
||||
D1 --> D2
|
||||
A3 -.读写.-> D4
|
||||
A4 -.监听/读写.-> D4
|
||||
A7 -.监听/读写.-> D4
|
||||
A6 -.扫描.-> D5
|
||||
B3 -.解析.-> D6
|
||||
B3 --> C1
|
||||
C1 --> D3
|
||||
C2 --> C1
|
||||
UI --> CORE
|
||||
UI --> UTIL
|
||||
CORE --> UTIL
|
||||
```
|
||||
|
||||
**分层职责:**
|
||||
|
||||
- **主窗口**:ElaWindow 容器与导航注册;**全局唯一的 `PythonRunner` 实例**,接收全量/按需转码页的 `startConvert` 信号,负责**任务互斥**、引擎进程启停与输出回收;
|
||||
- **UI 层**:页面构建、用户交互、表格/树渲染,不直接访问数据库/源码,统一经由支撑层与核心层完成;
|
||||
- **业务核心层**:以**策略模式**隔离 UF20/UFT3 差异(`BusinessStrategy`),以**模板方法模式**统一配置读取流程(`ConfigReaderBase` + Traits);元数据解析器组负责将源码解析为结构化键值数据;
|
||||
- **公共支撑层**:缓存读写、调用链分析、配置、日志、版本检查等横切能力。
|
||||
|
||||
### 2.3 目录结构
|
||||
|
||||
```
|
||||
Uft30ChangeCode/
|
||||
├── src/
|
||||
│ ├── mainwindow/ # MainWindow(ElaWindow 主窗口 + 导航注册 + 转码任务调度)
|
||||
│ ├── metadataupdate/ # 元数据解析核心(MetaDataProcessor / FileDB
|
||||
│ │ # + uf2datatype / uf2stdfields / uf2table / uf2interface
|
||||
│ │ # / uf2errnumber / uf2menuandfunction / uf2module 等)
|
||||
│ ├── pythonrunner/ # PythonRunner(外部转码引擎进程封装,QProcess)
|
||||
│ ├── components/ # 通用组件(ElaSearchEdit 搜索框、InterDialog 弹窗)
|
||||
│ ├── pages/
|
||||
│ │ ├── businessconvert/ # 业务转码容器页(ElaPivot:全量/按需 Tab)
|
||||
│ │ ├── batchconvert/ # 全量转码页
|
||||
│ │ ├── nonlsconvert/ # 按需转码页
|
||||
│ │ ├── codesearch/ # 代码检索页(调用链路查询)
|
||||
│ │ ├── functionsearch/ # 功能查询(FunctionSearchPage 基类 + UF20/UFT3 子页)
|
||||
│ │ ├── datamapping/ # 数据映射(表替换 / 函数替换)
|
||||
│ │ ├── metadatupdate/ # 元数据更新页
|
||||
│ │ ├── settings/ # 设置页
|
||||
│ │ └── about/ # 关于(AboutDialog 对话框)
|
||||
│ └── utils/ # Constants.h / business_strategy / configreaderbase
|
||||
│ # / uf2configreader / uft3configreader / callchainanalyzer
|
||||
│ # / datacache / configmanager / logmanager
|
||||
│ # / version / versionchecker
|
||||
├── 3rd/ElaWidgetTools/ # 第三方 UI 库
|
||||
├── bin/
|
||||
│ ├── Uft30ChangeCode.exe # 构建产物
|
||||
│ └── uf2touft3/
|
||||
│ ├── uf2touft3.exe # 转码引擎
|
||||
│ ├── config/ # cust.json / cust_nonls.json / config.ini / change_code.ini
|
||||
│ ├── cache/ # uf20_cache.db / uft3_cache.db(元数据缓存库)
|
||||
│ └── change_code/ # 默认代码检索根目录
|
||||
└── docs/ # 本文档与使用手册
|
||||
```
|
||||
|
||||
### 2.4 模块清单
|
||||
|
||||
| 模块 | 主要类 | 设计模式 | 职责 |
|
||||
| --- | --- | --- | --- |
|
||||
| 主窗口导航 | `MainWindow` | — | ElaWindow 容器、页面注册、**转码任务调度与互斥**、引擎进程管理 |
|
||||
| 业务转码容器 | `BusinessConvertPage` | — | ElaPivot 承载全量/按需两个工作台 |
|
||||
| 全量转码 | `BatchConvertPage` | — | `cust.json` 的 `convert.dir` 清单管理(仅 LS_ 功能)、触发转码 |
|
||||
| 按需转码 | `NonLsConvertPage` | — | 非 LS 功能清单管理、递归模式选择、外部修改同步 |
|
||||
| 功能查询 | `FunctionSearchPage`(基类)、`UF20/UFT3FunctionSearchPage` | 模板方法(基类复用) | 元数据检索(多关键词批量查询);UF20 双击/右键一键分流加入转码清单(合并单次提示),UFT3 纯查询 |
|
||||
| 代码检索 | `CodeSearchPage`、`CallChainAnalyzer` | — | 调用链路查询、索引构建与缓存、导出 |
|
||||
| 数据映射 | `TableMappingPage`、`FunctionMappingPage` | — | `cust.json` 表替换/函数替换映射维护 |
|
||||
| 元数据更新 | `MetadataUpdatePage` + `src/metadataupdate/` 解析器组 | 观察者(信号槽) | 后台解析源码、进度上报、原子落库 |
|
||||
| 设置 | `SettingsPage` | — | 项目环境路径、主题配置 |
|
||||
| 缓存 | `DataCache` | 单例 | QSqlDatabase 双库读写、功能查询内存缓存 |
|
||||
| 引擎调用 | `PythonRunner` | — | 外部进程封装(打包 exe / Python 脚本双模式) |
|
||||
| 日志 | `LogManager` | 单例 | 统一日志 |
|
||||
| 版本检查 | `VersionChecker` | — | 启动时在线检查新版本(`update_info.json`) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 全局流程
|
||||
|
||||
### 3.1 应用启动流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S([main 入口]) --> A[初始化 QApplication / 高分屏属性]
|
||||
A --> B[初始化 LogManager 日志单例]
|
||||
B --> C[初始化 ConfigManager 读取全局配置]
|
||||
C --> D[构造 MainWindow ElaWindow]
|
||||
D --> E["注册导航页面<br/>功能转码:业务转码 / 代码检索<br/>功能查询:UF20 / UFT3<br/>数据映射:表替换 / 函数替换<br/>元数据更新;底部:设置 / 帮助 / 关于"]
|
||||
E --> F[DataCache.initDatabase<br/>打开 uf20_cache.db / uft3_cache.db]
|
||||
F --> G{缓存库是否有效?}
|
||||
G -- 是 --> H[正常进入主界面]
|
||||
G -- 否/为空 --> I[各页面提示: 请先执行元数据更新]
|
||||
H --> J[VersionChecker 后台检查在线版本]
|
||||
I --> J
|
||||
J --> K["singleShot(0) 首帧渲染后:<br/>异步预热 uf20 / uft3 功能缓存"]
|
||||
K --> L([等待用户操作])
|
||||
```
|
||||
|
||||
> 说明:页面在启动时统一注册到 `MainWindow`(ElaWindow 侧边栏分组导航 + 底部按钮)。`DataCache` 在启动时打开两个缓存库(不存在则自动创建),功能查询使用**内存缓存**(`refreshFunctionCache`),首次查询前需元数据落库。启动流程末尾经 `QTimer::singleShot(0)` 在首帧渲染后提交线程池**异步预热**两库功能缓存(`warmupFunctionCacheAsync`,幂等:已就绪或在途则跳过),用户进入功能查询页时无需再等待主线程重建(详见 §8.6)。
|
||||
|
||||
### 3.2 页面导航机制
|
||||
|
||||
- `MainWindow` 基于 `ElaWindow` 实现,侧边栏按分组注册页面:**功能转码**(业务转码、代码检索)、**功能查询**(UF20、UFT3)、**数据映射**(表替换、函数替换)、**元数据更新**;底部为设置、帮助、关于;
|
||||
- 业务转码页内部通过 ElaPivot 切换全量/按需两个工作台;
|
||||
- 页面切换由 ElaWindow 内部路由完成,各页面通过 `showEvent` 感知可见性变化(如按需转码页每次显示时刷新表格,代码检索页每次显示时重读 `cust.json` 清单并按需构建索引)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心数据流
|
||||
|
||||
### 4.1 元数据更新流程
|
||||
|
||||
元数据更新是全工具的数据基石:解析 UF20/UFT3 源码目录,构建功能/函数索引并写入 SQLite 缓存库。解析在**工作线程**中执行,通过信号槽向页面汇报进度,UI 不卡顿。支持**增量更新**(按源文件 mtime 比对跳过未变更文件)与**强制全量更新**(二次确认后全量重扫)。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户(元数据更新页)
|
||||
participant P as MetadataUpdatePage
|
||||
participant R as 元数据解析器组<br/>(src/metadataupdate)
|
||||
participant T as 工作线程
|
||||
participant C as DataCache
|
||||
participant DB as (SQLite 缓存库)
|
||||
|
||||
U->>P: 勾选更新范围 / 点击"更新UFT30 / 更新UF20"
|
||||
P->>R: 启动解析任务(增量 或 强制全量)
|
||||
R->>T: 提交后台解析
|
||||
loop 遍历源码文件
|
||||
T->>T: 解析元数据(uf2* / uft3* 解析器)
|
||||
T->>P: 信号: 进度 / 当前处理项
|
||||
T->>C: saveDataset(键值行, srcMtime)
|
||||
C->>DB: 原子写(临时表 + RENAME)+ 更新 _meta
|
||||
end
|
||||
T-->>R: 解析完成
|
||||
R->>P: 信号: 更新完成(统计结果)
|
||||
P->>C: refreshFunctionCache()
|
||||
P->>U: 展示结果
|
||||
```
|
||||
|
||||
**设计要点:**
|
||||
|
||||
1. **后台化**:解析与写库全部在工作线程,UI 线程仅收发信号;
|
||||
2. **原子落库**:`saveDataset` 以"临时表 + RENAME"方式整体替换表数据,并更新 `_meta` 元信息表(记录源文件 mtime,供增量判断);
|
||||
3. **增量/全量**:默认增量(mtime 比对),且先做**源码目录整体 mtime 快速比对**(0.5s 容差),源码无变化时整个更新秒级跳过;"强制全量更新"复选框需二次确认;
|
||||
4. **缓存生效时机**:更新完成后调用 `refreshFunctionCache` 重建功能查询内存缓存,各页面"是否存在"判断随之生效。
|
||||
|
||||
### 4.2 数据缓存与存在性判断流程
|
||||
|
||||
`DataCache` 是所有"是否存在 / 元数据查询"的统一入口,底层经 `QSqlDatabase` 访问 SQLite,功能查询走内存索引缓存。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([页面发起查询<br/>如 checkUF20CnameExists / searchUF20Functions]) --> B{内存功能缓存<br/>是否已构建?}
|
||||
B -- 否 --> C[读取 *interface 表<br/>refreshFunctionCache 建内存索引]
|
||||
C --> D{加载成功?}
|
||||
D -- 是 --> E[在内存索引中匹配]
|
||||
D -- 否 --> F[返回 未找到<br/>并记录 logWarning]
|
||||
B -- 是 --> E
|
||||
E --> G{命中?}
|
||||
G -- 是 --> H[返回 存在 + 元数据<br/>cname / eName / functionNo]
|
||||
G -- 否 --> F
|
||||
```
|
||||
|
||||
### 4.3 配置读取流程(模板方法 + Traits)
|
||||
|
||||
`ConfigReaderBase` 定义统一的读取骨架(打开文件 → 逐段解析 → 校验 → 输出结构化配置),UF20 与 UFT3 的差异通过子类 + `ConfigReaderTraits` 萃取,避免大量 if-else 分支。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([调用方指定版本]) --> B{版本?}
|
||||
B -- UF20 --> C[UF2ConfigReader]
|
||||
B -- UFT3 --> D[UFT3ConfigReader]
|
||||
C --> T["Traits<UF2><br/>关键字段/节名/正则差异"]
|
||||
D --> T2["Traits<UFT3><br/>关键字段/节名/正则差异"]
|
||||
T --> E[ConfigReaderBase::read<br/>模板方法统一骨架]
|
||||
T2 --> E
|
||||
E --> F[打开配置文件]
|
||||
F --> G{打开成功?}
|
||||
G -- 否 --> X[logWarning + 返回失败]
|
||||
G -- 是 --> H[逐节解析 parseSection 子类实现]
|
||||
H --> I{校验通过?}
|
||||
I -- 否 --> X
|
||||
I -- 是 --> J[输出结构化配置对象]
|
||||
X --> K([结束])
|
||||
J --> K
|
||||
```
|
||||
|
||||
### 4.4 转码执行流程(引擎调用)
|
||||
|
||||
转码动作由外部引擎 `bin/uf2touft3/uf2touft3.exe` 执行。`MainWindow` 持有全局唯一 `PythonRunner`(`UsePackagedExe` 模式,兼容 Python 脚本模式),并负责**全量/按需任务互斥**。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant P as BatchConvertPage /<br/>NonLsConvertPage
|
||||
participant M as MainWindow
|
||||
participant R as PythonRunner
|
||||
participant E as uf2touft3.exe(引擎)
|
||||
|
||||
U->>P: 点击"开始转换"
|
||||
P->>M: startConvert 信号
|
||||
M->>M: beginConvert 任务互斥<br/>(禁用另一 Tab 的开始按钮)
|
||||
M->>R: 组装参数并启动(UsePackagedExe)
|
||||
R->>E: QProcess 启动引擎进程
|
||||
loop 引擎运行
|
||||
E-->>R: stdout / stderr
|
||||
R->>M: standardOutput / standardError 信号
|
||||
M->>M: 前置失败检测(FAIL 行计数)<br/>再解析翻译进度
|
||||
M->>P: 进度与状态展示(进度条 / 停止转换按钮)
|
||||
end
|
||||
E-->>R: finished(exitCode)
|
||||
R->>M: finished 信号
|
||||
M->>M: 成败路由: exitCode==0 且 m_failCount==0<br/>→ onConvertFinished / 否则 onConvertFailed
|
||||
M->>M: 解除任务互斥,展示结果 + 日志
|
||||
M->>M: 成功时: 代码检索页 refreshIndexSilent()<br/>(双模式静默刷新索引)
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
- **全量转码**:引擎读取 `cust.json` 的 `convert.dir` 清单,对清单内 LS_ 功能整体转码;
|
||||
- **按需转码**:`NonLsConvertPage::buildArguments()` 组装引擎参数(含 `-m 2` 模式标识),功能清单与递归标志由 `cust_nonls.json` 提供,引擎按映射关系(`cust.json`)完成表名/函数名替换;
|
||||
- **成败判定**:引擎在个别文件转码失败时退出码仍可能为 0,因此 `MainWindow` 在输出解析链最前端检测 `FAIL` 行(`[N/M] FAIL 函数名`)与汇总行(`批量转码完成: X/Y 通过, Z 失败`)累计 `m_failCount`;收尾按 `正常退出 && 退出码==0 && m_failCount==0` 判定成功,失败时页面调用 `onConvertFailed`——进度条**保持当前进度**(不置 100%)、状态文字"转换失败(N 个文件转换失败)"(详见 §8.7);
|
||||
- **转码后联动**:任务成功后自动静默刷新代码检索索引,保证调用链路反映最新代码。
|
||||
|
||||
---
|
||||
|
||||
## 5. 业务模块详细设计
|
||||
|
||||
### 5.1 业务转码容器(BusinessConvertPage)
|
||||
|
||||
**职责**:单页承载全量转码与按需转码两个工作台,顶部 ElaPivot Tab 切换。
|
||||
|
||||
- 两个工作台(`BatchConvertPage` / `NonLsConvertPage`)作为子页内嵌;
|
||||
- **任务互斥**在 `MainWindow` 层统一控制:任一工作台转换期间,另一工作台的"开始转换"按钮禁用并提示等待,避免并发冲突。
|
||||
|
||||
### 5.2 全量转码模块(BatchConvertPage)
|
||||
|
||||
**职责**:维护 LS_ 功能转码清单并对功能执行整体转码,表格展示"UF20函数名 / 是否存在 / 操作"。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([进入全量转码页]) --> B["加载清单<br/>cust.json → convert.dir 数组"]
|
||||
B --> C[updateTable 渲染 ElaTableView]
|
||||
C --> D{逐行判断}
|
||||
D --> E["checkUF20CnameExists(函数名)<br/>查询 DataCache"]
|
||||
E --> F["存在 → 显示 是<br/>不存在 → 显示 否"]
|
||||
F --> G{用户点击开始转换}
|
||||
G --> H["startConvert 信号 → MainWindow<br/>任务互斥 + PythonRunner 启动引擎"]
|
||||
H --> I{转码结果}
|
||||
I -- 成功 --> K[提示成功 + 日志]
|
||||
I -- 失败 --> L[提示失败原因 + logWarning]
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
- 清单**仅允许 LS_ 开头**的功能(`validateLsFunctionNames` 校验),非 LS 输入被拒绝并提示切换到按需转码;
|
||||
- 清单持久化于 `cust.json` 的 `convert.dir` 数组(元素为 `"函数名.service_design"`);保存时用**正则定位数组做局部替换**,保留文件其余内容与格式;
|
||||
- `QFileSystemWatcher + QTimer`(300ms 防抖)监听 `cust.json` 外部修改,与手工编辑双向同步;
|
||||
- "是否存在"列依赖元数据缓存;**首次运行若未执行元数据更新,可能整列显示"否"**(详见 §8.2)。
|
||||
|
||||
### 5.3 按需转码模块(NonLsConvertPage)
|
||||
|
||||
**职责**:管理"非 LS 功能"的按需转码清单,支持**单文件转码**与**递归转码**两种模式(下拉选择),转码范围不包含核心文件、module 模块文件。清单持久化于 `bin/uf2touft3/config/cust_nonls.json`,并监听外部修改。
|
||||
|
||||
#### 5.3.1 数据结构
|
||||
|
||||
| 成员 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `m_funcList` | `QStringList` | 功能名有序清单(与表格行序一致) |
|
||||
| `m_recursionMap` | `QMap<QString,int>` | 功能名 → 递归标志(`0`=否,`-1`=是),与 json 的 `convert.dir` 同值 |
|
||||
| `m_tableModel` | `QStandardItemModel` | 表格模型,列:`UF20函数名 / 是否存在 / 是否递归 / 操作` |
|
||||
| `m_configWatcher` | `QFileSystemWatcher` | 监听 `cust_nonls.json` 文件与其所在目录 |
|
||||
| `m_reloadTimer` | `QTimer` | 防抖定时器,外部修改后延迟重载 |
|
||||
| `m_lastFileMtime` | `QDateTime` | 上次已处理的文件修改时间,用于过滤自身写入触发的变更 |
|
||||
|
||||
#### 5.3.2 添加功能流程(来自 UF20 功能查询页双击)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant S as UF20FunctionSearchPage
|
||||
participant N as NonLsConvertPage
|
||||
participant J as cust_nonls.json
|
||||
|
||||
U->>S: 双击非 LS 函数
|
||||
S->>S: 校验非 LS / 去重
|
||||
S->>N: addFunction(函数名)
|
||||
N->>N: m_funcList.append(name)
|
||||
N->>N: m_recursionMap.insert(name, 0) // 默认"否"
|
||||
N->>N: updateTable() // 立即渲染新行
|
||||
N->>J: saveToCustNonLsJson(m_funcList)
|
||||
Note over N,J: 写盘后刷新 m_lastFileMtime,<br/>避免 watcher 误判为外部修改
|
||||
N-->>U: 弹窗"已将功能 [x] 添加到按需转码"
|
||||
```
|
||||
|
||||
#### 5.3.3 表格刷新流程(updateTable)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([updateTable]) --> B["removeRows 清空模型<br/>(不使用 blockSignals)"]
|
||||
B --> C{遍历 m_funcList}
|
||||
C --> D[构建行: 函数名 / 是否存在<br/>存在性查询 DataCache]
|
||||
D --> E["是否递归列: setIndexWidget<br/>放置 ElaComboBox(否/是)"]
|
||||
E --> F["操作列: setIndexWidget<br/>放置编辑按钮"]
|
||||
F --> G{还有下一行?}
|
||||
G -- 是 --> D
|
||||
G -- 否 --> H["viewport()->update()<br/>resizeRowsToContents()<br/>scrollToTop() / clearSelection()"]
|
||||
H --> I([刷新完成])
|
||||
```
|
||||
|
||||
**关键设计决策——禁用 `blockSignals`:**
|
||||
|
||||
早期版本在重建模型时使用 `m_tableModel->blockSignals(true)` 防止勾选误触发写盘。但 `QTableView` 依赖 `rowsInserted/rowsRemoved` 信号更新内部布局缓存,屏蔽后**新增行不可见**(数据层行数正确、视图层不渲染)。现方案:
|
||||
|
||||
- 重建期间不屏蔽信号;
|
||||
- 误触发防护改由数据侧保证:`m_recursionMap` 先行预填充,下拉切换回调中"**值相等则短路**",重建时 `setCurrentIndex` 不会产生多余写盘。
|
||||
|
||||
#### 5.3.4 递归模式切换流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([用户切换下拉 否/是]) --> B["currentIndexChanged(index)"]
|
||||
B --> C["recursion = index==1 ? -1 : 0"]
|
||||
C --> D{m_recursionMap 中<br/>旧值 == 新值?}
|
||||
D -- 是 --> E([短路返回, 不写盘])
|
||||
D -- 否 --> F[m_recursionMap 更新]
|
||||
F --> G[saveToCustNonLsJson 持久化]
|
||||
G --> H[刷新 m_lastFileMtime]
|
||||
H --> I([完成])
|
||||
```
|
||||
|
||||
#### 5.3.5 外部修改同步流程(文件监听 + 防抖)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["QFileSystemWatcher<br/>fileChanged / directoryChanged"] --> B[启动/重启 m_reloadTimer 防抖]
|
||||
B --> C{"onReloadTimeout:<br/>当前 mtime != m_lastFileMtime ?"}
|
||||
C -- 否 --> D([忽略: 自身写入或无效变更])
|
||||
C -- 是 --> E[loadFromCustNonLsJson 重新解析]
|
||||
E --> F{解析成功?}
|
||||
F -- 是 --> G[m_funcList / m_recursionMap 更新<br/>updateTable 重绘]
|
||||
F -- 否 --> H[logWarning + 保留内存现状]
|
||||
G --> I([界面与磁盘一致])
|
||||
H --> I
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
- `fileChanged` 在部分编辑器保存时会**移除监听**,回调中检测到即对路径重新 `addPath`,保证持续监听;
|
||||
- 目录监听用于捕获"文件被删除重建"的场景;
|
||||
- mtime 比较过滤掉程序自身写盘引发的回环刷新;
|
||||
- **兼容旧版格式**:旧版 `convert.dir` 为数组(无递归标志),加载时自动兼容、一律按"否"处理;新版为对象 `{功能名: 0|-1}`,引擎兜底键 `"*"` 由程序保存时自动保留。
|
||||
|
||||
#### 5.3.6 按需转码执行模式
|
||||
|
||||
| 模式 | 递归标志 | 行为 |
|
||||
| --- | --- | --- |
|
||||
| 非递归(单文件) | `0` | 仅对目标文件本身执行转码,不处理该文件内调用的函数 |
|
||||
| 递归 | `-1` | 转码目标文件的同时,自动对文件中所调用的关联函数一并完成转码 |
|
||||
|
||||
两种模式均**不包含核心文件、module 模块文件**。执行时 `buildArguments()` 组装引擎参数(含 `-m 2`),由 `MainWindow` 统一经 `PythonRunner` 启动引擎。
|
||||
|
||||
### 5.4 功能查询模块(FunctionSearchPage 基类 + UF20/UFT3 子页)
|
||||
|
||||
**职责**:检索 UF20 / UFT3 功能元数据(依赖 `DataCache`),列:`函数名 / 英文名 / 功能号`;支持函数名/功能号字段切换、精确匹配开关与**多关键词批量查询**。UF20 页结果表支持多选,双击/右键一键加入转码工作台;UFT3 页为纯查询(不接入转码链路)。
|
||||
|
||||
#### 5.4.1 多关键词批量查询
|
||||
|
||||
- **拆词**:查询框输入按**中英文逗号、分号与空白**统一拆分为关键词列表(`splitKeywords`,全角字符以 `QStringLiteral` 字符串字面量处理,规避多字节字符常量问题);
|
||||
- **匹配语义**:多关键词之间为 **OR 并集**——任一关键词在所选字段(函数名/功能号/任一)命中即收录结果;
|
||||
- **去重**:结果按 `cname + '|' + functionNo` 组合键去重(同一功能可能被多个关键词或多个字段重复命中);
|
||||
- **零命中统计**:`DataCache` 多词检索接口带 `keywordsWithoutMatch` 出参,返回零命中关键词个数;
|
||||
- **接口形态**:单词版 `searchUF20Functions(keyword, ...)` 内部委托多词版(`QStringList{keyword}`),两库多词重载共用匿名命名空间 `filterFunctionInfos()` 的单次遍历实现。
|
||||
|
||||
**查询反馈(tip 栏)**:页面底部提示栏随查询状态变化——空关键字提示"请输入查询内容"、无结果提示"⚠️ 未找到匹配的功能"、成功后显示"已查询 X 个业务功能函数(M 个无匹配)"并拼接默认操作提示(UF20 含双击/右键操作说明与批量查询示例),全程不弹模态对话框;提示文字颜色经 `ElaTheme` 主题取色并连接 `themeModeChanged` 信号联动刷新,日间/夜间模式均可读。
|
||||
|
||||
#### 5.4.2 UF20 功能查询一键加入转码工作台
|
||||
|
||||
UF20 页结果表为**扩展多选**(ExtendedSelection)+ 自定义右键菜单;**双击与右键共用**收集逻辑 `addSelectedRowsToConvert()`:收集选区(或右键行)按 `LS_` 前缀分流为两个列表,**一次 emit** 合并信号:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant S as UF20FunctionSearchPage
|
||||
participant M as MainWindow
|
||||
participant B as BatchConvertPage(全量)
|
||||
participant N as NonLsConvertPage(按需)
|
||||
|
||||
U->>S: 双击 / 右键"一键添加功能"(可多选)
|
||||
S->>S: 收集选区,按 LS_ 前缀分流
|
||||
S->>M: addFunctionsToConvert(lsNames, nonLsNames)
|
||||
M->>B: addFunctionsBatch(lsNames, notify=false) 静默
|
||||
M->>N: addFunctionsBatch(nonLsNames, notify=false) 静默
|
||||
Note over B,N: 各自去重(已存在跳过)、写 cust.json / cust_nonls.json;<br/>返回 {新增数, 跳过数},保存失败返回 {-1,-1} 并弹警告
|
||||
M->>U: 合并为单个提示框(RichText):<br/>"已将 N 个功能加入全量转码,已将 M 个功能加入按需转码"
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
- **单次提示**:两工作台 `addFunctionsBatch` 支持静默模式(`notify=false` 不弹任何提示框),由 `MainWindow` 统一汇总——加入成功后**只弹一个**消息框,"全量转码"/"按需转码"字样经 `Qt::RichText` **加粗**;重复项自动跳过并注明"另有 N 个已存在";全部已存在时提示"均已在转码列表中";
|
||||
- **右键交互**:右键行不在选区内时设为唯一选中(Windows 惯例),在选区内则保持多选;空白区域右键不弹菜单;
|
||||
- 批量添加**逐条免弹窗**,替代早期逐条模态确认。
|
||||
|
||||
> **UFT3 功能查询**:界面与 UF20 版一致(含批量关键词查询),仅用于核对 UFT3 元数据,**双击/右键不加入转码清单**;代码检索中"原代码已支持"行的功能号前缀即来自 UFT3 元数据。
|
||||
|
||||
### 5.5 代码检索模块(CodeSearchPage + CallChainAnalyzer)
|
||||
|
||||
**职责**:功能**调用链路查询**(非内容检索)——展示某功能的向上调用方(谁调用了它)与向下被调(它调用了谁),用于转码前评估影响范围、转码后核对结果。
|
||||
|
||||
#### 5.5.1 双模式设计
|
||||
|
||||
页面顶部 ElaPivot 切换两种模式,各模式**独立的分析器实例、检索路径、调用链树**,互不干扰:
|
||||
|
||||
| 模式 | 检索入口 | 导出 |
|
||||
| --- | --- | --- |
|
||||
| 转码业务 | 左侧"业务转码函数列表"(每次显示时重读 `cust.json` 的 `convert.dir`),双击查询 | 导出调用文件(复制需转码文件)+ `_call_chain.txt` |
|
||||
| 非转码业务 | 顶部输入行:**函数名**(支持 `功能号-函数名` 带前缀格式)或**功能号**(经 UFT3 元数据解析为函数英文名)查询 | 仅导出 `_call_chain.txt` |
|
||||
|
||||
检索路径按模式持久化于 `change_code.ini` 的 `[codeSearch]` 节(`convertPath` / `searchPath`)。
|
||||
|
||||
#### 5.5.2 索引构建与缓存加速
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([显示页面 / 切换模式]) --> B{索引已就绪?}
|
||||
B -- 是 --> Z([直接可用])
|
||||
B -- "否,且路径未被手动更改" --> C{"cacheQuickValid<br/>快速粗判缓存(stat 根目录子项,毫秒级)"}
|
||||
C -- 命中 --> D["后台 loadFromCache<br/>(跳过指纹校验,秒级加载)"]
|
||||
C -- 未命中 --> E[后台 buildIndex 全量构建]
|
||||
D -- 失败 --> E
|
||||
E --> F[saveToCache 写缓存]
|
||||
D --> Z
|
||||
F --> Z
|
||||
B -- "否,且路径被手动更改" --> G[提示用户点击"刷新索引"手动触发]
|
||||
```
|
||||
|
||||
- 构建与缓存加载均在 `QtConcurrent` 后台线程执行,界面显示模态进度框;
|
||||
- **手动更改路径**后需点"刷新索引"重新加载(弹窗提示);
|
||||
- 每次转码任务完成后 `MainWindow` 调用 `refreshIndexSilent()`:双模式**静默**(无弹窗)依次后台刷新并写缓存。
|
||||
|
||||
#### 5.5.3 调用链查询与树渲染
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([runQuery 函数名]) --> B["buildCallTree(向下被调链)<br/>buildCallerForest(向上调用方森林,每条链以向下链收尾)"]
|
||||
B --> C[populateTree 渲染调用链树]
|
||||
C --> D["树列:函数名 / 目录 / 模块<br/>默认展开 2 级,列宽按内容自适应"]
|
||||
D --> E{节点状态}
|
||||
E -- ok --> F[普通行]
|
||||
E -- notfound --> G["[未找到] 红色字"]
|
||||
E -- cycle --> H["[循环调用] 橙色字"]
|
||||
E -- depthlimit --> I["[层级截断]"]
|
||||
E -- supported --> J["[原代码已支持] 整行绿色高亮<br/>行前带 UFT3 功能号前缀(功能号来自 UFT3 元数据)"]
|
||||
D --> K["当前查询节点整行蓝色高亮<br/>(自定义 Delegate 强制绘制,绕过 Ela 主题覆盖)"]
|
||||
```
|
||||
|
||||
- 状态 `supported` 表示函数未在检索目录源码中找到,即无需转码、原代码已支持;
|
||||
- 高亮经 `RowHighlightRole` / `RowHighlightBlueRole` + 自定义 `CallTreeDelegate` 强制绘制,避免 Ela 主题覆盖背景色。
|
||||
|
||||
#### 5.5.4 导出设计
|
||||
|
||||
- **导出调用文件**(转码业务):收集链路上所有非 `supported` 节点的源文件,按相对检索根的目录结构复制到 `<检索路径>\function_output\<查询函数名>\`,并生成链路文本 `_call_chain.txt`(缩进树形,含状态标记);
|
||||
- **导出调用链路**(非转码业务):仅生成 `_call_chain.txt`,不复制源文件;
|
||||
- 目录名经 `sanitizeDirName` 清洗 Windows 非法字符。
|
||||
|
||||
### 5.6 数据映射模块(TableMappingPage / FunctionMappingPage)
|
||||
|
||||
**职责**:维护 UF20 → UFT3 的**表替换**(`tableCompareUf2_Uf3`)与**函数替换**(`functionReplace`)映射,两页代码同构,均读写 `cust.json` 对应节点;转码引擎按映射替换代码中的表名/函数名。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([进入表替换 / 函数替换页]) --> B["readCustJson 读取映射节点"]
|
||||
B --> C["渲染表格(QStandardItemModel)<br/>QSortFilterProxyModel 两列不区分大小写过滤"]
|
||||
C --> D{用户操作}
|
||||
D -- 双击 / F2 编辑 --> E[更新模型]
|
||||
D -- 添加 --> F["弹窗:UF20 名必填 + 查重"]
|
||||
D -- 删除选中 --> G[二次确认(支持多选)]
|
||||
E --> H[点击保存]
|
||||
F --> H
|
||||
G --> H
|
||||
H --> I["读取原始文本<br/>extractTopLevelKeyOrder 提取顶层 key 顺序"]
|
||||
I --> J["writeCustJson 按序写回:<br/>本映射节点有序输出,其余节点原样保留"]
|
||||
J --> K["保存后 2s 内忽略 fileChanged<br/>(QTimer)并重新 addPath"]
|
||||
L[外部编辑 cust.json] -. fileChanged .-> M[loadData 立即刷新]
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
- 保存采用**保留顶层 key 顺序的整体重写**:先从原始文本用正则提取 key 出现顺序,再按顺序写出,避免 JSON 重排破坏手工维护的文件结构;
|
||||
- 自身保存后 2 秒内的 `fileChanged` 事件被忽略(防回环),此后恢复监听;
|
||||
- 添加弹窗校验:UF20 名非空且不与现有行重复。
|
||||
|
||||
### 5.7 元数据更新页面(MetadataUpdatePage)
|
||||
|
||||
**职责**:元数据更新的人机入口——勾选更新范围、触发更新、展示进度与统计结果(见 §4.1 时序图)。
|
||||
|
||||
- 页面分 **UFT30更新范围** 与 **UF20更新范围** 两个勾选卡片(UFT30:数据类型/标准字段/表结构/接口数据;UF20:数据类型/标准字段/标准错误号/接口数据/常量/菜单功能/模块/表结构),提供全选/全不选快捷操作;
|
||||
- **强制全量更新**复选框(勾选需二次确认):默认增量更新(mtime 比对),勾选后全量重扫重建;
|
||||
- 源码扫描范围来自设置页配置的项目环境路径(见 §5.8)。
|
||||
|
||||
### 5.8 设置页面(SettingsPage)
|
||||
|
||||
**职责**:环境路径与界面主题配置。
|
||||
|
||||
| 配置项 | 说明 |
|
||||
| --- | --- |
|
||||
| UFT30 / UF20 / UF20账户项目环境路径 | 元数据更新与转码引擎的源码扫描根目录 |
|
||||
| 转码生成路径 | 转码结果输出目录 |
|
||||
|
||||
- 路径保存于 `config.ini` 的 `[projectPath]` 节;保存时程序自动同步维护 `uft30pub = <UFT30路径>\upub\dev_codes`;
|
||||
- 主题模式(日间/夜间)即时切换;"恢复默认"需二次确认。
|
||||
|
||||
---
|
||||
|
||||
## 6. 公共支撑模块
|
||||
|
||||
### 6.1 DataCache(单例)
|
||||
|
||||
- 元数据缓存的门面:双库(`uf20_cache.db` / `uft3_cache.db`,位于 `bin/uf2touft3/cache/`),由 QSqlDatabase 直接管理,无独立 SQLite 封装类;表结构统一为 `(name TEXT PRIMARY KEY, data TEXT)`(`data` 存 JSON),与 Python 侧 `src/store/schema.py` 对齐;
|
||||
- **原子写**:`saveDataset()` 先写临时表再 `RENAME` 替换正式表,随后更新 `_meta` 表记录源文件 mtime,供增量更新判断;
|
||||
- **写闸门**:`saveDataset()` 全程持有 `m_writeGate` 写闸门;`clearCache()` 以 `tryLock` 探测在途落库任务——在途时仅重置内存状态并记录警告、跳过连接重置,避免关闭正在写入的连接(闸门在 `database()` 取连接之后获取,锁序安全无死锁,详见 §8.4);
|
||||
- **内存功能索引**:`refreshFunctionCache()` 将两库功能表加载至内存(`FunctionInfo{cname, eName, functionNo}`),支撑功能查询页即时检索;启动时 `MainWindow` 经 `warmupFunctionCacheAsync(db)` **异步预热**(后台线程构建内存索引,幂等:已就绪/在途跳过,详见 §8.6);
|
||||
- **检索 API**:`searchUF20Functions()` / `searchUFT3Functions()` 支持按中文名/英文名/功能号字段过滤,均有**单词与多词两个重载**——多词版(`QStringList` + `keywordsWithoutMatch` 出参)实现 OR 并集、按 `cname|functionNo` 去重与零命中统计,单词版内部委托多词版;两库共用匿名命名空间 `filterFunctionInfos()` 过滤实现——代码检索"按功能号检索"即经 `searchUFT3Functions(raw, "function_no", true)` 将功能号解析为函数英文名;
|
||||
- **数据集 API**:`datasetExists()` / `getAllRows()` / `getRowData()` 供功能查询页读取指定功能完整数据。
|
||||
|
||||
### 6.2 LogManager(单例)
|
||||
|
||||
- 两步初始化:`initLogConfig()` 读取 `change_code.ini` 的 `[log]` 节(级别/开关),`initLogFile()` 按"日期前缀"命名在 `exe/logs/` 目录创建日志文件;
|
||||
- 提供 `log / logInfo / logWarning / logError` 四级接口,MainWindow、ConfigManager、ConfigReaderBase 及各页面统一经单例写入。
|
||||
|
||||
### 6.3 ConfigManager(单例)
|
||||
|
||||
- 管理 `config.ini`(`[projectPath]` 节),启动时全量加载至内存缓存;
|
||||
- `setValue()` 更新缓存并立即回写文件;读取侧复用 `ConfigReaderBase`(QSettings 兼容读取,加载过程经 LogManager 记录)。
|
||||
|
||||
### 6.4 Constants.h(全局常量)
|
||||
|
||||
| 常量 | 值 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| ToolDir / ChangeCodeDir / FunctionOutput / CacheDir | `uf2touft3` / `change_code` / `function_output` / `cache` | 引擎目录与输出子目录 |
|
||||
| Exe | `uf2touft3.exe` | 转码引擎可执行文件名 |
|
||||
| CustJson / CustNonLsJson | `config/cust.json` / `config/cust_nonls.json` | 转码配置文件 |
|
||||
| Uf20DbFile / Uft3DbFile | `uf20_cache.db` / `uft3_cache.db` | 双缓存库文件名 |
|
||||
| ChangeCodeIni | `change_code.ini` | 路径持久化与日志配置文件 |
|
||||
| CodeSearch::ConvertPathKey / SearchPathKey | `codeSearch/convertPath` / `codeSearch/searchPath` | 代码检索路径配置键 |
|
||||
| updateInfo() | `<Base>/download/uft3changecode/update_info.json` | 版本检查地址 |
|
||||
|
||||
另含界面表格列索引、递归扫描标志等常量,集中管理避免魔法数字。
|
||||
|
||||
### 6.5 CallChainAnalyzer(调用链分析)
|
||||
|
||||
代码检索页持有两个实例(转码业务 / 非转码业务),各自独立索引与缓存。
|
||||
|
||||
| 环节 | 方法 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 全量构建 | `buildIndex` | 扫描源码目录解析调用关系,完成后 `saveToCache` 持久化 |
|
||||
| 快速粗判 | `cacheQuickValid` | 比对源目录特征与缓存指纹,秒级判断缓存是否可复用 |
|
||||
| 缓存加载 | `loadFromCache(skipFingerprint)` | 命中时秒级恢复索引;`skipFingerprint=true` 跳过指纹校验强制重载 |
|
||||
| 最优匹配 | `lookupBest` | 将输入函数名解析为索引中的目标函数 |
|
||||
| 向下被调 | `buildCallTree` | 以查询函数为根展开"它调用了谁" |
|
||||
| 向上调用方 | `buildCallerForest` | 反向展开"谁调用了它" |
|
||||
|
||||
- 节点状态:`ok` / `notfound`(索引中未找到)/ `cycle`(循环调用,终止展开)/ `depthlimit`(超出深度上限)/ `supported`(转码业务中该函数已支持转码,绿色标识);
|
||||
- 查询流程:双击列表行 → `runQuery` → buildCallTree + buildCallerForest → 填充树控件 → `expandToDepth(2)` 默认展开两层;蓝色当前行、绿色 supported 行经自定义 Role 由 CallTreeDelegate 强制绘制。
|
||||
|
||||
### 6.6 PythonRunner(引擎进程封装)
|
||||
|
||||
- `RunMode` 枚举:`UsePackagedExe`(打包 exe)/ `UsePythonScript`(Python 脚本)/ `AutoDetect`;本程序固定采用 UsePackagedExe,指向 `bin/uf2touft3/uf2touft3.exe`;
|
||||
- 封装 QProcess:`start()` 启动、`stop()` 终止、`runSync()` 同步等待;`start()` 为**异步启动**(不再 `waitForStarted` 阻塞 UI 线程),重复发起检查覆盖 `Starting/Running` 状态;
|
||||
- 信号:`finished(exitCode)` / `standardOutput(QString)` / `standardError(QString)` / `errorOccurred(QProcess::ProcessError)`;MainWindow 将输出汇聚至页面日志区,`finished` 用于解除任务互斥,`errorOccurred` 的 `FailedToStart` 用于启动失败收尾(详见 §8.5)。
|
||||
|
||||
### 6.7 VersionChecker
|
||||
|
||||
- 启动时经 QNetworkAccessManager 拉取 `update_info.json`,解析多版本发布列表,取 `Version::compare` 比较后的最大版本为最新版;
|
||||
- 与当前版本比较后发射 `newVersionAvailable(latestVersion, releaseNotes)` / `checkError(msg)` / `noUpdateNeeded()`,MainWindow 弹出更新提示,"关于"页亦可手动检查。
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据设计
|
||||
|
||||
### 7.1 SQLite 缓存库
|
||||
|
||||
- 位置:`bin/uf2touft3/cache/`,双库 `uf20_cache.db` / `uft3_cache.db`;
|
||||
- 每个元数据文件一张表:`(name TEXT PRIMARY KEY, data TEXT)`,`data` 为该文件解析结果的 JSON;
|
||||
- `_meta` 表记录每张表对应的源文件 mtime,元数据更新时据此跳过未变化文件(增量更新);
|
||||
- 原子写:新建临时表插入完成后 `RENAME` 覆盖正式表,避免中途失败留下半写状态。
|
||||
|
||||
### 7.2 cust.json(转码配置)
|
||||
|
||||
| 配置 | 内容 | 消费方 |
|
||||
| --- | --- | --- |
|
||||
| `tableCompareUf2_Uf3` | UF20→UFT3 表名映射 | 全量/按需转码的表替换 |
|
||||
| `functionReplace` | 函数替换规则 | 转码的函数替换 |
|
||||
| 其余键 | 引擎自身配置 | 原样透传 |
|
||||
|
||||
- 数据映射两页各自只读写所属节,其余内容原样保留;
|
||||
- 程序保存时经 `extractTopLevelKeyOrder` 提取顶层 key 顺序、`writeCustJson` 按原顺序写回,不破坏 JSON 键序。
|
||||
|
||||
### 7.3 cust_nonls.json(非转码业务配置)
|
||||
|
||||
```json
|
||||
{
|
||||
"convert": {
|
||||
"dir": { "功能名": 0, "另一功能": -1, "*": 0 },
|
||||
"ext": ["c", "h"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `dir.<功能名>`:`0` 纳入转码、`-1` 不纳入;`"*"` 为兜底键;
|
||||
- `ext`:纳入转码的扩展名白名单;
|
||||
- 兼容旧版数组格式(仅扩展名列表),读取时自动转换。
|
||||
|
||||
### 7.4 ini 配置
|
||||
|
||||
| 文件 | 节/键 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| config.ini | `[projectPath]` | UFT30/UF20/项目环境路径、转码生成路径;保存时同步维护 `uft30pub = <UFT30路径>\upub\dev_codes` |
|
||||
| change_code.ini | `[log]` | 日志级别/开关,LogManager 启动时读取 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 关键设计决策与问题记录
|
||||
|
||||
### 8.1 表格刷新不使用 blockSignals
|
||||
|
||||
按需转码页 `updateTable()` 全量刷新模型时**有意不用** blockSignals:QTableView 依赖 `rowsRemoved` / `rowsInserted` 信号更新内部布局缓存,屏蔽信号会导致新增行不可见。替代方案:先 `removeRows` 清空再逐行重建;`m_recursionMap` 已预填充,值相等时不写盘,避免刷新回环。
|
||||
|
||||
### 8.2 启动时引擎存在性校验
|
||||
|
||||
MainWindow 启动即校验 `bin/uf2touft3/uf2touft3.exe` 是否存在:缺失时记录错误日志并界面提示,后续转码请求直接拒绝,避免 QProcess 启动失败的报错含糊难排查。
|
||||
|
||||
### 8.3 外部配置修改的监听与防抖
|
||||
|
||||
两套机制并行,按页面职责选择:
|
||||
|
||||
| 页面 | 监听对象 | 机制 |
|
||||
| --- | --- | --- |
|
||||
| 全量转码 / 按需转码 | cust.json / cust_nonls.json | 同时监听**文件与所在目录**(Windows 编辑器原子替换"写临时文件→重命名"常使 fileChanged 失效,directoryChanged 更可靠);变更后经 300ms 防抖定时器,比对 mtime 有变化才重载 |
|
||||
| 数据映射(表/函数替换) | cust.json | `fileChanged` 后立即重载;程序自身保存后 2 秒内(`m_isSaving` + `QTimer::singleShot(2000)`)忽略变更,防止保存回环 |
|
||||
|
||||
### 8.4 落库与清缓存的并发保护(写闸门)
|
||||
|
||||
`saveDataset()`(后台线程落库)与 `clearCache()`(重置缓存连接)并发时,直接关闭连接会中断在途写入。设计:`saveDataset` 取得数据库连接后全程持有 `m_writeGate` 写闸门;`clearCache` 等待预热任务结束后以 `tryLock` 探测闸门——加锁失败说明落库在途,仅重置内存状态并记录警告、跳过连接重置;加锁成功则持闸门完成连接清理。锁序安全:`saveDataset` 在取闸门前已完成 `database()` 调用(`m_connMutex` 短暂持锁即释放),与 `clearCache` 的"闸门 → 连接锁"顺序不构成死锁环。
|
||||
|
||||
### 8.5 转码引擎异步启动与幂等收尾
|
||||
|
||||
`PythonRunner::start()` 原在主线程 `waitForStarted(3000)` 等待启动,杀毒软件扫描引擎 exe 时界面可卡顿达 3 秒。现改为异步启动:`start()` 触发 `QProcess::start` 后立即返回,启动结果经 `started` / `errorOccurred` 信号异步上报。MainWindow 连接 `errorOccurred`,对 `FailedToStart` 立即执行失败收尾(恢复两 Tab 界面并提示);配合 `m_taskActive` 在途守卫,正常结束(`finished`)与启动失败(Qt 对 FailedToStart 亦会补发 `finished(-1, CrashExit)`)两条路径的收尾**幂等**——先到者执行、后到者跳过,避免重复弹窗。`runSync()` 同步语义不受影响(启动失败视为"已结束",`waitForFinished` 立即返回)。
|
||||
|
||||
### 8.6 功能缓存启动预热
|
||||
|
||||
功能查询内存缓存(2 万~5 万条)原先在**首次进入页面时于主线程同步重建**(uf20 约 4.3s、uft3 约 2.7s),且 uft3 无触发点、每次进页都重建。现改为**启动即预热**:`MainWindow` 构造尾部经 `QTimer::singleShot(0, ...)` 在首帧渲染完成后调用 `DataCache::warmupFunctionCacheAsync("uf20"/"uft3")`,向 QtConcurrent 线程池提交后台重建;接口**幂等**(缓存已就绪或预热在途时直接返回),进页面时的同步路径仅在预热未完成且等待超限(≤5s)时才兜底重建,正常情况下进页面"接管"预热结果即可,无感知加载。元数据更新落库后 `refreshFunctionCache` 触发重建,预热不干扰该链路。
|
||||
|
||||
### 8.7 转码失败的准确判定(FAIL 行检测)
|
||||
|
||||
引擎在**个别文件转码失败时退出码仍为 0**(实测:`[N/M] FAIL 函数名` 行后流水线正常收尾),仅凭退出码会把失败误报为"转换完成"。设计:`MainWindow::onPythonRunnerOutput` 在解析链**最前端**做失败检测——正则累计 `FAIL` 行数(含 `[n/m]` 计数格式),并匹配汇总行 `批量转码完成: X/Y 通过, Z 失败` 取最大值,累计至 `m_failCount`;`[n/m]` 翻译进度解析仅在无 FAIL 行时执行(FAIL 行含 `[n/m]` 会被误判为进度百分比)。收尾(`onPythonRunnerFinished`)按 `正常退出 && 退出码==0 && m_failCount==0` 路由成败:失败时调用两页 `onConvertFailed`——进度条**保持当前进度**(不置 100%)、状态显示"转换失败(N 个文件转换失败)"、日志记录 ERROR 并弹窗提示查看 `logs\` 日志。
|
||||
|
||||
---
|
||||
|
||||
## 9. 构建与部署
|
||||
|
||||
### 9.1 构建
|
||||
|
||||
- Qt 6.6.2(MinGW 64-bit)+ ElaWidgetTools + CMake;
|
||||
- 第三方库经 `3rdparty/` 引入,构建产出 `bin/Uft30ChangeCode.exe`。
|
||||
|
||||
### 9.2 部署清单(bin/)
|
||||
|
||||
| 路径 | 说明 |
|
||||
| --- | --- |
|
||||
| `Uft30ChangeCode.exe` | 主程序 |
|
||||
| `uf2touft3/uf2touft3.exe` | 转码引擎(外部进程) |
|
||||
| `uf2touft3/config/cust.json` | 表/函数映射配置(数据映射页读写) |
|
||||
| `uf2touft3/config/cust_nonls.json` | 非转码业务配置(业务转码页读写) |
|
||||
| `uf2touft3/cache/uf20_cache.db` / `uft3_cache.db` | 元数据缓存库(首次元数据更新生成) |
|
||||
| `config.ini` / `change_code.ini` | 程序路径配置 / 日志配置 |
|
||||
| `logs/` | 日志输出目录(运行生成) |
|
||||
| `change_code/`、`uf2touft3/function_output/` | 转码输出 / 调用链导出目录(运行生成) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录:页面与源码对照
|
||||
|
||||
| 页面/功能 | 源码位置 |
|
||||
| --- | --- |
|
||||
| 主窗口与导航、引擎调度 | `src/mainwindow/` |
|
||||
| 业务转码 | `src/pages/businessconvert/`(容器)+ `src/pages/batchconvert/`(全量)+ `src/pages/nonlsconvert/`(按需) |
|
||||
| 功能查询 | `src/pages/functionsearch/`(UF20 / UFT3 双模式) |
|
||||
| 代码检索 | `src/pages/codesearch/`(含 CallChainAnalyzer) |
|
||||
| 数据映射 | `src/pages/datamapping/`(TableMappingPage / FunctionMappingPage) |
|
||||
| 元数据更新 | `src/pages/metadataupdate/`(页面)+ `src/metadataupdate/`(解析器组) |
|
||||
| 设置 / 关于 | `src/pages/settings/` / `src/pages/about/` |
|
||||
| 公共支撑 | `src/utils/`(DataCache、LogManager、ConfigManager、VersionChecker、Constants 等)、`src/pythonrunner/`、`src/components/` |
|
||||
Reference in New Issue
Block a user