1025 lines
68 KiB
Markdown
1025 lines
68 KiB
Markdown
# 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 系统分层架构
|
||
|
||
```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/ # 数据映射(表替换 / 函数替换 / 不转码功能函数)
|
||
│ │ ├── 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 应用启动流程
|
||
|
||
```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` 清单(V2 对象数组 `{name, core, viaUmgr, dialect}`),对清单内 LS_ 功能整体转码,核心/柜面接口/SQL 方言随清单下发(契约见 §7.5);
|
||
- **按需转码**:`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函数名 / 是否存在 / 核心 / 是否柜面接口"(无操作列,双击函数名所在行触发编辑)。
|
||
|
||
```mermaid
|
||
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 功能查询页双击)
|
||
|
||
```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["双击函数名所在行触发编辑<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`,无方言)均可自动兼容,均按"否/mdb"兜底加载;新版为对象 `{功能名: {depth, dialect}}`,引擎兜底键 `"*"`(数值)由程序保存时原样保留。
|
||
|
||
#### 5.3.6 按需转码执行模式
|
||
|
||
| 模式 | 递归标志 | 行为 |
|
||
| --- | --- | --- |
|
||
| 非递归(单文件) | `0` | 仅对目标文件本身执行转码,不处理该文件内调用的函数 |
|
||
| 递归 | `-1` | 转码目标文件的同时,自动对文件中所调用的关联函数一并完成转码 |
|
||
|
||
两种模式均**不包含核心文件、module 模块文件**。执行时 `buildArguments()` 组装引擎参数(含 `-m 2`),由 `MainWindow` 统一经 `PythonRunner` 启动引擎。
|
||
|
||
### 5.4 功能查询模块(FunctionSearchPage 基类 + UF20/UFT3 子页 + UF20 & 内存表映射查询页)
|
||
|
||
**职责**:检索 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.4.3 UF20 & 内存表映射查询页(TableSearchPage)
|
||
|
||
**职责**:检索 UF20(2.0)表与内存表(UFT3)之间的映射关系,四列结果:`UF20表名 / UF20表中文名 / 内存表名 / 内存表中文名`;**双击记录行可添加映射到「数据映射 → 表替换」页**。不继承 `FunctionSearchPageBase`(结果结构为 4 列表信息,与 `FunctionInfo` 3 列函数信息不同),UI 风格(边距/搜索框/结果表/主题感知 tip)与功能查询页对齐。
|
||
|
||
**数据源三件套**:
|
||
|
||
| 列 | 来源 |
|
||
| --- | --- |
|
||
| UF20表名 / 内存表名 | `bin/uf2touft3/config/table_mapping.json`(独立映射文件,顶层键 `tableCompareUf2_Uf3`:key=UF20表名,value=内存表名;value 可为字符串(一对一)或字符串数组(一对多,拆为多条映射对,一个 UF20 表展示多行)),每次查询现读(文件小) |
|
||
| UF20表中文名 | `uf20_cache.db` 的 `uf2_table`:`data` JSON 的 `cname` 字段 |
|
||
| 内存表中文名 | `uft3_cache.db` 的 `uft3_table`:`data` JSON 的 `chineseName` 字段 |
|
||
|
||
**查询流程**(每关键词,支持多关键词 OR 并集,`splitKeywords` 与功能查询页同口径):
|
||
|
||
1. **映射命中**(key 或 value)→ 完整行:以 key 取 UF20 中文名、以 value 取内存表中文名;一对多映射(value 为数组)拆为多条记录展示;
|
||
2. **映射未命中** → 双库单侧探测:命中 `uf2_table` 出单侧行(映射两列置空),命中 `uft3_table` 同理,两侧同时命中出两行;
|
||
3. **去重**:映射整体行已覆盖的表名不再补单侧行(covered 集合),同一映射对不被多关键词重复输出(seenPairs 集合);
|
||
4. 精确匹配为全等;模糊匹配为不区分大小写的包含。
|
||
|
||
**轻量数据访问**:两库 `data` 列为完整表定义 JSON(单行可达数十 KB),禁止整表搬运逐行解析——`DataCache` 新增 `getUf20TableNameMap()` / `getUft3TableNameMap()`,SQL 层 `json_extract(data, '$.cname' / '$.chineseName')` 只提取中文名,首次调用全量构建"表名→中文名"映射后**内存缓存**(懒加载,表数量远小于接口表,秒级内完成),元数据落库后随 `refreshFunctionCache()` 一并失效重建;构建失败不置有效、下次自动重试;`json_extract` 不可用时回退 `getAllRows` + C++ 逐行解析兜底。
|
||
|
||
**tip 反馈**(页面底部,不弹窗):默认提示"💡 输入 UF20 表名或内存表名进行查询,支持精确/模糊匹配;双击记录行可添加到「数据映射 → 表替换」";空输入提示输入表名;双库均为空提示"请先在「元数据更新」页面执行更新";无命中提示"未找到匹配的表";成功显示"已查询 X 条记录(其中 Y 个无映射关系,N 个表中文名缺失,M 个关键词无匹配)"——一对多映射按多条记录计;中文名缺失指映射里有该表但缓存库未收录(元数据更新未覆盖其来源工程,如 `table_mapping.json` 覆盖完整标准套件而本地仅更新了部分工程);`table_mapping.json` 读取失败时附加降级说明(仅展示数据库检索结果)。
|
||
|
||
#### 5.4.4 双击添加映射到表替换页
|
||
|
||
查询结果行**双击**即可将映射关系添加到「数据映射 → 表替换」页,跨页信号链路与 UF20 功能查询一键加入转码工作台同构:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 用户
|
||
participant S as TableSearchPage(映射查询)
|
||
participant M as MainWindow
|
||
participant T as TableMappingPage(表替换)
|
||
|
||
U->>S: 双击结果行
|
||
S->>S: 取第 1 列(UF20表名)/第 3 列(内存表名),<br/>任一为空即无完整映射关系,tip 警告
|
||
S->>M: requestAddMapping(uf20Name, memName)
|
||
M->>T: appendPairIfAbsent(uf20Name, memName)
|
||
Note over T: trimmed 口径判重(与添加/收集同口径);<br/>已存在返回提示(含当前映射值,不覆盖);<br/>新增入模型置脏后立即落盘(自动保存)
|
||
M->>S: onMappingAddResult(error)
|
||
S->>U: tip 反馈:成功"已添加到表替换并自动保存" / 失败原因
|
||
```
|
||
|
||
**要点:**
|
||
|
||
- **完整映射校验**:仅当该行 UF20 表名与内存表名均非空(即有完整映射关系)才可添加;单侧行(无映射)双击提示警告不入库;
|
||
- **判重不覆盖**:UF20 表名在表替换中已存在时跳过,tip 注明当前映射值——与表替换页手动添加口径一致,避免隐式覆盖;
|
||
- **自动保存语义**:`appendPairIfAbsent` 追加到模型并置 `m_dirty` 后立即调用 `saveToDisk()` 落盘(无弹窗,成功静默)——`onSave` 的弹窗壳与落盘核心分离,保存窗口(监控抑制)逻辑共用;自动保存失败时原因带回映射查询页 tip 展示;
|
||
- **table_mapping.json 同步**:表替换页覆写基类落盘钩子 `onSavedToDisk()`——保存成功(含双击自动保存)后,以表替换模型为源与 `table_mapping.json` 的 `tableCompareUf2_Uf3` 对象逐成员对账:
|
||
- **新增**:模型中存在、tm 中缺失的 key(且 value 非空)按原缩进/换行格式追加到对象末尾;
|
||
- **修改**:tm 中为字符串值(一对一)且与模型 value 不同的条目就地更新为模型值;
|
||
- **一对多数组跳过**:tm 中 value 为数组的条目(41 个)一律保留原文不覆盖——表替换是单值模型,覆盖会丢失其余映射;跳过数计入同步日志;
|
||
- **删除暂不同步**:表替换删除条目仅作用于 cust.json(转码口径),tm 中同名条目保留(tm 是全量参考映射,含 328 个 cust 未维护的独有条目);空 value 也不同步;
|
||
- **实现要点**:成员级文本解析(key 串/冒号/value 串或跨行数组,括号配对且跳过字符串字面量,记录每个成员原文起止),未变更成员与数组保持原文字节不变,仅替换/追加成员行后按原缩进与换行重建对象内部;写前整体重解析校验,任何结构异常放弃写入并记错误日志;同步结果记 info 日志(新增 N,更新 M,一对多数组跳过 X,空值跳过 Y);
|
||
- 映射查询页每次查询现读该文件,同步后的新增/修改即时可查;
|
||
- **页面持有**:`MainWindow` 新增 `m_tableMappingPage` 成员,页面创建 lambda 运行时解引用(构造时序上映射查询页先建、表替换页后建,两页构造完成后成员指针非空)。
|
||
|
||
### 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 数据映射模块(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 样式联动。
|
||
|
||
```mermaid
|
||
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()` 供功能查询页读取指定功能完整数据;
|
||
- **表查询映射缓存**:`getUf20TableNameMap()` / `getUft3TableNameMap()` 返回"表名→中文名"全量轻量映射(SQL 层 `json_extract` 只取中文名字段,避免整表搬运大 JSON;`json_extract` 不可用时回退 `getAllRows` + 逐行解析),首次调用构建后内存缓存,随 `refreshFunctionCache()` 一并失效重建,供 UF20 & 内存表映射查询页使用(见 §5.4.3)。
|
||
|
||
### 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 格式,见 §7.5) | 全量转码页维护、引擎读取 |
|
||
| 其余键 | 引擎自身配置 | 原样透传 |
|
||
|
||
- 数据映射三页各自只读写所属顶层键(键名统一引用 `Constants::CustKeys`,见 §6.4),其余内容原样保留;
|
||
- 程序保存时经 `CustJsonIo::extractTopLevelKeyOrder` 提取顶层 key 顺序、`CustJsonIo::writeCustJsonOrdered` 按原顺序 `QSaveFile` 原子写回,不破坏 JSON 键序(见 §6.8)。
|
||
|
||
### 7.3 cust_nonls.json(非转码业务配置)
|
||
|
||
```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 启动时读取 |
|
||
|
||
### 7.5 转码配置格式契约(cust.json / cust_nonls.json)
|
||
|
||
> 版本:1.0(2026-09-16)
|
||
> 变更发起方:转码工具 UI(Uft30ChangeCode,Qt 客户端)
|
||
> 契约接收方:转码引擎(uf2touft3)
|
||
> 状态:已评审确认,待引擎侧实现联调
|
||
|
||
#### 1. 变更背景
|
||
|
||
全量转码页新增【核心】【是否柜面接口】两列,按需转码页新增【db模式】列(支持 SQL 的 db/mdb 语法转换)。相应地,两份配置文件的 `convert.dir` 节点格式升级,引擎需按本契约完成解析与转码逻辑适配。
|
||
|
||
#### 2. cust.json(全量转码清单)
|
||
|
||
##### 2.1 格式变更
|
||
|
||
```jsonc
|
||
// 旧格式(V1):字符串数组
|
||
"convert": {
|
||
"dir": [
|
||
"LS_证券参数_股票名单增加.service_design"
|
||
],
|
||
"ext": [ ".service_design", ".function_design", ... ]
|
||
}
|
||
|
||
// 新格式(V2):对象数组
|
||
"convert": {
|
||
"dir": [
|
||
{
|
||
"name": "LS_债券质押协议回购_初始交易委托.service_design",
|
||
"core": "ucbp",
|
||
"viaUmgr": true,
|
||
"dialect": "db"
|
||
}
|
||
],
|
||
"ext": [ ".service_design", ".function_design", ... ]
|
||
}
|
||
```
|
||
|
||
##### 2.2 字段定义
|
||
|
||
| 字段 | 类型 | 必填 | 语义 | 合法取值 |
|
||
|---|---|---|---|---|
|
||
| `name` | string | 是 | 功能短名 + 设计器扩展名(后缀由 UI 按清单扩展名拼接,当前固定 `.service_design`) | 非空字符串 |
|
||
| `core` | string | 是 | 目标核心 | `"ucbp"` \| `"uses"` \| `"uarg"` \| `"usms"` |
|
||
| `viaUmgr` | bool | 是 | 是否柜面接口 | `true` \| `false` |
|
||
| `dialect` | string | 是 | SQL 语法方言,由 `core` 派生,UI 不提供独立修改入口 | `"mdb"` \| `"db"` |
|
||
|
||
**dialect 派生规则(UI 侧保证,引擎可据此校验):**
|
||
|
||
| core | dialect |
|
||
|---|---|
|
||
| `ucbp` | `mdb` |
|
||
| `uses` | `mdb` |
|
||
| `uarg` | `db` |
|
||
| `usms` | `db` |
|
||
|
||
##### 2.3 引擎解析要求
|
||
|
||
1. **双格式兼容**:`dir` 元素为字符串时按旧格式处理(无 core/viaUmgr/dialect,行为与现有引擎一致);为对象时按新格式处理。两者可混合出现的可能性极低,但解析器不应因此崩溃。
|
||
2. **字段兜底建议**:对象缺字段时,建议按 UI 默认值兜底并记录警告日志——`core="ucbp"`、`viaUmgr=false`、`dialect` 按 2.2 派生表由 core 推导。
|
||
3. **非法值处理**:`core` 不在枚举内、`dialect` 与派生规则矛盾时,记录警告并按 `dialect` 字段实际值执行(以文件为准,不做强制纠正),或按团队约定明确拒绝该条目——**请引擎侧反馈选定哪种策略**。
|
||
4. `viaUmgr=true` 时柜面接口的转码行为差异由引擎侧定义并实现。
|
||
|
||
#### 3. cust_nonls.json(按需转码清单)
|
||
|
||
##### 3.1 格式变更
|
||
|
||
```jsonc
|
||
// 旧格式(V1):功能名 → 数值
|
||
"convert": {
|
||
"dir": {
|
||
"*": -1,
|
||
"AS_证券日终(用户)_证券信息更新": 0
|
||
},
|
||
"ext": [ ".service_design", ... ]
|
||
}
|
||
|
||
// 新格式(V2):清单条目为对象;引擎兜底键 "*" 保持数值原样
|
||
"convert": {
|
||
"dir": {
|
||
"AS_证券参数_交易风险提示证券代码批量导入": { "depth": 0, "dialect": "db" },
|
||
"*": -1
|
||
},
|
||
"ext": [ ".service_design", ... ]
|
||
}
|
||
```
|
||
|
||
##### 3.2 字段定义
|
||
|
||
| 字段 | 类型 | 必填 | 语义 | 合法取值 |
|
||
|---|---|---|---|---|
|
||
| `depth` | int | 是 | 递归深度标志,语义与旧格式数值完全一致:`0`=否(单文件转码)、`-1`=是(递归转码) | `0` \| `-1` |
|
||
| `dialect` | string | 是 | SQL 语法方言,对应 UI【db模式】列:选"是"→`db`,选"否"→`mdb` | `"db"` \| `"mdb"` |
|
||
|
||
##### 3.3 特殊键与解析要求
|
||
|
||
1. **`"*"` 兜底键**:保持旧格式数值(`0`/`-1`),**不升级为对象、不携带 dialect**。引擎对非清单功能的默认行为不变,UI 侧原样透传、不展示、不修改。
|
||
2. **双格式兼容**:`dir` 条目值为数值时按旧格式处理(dialect 默认 `mdb`);为对象时按新格式解析。
|
||
3. **字段兜底建议**:对象缺 `depth` 按 `0`,缺 `dialect` 按 `mdb`,均记警告日志。
|
||
4. **旧格式数组兼容**:更早期版本 `dir` 为字符串数组(无递归标志),引擎若仍有该格式在途配置,维持现有兼容逻辑即可。
|
||
|
||
#### 4. UI 侧写入保证(引擎可信赖的前提)
|
||
|
||
| 保证项 | 说明 |
|
||
|---|---|
|
||
| 原子写入 | 两份配置均经 `QSaveFile` 原子提交,不会出现写一半的截断文件 |
|
||
| JSON 合法性 | cust.json 文本级替换写回前做整体 JSON 解析校验,非法即中止保存 |
|
||
| 字段完整性 | V2 对象条目四字段/两字段总是齐全,不输出缺字段条目(兜底逻辑仅为防御外部手改) |
|
||
| `ext` 节点 | 不变,引擎按现状使用 |
|
||
| 顶层键顺序 | cust.json 其余顶层键与格式不被重排(文本级区间替换) |
|
||
|
||
#### 5. 版本与联调约定
|
||
|
||
1. **不兼容变更**:V2 格式对旧引擎不可解析(全量转码清单会为空)。**UI 与引擎必须同版本发布**,禁止单独升级任一侧。
|
||
2. **升级方向兼容**:新引擎需兼容读 V1(存量用户配置),新 UI 兼容读 V1/V2。
|
||
3. **回退方案**:若需回退到旧 UI,旧 UI 读 V2 配置会得到空清单,需手工将配置改回 V1 或删除配置重新生成。
|
||
4. **待引擎侧反馈**:
|
||
- 2.3-3 非法值处理策略(告警放行 or 拒绝条目);
|
||
- `viaUmgr=true` 柜面接口转码的具体行为定义;
|
||
- `dialect` 驱动 SQL 语法转换(mdb/db)的实现范围与差异点清单。
|
||
|
||
#### 6. 格式速查(供引擎实现对照)
|
||
|
||
```
|
||
cust.json convert.dir : V1 string[] → V2 {name, core, viaUmgr, dialect}[]
|
||
core 枚举 : ucbp | uses | uarg | usms
|
||
dialect 派生 : ucbp/uses→mdb ; uarg/usms→db
|
||
|
||
cust_nonls.json convert.dir : V1 {name: 0|-1, "*": -1}
|
||
→ V2 {name: {depth: 0|-1, dialect: "db"|"mdb"}, "*": -1}
|
||
depth 语义 : 0=单文件转码 ; -1=递归转码(与 V1 数值一致)
|
||
"*" 键 : 保持数值,恒不升级为对象
|
||
```
|
||
|
||
---
|
||
|
||
## 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 双模式 + UF20 & 内存表映射查询) |
|
||
| 代码检索 | `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/` | |