diff --git a/README.md b/README.md index e69de29..8f773f7 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,116 @@ +# Uft30ChangeCode(UFT30 代码转换工具) + +一款基于 **Qt 6 + MinGW** 开发的桌面工具,用于将 UF20 业务代码转换为 UFT3 格式,并提供功能查询、代码检索、元数据更新、数据映射等配套能力。UI 采用 [ElaWidgetTools](3rd/ElaWidgetTools) 组件库。 + +- 当前版本:**v1.4.0** +- 运行平台:Windows +- 界面框架:Qt Widgets + ElaWidgetTools + +## 功能特性 + +| 模块 | 说明 | +| --- | --- | +| 业务转码 | 将 UF20 函数批量转换为 UFT3 函数格式,支持进度显示与中止 | +| 功能查询 | UF20 / UFT3 功能(函数)查找,支持 2.0 账户功能查询 | +| 代码检索 | "转码业务" / "非转码业务" 调用链路分析,支持涉及文件与调用链导出 | +| 元数据更新 | 更新 UF20 / UFT3 标准字段、数据类型、接口数据等,支持增量更新与强制全量更新 | +| 数据映射 | 表映射、函数映射 | +| 设置 | 配置 UFT30 / UF20 项目路径、账户环境路径、转码生成路径等 | +| 其它 | 在线帮助文档、关于页、版本更新提示;全局文字可选中复制、表格 Ctrl+C 导出 | + +## 目录结构 + +``` +Uft30ChangeCode/ +├─ main.cpp # 程序入口,安装全局文字可复制过滤器 +├─ Uft30ChangeCode.pro # qmake 工程文件 +├─ build.bat # 一键构建脚本(Release) +├─ build_advanced.bat # 进阶构建脚本 +├─ config/ +│ └─ change_code.ini # 默认配置模板(构建时按需部署到 bin/uf2touft3/) +├─ src/ +│ ├─ mainwindow/ # 主窗口 +│ ├─ pages/ # 各功能页面(batchconvert / codesearch / functionsearch / metadatupdate / datamapping / settings / about) +│ ├─ metadataupdate/ # 元数据扫描与处理(UF2 / UFT3 接口、数据类型、字段等) +│ ├─ pythonrunner/ # 调用外部转码工具(uf2touft3.exe) +│ ├─ components/ # 自定义控件 +│ └─ utils/ # 公共工具(日志、配置读取、调用链分析、版本检查、常量等) +├─ 3rd/ # 第三方库(ElaWidgetTools 等) +├─ resources/ resources.qrc # 图标等资源 +├─ help/ # 帮助文档(Writerside) +└─ update_info.json # 版本发布说明 +``` + +## 构建 + +### 环境要求 + +- Qt 6.6.2(mingw_64) +- MinGW 11.2.0(Qt Tools 自带) + +`build.bat` 中默认路径如下,如与本机不一致请自行修改: + +```bat +set QT_PATH=D:\01.InstallInfo\InstallPath\QT\6.6.2\mingw_64 +set MINGW_PATH=D:\01.InstallInfo\InstallPath\QT\Tools\mingw1120_64 +``` + +### 一键构建 + +```bat +build.bat +``` + +脚本会依次执行:关闭正在运行的程序 → 清理旧构建产物 → `qmake` → `mingw32-make release` → `windeployqt` 部署 Qt 运行库 → 按需部署默认 `change_code.ini`。 + +构建产物:`bin\Uft30ChangeCode.exe` + +> 注意:外部转码工具 `bin\uf2touft3\uf2touft3.exe` 需自行放置,程序运行时依赖它完成实际转码。 + +### 手动构建 + +```bat +qmake Uft30ChangeCode.pro -spec win32-g++ +mingw32-make release +``` + +## 配置说明(`config/change_code.ini`) + +构建时若 `bin/uf2touft3/change_code.ini` 不存在,会从 `config/change_code.ini` 拷贝一份默认模板;已存在则保留用户配置不覆盖。 + +### `[codeSearch]` 代码检索路径 + +| 键 | 说明 | +| --- | --- | +| `convertPath` | 转码业务代码检索路径 | +| `searchPath` | 非转码业务代码检索路径 | + +### `[log]` 日志配置 + +| 键 | 默认值 | 说明 | +| --- | --- | --- | +| `level` | `INFO` | 日志等级:`DEBUG` < `INFO` < `WARNING` < `ERROR`,等级越高输出越少 | +| `path` | `./logs` | 日志输出目录(相对路径基于 exe 目录,绝对路径直接生效) | +| `file` | `change_code.log` | 日志文件名 | +| `fileEnabled` | `true` | 是否写入日志文件 | +| `maxSize` | `10MB` | 单个日志文件最大大小(支持 `B`/`KB`/`MB`/`GB`,`0` 表示不限制) | +| `maxFiles` | `100` | 日志文件轮转保留数量 | +| `maxAge` | `30` | 日志文件保留天数(`0` 表示不限制) | +| `console` | `true` | 是否在控制台打印日志 | +| `caller` | `false` | 是否在日志中附加调用堆栈(仅 Windows 生效) | +| `format` | `text` | 日志格式:`text` / `json` | +| `timeFormat` | `2006-01-02 15:04:05.000` | 时间格式(支持 Go 风格布局或 Qt 格式) | + +## 帮助文档 + +更多使用说明见 `help/` 目录(Writerside 工程),涵盖业务转码、功能查询、代码检索、元数据更新、数据映射、设置等模块的图文操作指引。 + +## 版本记录 + +| 版本 | 主要更新 | +| --- | --- | +| v1.4.0 | 提升业务转码性能;元数据更新支持增量/强制全量;新增代码检索与函数调用链路分析;支持全局文字选中复制及表格 Ctrl+C 导出 | +| v1.3.0 | 修复设置路径不生效;修复元数据更新后转码函数检查状态异常 | +| v1.2.0 | 新增数据映射(表映射、函数映射);新增版本更新提示 | +| v1.1.0 | 支持 2.0 账户功能查询及业务转码 | +| v1.0.0 | 首个版本:业务转码、UF20/UFT3 功能查找、帮助文档、接口元数据更新、设置 | diff --git a/Uft30ChangeCode.pro b/Uft30ChangeCode.pro index e873c51..23f0461 100644 --- a/Uft30ChangeCode.pro +++ b/Uft30ChangeCode.pro @@ -31,6 +31,8 @@ SOURCES += main.cpp \ src/metadataupdate/uft3stdfields.cpp \ src/metadataupdate/uft3table.cpp \ src/utils/datacache.cpp \ + src/utils/business_strategy.cpp \ + src/utils/configreaderbase.cpp \ src/utils/uf2configreader.cpp \ src/utils/uft3configreader.cpp \ src/utils/logmanager.cpp \ @@ -64,6 +66,8 @@ HEADERS += src/mainwindow/mainwindow.h \ src/metadataupdate/uft3stdfields.h \ src/metadataupdate/uft3table.h \ src/utils/datacache.h \ + src/utils/business_strategy.h \ + src/utils/configreaderbase.h \ src/utils/uf2configreader.h \ src/utils/uft3configreader.h \ src/utils/logmanager.h \ @@ -86,6 +90,7 @@ INCLUDEPATH += 3rd/ElaWidgetTools/include win32 { LIBS += -L$$PWD/3rd/ElaWidgetTools/lib -lElaWidgetTools LIBS += -ld3d11 -ldxgi + LIBS += -ldbghelp # LogManager stack trace (caller) RC_FILE = app.rc } diff --git a/build.bat b/build.bat index e592080..48c1e1b 100644 --- a/build.bat +++ b/build.bat @@ -86,6 +86,17 @@ if errorlevel 1 ( ) echo. +REM Deploy default change_code.ini (only copy when missing, to keep user's existing config) +echo Deploying default change_code.ini... +if not exist "bin\uf2touft3" mkdir "bin\uf2touft3" +if not exist "bin\uf2touft3\change_code.ini" ( + copy "config\change_code.ini" "bin\uf2touft3\change_code.ini" >nul + echo [OK] change_code.ini -> bin\uf2touft3\ (default copied) +) else ( + echo [SKIP] bin\uf2touft3\change_code.ini already exists, keep user config +) +echo. + echo ========================================== echo Build SUCCESSFUL! echo ========================================== diff --git a/build_advanced.bat b/build_advanced.bat index dd15fd4..038649f 100644 --- a/build_advanced.bat +++ b/build_advanced.bat @@ -147,4 +147,15 @@ if exist "bin\uf2touft3\uf2touft3.exe" ( echo [WARN] Please make sure bin\uf2touft3\uf2touft3.exe exists ) echo. + +REM Deploy default change_code.ini (only copy when missing, to keep user's existing config) +echo Deploying default change_code.ini... +if not exist "bin\uf2touft3" mkdir "bin\uf2touft3" +if not exist "bin\uf2touft3\change_code.ini" ( + copy "config\change_code.ini" "bin\uf2touft3\change_code.ini" >nul + echo [OK] change_code.ini -> bin\uf2touft3\ (default copied) +) else ( + echo [SKIP] bin\uf2touft3\change_code.ini already exists, keep user config +) +echo. pause diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 4558696..cfc4ec5 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -47,6 +47,7 @@ MainWindow::MainWindow(QWidget *parent) initWindow(); initContent(); + LogManager::instance()->initLogConfig(); // 先读取 change_code.ini 的 [log] 配置,再开始写日志 LogManager::instance()->initLogFile(); LogManager::instance()->logInfo("应用程序启动"); diff --git a/src/metadataupdate/interface_scanner.h b/src/metadataupdate/interface_scanner.h new file mode 100644 index 0000000..6db530a --- /dev/null +++ b/src/metadataupdate/interface_scanner.h @@ -0,0 +1,98 @@ +#ifndef INTERFACE_SCANNER_H +#define INTERFACE_SCANNER_H + +#include +#include +#include +#include + +// ============================================================================ +// 接口扫描器的公共类型与抽象接口 +// +// 目的:消除 UF2Interface / Uft3Interface 之间“按业务版本平行复制”的实现, +// 使 MetadataProcessor 只依赖 IInterfaceScanner 接口,按业务版本注入具体实现 +// (策略模式)。各版本差异化的参数统一收敛到 ScanContext,避免接口方法签名分裂。 +// ============================================================================ + +struct FieldInfo { + QString name; + QString flag; + QString desc; + QString uuid; + QString paramType; + QString type; + QString cname; + QString hsType; +}; + +struct InterfaceInfo { + QString cname; + QString eName; + QString functionNo; + QString flag; + QString path; + QString code; + QString id; + QString sysStatus; + QString description; + QString moudle; + QString moudleEName; + QString serverType; + bool returnResultSet = false; + bool needTransMonitor = false; + bool checkLicence = false; + + QMap inputFields; + QMap outputFields; + QMap variableFields; + QMap internalFields; +}; + +// 统一文件条目(合并原 Uf2FileEntry / Uft3FileEntry) +struct FileEntry { + QString path; + qint64 mtime = 0; + QString serverType; // UF2 使用(trade / acct / dbsett) + bool isTrans = false; // UFT3 使用(是否交易码) +}; + +// 统一扫描结果(UF2 无独立交易码子集,transCodeMap 为空) +struct ScanResult { + QMap resultMap; + QMap transCodeMap; +}; + +// 扫描/组装上下文:承载各业务版本差异化的参数,避免接口方法签名分裂 +struct ScanContext { + QMap fileFilterDict; + QString ufAcct20ProjectDir; // UF2 专用 + QString uftDbSett; // UF2 专用 + QJsonObject custJson; // UF2 专用 + QString moduleGenerationCheck; // UFT3 专用 + QString serverType; // 增量组装用(UF2) + QMap moduleCodeMap; // 增量组装用(UF2) +}; + +// 接口扫描器抽象接口(策略) +class IInterfaceScanner { +public: + virtual ~IInterfaceScanner() = default; + + // 解析单个接口文件首行属性,得到接口信息 + virtual InterfaceInfo loadInterface(const QString& filePath) = 0; + + // 递归扫描目录,返回全部接口信息;UFT3 另返回交易码子集 + virtual ScanResult scanDir(const QString& dirPath, const ScanContext& ctx) = 0; + + // 单文件重新组装(增量比较用) + virtual InterfaceInfo assemble(const QString& filePath, const ScanContext& ctx) = 0; + + // 仅收集文件路径与修改时间,用于轻量增量缓存 + virtual QList collectFiles(const QString& dirPath, const ScanContext& ctx) = 0; + + // 构建模块编码映射(UF2 专用;UFT3 默认返回空) + virtual QMap buildModuleCodeMap(const QString& /*dirPath*/, const ScanContext& /*ctx*/) + { return {}; } +}; + +#endif // INTERFACE_SCANNER_H diff --git a/src/metadataupdate/metadataprocessor.cpp b/src/metadataupdate/metadataprocessor.cpp index 2c176cf..1091705 100644 --- a/src/metadataupdate/metadataprocessor.cpp +++ b/src/metadataupdate/metadataprocessor.cpp @@ -261,122 +261,20 @@ bool MetadataProcessor::processUf2Interfaces(const QString& basePath) LogManager::instance()->log("========== 开始处理 UF2.0 接口 =========="); LogManager::instance()->log(QString("基础路径(basePath): %1").arg(basePath)); - QStringList completeNameList = {"module.xml", ".project", "冲突检测.wlua"}; - QStringList containNameList = {".vbusiness", "/公共资源/", "/数据库/", "/脚本/", "/通用数据/", "/.svn/"}; - QMap filterDict; - filterDict["completeNameList"] = completeNameList; - filterDict["containNameList"] = containNameList; - - QString dirPath = m_uf20Path; - QString ufAcct20ProjectDir = m_ufAcct20Path; - QString uftDbSett = m_uftDbSettPath; - - QString supportUftdbSett = m_supportUftdbSett; - if (supportUftdbSett == "0") { - uftDbSett.clear(); - } - - LogManager::instance()->log(QString("UF20 项目路径: %1").arg(dirPath)); - LogManager::instance()->log(QString("UF20 会计项目路径: %1").arg(ufAcct20ProjectDir)); - LogManager::instance()->log(QString("UFT 数据库设置路径: %1").arg(uftDbSett)); + filterDict["completeNameList"] = {"module.xml", ".project", "冲突检测.wlua"}; + filterDict["containNameList"] = {".vbusiness", "/公共资源/", "/数据库/", "/脚本/", "/通用数据/", "/.svn/"}; + ScanContext ctx; + ctx.fileFilterDict = filterDict; + ctx.ufAcct20ProjectDir = m_ufAcct20Path; + ctx.uftDbSett = (m_supportUftdbSett == "0") ? QString() : m_uftDbSettPath; QString custJsonPath = QDir(m_uf2touft3Path).filePath("cust.json"); - QJsonObject custJson = readJson(custJsonPath); - - QString outputPath = QDir(m_uf2touft3Path).filePath("uf2.json"); + ctx.custJson = readJson(custJsonPath); Uf2Interface uf2; - QJsonObject oldJson = readJson(outputPath); - QJsonObject oldCache = readCache(); - - // 仅收集文件路径与修改时间(不解析内容),用于增量判定 - QList entries; - entries << uf2.collectFiles(dirPath, "trade", filterDict); - if (!ufAcct20ProjectDir.isEmpty()) entries << uf2.collectFiles(ufAcct20ProjectDir, "acct", filterDict); - if (!uftDbSett.isEmpty()) entries << uf2.collectFiles(uftDbSett, "dbsett", filterDict); - - QMap moduleCodeMap = uf2.buildModuleCodeMap(dirPath, ufAcct20ProjectDir, uftDbSett, custJson); - - bool full = m_forceFull || oldJson.isEmpty() - || !oldCache.contains("uf2") || oldCache["uf2"].toObject().isEmpty(); - - QJsonObject resultJson; - QJsonObject newUf2Cache; - - if (full) { - LogManager::instance()->log("UF2.0 全量扫描模式"); - QElapsedTimer timer; - timer.start(); - - QMap mtimeByPath; - for (const Uf2FileEntry& e : entries) mtimeByPath[e.path] = e.mtime; - - QMap resultMap = uf2.scanDir(dirPath, ufAcct20ProjectDir, uftDbSett, filterDict, custJson); - for (const QString& key : resultMap.keys()) { - const InterfaceInfo& info = resultMap[key]; - resultJson[key] = interfaceInfoToUF2Json(info); - QJsonObject rec; - rec["mtime"] = QString::number(mtimeByPath.value(info.path, 0)); - rec["key"] = key; - newUf2Cache[info.path] = rec; - } - - LogManager::instance()->log(QString("UF2.0 全量扫描耗时: %1ms").arg(timer.elapsed())); - } else { - LogManager::instance()->log("UF2.0 增量扫描模式"); - QElapsedTimer timer; - timer.start(); - QJsonObject oldUf2 = oldCache["uf2"].toObject(); - QSet currentPaths; - resultJson = oldJson; // 以旧结果为基准,仅更新变化的文件 - - for (const Uf2FileEntry& e : entries) { - currentPaths.insert(e.path); - QJsonObject rec = oldUf2.value(e.path).toObject(); - bool changed = rec.isEmpty() || rec["mtime"].toString().toLongLong() != e.mtime; - if (changed) { - InterfaceInfo info = uf2.assemble(e.path, e.serverType, moduleCodeMap); - QString newKey = info.cname; - QString oldKey = rec["key"].toString(); - if (!oldKey.isEmpty() && oldKey != newKey && resultJson.contains(oldKey)) { - resultJson.remove(oldKey); - } - resultJson[newKey] = interfaceInfoToUF2Json(info); - QJsonObject crec; - crec["mtime"] = QString::number(e.mtime); - crec["key"] = newKey; - newUf2Cache[e.path] = crec; - } else { - newUf2Cache[e.path] = rec; // 未变化:直接沿用旧缓存记录 - } - } - - // 删除已从磁盘移除的文件对应的条目 - for (auto it = oldUf2.begin(); it != oldUf2.end(); ++it) { - if (!currentPaths.contains(it.key())) { - QString oldKey = it.value().toObject()["key"].toString(); - if (!oldKey.isEmpty() && resultJson.contains(oldKey)) { - resultJson.remove(oldKey); - } - } - } - - LogManager::instance()->log(QString("UF2.0 增量扫描耗时: %1ms").arg(timer.elapsed())); - } - - if (!writeJson(outputPath, resultJson)) { - return false; - } - - QJsonObject merged = oldCache; - merged["uf2"] = newUf2Cache; - if (!writeCache(merged)) { - LogManager::instance()->logWarning("UF2.0 缓存写入失败(不影响本次结果)"); - } - - LogManager::instance()->log(QString("UF2.0 接口处理完成,结果已保存到: %1").arg(outputPath)); - return true; + QString outputPath = QDir(m_uf2touft3Path).filePath("uf2.json"); + return processInterfaces(&uf2, ctx, m_uf20Path, outputPath, QString(), "uf2", false); } bool MetadataProcessor::processUft3Interfaces(const QString& basePath) @@ -384,56 +282,62 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath) LogManager::instance()->log("========== 开始处理 UFT3.0 接口 =========="); LogManager::instance()->log(QString("基础路径(basePath): %1").arg(basePath)); - QString moduleGenerationCheck = m_moduleGenerationCheck; - LogManager::instance()->log(QString("模块生成检查: %1").arg(moduleGenerationCheck)); - - QStringList completeNameList = {"module.xml", "project.xml", "冲突检测.wlua"}; - QStringList containNameList = {".extinterface", ".uftatomfunction", ".uftatomservice", ".uftfunction", ".uftservice"}; - QMap filterDict; - filterDict["completeNameList"] = completeNameList; - filterDict["containNameList"] = containNameList; + filterDict["completeNameList"] = {"module.xml", "project.xml", "冲突检测.wlua"}; + filterDict["containNameList"] = {".extinterface", ".uftatomfunction", ".uftatomservice", ".uftfunction", ".uftservice"}; - QString dirPath = m_uft30Path; - LogManager::instance()->log(QString("UFT30 项目路径: %1").arg(dirPath)); - - QString outputPath = QDir(m_uf2touft3Path).filePath("uft3.json"); - QString transOutputPath = QDir(m_uf2touft3Path).filePath("uft3Trans.json"); + ScanContext ctx; + ctx.fileFilterDict = filterDict; + ctx.moduleGenerationCheck = m_moduleGenerationCheck; Uft3Interface uft3; - QJsonObject oldMainJson = readJson(outputPath); - QJsonObject oldTransJson = readJson(transOutputPath); + QString outputPath = QDir(m_uf2touft3Path).filePath("uft3.json"); + QString transOutputPath = QDir(m_uf2touft3Path).filePath("uft3Trans.json"); + return processInterfaces(&uft3, ctx, m_uft30Path, outputPath, transOutputPath, "uft3", true); +} + +bool MetadataProcessor::processInterfaces(IInterfaceScanner* scanner, + const ScanContext& baseCtx, + const QString& dirPath, + const QString& outputPath, + const QString& transOutputPath, + const QString& cacheKey, + bool isUft3) +{ + QList entries = scanner->collectFiles(dirPath, baseCtx); + + QMap moduleCodeMap = scanner->buildModuleCodeMap(dirPath, baseCtx); + + QJsonObject oldJson = readJson(outputPath); + QJsonObject oldTransJson = transOutputPath.isEmpty() ? QJsonObject() : readJson(transOutputPath); QJsonObject oldCache = readCache(); - QList entries = uft3.collectFiles(dirPath, filterDict, moduleGenerationCheck); - - bool full = m_forceFull || oldMainJson.isEmpty() - || !oldCache.contains("uft3") || oldCache["uft3"].toObject().isEmpty(); + bool full = m_forceFull || oldJson.isEmpty() + || !oldCache.contains(cacheKey) || oldCache[cacheKey].toObject().isEmpty(); QJsonObject resultJson; QJsonObject transCodeJson; - QJsonObject newUft3Cache; + QJsonObject newCache; if (full) { - LogManager::instance()->log("UFT3.0 全量扫描模式"); QElapsedTimer timer; timer.start(); QMap mtimeByPath; - for (const Uft3FileEntry& e : entries) mtimeByPath[e.path] = e.mtime; + for (const FileEntry& e : entries) mtimeByPath[e.path] = e.mtime; - auto result = uft3.scanDir(dirPath, filterDict, moduleGenerationCheck); - QMap resultMap = result.first; - QMap transCodeMap = result.second; + ScanResult scanResult = scanner->scanDir(dirPath, baseCtx); + QMap resultMap = scanResult.resultMap; + QMap transCodeMap = scanResult.transCodeMap; for (const QString& key : resultMap.keys()) { const InterfaceInfo& info = resultMap[key]; - resultJson[key] = interfaceInfoToUFT3Json(info); + resultJson[key] = isUft3 ? interfaceInfoToUFT3Json(info) : interfaceInfoToUF2Json(info); QJsonObject rec; rec["mtime"] = QString::number(mtimeByPath.value(info.path, 0)); rec["key"] = key; rec["trans"] = false; - newUft3Cache[info.path] = rec; + newCache[info.path] = rec; } for (const QString& key : transCodeMap.keys()) { const InterfaceInfo& info = transCodeMap[key]; @@ -442,29 +346,32 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath) rec["mtime"] = QString::number(mtimeByPath.value(info.path, 0)); rec["key"] = key; rec["trans"] = true; - newUft3Cache[info.path] = rec; + newCache[info.path] = rec; } - - LogManager::instance()->log(QString("UFT3.0 全量扫描耗时: %1ms").arg(timer.elapsed())); + LogManager::instance()->log(QString("%1 全量扫描耗时: %2ms").arg(cacheKey).arg(timer.elapsed())); } else { - LogManager::instance()->log("UFT3.0 增量扫描模式"); QElapsedTimer timer; timer.start(); - QJsonObject oldUft3 = oldCache["uft3"].toObject(); + + QJsonObject oldCacheForVer = oldCache[cacheKey].toObject(); QSet currentPaths; - resultJson = oldMainJson; // 以旧结果为基准 + resultJson = oldJson; transCodeJson = oldTransJson; - for (const Uft3FileEntry& e : entries) { + for (const FileEntry& e : entries) { currentPaths.insert(e.path); - QJsonObject rec = oldUft3.value(e.path).toObject(); + QJsonObject rec = oldCacheForVer.value(e.path).toObject(); bool changed = rec.isEmpty() || rec["mtime"].toString().toLongLong() != e.mtime; if (changed) { - InterfaceInfo info = uft3.assemble(e.path); + ScanContext actx = baseCtx; + actx.serverType = e.serverType; + actx.moduleCodeMap = moduleCodeMap; + InterfaceInfo info = scanner->assemble(e.path, actx); QString newKey = info.cname; QString oldKey = rec["key"].toString(); bool oldTrans = rec["trans"].toBool(); bool newTrans = e.isTrans; + // 若文件归属(主接口/交易码)或名称发生变化,先移除旧条目 if (!oldKey.isEmpty()) { if (oldTrans) { @@ -473,21 +380,23 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath) if (resultJson.contains(oldKey)) resultJson.remove(oldKey); } } - QJsonObject val = interfaceInfoToUFT3Json(info); - if (newTrans) transCodeJson[newKey] = val; + + QJsonObject val = isUft3 ? interfaceInfoToUFT3Json(info) : interfaceInfoToUF2Json(info); + if (newTrans && !transOutputPath.isEmpty()) transCodeJson[newKey] = val; else resultJson[newKey] = val; + QJsonObject crec; crec["mtime"] = QString::number(e.mtime); crec["key"] = newKey; crec["trans"] = newTrans; - newUft3Cache[e.path] = crec; + newCache[e.path] = crec; } else { - newUft3Cache[e.path] = rec; + newCache[e.path] = rec; // 未变化:直接沿用旧缓存记录 } } // 删除已从磁盘移除的文件对应的条目 - for (auto it = oldUft3.begin(); it != oldUft3.end(); ++it) { + for (auto it = oldCacheForVer.begin(); it != oldCacheForVer.end(); ++it) { if (!currentPaths.contains(it.key())) { QJsonObject rec = it.value().toObject(); QString oldKey = rec["key"].toString(); @@ -499,24 +408,21 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath) } } } - - LogManager::instance()->log(QString("UFT3.0 增量扫描耗时: %1ms").arg(timer.elapsed())); + LogManager::instance()->log(QString("%1 增量扫描耗时: %2ms").arg(cacheKey).arg(timer.elapsed())); } - if (!writeJson(outputPath, resultJson)) { - return false; - } - if (!writeJson(transOutputPath, transCodeJson)) { - return false; + if (!writeJson(outputPath, resultJson)) return false; + if (!transOutputPath.isEmpty()) { + if (!writeJson(transOutputPath, transCodeJson)) return false; } QJsonObject merged = oldCache; - merged["uft3"] = newUft3Cache; + merged[cacheKey] = newCache; if (!writeCache(merged)) { - LogManager::instance()->logWarning("UFT3.0 缓存写入失败(不影响本次结果)"); + LogManager::instance()->logWarning(QString("%1 缓存写入失败(不影响本次结果)").arg(cacheKey)); } - LogManager::instance()->log(QString("UFT3.0 接口处理完成,结果已保存到: %1").arg(outputPath)); + LogManager::instance()->log(QString("%1 接口处理完成,结果已保存到: %2").arg(cacheKey).arg(outputPath)); return true; } diff --git a/src/metadataupdate/metadataprocessor.h b/src/metadataupdate/metadataprocessor.h index 07da95c..3627c34 100644 --- a/src/metadataupdate/metadataprocessor.h +++ b/src/metadataupdate/metadataprocessor.h @@ -7,7 +7,7 @@ #include #include -struct InterfaceInfo; +#include "interface_scanner.h" class MetadataProcessor : public QObject { @@ -45,6 +45,15 @@ private: // 轻量增量缓存(仅记录 文件路径->修改时间(mtime)+该文件在 JSON 中的 key) QJsonObject readCache(); bool writeCache(const QJsonObject& cache); + + // 统一处理入口:通过 IInterfaceScanner 策略多态处理,消除 UF2/UFT3 平行实现 + bool processInterfaces(IInterfaceScanner* scanner, + const ScanContext& baseCtx, + const QString& dirPath, + const QString& outputPath, + const QString& transOutputPath, + const QString& cacheKey, + bool isUft3); QString m_basePath; QString m_uf2touft3Path; diff --git a/src/metadataupdate/uf2datatype.cpp b/src/metadataupdate/uf2datatype.cpp index 1829299..dabfb02 100644 --- a/src/metadataupdate/uf2datatype.cpp +++ b/src/metadataupdate/uf2datatype.cpp @@ -1,5 +1,7 @@ #include "uf2datatype.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -12,9 +14,8 @@ QMap> Uf2DataType::loadData(const QString& dirPa { QMap> resultMap; - QString filePath = dirPath; - filePath.replace('\\', '/'); - filePath += "/公共资源/datatypes.xml"; + QString filePath = PathUtils::normPath(dirPath); + filePath += Constants::MetaPaths::Uf2DataType; LogManager::instance()->log(QString("加载UF2数据类型文件: %1").arg(filePath)); diff --git a/src/metadataupdate/uf2errnumber.cpp b/src/metadataupdate/uf2errnumber.cpp index a58b60c..3d70c61 100644 --- a/src/metadataupdate/uf2errnumber.cpp +++ b/src/metadataupdate/uf2errnumber.cpp @@ -1,5 +1,7 @@ #include "uf2errnumber.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -12,9 +14,8 @@ QMap> Uf2ErrNumber::loadData(const QString& dirP { QMap> resultMap; - QString filePath = dirPath; - filePath.replace('\\', '/'); - filePath += "/公共资源/errNumbers.xml"; + QString filePath = PathUtils::normPath(dirPath); + filePath += Constants::MetaPaths::Uf2ErrNumber; LogManager::instance()->log(QString("加载UF2错误号文件: %1").arg(filePath)); diff --git a/src/metadataupdate/uf2interface.cpp b/src/metadataupdate/uf2interface.cpp index d6d382f..c210d34 100644 --- a/src/metadataupdate/uf2interface.cpp +++ b/src/metadataupdate/uf2interface.cpp @@ -1,5 +1,6 @@ #include "uf2interface.h" #include "utils/logmanager.h" +#include "utils/Constants.h" #include #include #include @@ -17,8 +18,7 @@ InterfaceInfo Uf2Interface::loadInterface(const QString& filePath) InterfaceInfo result; result.path = filePath; - QString sFilePath = filePath; - sFilePath.replace('\\', '/'); + QString sFilePath = PathUtils::normPath(filePath); QFileInfo fileInfo(sFilePath); if (!fileInfo.exists()) { @@ -161,19 +161,18 @@ bool Uf2Interface::shouldFilter(const QString& filePath, return false; } -QMap Uf2Interface::scanDir(const QString& dirPath, - const QString& ufAcct20ProjectDir, - const QString& uftDbSett, - const QMap& fileFilterDict, - const QJsonObject& custJson) +ScanResult Uf2Interface::scanDir(const QString& dirPath, const ScanContext& ctx) { QMap resultMap; - QStringList completeNameList = fileFilterDict.value("completeNameList"); - QStringList containNameList = fileFilterDict.value("containNameList"); - + QStringList completeNameList = ctx.fileFilterDict.value("completeNameList"); + QStringList containNameList = ctx.fileFilterDict.value("containNameList"); + const QString& ufAcct20ProjectDir = ctx.ufAcct20ProjectDir; + const QString& uftDbSett = ctx.uftDbSett; + const QJsonObject& custJson = ctx.custJson; + QMap moduleCodeMap; moduleCodeMap["转融通"] = "ref"; - + // 预提取cust.json中的模块英文名映射 QMap custMoudleENameMap; if (!custJson.isEmpty() && custJson.contains("moudleEName")) { @@ -192,8 +191,7 @@ QMap Uf2Interface::scanDir(const QString& dirPath, QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); if (filePath.contains("/数据库/")) { continue; @@ -242,8 +240,7 @@ QMap Uf2Interface::scanDir(const QString& dirPath, QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); if (shouldFilter(filePath, completeNameList, containNameList)) { continue; } @@ -277,8 +274,7 @@ QMap Uf2Interface::scanDir(const QString& dirPath, QFileInfoList dirList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo& dirInfo : dirList) { - QString subDirPath = dirInfo.absoluteFilePath(); - subDirPath.replace('\\', '/'); + QString subDirPath = PathUtils::normPath(dirInfo.absoluteFilePath()); scanInterfaceFilesRecursive(subDirPath, serverType); } }; @@ -292,17 +288,19 @@ QMap Uf2Interface::scanDir(const QString& dirPath, scanInterfaceFiles(uftDbSett, "dbsett"); LogManager::instance()->log(QString("扫描完成,共找到 %1 个接口").arg(resultMap.size())); - - return resultMap; + + ScanResult result; + result.resultMap = resultMap; + return result; } -QMap Uf2Interface::buildModuleCodeMap(const QString& dirPath, - const QString& ufAcct20ProjectDir, - const QString& uftDbSett, - const QJsonObject& custJson) +QMap Uf2Interface::buildModuleCodeMap(const QString& dirPath, const ScanContext& ctx) { QMap moduleCodeMap; moduleCodeMap["转融通"] = "ref"; + const QString& ufAcct20ProjectDir = ctx.ufAcct20ProjectDir; + const QString& uftDbSett = ctx.uftDbSett; + const QJsonObject& custJson = ctx.custJson; // 预提取cust.json中的模块英文名映射 QMap custMoudleENameMap; @@ -320,8 +318,7 @@ QMap Uf2Interface::buildModuleCodeMap(const QString& dirPath, filters << "module.xml"; QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); if (filePath.contains("/数据库/")) continue; QString modName = fileInfo.dir().dirName(); QString modEName = getModuleEName(filePath); @@ -339,18 +336,17 @@ QMap Uf2Interface::buildModuleCodeMap(const QString& dirPath, return moduleCodeMap; } -InterfaceInfo Uf2Interface::assemble(const QString& filePath, - const QString& serverType, - const QMap& moduleCodeMap) +InterfaceInfo Uf2Interface::assemble(const QString& filePath, const ScanContext& ctx) { - QString sFilePath = filePath; - sFilePath.replace('\\', '/'); + QString sFilePath = PathUtils::normPath(filePath); int lastSlash = sFilePath.lastIndexOf('/'); QString pathWithoutFile = sFilePath.left(lastSlash); int secondLastSlash = pathWithoutFile.lastIndexOf('/'); QString pathWithoutLastDir = pathWithoutFile.left(secondLastSlash); int thirdLastSlash = pathWithoutLastDir.lastIndexOf('/'); QString modName = pathWithoutLastDir.mid(thirdLastSlash + 1); + const QString& serverType = ctx.serverType; + const QMap& moduleCodeMap = ctx.moduleCodeMap; InterfaceInfo interfaceInfo = loadInterface(filePath); interfaceInfo.moudle = modName; @@ -359,13 +355,11 @@ InterfaceInfo Uf2Interface::assemble(const QString& filePath, return interfaceInfo; } -QList Uf2Interface::collectFiles(const QString& dirPath, - const QString& serverType, - const QMap& fileFilterDict) +QList Uf2Interface::collectFiles(const QString& dirPath, const ScanContext& ctx) { - QList result; - QStringList completeNameList = fileFilterDict.value("completeNameList"); - QStringList containNameList = fileFilterDict.value("containNameList"); + QList result; + QStringList completeNameList = ctx.fileFilterDict.value("completeNameList"); + QStringList containNameList = ctx.fileFilterDict.value("containNameList"); std::function scanInterfaceFilesRecursive = [&](const QString& path, const QString& st) { @@ -374,10 +368,9 @@ QList Uf2Interface::collectFiles(const QString& dirPath, if (!dir.exists()) return; QFileInfoList fileList = dir.entryInfoList(QStringList() << "*.*", QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); if (shouldFilter(filePath, completeNameList, containNameList)) continue; - Uf2FileEntry entry; + FileEntry entry; entry.path = filePath; entry.serverType = st; entry.mtime = fileInfo.lastModified().toMSecsSinceEpoch(); @@ -386,6 +379,10 @@ QList Uf2Interface::collectFiles(const QString& dirPath, QFileInfoList dirList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo& dirInfo : dirList) scanInterfaceFilesRecursive(dirInfo.absoluteFilePath(), st); }; - scanInterfaceFilesRecursive(dirPath, serverType); + scanInterfaceFilesRecursive(dirPath, "trade"); + if (!ctx.ufAcct20ProjectDir.isEmpty()) + scanInterfaceFilesRecursive(ctx.ufAcct20ProjectDir, "acct"); + if (!ctx.uftDbSett.isEmpty()) + scanInterfaceFilesRecursive(ctx.uftDbSett, "dbsett"); return result; } diff --git a/src/metadataupdate/uf2interface.h b/src/metadataupdate/uf2interface.h index c5790f1..88d36ea 100644 --- a/src/metadataupdate/uf2interface.h +++ b/src/metadataupdate/uf2interface.h @@ -5,80 +5,28 @@ #include #include #include -#include #include -struct FieldInfo { - QString name; - QString flag; - QString desc; - QString uuid; - QString paramType; - QString type; - QString cname; - QString hsType; -}; +#include "interface_scanner.h" -struct InterfaceInfo { - QString cname; - QString eName; - QString functionNo; - QString flag; - QString path; - QString code; - QString id; - QString sysStatus; - QString description; - QString moudle; - QString moudleEName; - QString serverType; - bool returnResultSet = false; - bool needTransMonitor = false; - bool checkLicence = false; - - QMap inputFields; - QMap outputFields; - QMap variableFields; - QMap internalFields; -}; - -// 仅记录文件路径、归属类型、修改时间,用于轻量增量缓存 -struct Uf2FileEntry { - QString path; - QString serverType; // trade / acct / dbsett - qint64 mtime = 0; -}; - -class Uf2Interface : public QObject +class Uf2Interface : public QObject, public IInterfaceScanner { Q_OBJECT public: explicit Uf2Interface(QObject *parent = nullptr); - - InterfaceInfo loadInterface(const QString& filePath); - QMap scanDir(const QString& dirPath, - const QString& ufAcct20ProjectDir, - const QString& uftDbSett, - const QMap& fileFilterDict, - const QJsonObject& custJson = QJsonObject()); - // 以下为增量更新提供复用能力(与 scanDir 内部逻辑一致) - QMap buildModuleCodeMap(const QString& dirPath, - const QString& ufAcct20ProjectDir, - const QString& uftDbSett, - const QJsonObject& custJson = QJsonObject()); - InterfaceInfo assemble(const QString& filePath, - const QString& serverType, - const QMap& moduleCodeMap); - QList collectFiles(const QString& dirPath, - const QString& serverType, - const QMap& fileFilterDict); + // ---- IInterfaceScanner 实现 ---- + InterfaceInfo loadInterface(const QString& filePath) override; + ScanResult scanDir(const QString& dirPath, const ScanContext& ctx) override; + InterfaceInfo assemble(const QString& filePath, const ScanContext& ctx) override; + QList collectFiles(const QString& dirPath, const ScanContext& ctx) override; + QMap buildModuleCodeMap(const QString& dirPath, const ScanContext& ctx) override; private: QString getModuleEName(const QString& moduleXmlPath); - bool shouldFilter(const QString& filePath, - const QStringList& completeNameList, + bool shouldFilter(const QString& filePath, + const QStringList& completeNameList, const QStringList& containNameList); }; -#endif // UF2INTERFACE_H \ No newline at end of file +#endif // UF2INTERFACE_H diff --git a/src/metadataupdate/uf2stdfields.cpp b/src/metadataupdate/uf2stdfields.cpp index b4658e5..d0c1b1f 100644 --- a/src/metadataupdate/uf2stdfields.cpp +++ b/src/metadataupdate/uf2stdfields.cpp @@ -1,5 +1,7 @@ #include "uf2stdfields.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -14,9 +16,8 @@ QMap> Uf2StdFields::loadData( { QMap> resultMap; - QString filePath = dirPath; - filePath.replace('\\', '/'); - filePath += "/公共资源/stdfields.xml"; + QString filePath = PathUtils::normPath(dirPath); + filePath += Constants::MetaPaths::Uf2StdField; LogManager::instance()->log(QString("加载UF2标准字段文件: %1").arg(filePath)); diff --git a/src/metadataupdate/uft3datatype.cpp b/src/metadataupdate/uft3datatype.cpp index 083da61..22e824b 100644 --- a/src/metadataupdate/uft3datatype.cpp +++ b/src/metadataupdate/uft3datatype.cpp @@ -1,5 +1,7 @@ #include "uft3datatype.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -12,9 +14,8 @@ QMap> Uft3DataType::loadData(const QString& dirP { QMap> resultMap; - QString filePath = dirPath; - filePath.replace('\\', '/'); - filePath += "/metadata/datatype.datatype"; + QString filePath = PathUtils::normPath(dirPath); + filePath += Constants::MetaPaths::Uft3DataType; LogManager::instance()->log(QString("加载UFT3数据类型文件: %1").arg(filePath)); diff --git a/src/metadataupdate/uft3interface.cpp b/src/metadataupdate/uft3interface.cpp index 33e7e73..1f8e056 100644 --- a/src/metadataupdate/uft3interface.cpp +++ b/src/metadataupdate/uft3interface.cpp @@ -1,5 +1,6 @@ #include "uft3interface.h" #include "utils/logmanager.h" +#include "utils/Constants.h" #include "filedb.h" #include #include @@ -8,6 +9,20 @@ #include #include +namespace { + // 从接口文件首行的属性串中提取 key="value" 中的 value(已 trimmed) + QString extractAttr(const QString& line, const QString& attrName) + { + const QString token = attrName + "=\""; + int pos = line.indexOf(token); + if (pos == -1) return QString(); + pos += token.length(); + int endPos = line.indexOf('"', pos); + if (endPos == -1) return QString(); + return line.mid(pos, endPos - pos).trimmed(); + } +} + Uft3Interface::Uft3Interface(QObject *parent) : QObject(parent) { } @@ -17,8 +32,7 @@ InterfaceInfo Uft3Interface::loadInterface(const QString& filePath) InterfaceInfo result; result.path = filePath; - QString sFilePath = filePath; - sFilePath.replace('\\', '/'); + QString sFilePath = PathUtils::normPath(filePath); QFileInfo fileInfo(sFilePath); if (!fileInfo.exists()) { @@ -37,59 +51,12 @@ InterfaceInfo Uft3Interface::loadInterface(const QString& filePath) line = in.readLine(); if (!line.isEmpty()) { - int pos = line.indexOf("chineseName=\""); - if (pos != -1) { - pos += QString("chineseName=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.cname = line.mid(pos, endPos - pos).trimmed(); - } - } - - pos = line.indexOf("objectId=\""); - if (pos != -1) { - pos += QString("objectId=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.functionNo = line.mid(pos, endPos - pos).trimmed(); - } - } - - pos = line.indexOf("description=\""); - if (pos != -1) { - pos += QString("description=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.description = line.mid(pos, endPos - pos).trimmed(); - } - } - - pos = line.indexOf("interfaceFlag=\""); - if (pos != -1) { - pos += QString("interfaceFlag=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.flag = line.mid(pos, endPos - pos).trimmed(); - } - } - - pos = line.indexOf("id=\""); - if (pos != -1) { - pos += QString("id=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.id = line.mid(pos, endPos - pos).trimmed(); - } - } - - pos = line.indexOf("sysStatus=\""); - if (pos != -1) { - pos += QString("sysStatus=\"").length(); - int endPos = line.indexOf('"', pos); - if (endPos != -1) { - result.sysStatus = line.mid(pos, endPos - pos).trimmed(); - } - } + result.cname = extractAttr(line, "chineseName"); + result.functionNo = extractAttr(line, "objectId"); + result.description = extractAttr(line, "description"); + result.flag = extractAttr(line, "interfaceFlag"); + result.id = extractAttr(line, "id"); + result.sysStatus = extractAttr(line, "sysStatus"); } result.eName = fileInfo.baseName(); @@ -197,18 +164,13 @@ void Uft3Interface::scanDirRecursive(const QString& dirPath, QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); - + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); + if (shouldFilter(filePath, dirPath, completeNameList, containNameList, moduleGenerationCheck)) { continue; } - - bool isTransCode = (filePath.contains("/cbptrans/") || - filePath.contains("/mgrcbptrans/") || - filePath.contains("/converttrans/") || - filePath.contains("/ses/")) && - !filePath.contains("/cbptrans/cbptranspub/"); + + bool isTransCode = PathUtils::isTransCodePath(filePath); QString modName = fileInfo.dir().dirName(); InterfaceInfo interfaceInfo = loadInterface(filePath); @@ -229,23 +191,24 @@ void Uft3Interface::scanDirRecursive(const QString& dirPath, } } -QPair, QMap> -Uft3Interface::scanDir(const QString& dirPath, - const QMap& fileFilterDict, - const QString& moduleGenerationCheck) +ScanResult Uft3Interface::scanDir(const QString& dirPath, const ScanContext& ctx) { QMap resultMap; QMap transCodeMap; - - QStringList completeNameList = fileFilterDict.value("completeNameList"); - QStringList containNameList = fileFilterDict.value("containNameList"); - + + QStringList completeNameList = ctx.fileFilterDict.value("completeNameList"); + QStringList containNameList = ctx.fileFilterDict.value("containNameList"); + const QString& moduleGenerationCheck = ctx.moduleGenerationCheck; + scanDirRecursive(dirPath, completeNameList, containNameList, moduleGenerationCheck, resultMap, transCodeMap); - - return qMakePair(resultMap, transCodeMap); + + ScanResult result; + result.resultMap = resultMap; + result.transCodeMap = transCodeMap; + return result; } -InterfaceInfo Uft3Interface::assemble(const QString& filePath) +InterfaceInfo Uft3Interface::assemble(const QString& filePath, const ScanContext&) { QFileInfo fileInfo(filePath); fileInfo.setFile(fileInfo.absoluteFilePath()); // 标准化路径 @@ -255,13 +218,12 @@ InterfaceInfo Uft3Interface::assemble(const QString& filePath) return interfaceInfo; } -QList Uft3Interface::collectFiles(const QString& dirPath, - const QMap& fileFilterDict, - const QString& moduleGenerationCheck) +QList Uft3Interface::collectFiles(const QString& dirPath, const ScanContext& ctx) { - QList result; - QStringList completeNameList = fileFilterDict.value("completeNameList"); - QStringList containNameList = fileFilterDict.value("containNameList"); + QList result; + QStringList completeNameList = ctx.fileFilterDict.value("completeNameList"); + QStringList containNameList = ctx.fileFilterDict.value("containNameList"); + const QString& moduleGenerationCheck = ctx.moduleGenerationCheck; std::function scanInterfaceFilesRecursive = [&](const QString& path) { @@ -271,18 +233,15 @@ QList Uft3Interface::collectFiles(const QString& dirPath, QDir dir(path); QFileInfoList fileList = dir.entryInfoList(QStringList() << "*.*", QDir::Files); for (const QFileInfo& fileInfo : fileList) { - QString filePath = fileInfo.absoluteFilePath(); - filePath.replace('\\', '/'); + QString filePath = PathUtils::normPath(fileInfo.absoluteFilePath()); if (shouldFilter(filePath, dirPath, completeNameList, containNameList, moduleGenerationCheck)) { continue; } - bool isTransCode = (filePath.contains("/cbptrans/") || filePath.contains("/mgrcbptrans/") - || filePath.contains("/converttrans/") || filePath.contains("/ses/")) - && !filePath.contains("/cbptrans/cbptranspub/"); + bool isTransCode = PathUtils::isTransCodePath(filePath); - Uft3FileEntry entry; + FileEntry entry; entry.path = filePath; entry.mtime = fileInfo.lastModified().toMSecsSinceEpoch(); entry.isTrans = isTransCode; diff --git a/src/metadataupdate/uft3interface.h b/src/metadataupdate/uft3interface.h index 9e056eb..f9a37f0 100644 --- a/src/metadataupdate/uft3interface.h +++ b/src/metadataupdate/uft3interface.h @@ -5,35 +5,21 @@ #include #include #include -#include #include -#include "uf2interface.h" +#include "interface_scanner.h" -// 仅记录文件路径、修改时间、是否属于交易码,用于轻量增量缓存 -struct Uft3FileEntry { - QString path; - qint64 mtime = 0; - bool isTrans = false; -}; - -class Uft3Interface : public QObject +class Uft3Interface : public QObject, public IInterfaceScanner { Q_OBJECT public: explicit Uft3Interface(QObject *parent = nullptr); - - InterfaceInfo loadInterface(const QString& filePath); - QPair, QMap> - scanDir(const QString& dirPath, - const QMap& fileFilterDict, - const QString& moduleGenerationCheck); - // 以下为增量更新提供复用能力(与 scanDir 内部逻辑一致) - InterfaceInfo assemble(const QString& filePath); - QList collectFiles(const QString& dirPath, - const QMap& fileFilterDict, - const QString& moduleGenerationCheck); + // ---- IInterfaceScanner 实现 ---- + InterfaceInfo loadInterface(const QString& filePath) override; + ScanResult scanDir(const QString& dirPath, const ScanContext& ctx) override; + InterfaceInfo assemble(const QString& filePath, const ScanContext& ctx) override; + QList collectFiles(const QString& dirPath, const ScanContext& ctx) override; private: bool shouldFilter(const QString& filePath, @@ -41,7 +27,7 @@ private: const QStringList& completeNameList, const QStringList& containNameList, const QString& moduleGenerationCheck); - + void scanDirRecursive(const QString& dirPath, const QStringList& completeNameList, const QStringList& containNameList, @@ -50,4 +36,4 @@ private: QMap& transCodeMap); }; -#endif // UFT3INTERFACE_H \ No newline at end of file +#endif // UFT3INTERFACE_H diff --git a/src/metadataupdate/uft3stdfields.cpp b/src/metadataupdate/uft3stdfields.cpp index 988fbf9..96168e6 100644 --- a/src/metadataupdate/uft3stdfields.cpp +++ b/src/metadataupdate/uft3stdfields.cpp @@ -1,5 +1,7 @@ #include "uft3stdfields.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -14,9 +16,8 @@ QMap> Uft3StdFields::loadData( { QMap> resultMap; - QString filePath = dirPath; - filePath.replace('\\', '/'); - filePath += "/metadata/stdfield.stdfield"; + QString filePath = PathUtils::normPath(dirPath); + filePath += Constants::MetaPaths::Uft3StdField; LogManager::instance()->log(QString("加载UFT3标准字段文件: %1").arg(filePath)); diff --git a/src/metadataupdate/uft3table.cpp b/src/metadataupdate/uft3table.cpp index 69c4fa2..5614447 100644 --- a/src/metadataupdate/uft3table.cpp +++ b/src/metadataupdate/uft3table.cpp @@ -1,5 +1,7 @@ #include "uft3table.h" #include "utils/logmanager.h" +#include "utils/Constants.h" +#include "utils/Constants.h" #include #include #include @@ -14,9 +16,8 @@ QMap> Uft3Table::loadData(const QString& dirPat { QMap> resultMap; - QString scanPath = dirPath; - scanPath.replace('\\', '/'); - scanPath += "/uftstructure"; + QString scanPath = PathUtils::normPath(dirPath); + scanPath += Constants::MetaPaths::Uft3StructDir; LogManager::instance()->log(QString("加载UFT3表结构目录: %1").arg(scanPath)); @@ -29,7 +30,7 @@ QMap> Uft3Table::loadData(const QString& dirPat scanDirectory(scanPath, resultMap); for (const QString& filePath : _fileList) { - if (filePath.contains(".uftstructure")) { + if (filePath.contains(Constants::MetaPaths::Uft3StructExt)) { parseTableFile(filePath, resultMap); } } @@ -57,8 +58,7 @@ void Uft3Table::scanDirectory(const QString& dirPath, QMap>& resultMap) { - QString sFilePath = filePath; - sFilePath.replace('\\', '/'); + QString sFilePath = PathUtils::normPath(filePath); QFile file(sFilePath); if (!file.open(QIODevice::ReadOnly)) { @@ -84,7 +84,7 @@ void Uft3Table::parseTableFile(const QString& filePath, QMap &modes, int idx) CallChainAnalyzer *ca = m_analyzers[mode]; ca->setRootPath(path); - // 后台线程构建,复用进度框;无弹窗(静默),完成后继续下一模式 + // 后台线程构建,静默无弹窗(转码完成后自动调用,不弹进度框),完成后继续下一模式 runIndexBuild(ca, "正在刷新代码索引,请稍候...", [this, modes, idx, mode, path, ca](bool ok, const QString &message) { if (ok) { @@ -487,7 +487,8 @@ void CodeSearchPage::buildSilentChain(const QList &modes, int idx) .arg(mode == 0 ? "转码业务" : "非转码业务", message)); } buildSilentChain(modes, idx + 1); - }); + }, + /*showProgress=*/false); } void CodeSearchPage::onFunctionDoubleClicked(const QModelIndex &index) @@ -746,24 +747,31 @@ void CodeSearchPage::autoBuildIndexIfNeeded() void CodeSearchPage::runAsync(CallChainAnalyzer *ca, const QString &title, std::function(CallChainAnalyzer *)> task, - std::function onDone) + std::function onDone, + bool showProgress) { - // 模态进度框(不确定进度),后台任务期间维持界面响应 - auto *dlg = new QProgressDialog(title, QString(), 0, 0, this); - dlg->setWindowTitle("请稍候"); - dlg->setWindowModality(Qt::WindowModal); - dlg->setCancelButton(nullptr); - dlg->setRange(0, 0); // 不确定进度 - dlg->setMinimumDuration(0); - dlg->show(); + // 模态进度框(不确定进度),后台任务期间维持界面响应; + // showProgress 为 false 时(如转码后自动刷新索引)完全静默,不弹任何框 + QProgressDialog *dlg = nullptr; + if (showProgress) { + dlg = new QProgressDialog(title, QString(), 0, 0, this); + dlg->setWindowTitle("请稍候"); + dlg->setWindowModality(Qt::WindowModal); + dlg->setCancelButton(nullptr); + dlg->setRange(0, 0); // 不确定进度 + dlg->setMinimumDuration(0); + dlg->show(); + } using BuildResult = QPair; auto *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, [=]() { const BuildResult r = watcher->result(); watcher->deleteLater(); - dlg->close(); - dlg->deleteLater(); + if (dlg) { + dlg->close(); + dlg->deleteLater(); + } if (onDone) { onDone(r.first, r.second); } @@ -777,13 +785,14 @@ void CodeSearchPage::runAsync(CallChainAnalyzer *ca, const QString &title, } void CodeSearchPage::runIndexBuild(CallChainAnalyzer *ca, const QString &title, - std::function onDone) + std::function onDone, + bool showProgress) { runAsync(ca, title, [](CallChainAnalyzer *ca) -> QPair { QString msg; const bool ok = ca->buildIndex(&msg); return {ok, msg}; - }, onDone); + }, onDone, showProgress); } void CodeSearchPage::runCacheLoad(CallChainAnalyzer *ca, const QString &title, bool skipFingerprint, diff --git a/src/pages/codesearch/codesearchpage.h b/src/pages/codesearch/codesearchpage.h index c41126a..5870f7b 100644 --- a/src/pages/codesearch/codesearchpage.h +++ b/src/pages/codesearch/codesearchpage.h @@ -89,13 +89,16 @@ private: void applyMode(); CallChainAnalyzer *currentAnalyzer() const; - /// 后台线程异步执行索引任务(避免界面卡顿),期间显示模态进度框;完成后回调 onDone(ok, message) + /// 后台线程异步执行索引任务(避免界面卡顿);showProgress 为 true 时显示模态进度框, + /// 为 false 时完全静默(转码后自动刷新使用),完成后回调 onDone(ok, message) void runAsync(CallChainAnalyzer *ca, const QString &title, std::function(CallChainAnalyzer *)> task, - std::function onDone); - /// 后台线程异步构建索引(扫描目录),完成后回调 onDone + std::function onDone, + bool showProgress = true); + /// 后台线程异步构建索引(扫描目录),完成后回调 onDone;showProgress 控制是否显示进度框 void runIndexBuild(CallChainAnalyzer *ca, const QString &title, - std::function onDone); + std::function onDone, + bool showProgress = true); /// 后台线程异步从缓存加载索引;skipFingerprint 为 true 时跳过全量目录指纹校验 void runCacheLoad(CallChainAnalyzer *ca, const QString &title, bool skipFingerprint, std::function onDone); diff --git a/src/pages/functionsearch/uf20functionsearchpage.cpp b/src/pages/functionsearch/uf20functionsearchpage.cpp index d332aa4..4293a74 100644 --- a/src/pages/functionsearch/uf20functionsearchpage.cpp +++ b/src/pages/functionsearch/uf20functionsearchpage.cpp @@ -29,8 +29,8 @@ void UF20FunctionSearchPage::initUI() searchLayout->setSpacing(10); m_searchComboBox = new ElaComboBox; - m_searchComboBox->addItem("功能名称", "cname"); - m_searchComboBox->addItem("功能编号", "function_no"); + m_searchComboBox->addItem("函数名", "cname"); + m_searchComboBox->addItem("功能号", "function_no"); searchLayout->addWidget(m_searchComboBox); m_searchEdit = new ElaSearchEdit; @@ -47,7 +47,7 @@ void UF20FunctionSearchPage::initUI() m_tableModel = new QStandardItemModel(this); m_tableModel->setColumnCount(3); - m_tableModel->setHorizontalHeaderLabels({"功能名称", "英文名", "功能编号"}); + m_tableModel->setHorizontalHeaderLabels({"函数名", "英文名", "功能号"}); m_resultTable = new ElaTableView; m_resultTable->setModel(m_tableModel); diff --git a/src/pages/functionsearch/uft3functionsearchpage.cpp b/src/pages/functionsearch/uft3functionsearchpage.cpp index 038fe2a..f90bd70 100644 --- a/src/pages/functionsearch/uft3functionsearchpage.cpp +++ b/src/pages/functionsearch/uft3functionsearchpage.cpp @@ -29,8 +29,8 @@ void UFT3FunctionSearchPage::initUI() searchLayout->setSpacing(10); m_searchComboBox = new ElaComboBox; - m_searchComboBox->addItem("功能名称", "cname"); - m_searchComboBox->addItem("功能编号", "function_no"); + m_searchComboBox->addItem("函数名", "cname"); + m_searchComboBox->addItem("功能号", "function_no"); searchLayout->addWidget(m_searchComboBox); m_searchEdit = new ElaSearchEdit; @@ -47,7 +47,7 @@ void UFT3FunctionSearchPage::initUI() m_tableModel = new QStandardItemModel(this); m_tableModel->setColumnCount(3); - m_tableModel->setHorizontalHeaderLabels({"功能名称", "英文名", "功能编号"}); + m_tableModel->setHorizontalHeaderLabels({"函数名", "英文名", "功能号"}); m_resultTable = new ElaTableView; m_resultTable->setModel(m_tableModel); diff --git a/src/pages/metadatupdate/metadatupdatepage.cpp b/src/pages/metadatupdate/metadatupdatepage.cpp index 8105da7..e1c04c8 100644 --- a/src/pages/metadatupdate/metadatupdatepage.cpp +++ b/src/pages/metadatupdate/metadatupdatepage.cpp @@ -1,6 +1,7 @@ #include "metadatupdatepage.h" #include "utils/Constants.h" #include "utils/logmanager.h" +#include "utils/business_strategy.h" #include #include #include @@ -429,11 +430,8 @@ void MetadataUpdatePage::onWorkerFinished() // 数据缓存重载必须在主线程执行(DataCache 单例与静态 cnameCache 非线程安全) if (r.needReload) { _processLabel->setText("正在更新数据缓存..."); - if (m_currentTaskName == "UF20") { - UF2ConfigReader::reloadUF20Config(); - } else { - UFT3ConfigReader::reloadUFT3Config(); - } + // 通过业务策略工厂按当前任务选择对应版本的配置重载,取代 UF20/UFT3 if/else + createBusinessStrategy(m_currentTaskName)->reloadConfig(); } onUpdateFinished(r.success, QString("%1更新完成").arg(m_currentTaskName)); diff --git a/src/utils/Constants.h b/src/utils/Constants.h index 5c051b9..561ccb6 100644 --- a/src/utils/Constants.h +++ b/src/utils/Constants.h @@ -58,6 +58,22 @@ namespace Cache { inline const QString DbFile = "config_cache.db"; } +// --------------------------------------------------------------------------- +// 元数据文件相对子路径(相对于业务工程目录 dirPath 拼接) +// --------------------------------------------------------------------------- +namespace MetaPaths { + // UFT3 + inline const QString Uft3StdField = "/metadata/stdfield.stdfield"; // UFT3 标准字段文件 + inline const QString Uft3DataType = "/metadata/datatype.datatype"; // UFT3 数据类型文件 + inline const QString Uft3StructDir = "/uftstructure"; // UFT3 表结构目录 + // UF2 + inline const QString Uf2StdField = "/公共资源/stdfields.xml"; // UF2 标准字段文件 + inline const QString Uf2DataType = "/公共资源/datatypes.xml"; // UF2 数据类型文件 + inline const QString Uf2ErrNumber = "/公共资源/errNumbers.xml"; // UF2 错误号文件 + // 文件扩展名 + inline const QString Uft3StructExt = ".uftstructure"; // UFT3 表结构文件扩展名 +} + // --------------------------------------------------------------------------- // 运行时路径辅助函数(基于 applicationDirPath 拼接) // --------------------------------------------------------------------------- @@ -84,4 +100,36 @@ inline QString functionOutput() { return Paths::FunctionOutput; } } // namespace Constants +// ============================================================================ +// 路径与规则相关的公共工具(原 pathutils.h 合并至此) +// - 反斜杠标准化(replace('\\','/')) +// - “转码接口”路径判定规则(原在 uft3interface/uf2interface 多处硬编码) +// 仅依赖 ,不引入额外耦合 +// ============================================================================ + +namespace PathUtils { + +// 将 Windows 反斜杠统一为斜杠,返回标准化后的副本(不修改入参) +inline QString normPath(const QString& path) +{ + QString p = path; + p.replace('\\', '/'); + return p; +} + +// 判断路径是否属于“转码”接口:位于 cbptrans / mgrcbptrans / converttrans / ses +// 目录下,但排除 cbptrans/cbptranspub 公共转码目录。内部先做路径标准化, +// 因此即使传入含反斜杠的原始路径也能正确判定。 +inline bool isTransCodePath(const QString& filePath) +{ + const QString p = normPath(filePath); + return (p.contains("/cbptrans/") || + p.contains("/mgrcbptrans/") || + p.contains("/converttrans/") || + p.contains("/ses/")) && + !p.contains("/cbptrans/cbptranspub/"); +} + +} // namespace PathUtils + #endif // CONSTANTS_H diff --git a/src/utils/business_strategy.cpp b/src/utils/business_strategy.cpp new file mode 100644 index 0000000..f5e5fcc --- /dev/null +++ b/src/utils/business_strategy.cpp @@ -0,0 +1,73 @@ +#include "business_strategy.h" +#include "uf2configreader.h" +#include "uft3configreader.h" + +namespace { + +// UF2(UF20) 策略:对接 UF2ConfigReader。 +class Uf2BusinessStrategy : public IBusinessStrategy { +public: + BusinessVersion version() const override { return BusinessVersion::UF2; } + QString taskName() const override { return QStringLiteral("UF20"); } + + bool reloadConfig() const override { + return UF2ConfigReader::reloadUF20Config(); + } + bool checkFunctionExists(const QString& funcName) const override { + return UF2ConfigReader::checkFuncExistsStatic(funcName); + } + QSet getAllCnames() const override { + return UF2ConfigReader::getAllCnamesStatic(); + } + void clearCnameCache() const override { + UF2ConfigReader::clearCnameCache(); + } +}; + +// UFT3(UFT30) 策略:对接 UFT3ConfigReader。 +class Uft3BusinessStrategy : public IBusinessStrategy { +public: + BusinessVersion version() const override { return BusinessVersion::UFT3; } + QString taskName() const override { return QStringLiteral("UFT3"); } + + bool reloadConfig() const override { + return UFT3ConfigReader::reloadUFT3Config(); + } + bool checkFunctionExists(const QString& funcName) const override { + return UFT3ConfigReader::checkFuncExistsStatic(funcName); + } + QSet getAllCnames() const override { + return UFT3ConfigReader::getAllCnamesStatic(); + } + void clearCnameCache() const override { + UFT3ConfigReader::clearCnameCache(); + } +}; + +} // namespace + +std::unique_ptr createBusinessStrategy(BusinessVersion version) +{ + switch (version) { + case BusinessVersion::UF2: + return std::unique_ptr(new Uf2BusinessStrategy()); + case BusinessVersion::UFT3: + default: + return std::unique_ptr(new Uft3BusinessStrategy()); + } +} + +BusinessVersion businessVersionFromTaskName(const QString& taskName) +{ + // 兼容 "UF20" 任务名与 "uf2" 简写,其余归为 UFT3。 + if (taskName.compare(QStringLiteral("UF20"), Qt::CaseInsensitive) == 0 + || taskName.compare(QStringLiteral("UF2"), Qt::CaseInsensitive) == 0) { + return BusinessVersion::UF2; + } + return BusinessVersion::UFT3; +} + +std::unique_ptr createBusinessStrategy(const QString& taskName) +{ + return createBusinessStrategy(businessVersionFromTaskName(taskName)); +} diff --git a/src/utils/business_strategy.h b/src/utils/business_strategy.h new file mode 100644 index 0000000..7b72a78 --- /dev/null +++ b/src/utils/business_strategy.h @@ -0,0 +1,47 @@ +#ifndef BUSINESS_STRATEGY_H +#define BUSINESS_STRATEGY_H + +#include +#include +#include + +// 业务版本:集中表达 UF2(UF20) 与 UFT3(UFT30) 两个业务版本。 +// 取代散落各处的 taskName == "UF20" / 字符串判断 与成对 if/else 分支。 +enum class BusinessVersion { + UF2, + UFT3 +}; + +// 业务策略接口:把「按业务版本走不同实现」的差异收敛到一族策略对象里。 +// 调用方只面向 IBusinessStrategy 编程,具体版本由工厂选择注入。 +class IBusinessStrategy { +public: + virtual ~IBusinessStrategy() = default; + + // 版本标识与展示用任务名(如 "UF20" / "UFT3") + virtual BusinessVersion version() const = 0; + virtual QString taskName() const = 0; + + // 重新加载该版本配置(对接对应 ConfigReader 的 reload) + virtual bool reloadConfig() const = 0; + + // 函数(接口)是否存在 + virtual bool checkFunctionExists(const QString& funcName) const = 0; + + // 读取该版本全部 cname + virtual QSet getAllCnames() const = 0; + + // 清空该版本 cname 缓存 + virtual void clearCnameCache() const = 0; +}; + +// 工厂:按业务版本创建对应策略。 +std::unique_ptr createBusinessStrategy(BusinessVersion version); + +// 便捷:从任务名/字符串推断业务版本("UF20"/"uf2" -> UF2,其余 -> UFT3)。 +BusinessVersion businessVersionFromTaskName(const QString& taskName); + +// 便捷:直接按任务名创建策略。 +std::unique_ptr createBusinessStrategy(const QString& taskName); + +#endif // BUSINESS_STRATEGY_H diff --git a/src/utils/callchainanalyzer.cpp b/src/utils/callchainanalyzer.cpp index 7628b80..57b744f 100644 --- a/src/utils/callchainanalyzer.cpp +++ b/src/utils/callchainanalyzer.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include CallChainAnalyzer::CallChainAnalyzer(QObject *parent) : QObject(parent) @@ -385,57 +387,53 @@ bool CallChainAnalyzer::loadFromCache(const QString &path, QString *message, boo QSharedPointer CallChainAnalyzer::lookupBest(const QString &name) const { - // 1) 精确匹配 chineseName - { - auto it = m_chineseNameIndex.find(name); - if (it != m_chineseNameIndex.end()) { - return it.value(); - } - } + // 责任链:按优先级依次尝试多种查找策略,任一命中即返回;全部落空返回 nullptr。 + // 新增/调整查找策略时只需在此列表中增删条目,无需改动调用方。 + using Strategy = std::function()>; + const QString transSuffix = QString::fromUtf8("_转码"); - // 2) 精确匹配 canonicalName(文件 basename) - { - auto it = m_canonicalNameIndex.find(name); - if (it != m_canonicalNameIndex.end()) { - return it.value(); - } - } - - // 3) 尝试添加 "_转码" 后缀匹配(部分 .uftservice 的 chineseName 带有 _转码) - { - const QString withSuffix = name + QString::fromUtf8("_转码"); - auto it = m_chineseNameIndex.find(withSuffix); - if (it != m_chineseNameIndex.end()) { - return it.value(); - } - } - - // 4) 尝试去除 "_转码" 后缀匹配 - if (name.endsWith(QString::fromUtf8("_转码"))) { - const QString withoutSuffix = name.chopped(3); // "_转码" = 3 UTF-8 chars → but QString::chopped counts QChars - auto it = m_chineseNameIndex.find(withoutSuffix); - if (it != m_chineseNameIndex.end()) { - return it.value(); - } - } - - // 5) 在 chineseName 中做前缀匹配(如 LS_证券周边_大约可买获取 匹配 LS_证券周边_大约可买获取_转码) - // 注意:name 作为前缀只在它是完整函数名(非已调用子函数名)时才合理, - // 所以这里只做开头前缀匹配避免误匹配 - if (name.length() >= 4) { - // name 必须至少是一个有效函数名的前半部分 - for (auto it = m_chineseNameIndex.constBegin(); - it != m_chineseNameIndex.constEnd(); ++it) { - if (it.key().startsWith(name)) { - // 只接受 chineseName 在 name 后紧跟 "_转码" 或其他已知后缀的情况 - const QString suffix = it.key().mid(name.length()); - if (suffix == QString::fromUtf8("_转码") || suffix == "_转码") { - return it.value(); + const QVector strategies = { + // 1) 精确匹配 chineseName + [&]() -> QSharedPointer { + return m_chineseNameIndex.value(name); + }, + // 2) 精确匹配 canonicalName(文件 basename) + [&]() -> QSharedPointer { + return m_canonicalNameIndex.value(name); + }, + // 3) 添加 "_转码" 后缀匹配(部分 .uftservice 的 chineseName 带有 _转码) + [&]() -> QSharedPointer { + return m_chineseNameIndex.value(name + transSuffix); + }, + // 4) 去除 "_转码" 后缀匹配 + [&]() -> QSharedPointer { + if (name.endsWith(transSuffix)) { + return m_chineseNameIndex.value(name.chopped(transSuffix.length())); + } + return nullptr; + }, + // 5) chineseName 前缀匹配(如 LS_证券周边_大约可买获取 匹配 ..._转码) + // name 作为前缀只在它是完整函数名(非已调用子函数名)时才合理, + // 因此仅接受 name 后紧跟 "_转码" 后缀的情况,避免误匹配。 + [&]() -> QSharedPointer { + if (name.length() >= 4) { + for (auto it = m_chineseNameIndex.constBegin(); + it != m_chineseNameIndex.constEnd(); ++it) { + if (it.key().startsWith(name) + && it.key().mid(name.length()) == transSuffix) { + return it.value(); + } } } + return nullptr; + }, + }; + + for (const Strategy &strategy : strategies) { + if (auto info = strategy()) { + return info; } } - return nullptr; } diff --git a/src/utils/configreaderbase.cpp b/src/utils/configreaderbase.cpp new file mode 100644 index 0000000..fb65adc --- /dev/null +++ b/src/utils/configreaderbase.cpp @@ -0,0 +1,165 @@ +#include "configreaderbase.h" +#include "Constants.h" +#include "datacache.h" +#include "logmanager.h" +#include +#include + +bool ConfigReaderBase::doLoadAllConfig(const ConfigReaderTraits& traits) +{ + m_failedFiles.clear(); + + if (!DataCache::instance()->initDatabase()) { + LogManager::instance()->logError( + QString("%1配置加载失败 - 数据库初始化失败").arg(traits.logTag)); + m_failedFiles.append(traits.mainFile); + return false; + } + + QString filePath = Constants::toolDir() + "/" + traits.mainFile; + + if (traits.hasConfig(traits.mainFile)) { + m_loaded = true; + DataCache::instance()->setDataLoaded(true); + return true; + } + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + LogManager::instance()->logError( + QString("%1配置加载失败 - 无法打开文件: %2").arg(traits.logTag).arg(filePath)); + m_failedFiles.append(traits.mainFile); + return false; + } + + QByteArray data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + file.close(); + + if (doc.isNull() || !doc.isObject()) { + LogManager::instance()->logError( + QString("%1配置加载失败 - JSON解析失败: %2").arg(traits.logTag).arg(filePath)); + m_failedFiles.append(traits.mainFile); + return false; + } + + QJsonObject config = doc.object(); + if (!traits.saveConfig(config)) { + LogManager::instance()->logError( + QString("%1配置加载失败 - 保存失败").arg(traits.logTag)); + m_failedFiles.append(traits.mainFile); + return false; + } + + m_loaded = true; + DataCache::instance()->setDataLoaded(true); + return true; +} + +bool ConfigReaderBase::doReloadConfig(const ConfigReaderTraits& traits) +{ + QString binPath = Constants::toolDir(); + QJsonObject mergedConfig; + + for (const auto& entry : traits.reloadFiles) { + const QString& fileName = entry.first; + const bool required = entry.second; + QString filePath = binPath + "/" + fileName; + + LogManager::instance()->logDebug( + QString("%1配置重新加载 - 正在处理文件: %2 (required=%3)") + .arg(traits.logTag).arg(filePath).arg(required)); + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString msg = QString("%1配置重新加载%2 - 无法打开文件: %3") + .arg(traits.logTag) + .arg(required ? QStringLiteral("失败") : QString()) + .arg(filePath); + if (required) { + LogManager::instance()->logError(msg); + return false; + } + LogManager::instance()->logWarning(msg); + continue; + } + + QByteArray data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + file.close(); + + if (doc.isNull() || !doc.isObject()) { + if (required) { + LogManager::instance()->logError( + QString("%1配置重新加载失败 - JSON解析失败: %2").arg(traits.logTag).arg(filePath)); + return false; + } + continue; + } + + QJsonObject obj = doc.object(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + mergedConfig[it.key()] = it.value(); + } + } + + if (mergedConfig.isEmpty()) { + LogManager::instance()->logError( + QString("%1配置重新加载失败 - 配置为空").arg(traits.logTag)); + return false; + } + + if (!traits.saveConfig(mergedConfig)) { + LogManager::instance()->logError( + QString("%1配置重新加载失败 - 保存失败").arg(traits.logTag)); + return false; + } + + return true; +} + +bool ConfigReaderBase::doLoadCnameCache(const ConfigReaderTraits& traits, + QSet& cache, bool& cacheLoaded) +{ + if (cacheLoaded) { + return true; + } + + cache.clear(); + + if (!DataCache::instance()->initDatabase()) { + LogManager::instance()->logError( + QString("%1 Cname缓存加载失败 - 数据库初始化失败").arg(traits.logTag)); + return false; + } + + cache = traits.getAllCnames(); + cacheLoaded = true; + return true; +} + +bool ConfigReaderBase::doCheckFuncExists(const ConfigReaderTraits& traits, const QString& funcName, + QSet& cache, bool& cacheLoaded) +{ + if (!cacheLoaded) { + doLoadCnameCache(traits, cache, cacheLoaded); + } + return cache.contains(funcName); +} + +QSet ConfigReaderBase::doGetAllCnames(const ConfigReaderTraits& traits, + QSet& cache, bool& cacheLoaded) +{ + if (!cacheLoaded) { + doLoadCnameCache(traits, cache, cacheLoaded); + } + return cache; +} + +void ConfigReaderBase::doClearCnameCache(const QString& logMessage, + QSet& cache, bool& cacheLoaded) +{ + cache.clear(); + cacheLoaded = false; + LogManager::instance()->logInfo(logMessage); +} diff --git a/src/utils/configreaderbase.h b/src/utils/configreaderbase.h new file mode 100644 index 0000000..6b39cc4 --- /dev/null +++ b/src/utils/configreaderbase.h @@ -0,0 +1,55 @@ +#ifndef CONFIGREADERBASE_H +#define CONFIGREADERBASE_H + +#include +#include +#include +#include +#include +#include +#include + +// 描述某个业务版本(UF2/UFT3)配置读取的差异点。 +// 共享流程集中在 ConfigReaderBase 中,通过该结构注入版本相关的行为。 +struct ConfigReaderTraits +{ + QString logTag; // 日志前缀,如 "UF20" / "UFT3" + QString mainFile; // 主配置文件名,如 "uf2.json" + // 重载时读取的文件列表:{文件名, 是否必需}。 + // required=true -> 打不开或解析失败即整体失败(logError) + // required=false -> 打不开或解析失败则跳过(logWarning) + QVector> reloadFiles; + std::function hasConfig; // DataCache 是否已缓存该配置 + std::function saveConfig; // DataCache 保存配置 + std::function()> getAllCnames; // DataCache 读取全部 cname +}; + +// 模板方法模式的公共基类:承载 UF2/UFT3 配置读取的共享流程。 +// 派生类保留各自独立的静态缓存与对外 API,仅将差异通过 ConfigReaderTraits 注入。 +class ConfigReaderBase +{ +public: + QStringList getFailedFiles() const { return m_failedFiles; } + +protected: + // 实例流程:首次加载主配置到 DataCache + bool doLoadAllConfig(const ConfigReaderTraits& traits); + + // 静态流程:重新加载配置(可合并多个文件);成功返回 true(缓存清理由调用方处理) + static bool doReloadConfig(const ConfigReaderTraits& traits); + + // 静态流程:cname 缓存相关。cache/cacheLoaded 由各派生类持有(版本独立) + static bool doLoadCnameCache(const ConfigReaderTraits& traits, + QSet& cache, bool& cacheLoaded); + static bool doCheckFuncExists(const ConfigReaderTraits& traits, const QString& funcName, + QSet& cache, bool& cacheLoaded); + static QSet doGetAllCnames(const ConfigReaderTraits& traits, + QSet& cache, bool& cacheLoaded); + static void doClearCnameCache(const QString& logMessage, + QSet& cache, bool& cacheLoaded); + + QStringList m_failedFiles; + bool m_loaded = false; +}; + +#endif // CONFIGREADERBASE_H diff --git a/src/utils/logmanager.cpp b/src/utils/logmanager.cpp index 26745ec..099bfab 100644 --- a/src/utils/logmanager.cpp +++ b/src/utils/logmanager.cpp @@ -4,6 +4,15 @@ #include #include #include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif LogManager* LogManager::m_instance = nullptr; @@ -28,22 +37,19 @@ LogManager* LogManager::instance() void LogManager::initLogFile() { - QString dir = Constants::logDir(); + QString dir = m_logDir.isEmpty() ? QDir::currentPath() : m_logDir; QDir logDir(dir); if (!logDir.exists()) { logDir.mkpath("."); } - QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd"); - m_logFilePath = logDir.absoluteFilePath(QString("uft3_change_code_%1.log").arg(timestamp)); + m_logFilePath = logDir.absoluteFilePath(m_fileName); - m_logFile.setFileName(m_logFilePath); - if (!m_logFile.open(QIODevice::Append | QIODevice::Text)) { - qDebug() << "无法打开日志文件:" << m_logFilePath; - return; + if (m_toFile) { + cleanupByAge(); // 启动即清理过期日志 + ensureFileOpen(); } - - writeLog("INFO", "日志系统初始化完成"); + writeLog(LogLevel::Info, "日志系统初始化完成"); } QString LogManager::getLogFilePath() const @@ -51,44 +57,307 @@ QString LogManager::getLogFilePath() const return m_logFilePath; } -void LogManager::writeLog(const QString& level, const QString& message) +void LogManager::ensureFileOpen() { - QMutexLocker locker(&m_mutex); - - QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); - QString logLine = QString("[%1] [%2] %3\n").arg(timestamp, level, message); - if (m_logFile.isOpen()) { - QTextStream stream(&m_logFile); - stream << logLine; - stream.flush(); + return; } - - if (level == "ERROR") { - qCritical().noquote() << message; - } else if (level == "WARNING") { - qWarning().noquote() << message; + m_logFile.setFileName(m_logFilePath); + if (!m_logFile.open(QIODevice::Append | QIODevice::Text)) { + qDebug() << "无法打开日志文件:" << m_logFilePath; + } +} + +void LogManager::rotateFile() +{ + if (m_logFile.isOpen()) { + m_logFile.close(); + } + + // 现有轮转文件整体后移一位:.N -> .N+1,超出 maxFiles 的丢弃 + for (int i = m_maxFiles - 1; i >= 1; --i) { + QString dst = QString("%1.%2").arg(m_logFilePath).arg(i + 1); + if (QFile::exists(dst)) { + QFile::remove(dst); + } + QString src = QString("%1.%2").arg(m_logFilePath).arg(i); + if (QFile::exists(src)) { + QFile::rename(src, dst); + } + } + // 当前文件 -> .1 + QString first = m_logFilePath + ".1"; + if (QFile::exists(first)) { + QFile::remove(first); + } + if (QFile::exists(m_logFilePath)) { + QFile::rename(m_logFilePath, first); + } + + cleanupByAge(); + ensureFileOpen(); +} + +void LogManager::cleanupByAge() +{ + if (m_maxAgeDays <= 0) { + return; + } + QFileInfo fi(m_logFilePath); + QDir dir(fi.absolutePath()); + QString base = fi.fileName(); + QFileInfoList list = dir.entryInfoList(QStringList() << (base + ".*"), QDir::Files); + QDateTime now = QDateTime::currentDateTime(); + for (const QFileInfo& f : list) { + if (f.lastModified().daysTo(now) > m_maxAgeDays) { + QFile::remove(f.absoluteFilePath()); + } + } +} + +void LogManager::initLogConfig() +{ + // 日志配置持久化在 change_code.ini 的 [log] 段,与 config.ini 分离,避免更新被覆盖 + QSettings cc(Constants::changeCodeIni(), QSettings::IniFormat); + + // 1) 日志等级 + const QString levelStr = cc.value("log/level", "INFO").toString().trimmed().toUpper(); + LogLevel level = LogLevel::Info; + if (levelStr == "DEBUG") level = LogLevel::Debug; + else if (levelStr == "INFO") level = LogLevel::Info; + else if (levelStr == "WARNING" || levelStr == "WARN") level = LogLevel::Warning; + else if (levelStr == "ERROR" || levelStr == "ERR") level = LogLevel::Error; + m_logLevel = level; + + // 2) 输出路径与文件名 + QString path = cc.value("log/path", QString()).toString().trimmed(); + if (!path.isEmpty() && !QDir::isAbsolutePath(path)) { + path = QCoreApplication::applicationDirPath() + "/" + QDir::fromNativeSeparators(path); + } + m_logDir = path; + m_fileName = cc.value("log/file", "change_code.log").toString().trimmed(); + if (m_fileName.isEmpty()) { + m_fileName = "change_code.log"; + } + + // 3) 轮转与清理 + m_maxSizeBytes = parseSizeToBytes(cc.value("log/maxSize", "10MB").toString()); + m_maxFiles = cc.value("log/maxFiles", 100).toInt(); + m_maxAgeDays = cc.value("log/maxAge", 30).toInt(); + + // 4) 控制台 / 文件 / 调用堆栈 / 格式 / 时间格式 + m_toConsole = cc.value("log/console", true).toBool(); + m_toFile = cc.value("log/fileEnabled", true).toBool(); + m_caller = cc.value("log/caller", false).toBool(); + m_format = cc.value("log/format", "text").toString().trimmed().toLower(); + m_timeFormat = translateTimeFormat( + cc.value("log/timeFormat", "2006-01-02 15:04:05.000").toString().trimmed()); + if (m_timeFormat.isEmpty()) { + m_timeFormat = "yyyy-MM-dd HH:mm:ss.zzz"; + } +} + +qint64 LogManager::parseSizeToBytes(const QString& s) +{ + QString str = s.trimmed().toUpper(); + if (str.isEmpty()) { + return 0; + } + int idx = 0; + while (idx < str.size() && (str.at(idx).isDigit() || str.at(idx) == QLatin1Char('.'))) { + ++idx; + } + bool ok = false; + double val = str.left(idx).toDouble(&ok); + if (!ok) { + val = 0; + } + QString unit = str.mid(idx).trimmed(); + qint64 mult = 1; + if (unit == "KB") mult = 1024; + else if (unit == "MB") mult = 1024LL * 1024; + else if (unit == "GB") mult = 1024LL * 1024 * 1024; + // "B" 或空单位 -> 1 + return static_cast(val * mult); +} + +QString LogManager::translateTimeFormat(const QString& fmt) +{ + // 含 Go 风格年份标记 2006 时,按 Go 布局翻译为 Qt 格式;否则按 Qt 格式原样使用 + if (!fmt.contains("2006")) { + return fmt; + } + QString out = fmt; + out.replace("2006", "yyyy"); + out.replace("Monday", "dddd"); + out.replace("Mon", "ddd"); + out.replace("01", "MM"); // 月份(补零) + out.replace("02", "dd"); // 日期(补零) + out.replace("15", "HH"); // 24 小时制 + out.replace("03", "hh"); // 12 小时制 + out.replace("04", "mm"); // 分钟 + out.replace("05", "ss"); // 秒 + out.replace("000", "zzz"); // 毫秒 + out.replace("PM", "AP"); + return out; +} + +#ifdef Q_OS_WIN +QString LogManager::captureStackTrace() +{ + static bool symInit = false; + static bool symOk = false; + if (!symInit) { + symInit = true; + symOk = SymInitialize(GetCurrentProcess(), nullptr, TRUE) != FALSE; + if (symOk) { + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS); + } + } + + const int maxFrames = 32; + void* frames[maxFrames]; + // 跳过前 2 帧(captureStackTrace、writeLog),保留调用方信息 + USHORT count = CaptureStackBackTrace(2, maxFrames, frames, nullptr); + + QStringList lines; + for (USHORT i = 0; i < count; ++i) { + DWORD64 addr = reinterpret_cast(frames[i]); + QString frameStr; + if (symOk) { + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO sym = reinterpret_cast(buffer); + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = MAX_SYM_NAME; + DWORD64 displacement = 0; + if (SymFromAddr(GetCurrentProcess(), addr, &displacement, sym)) { + frameStr = QString::fromLocal8Bit(sym->Name); + IMAGEHLP_LINE64 line; + memset(&line, 0, sizeof(line)); + line.SizeOfStruct = sizeof(line); + DWORD dispLine = 0; + if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &dispLine, &line)) { + frameStr += QString(" (%1:%2)").arg(QString::fromUtf8(line.FileName)).arg(line.LineNumber); + } + } else { + frameStr = QString("0x%1").arg(reinterpret_cast(frames[i]), 0, 16); + } + } else { + frameStr = QString("0x%1").arg(reinterpret_cast(frames[i]), 0, 16); + } + lines.append("#" + QString::number(i) + " " + frameStr); + } + return lines.join("\n"); +} +#else +QString LogManager::captureStackTrace() +{ + return QString(); +} +#endif + +void LogManager::setLogLevel(LogLevel level) +{ + m_logLevel = level; +} + +LogLevel LogManager::logLevel() const +{ + return m_logLevel; +} + +bool LogManager::logToConsole() const +{ + return m_toConsole; +} + +bool LogManager::logToFile() const +{ + return m_toFile; +} + +QString LogManager::logDir() const +{ + return m_logDir; +} + +void LogManager::writeLog(LogLevel level, const QString& message) +{ + // 等级过滤:低于当前配置等级的日志不写入文件也不输出到控制台 + if (static_cast(level) < static_cast(m_logLevel)) { + return; + } + + QMutexLocker locker(&m_mutex); + + QString levelStr; + switch (level) { + case LogLevel::Debug: levelStr = "DEBUG"; break; + case LogLevel::Info: levelStr = "INFO"; break; + case LogLevel::Warning: levelStr = "WARNING"; break; + case LogLevel::Error: levelStr = "ERROR"; break; + } + + QString timestamp = QDateTime::currentDateTime().toString(m_timeFormat); + QString stack = m_caller ? captureStackTrace() : QString(); + + QString logLine; + if (m_format == "json") { + QJsonObject obj; + obj["time"] = timestamp; + obj["level"] = levelStr; + obj["message"] = message; + if (m_caller) { + obj["caller"] = stack; + } + logLine = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact)) + "\n"; } else { - qDebug().noquote() << message; + logLine = QString("[%1] [%2] %3%4\n").arg(timestamp, levelStr, message, + m_caller ? ("\n" + stack) : QString()); + } + + if (m_toFile) { + ensureFileOpen(); + if (m_maxSizeBytes > 0 && QFileInfo(m_logFilePath).size() + logLine.size() > m_maxSizeBytes) { + rotateFile(); + } + if (m_logFile.isOpen()) { + QTextStream stream(&m_logFile); + stream << logLine; + stream.flush(); + } + } + + if (m_toConsole) { + switch (level) { + case LogLevel::Error: qCritical().noquote() << message; break; + case LogLevel::Warning: qWarning().noquote() << message; break; + default: qDebug().noquote() << message; break; + } } } void LogManager::log(const QString& message) { - writeLog("INFO", message); + writeLog(LogLevel::Info, message); } void LogManager::logInfo(const QString& message) { - writeLog("INFO", message); + writeLog(LogLevel::Info, message); } void LogManager::logWarning(const QString& message) { - writeLog("WARNING", message); + writeLog(LogLevel::Warning, message); } void LogManager::logError(const QString& message) { - writeLog("ERROR", message); -} \ No newline at end of file + writeLog(LogLevel::Error, message); +} + +void LogManager::logDebug(const QString& message) +{ + writeLog(LogLevel::Debug, message); +} diff --git a/src/utils/logmanager.h b/src/utils/logmanager.h index b229a15..d4f918a 100644 --- a/src/utils/logmanager.h +++ b/src/utils/logmanager.h @@ -7,6 +7,14 @@ #include #include +// 日志等级:数值越大越严重,输出越少(DEBUG < INFO < WARNING < ERROR) +enum class LogLevel : int { + Debug = 0, // 调试:最详细,逐文件/逐步骤等过程信息 + Info = 1, // 信息:常规流程节点 + Warning = 2, // 警告:可恢复的异常 + Error = 3 // 错误:操作失败 +}; + class LogManager : public QObject { Q_OBJECT @@ -19,6 +27,15 @@ public: void logInfo(const QString& message); void logWarning(const QString& message); void logError(const QString& message); + void logDebug(const QString& message); + + // 从 change_code.ini 的 [log] 段读取完整日志配置(在 initLogFile 前调用) + void initLogConfig(); + void setLogLevel(LogLevel level); + LogLevel logLevel() const; + bool logToConsole() const; + bool logToFile() const; + QString logDir() const; QString getLogFilePath() const; @@ -26,12 +43,29 @@ private: explicit LogManager(QObject *parent = nullptr); ~LogManager(); - void writeLog(const QString& level, const QString& message); - + void writeLog(LogLevel level, const QString& message); + void ensureFileOpen(); // 确保日志文件已打开(未打开则打开) + void rotateFile(); // 超过 maxSize 时按 maxFiles 轮转 + void cleanupByAge(); // 删除超过 maxAge 天的历史日志 + QString captureStackTrace(); // 调用堆栈(caller=true 时) + static qint64 parseSizeToBytes(const QString& s); // "10MB" -> 字节 + static QString translateTimeFormat(const QString& fmt); // Go 布局 -> Qt 格式 + static LogManager* m_instance; QFile m_logFile; QMutex m_mutex; QString m_logFilePath; + LogLevel m_logLevel = LogLevel::Info; + bool m_toConsole = true; // 是否输出到控制台 + bool m_toFile = true; // 是否写入日志文件 + QString m_logDir; // 日志目录(空 -> 当前目录) + QString m_fileName = "change_code.log"; // 日志文件名 + qint64 m_maxSizeBytes = 10LL * 1024 * 1024; // 单文件大小上限(字节),0=不限制 + int m_maxFiles = 100; // 轮转保留文件数 + int m_maxAgeDays = 30; // 保留天数,0=不限制 + bool m_caller = false; // 是否附加调用堆栈 + QString m_format = "text"; // 输出格式:text / json + QString m_timeFormat = "yyyy-MM-dd HH:mm:ss.zzz"; // Qt 时间格式 }; -#endif // LOGMANAGER_H \ No newline at end of file +#endif // LOGMANAGER_H diff --git a/src/utils/uf2configreader.cpp b/src/utils/uf2configreader.cpp index 7b3e93c..42c2406 100644 --- a/src/utils/uf2configreader.cpp +++ b/src/utils/uf2configreader.cpp @@ -1,103 +1,44 @@ #include "uf2configreader.h" -#include "Constants.h" #include "datacache.h" -#include "logmanager.h" -#include -#include -#include -#include QSet UF2ConfigReader::m_cnameCache; bool UF2ConfigReader::m_cnameCacheLoaded = false; -UF2ConfigReader::UF2ConfigReader(QObject *parent) : QObject(parent), m_loaded(false) +UF2ConfigReader::UF2ConfigReader(QObject *parent) : QObject(parent) { } -QString UF2ConfigReader::getBinPath() +const ConfigReaderTraits& UF2ConfigReader::traits() { - return Constants::toolDir(); + static const ConfigReaderTraits t = [] { + ConfigReaderTraits traits; + traits.logTag = QStringLiteral("UF20"); + traits.mainFile = QStringLiteral("uf2.json"); + traits.reloadFiles = { qMakePair(QStringLiteral("uf2.json"), true) }; + traits.hasConfig = [](const QString& fileName) { + return DataCache::instance()->hasUF20Config(fileName); + }; + traits.saveConfig = [](const QJsonObject& config) { + return DataCache::instance()->saveUF20Config(config); + }; + traits.getAllCnames = [] { + return DataCache::instance()->getAllUF20Cnames(); + }; + return traits; + }(); + return t; } bool UF2ConfigReader::loadAllUF20Config() { - m_failedFiles.clear(); - - if (!DataCache::instance()->initDatabase()) { - LogManager::instance()->logError("UF20配置加载 - 数据库初始化失败"); - m_failedFiles.append("uf2.json"); - return false; - } - - QString binPath = getBinPath(); - QString filePath = binPath + "/uf2.json"; - - if (DataCache::instance()->hasUF20Config("uf2.json")) { - m_loaded = true; - DataCache::instance()->setDataLoaded(true); - return true; - } - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - LogManager::instance()->logError(QString("UF20配置加载失败 - 无法打开文件: %1").arg(filePath)); - m_failedFiles.append("uf2.json"); - return false; - } - - QByteArray data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data); - file.close(); - - if (doc.isNull() || !doc.isObject()) { - LogManager::instance()->logError(QString("UF20配置加载失败 - JSON解析失败: %1").arg(filePath)); - m_failedFiles.append("uf2.json"); - return false; - } - - QJsonObject config = doc.object(); - if (!DataCache::instance()->saveUF20Config(config)) { - LogManager::instance()->logError("UF20配置加载失败 - 保存失败"); - m_failedFiles.append("uf2.json"); - return false; - } - - m_loaded = true; - DataCache::instance()->setDataLoaded(true); - return true; -} - -QStringList UF2ConfigReader::getFailedFiles() const -{ - return m_failedFiles; + return doLoadAllConfig(traits()); } bool UF2ConfigReader::reloadUF20Config() { - QString binPath = getBinPath(); - QString filePath = binPath + "/uf2.json"; - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - LogManager::instance()->logError(QString("UF20配置重新加载失败 - 无法打开文件: %1").arg(filePath)); + if (!doReloadConfig(traits())) { return false; } - - QByteArray data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data); - file.close(); - - if (doc.isNull() || !doc.isObject()) { - LogManager::instance()->logError(QString("UF20配置重新加载失败 - JSON解析失败: %1").arg(filePath)); - return false; - } - - QJsonObject config = doc.object(); - if (!DataCache::instance()->saveUF20Config(config)) { - LogManager::instance()->logError("UF20配置重新加载失败 - 保存失败: uf2.json"); - return false; - } - clearCnameCache(); return true; } @@ -107,31 +48,9 @@ bool UF2ConfigReader::isUF20Loaded() const return m_loaded; } -bool UF2ConfigReader::loadCnameCache() -{ - if (m_cnameCacheLoaded) { - return true; - } - - m_cnameCache.clear(); - - if (!DataCache::instance()->initDatabase()) { - LogManager::instance()->logError("UF20 Cname缓存加载失败 - 数据库初始化失败"); - return false; - } - - m_cnameCache = DataCache::instance()->getAllUF20Cnames(); - m_cnameCacheLoaded = true; - return true; -} - bool UF2ConfigReader::checkFuncExistsStatic(const QString& funcName) { - if (!m_cnameCacheLoaded) { - loadCnameCache(); - } - - return m_cnameCache.contains(funcName); + return doCheckFuncExists(traits(), funcName, m_cnameCache, m_cnameCacheLoaded); } bool UF2ConfigReader::checkFunctionExists(const QString& funcName) @@ -141,15 +60,10 @@ bool UF2ConfigReader::checkFunctionExists(const QString& funcName) void UF2ConfigReader::clearCnameCache() { - m_cnameCache.clear(); - m_cnameCacheLoaded = false; - LogManager::instance()->logInfo("Cname缓存已清空"); + doClearCnameCache(QStringLiteral("Cname缓存已清空"), m_cnameCache, m_cnameCacheLoaded); } QSet UF2ConfigReader::getAllCnamesStatic() { - if (!m_cnameCacheLoaded) { - loadCnameCache(); - } - return m_cnameCache; + return doGetAllCnames(traits(), m_cnameCache, m_cnameCacheLoaded); } diff --git a/src/utils/uf2configreader.h b/src/utils/uf2configreader.h index 608feb9..83fe600 100644 --- a/src/utils/uf2configreader.h +++ b/src/utils/uf2configreader.h @@ -5,8 +5,9 @@ #include #include #include +#include "configreaderbase.h" -class UF2ConfigReader : public QObject +class UF2ConfigReader : public QObject, public ConfigReaderBase { Q_OBJECT @@ -17,19 +18,14 @@ public: static bool reloadUF20Config(); bool isUF20Loaded() const; - QStringList getFailedFiles() const; + // getFailedFiles() 继承自 ConfigReaderBase bool checkFunctionExists(const QString& funcName); static bool checkFuncExistsStatic(const QString& funcName); static void clearCnameCache(); static QSet getAllCnamesStatic(); private: - static bool loadCnameCache(); - - static QString getBinPath(); - - QStringList m_failedFiles; - bool m_loaded; + static const ConfigReaderTraits& traits(); static QSet m_cnameCache; static bool m_cnameCacheLoaded; diff --git a/src/utils/uft3configreader.cpp b/src/utils/uft3configreader.cpp index 66553b8..16c9125 100644 --- a/src/utils/uft3configreader.cpp +++ b/src/utils/uft3configreader.cpp @@ -1,122 +1,47 @@ #include "uft3configreader.h" -#include "Constants.h" #include "datacache.h" -#include "logmanager.h" -#include -#include -#include -#include QSet UFT3ConfigReader::m_cnameCache; bool UFT3ConfigReader::m_cnameCacheLoaded = false; -UFT3ConfigReader::UFT3ConfigReader(QObject *parent) : QObject(parent), m_loaded(false) +UFT3ConfigReader::UFT3ConfigReader(QObject *parent) : QObject(parent) { } -QString UFT3ConfigReader::getBinPath() +const ConfigReaderTraits& UFT3ConfigReader::traits() { - return Constants::toolDir(); + static const ConfigReaderTraits t = [] { + ConfigReaderTraits traits; + traits.logTag = QStringLiteral("UFT3"); + traits.mainFile = QStringLiteral("uft3.json"); + traits.reloadFiles = { + qMakePair(QStringLiteral("uft3.json"), false), + qMakePair(QStringLiteral("uft3Trans.json"), false) + }; + traits.hasConfig = [](const QString& fileName) { + return DataCache::instance()->hasUFT3Config(fileName); + }; + traits.saveConfig = [](const QJsonObject& config) { + return DataCache::instance()->saveUFT3Config(config); + }; + traits.getAllCnames = [] { + return DataCache::instance()->getAllUFT3Cnames(); + }; + return traits; + }(); + return t; } bool UFT3ConfigReader::loadAllUFT3Config() { - m_failedFiles.clear(); - - if (!DataCache::instance()->initDatabase()) { - LogManager::instance()->logError("UFT3配置加载失败 - 数据库初始化失败"); - m_failedFiles.append("uft3.json"); - return false; - } - - QString binPath = getBinPath(); - QString filePath = binPath + "/uft3.json"; - - if (DataCache::instance()->hasUFT3Config("uft3.json")) { - m_loaded = true; - DataCache::instance()->setDataLoaded(true); - return true; - } - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - LogManager::instance()->logError(QString("UFT3配置加载失败 - 无法打开文件: %1").arg(filePath)); - m_failedFiles.append("uft3.json"); - return false; - } - - QByteArray data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data); - file.close(); - - if (doc.isNull() || !doc.isObject()) { - LogManager::instance()->logError(QString("UFT3配置加载失败 - JSON解析失败: %1").arg(filePath)); - m_failedFiles.append("uft3.json"); - return false; - } - - QJsonObject config = doc.object(); - if (!DataCache::instance()->saveUFT3Config(config)) { - LogManager::instance()->logError("UFT3配置加载失败 - 保存失败"); - m_failedFiles.append("uft3.json"); - return false; - } - - m_loaded = true; - DataCache::instance()->setDataLoaded(true); - return true; -} - -QStringList UFT3ConfigReader::getFailedFiles() const -{ - return m_failedFiles; + return doLoadAllConfig(traits()); } bool UFT3ConfigReader::reloadUFT3Config() { - QString binPath = getBinPath(); - QString filePath1 = binPath + "/uft3.json"; - QString filePath2 = binPath + "/uft3Trans.json"; - - QJsonObject mergedConfig; - - QFile file1(filePath1); - if (file1.open(QIODevice::ReadOnly | QIODevice::Text)) { - QByteArray data1 = file1.readAll(); - QJsonDocument doc1 = QJsonDocument::fromJson(data1); - if (!doc1.isNull() && doc1.isObject()) { - mergedConfig = doc1.object(); - } - file1.close(); - } else { - LogManager::instance()->logWarning(QString("UFT3配置重新加载 - 无法打开文件: %1").arg(filePath1)); - } - - QFile file2(filePath2); - if (file2.open(QIODevice::ReadOnly | QIODevice::Text)) { - QByteArray data2 = file2.readAll(); - QJsonDocument doc2 = QJsonDocument::fromJson(data2); - if (!doc2.isNull() && doc2.isObject()) { - QJsonObject transConfig = doc2.object(); - for (auto it = transConfig.begin(); it != transConfig.end(); ++it) { - mergedConfig[it.key()] = it.value(); - } - } - file2.close(); - } else { - LogManager::instance()->logWarning(QString("UFT3配置重新加载 - 无法打开文件: %1").arg(filePath2)); - } - - if (mergedConfig.isEmpty()) { - LogManager::instance()->logError("UFT3配置重新加载失败 - 两个JSON文件都无效"); + if (!doReloadConfig(traits())) { return false; } - - if (!DataCache::instance()->saveUFT3Config(mergedConfig)) { - LogManager::instance()->logError("UFT3配置重新加载失败 - 保存失败"); - return false; - } - clearCnameCache(); return true; } @@ -126,31 +51,9 @@ bool UFT3ConfigReader::isUFT3Loaded() const return m_loaded; } -bool UFT3ConfigReader::loadCnameCache() -{ - if (m_cnameCacheLoaded) { - return true; - } - - m_cnameCache.clear(); - - if (!DataCache::instance()->initDatabase()) { - LogManager::instance()->logError("UFT3 Cname缓存加载失败 - 数据库初始化失败"); - return false; - } - - m_cnameCache = DataCache::instance()->getAllUFT3Cnames(); - m_cnameCacheLoaded = true; - return true; -} - bool UFT3ConfigReader::checkFuncExistsStatic(const QString& funcName) { - if (!m_cnameCacheLoaded) { - loadCnameCache(); - } - - return m_cnameCache.contains(funcName); + return doCheckFuncExists(traits(), funcName, m_cnameCache, m_cnameCacheLoaded); } bool UFT3ConfigReader::checkFunctionExists(const QString& funcName) @@ -160,15 +63,10 @@ bool UFT3ConfigReader::checkFunctionExists(const QString& funcName) void UFT3ConfigReader::clearCnameCache() { - m_cnameCache.clear(); - m_cnameCacheLoaded = false; - LogManager::instance()->logInfo("UFT3 Cname缓存已清空"); + doClearCnameCache(QStringLiteral("UFT3 Cname缓存已清空"), m_cnameCache, m_cnameCacheLoaded); } QSet UFT3ConfigReader::getAllCnamesStatic() { - if (!m_cnameCacheLoaded) { - loadCnameCache(); - } - return m_cnameCache; + return doGetAllCnames(traits(), m_cnameCache, m_cnameCacheLoaded); } diff --git a/src/utils/uft3configreader.h b/src/utils/uft3configreader.h index 313ccf7..f7a5623 100644 --- a/src/utils/uft3configreader.h +++ b/src/utils/uft3configreader.h @@ -5,8 +5,9 @@ #include #include #include +#include "configreaderbase.h" -class UFT3ConfigReader : public QObject +class UFT3ConfigReader : public QObject, public ConfigReaderBase { Q_OBJECT @@ -17,19 +18,14 @@ public: static bool reloadUFT3Config(); bool isUFT3Loaded() const; - QStringList getFailedFiles() const; + // getFailedFiles() 继承自 ConfigReaderBase bool checkFunctionExists(const QString& funcName); static bool checkFuncExistsStatic(const QString& funcName); static void clearCnameCache(); static QSet getAllCnamesStatic(); private: - static bool loadCnameCache(); - - static QString getBinPath(); - - QStringList m_failedFiles; - bool m_loaded; + static const ConfigReaderTraits& traits(); static QSet m_cnameCache; static bool m_cnameCacheLoaded;