55 KiB
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,转码引擎按映射替换代码中的表名/函数名,清单内功能函数名跳过替换); - 系统设置:项目环境路径(含 UF20清算项目环境路径)与主题配置(元数据更新与转码引擎依赖这些路径)。
1.3 术语约定
| 术语 | 说明 |
|---|---|
| UF20 / UFT3 | 新旧两代系统版本,转码即将 UF20 风格代码转换为 UFT3 风格 |
| LS 功能 | "内存交易"改造范围的功能类别;全量转码仅支持 LS_ 开头功能,按需转码仅针对非 LS 功能 |
| 递归标志 | cust_nonls.json 中 convert.dir 条目 depth 的取值:0=否(不递归),-1=是(递归) |
| SQL 方言(dialect) | 转码引擎 SQL 语法转换目标:mdb / db。全量转码由核心派生(ucbp/uses→mdb,uarg/usms→db);按需转码由"db模式"列选择(否=mdb,是=db) |
| 元数据 | 从源码解析出的功能/函数索引信息,存于 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 系统分层架构
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/ # 数据映射(表替换 / 函数替换 / 不转码功能函数)
│ │ ├── metadataupdate/ # 元数据更新页
│ │ ├── settings/ # 设置页
│ │ └── about/ # 关于(AboutDialog 对话框)
│ └── utils/ # Constants.h / business_strategy / configreaderbase
│ # / uf2configreader / uft3configreader / callchainanalyzer
│ # / datacache / configmanager / logmanager / custjsonio
│ # / 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 功能清单管理、递归模式与 db 模式(SQL 方言)选择、外部修改同步 |
| 功能查询 | FunctionSearchPage(基类)、UF20/UFT3FunctionSearchPage |
模板方法(基类复用) | 元数据检索(多关键词批量查询);UF20 双击/右键一键分流加入转码清单(合并单次提示),UFT3 纯查询 |
| 代码检索 | CodeSearchPage、CallChainAnalyzer |
— | 调用链路查询、索引构建与缓存、导出 |
| 数据映射 | TableMappingPage、FunctionMappingPage、NoChangeFullNamePage |
— | cust.json 表替换/函数替换映射维护、不转码功能函数清单维护(增删改查) |
| 元数据更新 | MetadataUpdatePage + src/metadataupdate/ 解析器组 |
观察者(信号槽) | 后台解析源码、进度上报、原子落库 |
| 设置 | SettingsPage |
— | 项目环境路径(含 UF20清算项目环境路径 UftdbSett)、主题配置 |
| 缓存 | DataCache |
单例 | QSqlDatabase 双库读写、功能查询内存缓存 |
| 引擎调用 | PythonRunner |
— | 外部进程封装(打包 exe / Python 脚本双模式) |
| 日志 | LogManager |
单例 | 统一日志 |
| 版本检查 | VersionChecker |
— | 启动时在线检查新版本(update_info.json) |
3. 全局流程
3.1 应用启动流程
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 比对跳过未变更文件)与强制全量更新(二次确认后全量重扫)。
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: 展示结果
设计要点:
- 后台化:解析与写库全部在工作线程,UI 线程仅收发信号;
- 原子落库:
saveDataset以"临时表 + RENAME"方式整体替换表数据,并更新_meta元信息表(记录源文件 mtime,供增量判断); - 增量/全量:默认增量(mtime 比对),且先做源码目录整体 mtime 快速比对(0.5s 容差),源码无变化时整个更新秒级跳过;"强制全量更新"复选框需二次确认;
- 缓存生效时机:更新完成后调用
refreshFunctionCache重建功能查询内存缓存,各页面"是否存在"判断随之生效。
4.2 数据缓存与存在性判断流程
DataCache 是所有"是否存在 / 元数据查询"的统一入口,底层经 QSqlDatabase 访问 SQLite,功能查询走内存索引缓存。
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 分支。
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 脚本模式),并负责全量/按需任务互斥。
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清单(V2 对象数组{name, core, viaUmgr, dialect}),对清单内 LS_ 功能整体转码,核心/柜面接口/SQL 方言随清单下发(契约见docs/转码配置格式契约.md); - 按需转码:
NonLsConvertPage::buildArguments()组装引擎参数(含-m 2模式标识),功能清单、递归标志(depth)与 SQL 方言(dialect)由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函数名 / 是否存在 / 核心 / 是否柜面接口"(无操作列,双击函数名所在行触发编辑)。
flowchart TD
A([进入全量转码页]) --> B["加载清单<br/>cust.json → convert.dir 对象数组<br/>(V2: {name, core, viaUmgr, dialect})"]
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对象数组(V2 元素{name, core, viaUmgr, dialect},dialect 由 core 派生:ucbp/uses→mdb、uarg/usms→db);保存时用正则定位数组做局部替换,保留文件其余内容与格式;加载兼容 V1 字符串数组(按 core=ucbp、viaUmgr=false、dialect=mdb 兜底); QFileSystemWatcher + QTimer(300ms 防抖)监听cust.json外部修改,与手工编辑双向同步;- "是否存在"列依赖元数据缓存;首次运行若未执行元数据更新,可能整列显示"否"(详见 §8.2)。
5.3 按需转码模块(NonLsConvertPage)
职责:管理"非 LS 功能"的按需转码清单,支持单文件转码与递归转码两种模式(下拉选择),并为每项功能选择 db 模式(SQL 方言:否=mdb,是=db),转码范围不包含核心文件、module 模块文件。清单持久化于 bin/uf2touft3/config/cust_nonls.json,并监听外部修改。
5.3.1 数据结构
| 成员 | 类型 | 说明 |
|---|---|---|
m_funcList |
QStringList |
功能名有序清单(与表格行序一致) |
m_recursionMap |
QMap<QString,int> |
功能名 → 递归标志(0=否,-1=是),对应 json 的 convert.dir[名].depth |
m_dialectMap |
QMap<QString,QString> |
功能名 → SQL 方言(mdb=否/默认,db=是),对应 json 的 convert.dir[名].dialect |
m_tableModel |
QStandardItemModel |
表格模型,列:UF20函数名 / 是否存在 / 是否递归 / db模式(双击函数名所在行触发编辑) |
m_configWatcher |
QFileSystemWatcher |
监听 cust_nonls.json 文件与其所在目录 |
m_reloadTimer |
QTimer |
防抖定时器,外部修改后延迟重载 |
m_lastFileMtime |
QDateTime |
上次已处理的文件修改时间,用于过滤自身写入触发的变更 |
5.3.2 添加功能流程(来自 UF20 功能查询页双击)
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)
flowchart TD
A([updateTable]) --> B["removeRows 清空模型<br/>(不使用 blockSignals)"]
B --> C{遍历 m_funcList}
C --> D[构建行: 函数名 / 是否存在<br/>存在性查询 DataCache]
D --> E["是否递归列: setIndexWidget<br/>放置 ElaComboBox(否/是)"]
E --> F["双击函数名所在行触发编辑<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 递归模式切换流程
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 外部修改同步流程(文件监听 + 防抖)
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,无方言)均可自动兼容,均按"否/mdb"兜底加载;新版为对象{功能名: {depth, dialect}},引擎兜底键"*"(数值)由程序保存时原样保留。
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 合并信号:
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 索引构建与缓存加速
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 调用链查询与树渲染
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 数据映射模块(MappingTablePage 基类 + TableMappingPage / FunctionMappingPage / NoChangeFullNamePage)
职责:维护 UF20 → UFT3 的表替换(tableCompareUf2_Uf3)与函数替换(functionReplace)映射,以及不转码功能函数清单(noChangeFullNameCode,字符串数组——转码时清单内功能函数名保持原样、不做全名替换)。三页共同继承公共基类 MappingTablePage(UI 骨架收编),读写 cust.json 对应顶层键经 CustJsonIo(见 §6.8)与 Constants::CustKeys(见 §6.4);转码引擎按映射替换代码中的表名/函数名。
MappingTablePage 公共基类(src/pages/datamapping/mappingtablepage.h/.cpp):收编三页原本同构的 UI 骨架与交互样板——搜索框 + QStandardItemModel + 参数化列数的 MappingFilterProxyModel(不区分大小写 contains 过滤)+ ElaTableView + 添加/删除选中/保存按钮 + tip 标签 + JsonFileMonitor(见 §6.9)+ 主题联动。三页仅以纯虚点注入差异:
| 虚点 | 说明 |
|---|---|
populateModel(custJson) |
从 cust.json 填充模型(load() 内调用) |
collectMappingData(custJson, errorMessage) |
保存时校验并回填数据,返回 overrideRaw(键名→格式化原文);校验失败置 errorMessage(弹警告不落盘) |
onAddRow() |
添加一行(各页数据结构不同:对象条目 / 字符串数组) |
可覆写文案虚点:loadFailureText / saveLogText / reloadLogText / deleteConfirmSingle / deleteConfirmMulti。子类在构造体内调用非虚的 load()(规避构造期虚函数绑定到基类版本)。基类统一实现:删除多选二次确认、就地编辑脏标志(m_dirty,onModelItemChanged 中 m_isLoading 保护)、保存流程(见下时序)、外部修改重载(脏数据不覆盖、弹确认)与 tip 样式联动。
flowchart TD
A([进入 表替换 / 函数替换 / 不转码功能函数页]) --> B["readCustJson 读取映射节点"]
B --> C["populateModel 渲染表格<br/>QSortFilterProxyModel 不区分大小写过滤"]
C --> D{用户操作}
D -- 双击 / F2 编辑 --> E[更新模型 + 置脏标志]
D -- 添加 --> F["onAddRow:弹窗名称必填 + 查重"]
D -- 删除选中 --> G["onDeleteRow:二次确认(支持多选)"]
E --> H["onSave:beginSaveWindow"]
F --> H
G --> H
H --> I["collectMappingData 校验回填<br/>读取原始文本提取顶层 key 顺序"]
I --> J["CustJsonIo::writeCustJsonOrdered 按序原子写回<br/>endSave 结束保存窗口"]
K[外部编辑 cust.json] -. JsonFileMonitor::reloadRequested .-> L["onReloadRequested:脏数据弹确认<br/>否则 loadData 立即刷新"]
5.7 元数据更新页面(MetadataUpdatePage)
职责:元数据更新的人机入口——勾选更新范围、触发更新、展示进度与统计结果(见 §4.1 时序图)。
- 页面分 UFT30更新范围 与 UF20更新范围 两个勾选卡片(UFT30:数据类型/标准字段/表结构/接口数据;UF20:数据类型/标准字段/标准错误号/接口数据/常量/菜单功能/模块/表结构),提供全选/全不选快捷操作;
- 强制全量更新复选框(勾选需二次确认):默认增量更新(mtime 比对),勾选后全量重扫重建;
- 源码扫描范围来自设置页配置的项目环境路径(见 §5.8)。
5.8 设置页面(SettingsPage)
职责:环境路径与界面主题配置。
| 配置项 | 说明 |
|---|---|
| UFT30 / UF20 / UF20账户项目环境路径 | 元数据更新与转码引擎的源码扫描根目录 |
| UF20清算项目环境路径 | UF20 清算参数项目目录,写 config.ini 的 [projectPath] UftdbSett,供清算相关元数据处理使用 |
| 转码生成路径 | 转码结果输出目录 |
- 路径保存于
config.ini的[projectPath]节(含UftdbSett);"保存"与"恢复默认"统一经ConfigManager::saveConfig()保留布局式写回(手工注释/键顺序/外部新增键不丢失,见 §6.3),程序自动同步维护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,启动时按节全量加载至内存缓存(UTF8/本地编码探测);路径白名单键(uft30/uf20/ufAcct20/UftdbSett/createPath/uft30pub)加载时统一归一化为正斜杠; setValue()仅更新内存缓存并记录脏键(缓存中路径键恒为正斜杠,与加载侧对称,任何来源不再漂移),由设置页显式调saveConfig()落盘;saveConfig()为保留布局式写回:以磁盘当前内容为基底逐行输出,仅替换"显式 setValue 过"的键行(写盘时换回反斜杠),手工注释、空行、节顺序、外部新增键一律原样保留,缓存中有而文件没有的键在对应节尾追加;落盘前经 mtime 探测外部手编,对应用未显式修改过的键采纳磁盘值(外部已有键的修改不被内存覆盖),并以QSaveFile原子写。
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 |
转码配置文件 |
| CustKeys::ConvertDir / TableCompare / FunctionReplace / NoChangeFullName | convert / tableCompareUf2_Uf3 / functionReplace / noChangeFullNameCode |
cust.json 顶层键名集中定义(含 cust_nonls.json 的 convert),数据映射三页与转码工作台统一引用,避免散落字符串字面量 |
| 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 弹出更新提示,"关于"页亦可手动检查。
6.8 CustJsonIo(cust.json 保序读写工具)
命名空间级公共工具(src/utils/custjsonio.h/.cpp),供数据映射三个页面(表替换 / 函数替换 / 不转码功能函数)共用,消除原先两份重复实现:
| 函数 | 说明 |
|---|---|
extractTopLevelKeyOrder(rawJson) |
深度感知扫描原始 JSON 文本(跳过字符串字面量),仅记录 depth==1 时后跟冒号的顶层键,返回键顺序;正则方案无法匹配中文/特殊字符键且会误捕嵌套键,故弃用 |
indentJsonValue(json) |
对 JSON 文本整体缩进(每行前加 4 空格) |
formatJsonValue(value) |
通用 JSON 值格式化:对象/数组走 QJsonDocument::Indented 再整体缩进,字符串手工转义,其余按 bool/number/null |
formatOrderedJsonObject(pairs) |
按给定顺序格式化 string→string 对象(条目 8 空格缩进),供映射页输出 |
formatOrderedJsonArray(items) |
按给定顺序格式化字符串数组(条目 8 空格缩进,空数组输出 []),供不转码功能函数页输出 |
writeCustJsonOrdered(jsonPath, json, keyOrder, overrideRaw) |
QSaveFile 原子写:按 keyOrder 顺序输出顶层键,overrideRaw 中的键用调用方自定义格式化文本重写,其余键走 formatJsonValue;保存失败记录错误日志 |
6.9 JsonFileMonitor(JSON 配置外部修改监控组件)
公共监控组件(src/utils/jsonfilemonitor.h/.cpp),收编原先散落在 5 个页面(数据映射三页 + 全量转码页 + 非转码业务页)的文件+目录双监控样板——每处约 50 行的 QFileSystemWatcher 事件处理、防抖、回环过滤逻辑统一为一个可复用组件,页面侧仅剩 watch() + connect(reloadRequested) + 保存时 beginSaveWindow()/endSave() 三行。
| 成员/方法 | 说明 |
|---|---|
watch(filePath) |
切换监控目标:文件 + 所在目录双监控(Windows 下外部程序整文件替换会使文件句柄失效,目录事件兜底补挂) |
refreshWatch() |
补挂监控(fileChanged 后文件路径可能已从 watcher 移除,防抖触发前先补挂) |
currentMtime() |
当前文件 mtime 基准(文件不存在返回 0) |
beginSaveWindow() / endSave() |
保存窗口:窗口期内监控事件直接忽略;endSave() 立即提升保存代数(m_saveSeq),使窗口内已排队的防抖事件全部过期,替代旧实现"保存后固定 2 秒忽略"的硬等待 |
reloadRequested() 信号 |
外部修改确认(300ms 防抖 + mtime 基准过滤自身写入回环后)单发;页面侧检查脏标志后重载 |
回环过滤原理:beginSaveWindow() 预置当前 mtime 为基准;监控事件到达时若 mtime <= 基准 或处于保存窗口/保存代数过期,视为自身写入引发的变化,不触发重载——自身保存经 endSave() 提升基准后,外部手编(mtime 变大)才会命中。
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 |
函数替换规则 | 转码的函数替换 |
noChangeFullNameCode |
字符串数组(功能函数全名清单) | 转码引擎对清单内功能函数名不做全名替换 |
convert.dir |
对象数组 {name, core, viaUmgr, dialect}(全量转码清单,V2 格式,见 docs/转码配置格式契约.md) |
全量转码页维护、引擎读取 |
| 其余键 | 引擎自身配置 | 原样透传 |
- 数据映射三页各自只读写所属顶层键(键名统一引用
Constants::CustKeys,见 §6.4),其余内容原样保留; - 程序保存时经
CustJsonIo::extractTopLevelKeyOrder提取顶层 key 顺序、CustJsonIo::writeCustJsonOrdered按原顺序QSaveFile原子写回,不破坏 JSON 键序(见 §6.8)。
7.3 cust_nonls.json(非转码业务配置)
{
"convert": {
"dir": {
"功能名": { "depth": 0, "dialect": "mdb" },
"另一功能": { "depth": -1, "dialect": "db" },
"*": 0
},
"ext": ["c", "h"]
}
}
dir.<功能名>:对象{depth, dialect}——depth为0纳入转码(单文件)、-1递归转码;dialect为 SQL 方言(mdb默认 /db),对应界面"db模式"列(否=mdb、是=db);"*"为兜底键(数值,程序保存时原样透传);ext:纳入转码的扩展名白名单;- 兼容旧版数组格式(仅扩展名列表),读取时自动转换。
7.4 ini 配置
| 文件 | 节/键 | 说明 |
|---|---|---|
| config.ini | [projectPath] |
UFT30/UF20/UF20账户/清算项目环境路径(uft30 / uf20 / ufAcct20 / UftdbSett)、转码生成路径;保存时同步维护 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 / NoChangeFullNamePage) |
| 元数据更新 | src/pages/metadataupdate/(页面)+ src/metadataupdate/(解析器组) |
| 设置 / 关于 | src/pages/settings/ / src/pages/about/ |
| 公共支撑 | src/utils/(DataCache、LogManager、ConfigManager、CustJsonIo、VersionChecker、Constants 等)、src/pythonrunner/、src/components/ |