Files
change_code/docs/详细设计文档.md

68 KiB
Raw Permalink Blame History

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: 展示结果

设计要点:

  1. 后台化:解析与写库全部在工作线程,UI 线程仅收发信号;
  2. 原子落库:saveDataset 以"临时表 + RENAME"方式整体替换表数据,并更新 _meta 元信息表(记录源文件 mtime,供增量判断);
  3. 增量/全量:默认增量(mtime 比对),且先做源码目录整体 mtime 快速比对(0.5s 容差),源码无变化时整个更新秒级跳过;"强制全量更新"复选框需二次确认;
  4. 缓存生效时机:更新完成后调用 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&lt;UF2&gt;<br/>关键字段/节名/正则差异"]
    D --> T2["Traits&lt;UFT3&gt;<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 方言随清单下发(契约见 §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函数名 / 是否存在 / 核心 / 是否柜面接口"(无操作列,双击函数名所在行触发编辑)。

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 & 内存表映射查询页)

职责:检索 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.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 功能查询一键加入转码工作台同构:

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 索引构建与缓存加速

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() 供功能查询页读取指定功能完整数据;
  • 表查询映射缓存: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(非转码业务配置)

{
  "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 格式变更
// 旧格式(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 格式变更
// 旧格式(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/