优化查询链路的性能等
This commit is contained in:
parent
9cb7240060
commit
08efc50c3f
116
README.md
116
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 功能查找、帮助文档、接口元数据更新、设置 |
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
11
build.bat
11
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 ==========================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
initWindow();
|
||||
initContent();
|
||||
|
||||
LogManager::instance()->initLogConfig(); // 先读取 change_code.ini 的 [log] 配置,再开始写日志
|
||||
LogManager::instance()->initLogFile();
|
||||
LogManager::instance()->logInfo("应用程序启动");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
/// 后台线程异步构建索引(扫描目录),完成后回调 onDone;showProgress 控制是否显示进度框
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue