# 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)
页面注册 / 任务调度互斥 / 引擎调用"]
end
subgraph UI["UI 层(src/pages)"]
A2[元数据更新 MetadataUpdatePage]
A3a["业务转码容器 BusinessConvertPage
(ElaPivot:全量/按需)"]
A3[全量转码 BatchConvertPage]
A4[按需转码 NonLsConvertPage]
A5["功能查询 FunctionSearchPage
(UF20 / UFT3 两个子页)"]
A6[代码检索 CodeSearchPage]
A7["数据映射 TableMappingPage
FunctionMappingPage"]
A8[设置 SettingsPage]
A9[关于 AboutDialog]
end
subgraph CORE["业务核心层"]
B1["业务策略(src/utils)
BusinessStrategy + Factory
UF20 / UFT3 实现"]
B2["配置读取器(src/utils)
ConfigReaderBase + Traits
UF2ConfigReader / UFT3ConfigReader"]
B3["元数据解析器组(src/metadataupdate)
MetaDataProcessor / FileDB
+ uf2* / uft3* 解析器"]
end
subgraph UTIL["公共支撑层(src/utils)"]
C1[DataCache 缓存读写]
C2[CallChainAnalyzer 调用链分析]
C3[ConfigManager 配置管理]
C4[LogManager 日志]
C5[Constants 常量]
C6[VersionChecker 更新检查]
end
subgraph EXT["引擎与外部资源"]
D1[PythonRunner
QProcess 封装]
D2[("bin/uf2touft3/uf2touft3.exe
转码引擎")]
D3[("bin/uf2touft3/cache/
uf20_cache.db / uft3_cache.db")]
D4[("bin/uf2touft3/config/
cust.json / cust_nonls.json")]
D5[("bin/uf2touft3/change_code/
默认代码检索根目录")]
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["注册导航页面
功能转码:业务转码 / 代码检索
功能查询:UF20 / UFT3
数据映射:表替换 / 函数替换
元数据更新;底部:设置 / 帮助 / 关于"]
E --> F[DataCache.initDatabase
打开 uf20_cache.db / uft3_cache.db]
F --> G{缓存库是否有效?}
G -- 是 --> H[正常进入主界面]
G -- 否/为空 --> I[各页面提示: 请先执行元数据更新]
H --> J[VersionChecker 后台检查在线版本]
I --> J
J --> K["singleShot(0) 首帧渲染后:
异步预热 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 元数据解析器组
(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([页面发起查询
如 checkUF20CnameExists / searchUF20Functions]) --> B{内存功能缓存
是否已构建?}
B -- 否 --> C[读取 *interface 表
refreshFunctionCache 建内存索引]
C --> D{加载成功?}
D -- 是 --> E[在内存索引中匹配]
D -- 否 --> F[返回 未找到
并记录 logWarning]
B -- 是 --> E
E --> G{命中?}
G -- 是 --> H[返回 存在 + 元数据
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>
关键字段/节名/正则差异"]
D --> T2["Traits<UFT3>
关键字段/节名/正则差异"]
T --> E[ConfigReaderBase::read
模板方法统一骨架]
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 /
NonLsConvertPage
participant M as MainWindow
participant R as PythonRunner
participant E as uf2touft3.exe(引擎)
U->>P: 点击"开始转换"
P->>M: startConvert 信号
M->>M: beginConvert 任务互斥
(禁用另一 Tab 的开始按钮)
M->>R: 组装参数并启动(UsePackagedExe)
R->>E: QProcess 启动引擎进程
loop 引擎运行
E-->>R: stdout / stderr
R->>M: standardOutput / standardError 信号
M->>M: 前置失败检测(FAIL 行计数)
再解析翻译进度
M->>P: 进度与状态展示(进度条 / 停止转换按钮)
end
E-->>R: finished(exitCode)
R->>M: finished 信号
M->>M: 成败路由: exitCode==0 且 m_failCount==0
→ onConvertFinished / 否则 onConvertFailed
M->>M: 解除任务互斥,展示结果 + 日志
M->>M: 成功时: 代码检索页 refreshIndexSilent()
(双模式静默刷新索引)
```
**要点:**
- **全量转码**:引擎读取 `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["加载清单
cust.json → convert.dir 数组"]
B --> C[updateTable 渲染 ElaTableView]
C --> D{逐行判断}
D --> E["checkUF20CnameExists(函数名)
查询 DataCache"]
E --> F["存在 → 显示 是
不存在 → 显示 否"]
F --> G{用户点击开始转换}
G --> H["startConvert 信号 → MainWindow
任务互斥 + 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` | 功能名 → 递归标志(`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,
避免 watcher 误判为外部修改
N-->>U: 弹窗"已将功能 [x] 添加到按需转码"
```
#### 5.3.3 表格刷新流程(updateTable)
```mermaid
flowchart TD
A([updateTable]) --> B["removeRows 清空模型
(不使用 blockSignals)"]
B --> C{遍历 m_funcList}
C --> D[构建行: 函数名 / 是否存在
存在性查询 DataCache]
D --> E["是否递归列: setIndexWidget
放置 ElaComboBox(否/是)"]
E --> F["操作列: setIndexWidget
放置编辑按钮"]
F --> G{还有下一行?}
G -- 是 --> D
G -- 否 --> H["viewport()->update()
resizeRowsToContents()
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 中
旧值 == 新值?}
D -- 是 --> E([短路返回, 不写盘])
D -- 否 --> F[m_recursionMap 更新]
F --> G[saveToCustNonLsJson 持久化]
G --> H[刷新 m_lastFileMtime]
H --> I([完成])
```
#### 5.3.5 外部修改同步流程(文件监听 + 防抖)
```mermaid
flowchart TD
A["QFileSystemWatcher
fileChanged / directoryChanged"] --> B[启动/重启 m_reloadTimer 防抖]
B --> C{"onReloadTimeout:
当前 mtime != m_lastFileMtime ?"}
C -- 否 --> D([忽略: 自身写入或无效变更])
C -- 是 --> E[loadFromCustNonLsJson 重新解析]
E --> F{解析成功?}
F -- 是 --> G[m_funcList / m_recursionMap 更新
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;
返回 {新增数, 跳过数},保存失败返回 {-1,-1} 并弹警告
M->>U: 合并为单个提示框(RichText):
"已将 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
快速粗判缓存(stat 根目录子项,毫秒级)"}
C -- 命中 --> D["后台 loadFromCache
(跳过指纹校验,秒级加载)"]
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(向下被调链)
buildCallerForest(向上调用方森林,每条链以向下链收尾)"]
B --> C[populateTree 渲染调用链树]
C --> D["树列:函数名 / 目录 / 模块
默认展开 2 级,列宽按内容自适应"]
D --> E{节点状态}
E -- ok --> F[普通行]
E -- notfound --> G["[未找到] 红色字"]
E -- cycle --> H["[循环调用] 橙色字"]
E -- depthlimit --> I["[层级截断]"]
E -- supported --> J["[原代码已支持] 整行绿色高亮
行前带 UFT3 功能号前缀(功能号来自 UFT3 元数据)"]
D --> K["当前查询节点整行蓝色高亮
(自定义 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)
QSortFilterProxyModel 两列不区分大小写过滤"]
C --> D{用户操作}
D -- 双击 / F2 编辑 --> E[更新模型]
D -- 添加 --> F["弹窗:UF20 名必填 + 查重"]
D -- 删除选中 --> G[二次确认(支持多选)]
E --> H[点击保存]
F --> H
G --> H
H --> I["读取原始文本
extractTopLevelKeyOrder 提取顶层 key 顺序"]
I --> J["writeCustJson 按序写回:
本映射节点有序输出,其余节点原样保留"]
J --> K["保存后 2s 内忽略 fileChanged
(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 = \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() | `/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 = \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/` |