优化查询链路的性能等

This commit is contained in:
taocong 2026-07-24 15:29:18 +08:00
parent 9cb7240060
commit 08efc50c3f
35 changed files with 1316 additions and 761 deletions

116
README.md
View File

@ -0,0 +1,116 @@
# Uft30ChangeCodeUFT30 代码转换工具)
一款基于 **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.2mingw_64
- MinGW 11.2.0Qt 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 功能查找、帮助文档、接口元数据更新、设置 |

View File

@ -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
}

View File

@ -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 ==========================================

View File

@ -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

View File

@ -47,6 +47,7 @@ MainWindow::MainWindow(QWidget *parent)
initWindow();
initContent();
LogManager::instance()->initLogConfig(); // 先读取 change_code.ini 的 [log] 配置,再开始写日志
LogManager::instance()->initLogFile();
LogManager::instance()->logInfo("应用程序启动");

View File

@ -0,0 +1,98 @@
#ifndef INTERFACE_SCANNER_H
#define INTERFACE_SCANNER_H
#include <QString>
#include <QMap>
#include <QList>
#include <QJsonObject>
// ============================================================================
// 接口扫描器的公共类型与抽象接口
//
// 目的:消除 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<QString, FieldInfo> inputFields;
QMap<QString, FieldInfo> outputFields;
QMap<QString, FieldInfo> variableFields;
QMap<QString, FieldInfo> 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<QString, InterfaceInfo> resultMap;
QMap<QString, InterfaceInfo> transCodeMap;
};
// 扫描/组装上下文:承载各业务版本差异化的参数,避免接口方法签名分裂
struct ScanContext {
QMap<QString, QStringList> fileFilterDict;
QString ufAcct20ProjectDir; // UF2 专用
QString uftDbSett; // UF2 专用
QJsonObject custJson; // UF2 专用
QString moduleGenerationCheck; // UFT3 专用
QString serverType; // 增量组装用UF2
QMap<QString, QString> 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<FileEntry> collectFiles(const QString& dirPath, const ScanContext& ctx) = 0;
// 构建模块编码映射UF2 专用UFT3 默认返回空)
virtual QMap<QString, QString> buildModuleCodeMap(const QString& /*dirPath*/, const ScanContext& /*ctx*/)
{ return {}; }
};
#endif // INTERFACE_SCANNER_H

View File

@ -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<QString, QStringList> 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<Uf2FileEntry> 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<QString, QString> 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<QString, qint64> mtimeByPath;
for (const Uf2FileEntry& e : entries) mtimeByPath[e.path] = e.mtime;
QMap<QString, InterfaceInfo> 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<QString> 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<QString, QStringList> 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<FileEntry> entries = scanner->collectFiles(dirPath, baseCtx);
QMap<QString, QString> moduleCodeMap = scanner->buildModuleCodeMap(dirPath, baseCtx);
QJsonObject oldJson = readJson(outputPath);
QJsonObject oldTransJson = transOutputPath.isEmpty() ? QJsonObject() : readJson(transOutputPath);
QJsonObject oldCache = readCache();
QList<Uft3FileEntry> 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<QString, qint64> 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<QString, InterfaceInfo> resultMap = result.first;
QMap<QString, InterfaceInfo> transCodeMap = result.second;
ScanResult scanResult = scanner->scanDir(dirPath, baseCtx);
QMap<QString, InterfaceInfo> resultMap = scanResult.resultMap;
QMap<QString, InterfaceInfo> 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<QString> 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;
}

View File

@ -7,7 +7,7 @@
#include <QJsonDocument>
#include <QJsonObject>
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;

View File

@ -1,5 +1,7 @@
#include "uf2datatype.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -12,9 +14,8 @@ QMap<QString, QMap<QString, QString>> Uf2DataType::loadData(const QString& dirPa
{
QMap<QString, QMap<QString, QString>> 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));

View File

@ -1,5 +1,7 @@
#include "uf2errnumber.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -12,9 +14,8 @@ QMap<QString, QMap<QString, QString>> Uf2ErrNumber::loadData(const QString& dirP
{
QMap<QString, QMap<QString, QString>> 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));

View File

@ -1,5 +1,6 @@
#include "uf2interface.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include <QFile>
#include <QFileInfo>
#include <QDebug>
@ -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<QString, InterfaceInfo> Uf2Interface::scanDir(const QString& dirPath,
const QString& ufAcct20ProjectDir,
const QString& uftDbSett,
const QMap<QString, QStringList>& fileFilterDict,
const QJsonObject& custJson)
ScanResult Uf2Interface::scanDir(const QString& dirPath, const ScanContext& ctx)
{
QMap<QString, InterfaceInfo> 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<QString, QString> moduleCodeMap;
moduleCodeMap["转融通"] = "ref";
// 预提取cust.json中的模块英文名映射
QMap<QString, QString> custMoudleENameMap;
if (!custJson.isEmpty() && custJson.contains("moudleEName")) {
@ -192,8 +191,7 @@ QMap<QString, InterfaceInfo> 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<QString, InterfaceInfo> 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<QString, InterfaceInfo> 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<QString, InterfaceInfo> 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<QString, QString> Uf2Interface::buildModuleCodeMap(const QString& dirPath,
const QString& ufAcct20ProjectDir,
const QString& uftDbSett,
const QJsonObject& custJson)
QMap<QString, QString> Uf2Interface::buildModuleCodeMap(const QString& dirPath, const ScanContext& ctx)
{
QMap<QString, QString> moduleCodeMap;
moduleCodeMap["转融通"] = "ref";
const QString& ufAcct20ProjectDir = ctx.ufAcct20ProjectDir;
const QString& uftDbSett = ctx.uftDbSett;
const QJsonObject& custJson = ctx.custJson;
// 预提取cust.json中的模块英文名映射
QMap<QString, QString> custMoudleENameMap;
@ -320,8 +318,7 @@ QMap<QString, QString> 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<QString, QString> Uf2Interface::buildModuleCodeMap(const QString& dirPath,
return moduleCodeMap;
}
InterfaceInfo Uf2Interface::assemble(const QString& filePath,
const QString& serverType,
const QMap<QString, QString>& 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<QString, QString>& moduleCodeMap = ctx.moduleCodeMap;
InterfaceInfo interfaceInfo = loadInterface(filePath);
interfaceInfo.moudle = modName;
@ -359,13 +355,11 @@ InterfaceInfo Uf2Interface::assemble(const QString& filePath,
return interfaceInfo;
}
QList<Uf2FileEntry> Uf2Interface::collectFiles(const QString& dirPath,
const QString& serverType,
const QMap<QString, QStringList>& fileFilterDict)
QList<FileEntry> Uf2Interface::collectFiles(const QString& dirPath, const ScanContext& ctx)
{
QList<Uf2FileEntry> result;
QStringList completeNameList = fileFilterDict.value("completeNameList");
QStringList containNameList = fileFilterDict.value("containNameList");
QList<FileEntry> result;
QStringList completeNameList = ctx.fileFilterDict.value("completeNameList");
QStringList containNameList = ctx.fileFilterDict.value("containNameList");
std::function<void(const QString&, const QString&)> scanInterfaceFilesRecursive =
[&](const QString& path, const QString& st) {
@ -374,10 +368,9 @@ QList<Uf2FileEntry> 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<Uf2FileEntry> 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;
}

View File

@ -5,80 +5,28 @@
#include <QString>
#include <QMap>
#include <QList>
#include <QJsonObject>
#include <QXmlStreamReader>
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<QString, FieldInfo> inputFields;
QMap<QString, FieldInfo> outputFields;
QMap<QString, FieldInfo> variableFields;
QMap<QString, FieldInfo> 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<QString, InterfaceInfo> scanDir(const QString& dirPath,
const QString& ufAcct20ProjectDir,
const QString& uftDbSett,
const QMap<QString, QStringList>& fileFilterDict,
const QJsonObject& custJson = QJsonObject());
// 以下为增量更新提供复用能力(与 scanDir 内部逻辑一致)
QMap<QString, QString> buildModuleCodeMap(const QString& dirPath,
const QString& ufAcct20ProjectDir,
const QString& uftDbSett,
const QJsonObject& custJson = QJsonObject());
InterfaceInfo assemble(const QString& filePath,
const QString& serverType,
const QMap<QString, QString>& moduleCodeMap);
QList<Uf2FileEntry> collectFiles(const QString& dirPath,
const QString& serverType,
const QMap<QString, QStringList>& 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<FileEntry> collectFiles(const QString& dirPath, const ScanContext& ctx) override;
QMap<QString, QString> 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
#endif // UF2INTERFACE_H

View File

@ -1,5 +1,7 @@
#include "uf2stdfields.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -14,9 +16,8 @@ QMap<QString, QMap<QString, QString>> Uf2StdFields::loadData(
{
QMap<QString, QMap<QString, QString>> 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));

View File

@ -1,5 +1,7 @@
#include "uft3datatype.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -12,9 +14,8 @@ QMap<QString, QMap<QString, QString>> Uft3DataType::loadData(const QString& dirP
{
QMap<QString, QMap<QString, QString>> 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));

View File

@ -1,5 +1,6 @@
#include "uft3interface.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "filedb.h"
#include <QFile>
#include <QFileInfo>
@ -8,6 +9,20 @@
#include <QTextStream>
#include <functional>
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<QString, InterfaceInfo>, QMap<QString, InterfaceInfo>>
Uft3Interface::scanDir(const QString& dirPath,
const QMap<QString, QStringList>& fileFilterDict,
const QString& moduleGenerationCheck)
ScanResult Uft3Interface::scanDir(const QString& dirPath, const ScanContext& ctx)
{
QMap<QString, InterfaceInfo> resultMap;
QMap<QString, InterfaceInfo> 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<Uft3FileEntry> Uft3Interface::collectFiles(const QString& dirPath,
const QMap<QString, QStringList>& fileFilterDict,
const QString& moduleGenerationCheck)
QList<FileEntry> Uft3Interface::collectFiles(const QString& dirPath, const ScanContext& ctx)
{
QList<Uft3FileEntry> result;
QStringList completeNameList = fileFilterDict.value("completeNameList");
QStringList containNameList = fileFilterDict.value("containNameList");
QList<FileEntry> result;
QStringList completeNameList = ctx.fileFilterDict.value("completeNameList");
QStringList containNameList = ctx.fileFilterDict.value("containNameList");
const QString& moduleGenerationCheck = ctx.moduleGenerationCheck;
std::function<void(const QString&)> scanInterfaceFilesRecursive =
[&](const QString& path) {
@ -271,18 +233,15 @@ QList<Uft3FileEntry> 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;

View File

@ -5,35 +5,21 @@
#include <QString>
#include <QMap>
#include <QXmlStreamReader>
#include <QPair>
#include <QList>
#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<QString, InterfaceInfo>, QMap<QString, InterfaceInfo>>
scanDir(const QString& dirPath,
const QMap<QString, QStringList>& fileFilterDict,
const QString& moduleGenerationCheck);
// 以下为增量更新提供复用能力(与 scanDir 内部逻辑一致)
InterfaceInfo assemble(const QString& filePath);
QList<Uft3FileEntry> collectFiles(const QString& dirPath,
const QMap<QString, QStringList>& 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<FileEntry> 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<QString, InterfaceInfo>& transCodeMap);
};
#endif // UFT3INTERFACE_H
#endif // UFT3INTERFACE_H

View File

@ -1,5 +1,7 @@
#include "uft3stdfields.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -14,9 +16,8 @@ QMap<QString, QMap<QString, QString>> Uft3StdFields::loadData(
{
QMap<QString, QMap<QString, QString>> 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));

View File

@ -1,5 +1,7 @@
#include "uft3table.h"
#include "utils/logmanager.h"
#include "utils/Constants.h"
#include "utils/Constants.h"
#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
@ -14,9 +16,8 @@ QMap<QString, QMap<QString, QVariant>> Uft3Table::loadData(const QString& dirPat
{
QMap<QString, QMap<QString, QVariant>> 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<QString, QMap<QString, QVariant>> 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<QString, QMap<QString
void Uft3Table::parseTableFile(const QString& filePath, QMap<QString, QMap<QString, QVariant>>& 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<QString, QMap<QStri
int lastSlash = sFilePath.lastIndexOf('/');
if (lastSlash != -1) {
tableEngName = sFilePath.mid(lastSlash + 1);
tableEngName = tableEngName.left(tableEngName.indexOf(".uftstructure"));
tableEngName = tableEngName.left(tableEngName.indexOf(Constants::MetaPaths::Uft3StructExt));
}
int uftstructurePos = sFilePath.indexOf("uftstructure");

View File

@ -472,7 +472,7 @@ void CodeSearchPage::buildSilentChain(const QList<int> &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<int> &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<QPair<bool, QString>(CallChainAnalyzer *)> task,
std::function<void(bool, const QString &)> onDone)
std::function<void(bool, const QString &)> 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<bool, QString>;
auto *watcher = new QFutureWatcher<BuildResult>(this);
connect(watcher, &QFutureWatcher<BuildResult>::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<void(bool, const QString &)> onDone)
std::function<void(bool, const QString &)> onDone,
bool showProgress)
{
runAsync(ca, title, [](CallChainAnalyzer *ca) -> QPair<bool, QString> {
QString msg;
const bool ok = ca->buildIndex(&msg);
return {ok, msg};
}, onDone);
}, onDone, showProgress);
}
void CodeSearchPage::runCacheLoad(CallChainAnalyzer *ca, const QString &title, bool skipFingerprint,

View File

@ -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<QPair<bool, QString>(CallChainAnalyzer *)> task,
std::function<void(bool, const QString &)> onDone);
/// 后台线程异步构建索引(扫描目录),完成后回调 onDone
std::function<void(bool, const QString &)> onDone,
bool showProgress = true);
/// 后台线程异步构建索引(扫描目录),完成后回调 onDoneshowProgress 控制是否显示进度框
void runIndexBuild(CallChainAnalyzer *ca, const QString &title,
std::function<void(bool, const QString &)> onDone);
std::function<void(bool, const QString &)> onDone,
bool showProgress = true);
/// 后台线程异步从缓存加载索引skipFingerprint 为 true 时跳过全量目录指纹校验
void runCacheLoad(CallChainAnalyzer *ca, const QString &title, bool skipFingerprint,
std::function<void(bool, const QString &)> onDone);

View File

@ -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);

View File

@ -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);

View File

@ -1,6 +1,7 @@
#include "metadatupdatepage.h"
#include "utils/Constants.h"
#include "utils/logmanager.h"
#include "utils/business_strategy.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
@ -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));

View File

@ -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 多处硬编码)
// 仅依赖 <QString>,不引入额外耦合
// ============================================================================
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

View File

@ -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<QString> 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<QString> getAllCnames() const override {
return UFT3ConfigReader::getAllCnamesStatic();
}
void clearCnameCache() const override {
UFT3ConfigReader::clearCnameCache();
}
};
} // namespace
std::unique_ptr<IBusinessStrategy> createBusinessStrategy(BusinessVersion version)
{
switch (version) {
case BusinessVersion::UF2:
return std::unique_ptr<IBusinessStrategy>(new Uf2BusinessStrategy());
case BusinessVersion::UFT3:
default:
return std::unique_ptr<IBusinessStrategy>(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<IBusinessStrategy> createBusinessStrategy(const QString& taskName)
{
return createBusinessStrategy(businessVersionFromTaskName(taskName));
}

View File

@ -0,0 +1,47 @@
#ifndef BUSINESS_STRATEGY_H
#define BUSINESS_STRATEGY_H
#include <QString>
#include <QSet>
#include <memory>
// 业务版本:集中表达 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<QString> getAllCnames() const = 0;
// 清空该版本 cname 缓存
virtual void clearCnameCache() const = 0;
};
// 工厂:按业务版本创建对应策略。
std::unique_ptr<IBusinessStrategy> createBusinessStrategy(BusinessVersion version);
// 便捷:从任务名/字符串推断业务版本("UF20"/"uf2" -> UF2其余 -> UFT3
BusinessVersion businessVersionFromTaskName(const QString& taskName);
// 便捷:直接按任务名创建策略。
std::unique_ptr<IBusinessStrategy> createBusinessStrategy(const QString& taskName);
#endif // BUSINESS_STRATEGY_H

View File

@ -11,7 +11,9 @@
#include <QJsonObject>
#include <QRegularExpression>
#include <QSaveFile>
#include <QVector>
#include <QXmlStreamReader>
#include <functional>
CallChainAnalyzer::CallChainAnalyzer(QObject *parent)
: QObject(parent)
@ -385,57 +387,53 @@ bool CallChainAnalyzer::loadFromCache(const QString &path, QString *message, boo
QSharedPointer<const CallChainAnalyzer::FunctionInfo>
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<QSharedPointer<const FunctionInfo>()>;
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<Strategy> strategies = {
// 1) 精确匹配 chineseName
[&]() -> QSharedPointer<const FunctionInfo> {
return m_chineseNameIndex.value(name);
},
// 2) 精确匹配 canonicalName文件 basename
[&]() -> QSharedPointer<const FunctionInfo> {
return m_canonicalNameIndex.value(name);
},
// 3) 添加 "_转码" 后缀匹配(部分 .uftservice 的 chineseName 带有 _转码
[&]() -> QSharedPointer<const FunctionInfo> {
return m_chineseNameIndex.value(name + transSuffix);
},
// 4) 去除 "_转码" 后缀匹配
[&]() -> QSharedPointer<const FunctionInfo> {
if (name.endsWith(transSuffix)) {
return m_chineseNameIndex.value(name.chopped(transSuffix.length()));
}
return nullptr;
},
// 5) chineseName 前缀匹配(如 LS_证券周边_大约可买获取 匹配 ..._转码
// name 作为前缀只在它是完整函数名(非已调用子函数名)时才合理,
// 因此仅接受 name 后紧跟 "_转码" 后缀的情况,避免误匹配。
[&]() -> QSharedPointer<const FunctionInfo> {
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;
}

View File

@ -0,0 +1,165 @@
#include "configreaderbase.h"
#include "Constants.h"
#include "datacache.h"
#include "logmanager.h"
#include <QFile>
#include <QJsonDocument>
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<QString>& 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<QString>& cache, bool& cacheLoaded)
{
if (!cacheLoaded) {
doLoadCnameCache(traits, cache, cacheLoaded);
}
return cache.contains(funcName);
}
QSet<QString> ConfigReaderBase::doGetAllCnames(const ConfigReaderTraits& traits,
QSet<QString>& cache, bool& cacheLoaded)
{
if (!cacheLoaded) {
doLoadCnameCache(traits, cache, cacheLoaded);
}
return cache;
}
void ConfigReaderBase::doClearCnameCache(const QString& logMessage,
QSet<QString>& cache, bool& cacheLoaded)
{
cache.clear();
cacheLoaded = false;
LogManager::instance()->logInfo(logMessage);
}

View File

@ -0,0 +1,55 @@
#ifndef CONFIGREADERBASE_H
#define CONFIGREADERBASE_H
#include <QString>
#include <QStringList>
#include <QSet>
#include <QVector>
#include <QPair>
#include <QJsonObject>
#include <functional>
// 描述某个业务版本(UF2/UFT3)配置读取的差异点。
// 共享流程集中在 ConfigReaderBase 中,通过该结构注入版本相关的行为。
struct ConfigReaderTraits
{
QString logTag; // 日志前缀,如 "UF20" / "UFT3"
QString mainFile; // 主配置文件名,如 "uf2.json"
// 重载时读取的文件列表:{文件名, 是否必需}。
// required=true -> 打不开或解析失败即整体失败(logError)
// required=false -> 打不开或解析失败则跳过(logWarning)
QVector<QPair<QString, bool>> reloadFiles;
std::function<bool(const QString&)> hasConfig; // DataCache 是否已缓存该配置
std::function<bool(const QJsonObject&)> saveConfig; // DataCache 保存配置
std::function<QSet<QString>()> 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<QString>& cache, bool& cacheLoaded);
static bool doCheckFuncExists(const ConfigReaderTraits& traits, const QString& funcName,
QSet<QString>& cache, bool& cacheLoaded);
static QSet<QString> doGetAllCnames(const ConfigReaderTraits& traits,
QSet<QString>& cache, bool& cacheLoaded);
static void doClearCnameCache(const QString& logMessage,
QSet<QString>& cache, bool& cacheLoaded);
QStringList m_failedFiles;
bool m_loaded = false;
};
#endif // CONFIGREADERBASE_H

View File

@ -4,6 +4,15 @@
#include <QDir>
#include <QDateTime>
#include <QDebug>
#include <QSettings>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#ifdef Q_OS_WIN
#include <windows.h>
#include <dbghelp.h>
#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<qint64>(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<DWORD64>(frames[i]);
QString frameStr;
if (symOk) {
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO sym = reinterpret_cast<PSYMBOL_INFO>(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<quintptr>(frames[i]), 0, 16);
}
} else {
frameStr = QString("0x%1").arg(reinterpret_cast<quintptr>(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<int>(level) < static_cast<int>(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);
}
writeLog(LogLevel::Error, message);
}
void LogManager::logDebug(const QString& message)
{
writeLog(LogLevel::Debug, message);
}

View File

@ -7,6 +7,14 @@
#include <QTextStream>
#include <QMutex>
// 日志等级数值越大越严重输出越少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
#endif // LOGMANAGER_H

View File

@ -1,103 +1,44 @@
#include "uf2configreader.h"
#include "Constants.h"
#include "datacache.h"
#include "logmanager.h"
#include <QFile>
#include <QJsonDocument>
#include <QCoreApplication>
#include <QDir>
QSet<QString> 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<QString> UF2ConfigReader::getAllCnamesStatic()
{
if (!m_cnameCacheLoaded) {
loadCnameCache();
}
return m_cnameCache;
return doGetAllCnames(traits(), m_cnameCache, m_cnameCacheLoaded);
}

View File

@ -5,8 +5,9 @@
#include <QStringList>
#include <QJsonObject>
#include <QSet>
#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<QString> getAllCnamesStatic();
private:
static bool loadCnameCache();
static QString getBinPath();
QStringList m_failedFiles;
bool m_loaded;
static const ConfigReaderTraits& traits();
static QSet<QString> m_cnameCache;
static bool m_cnameCacheLoaded;

View File

@ -1,122 +1,47 @@
#include "uft3configreader.h"
#include "Constants.h"
#include "datacache.h"
#include "logmanager.h"
#include <QFile>
#include <QJsonDocument>
#include <QCoreApplication>
#include <QDir>
QSet<QString> 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<QString> UFT3ConfigReader::getAllCnamesStatic()
{
if (!m_cnameCacheLoaded) {
loadCnameCache();
}
return m_cnameCache;
return doGetAllCnames(traits(), m_cnameCache, m_cnameCacheLoaded);
}

View File

@ -5,8 +5,9 @@
#include <QStringList>
#include <QJsonObject>
#include <QSet>
#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<QString> getAllCnamesStatic();
private:
static bool loadCnameCache();
static QString getBinPath();
QStringList m_failedFiles;
bool m_loaded;
static const ConfigReaderTraits& traits();
static QSet<QString> m_cnameCache;
static bool m_cnameCacheLoaded;