重构转码逻辑;元数据更新逻辑优化;元数据更新存储模式重构
This commit is contained in:
@@ -27,9 +27,13 @@ SOURCES += main.cpp \
|
||||
src/metadataupdate/uf2datatype.cpp \
|
||||
src/metadataupdate/uf2stdfields.cpp \
|
||||
src/metadataupdate/uf2errnumber.cpp \
|
||||
src/metadataupdate/uf2constants.cpp \
|
||||
src/metadataupdate/uf2menuandfunction.cpp \
|
||||
src/metadataupdate/uf2module.cpp \
|
||||
src/metadataupdate/uft3datatype.cpp \
|
||||
src/metadataupdate/uft3stdfields.cpp \
|
||||
src/metadataupdate/uft3table.cpp \
|
||||
src/metadataupdate/uf2table.cpp \
|
||||
src/utils/datacache.cpp \
|
||||
src/utils/business_strategy.cpp \
|
||||
src/utils/configreaderbase.cpp \
|
||||
@@ -62,9 +66,13 @@ HEADERS += src/mainwindow/mainwindow.h \
|
||||
src/metadataupdate/uf2datatype.h \
|
||||
src/metadataupdate/uf2stdfields.h \
|
||||
src/metadataupdate/uf2errnumber.h \
|
||||
src/metadataupdate/uf2constants.h \
|
||||
src/metadataupdate/uf2menuandfunction.h \
|
||||
src/metadataupdate/uf2module.h \
|
||||
src/metadataupdate/uft3datatype.h \
|
||||
src/metadataupdate/uft3stdfields.h \
|
||||
src/metadataupdate/uft3table.h \
|
||||
src/metadataupdate/uf2table.h \
|
||||
src/utils/datacache.h \
|
||||
src/utils/business_strategy.h \
|
||||
src/utils/configreaderbase.h \
|
||||
|
||||
@@ -219,7 +219,7 @@ void MainWindow::onBatchConvertStart()
|
||||
if (!m_pythonRunner->canRun()) {
|
||||
LogManager::instance()->logError("错误: 找不到 uf2touft3/uf2touft3.exe!");
|
||||
QMessageBox::critical(this, "错误",
|
||||
QString("无法启动转换任务!\n\n请确保:\nuf2touft3/uf2touft3.exe 存在,且该目录下包含 config.ini\n\n期望路径:\n%1")
|
||||
QString("无法启动转换任务!\n\n请确保:\nuf2touft3/uf2touft3.exe 存在,且该目录下包含 config/config.ini\n\n期望路径:\n%1")
|
||||
.arg(exePath));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "uf2stdfields.h"
|
||||
#include "uf2datatype.h"
|
||||
#include "uf2errnumber.h"
|
||||
#include "uf2constants.h"
|
||||
#include "uf2menuandfunction.h"
|
||||
#include "uf2module.h"
|
||||
#include "uf2table.h"
|
||||
#include "uft3stdfields.h"
|
||||
#include "uft3datatype.h"
|
||||
#include "uft3table.h"
|
||||
@@ -17,6 +21,7 @@
|
||||
#include <QSet>
|
||||
#include <QSettings>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
MetadataProcessor::MetadataProcessor(QObject *parent) : QObject(parent)
|
||||
@@ -86,23 +91,8 @@ bool MetadataProcessor::isValidUtf8(const QByteArray& data)
|
||||
|
||||
|
||||
|
||||
bool MetadataProcessor::writeJson(const QString& filePath, const QJsonObject& jsonObject)
|
||||
{
|
||||
LogManager::instance()->log(QString("准备写入JSON文件: %1, 对象大小: %2").arg(filePath).arg(jsonObject.size()));
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
LogManager::instance()->logError(QString("无法打开文件: %1").arg(filePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc(jsonObject);
|
||||
qint64 bytesWritten = file.write(doc.toJson(QJsonDocument::Indented));
|
||||
file.close();
|
||||
|
||||
LogManager::instance()->log(QString("成功写入JSON文件: %1, 写入字节数: %2").arg(filePath).arg(bytesWritten));
|
||||
return true;
|
||||
}
|
||||
// 元数据解析结果统一落双库(cache/uf20_cache.db / cache/uft3_cache.db),
|
||||
// 不再生成任何中间 JSON 文件, 详见 saveRowsFromJson / DataCache::saveDataset。
|
||||
|
||||
QJsonObject MetadataProcessor::interfaceInfoToUFT3Json(const InterfaceInfo& info)
|
||||
{
|
||||
@@ -242,48 +232,76 @@ QJsonObject MetadataProcessor::interfaceInfoToUF2Json(const InterfaceInfo& info)
|
||||
return interfaceJson;
|
||||
}
|
||||
|
||||
QJsonObject MetadataProcessor::readCache()
|
||||
// 递归取目录内最新文件的修改时间(秒级时间戳, 对齐 Python initdata._dir_mtime)。
|
||||
// 路径为空或不存在返回 0.0。用于 _meta.src_mtime 缓存失效判断。
|
||||
namespace {
|
||||
double dirMtime(const QString& path)
|
||||
{
|
||||
QString cachePath = QDir(m_uf2touft3Path).filePath("metadata_cache.json");
|
||||
if (!QFile::exists(cachePath)) {
|
||||
return QJsonObject();
|
||||
if (path.isEmpty()) {
|
||||
LogManager::instance()->logWarning("[dirMtime] 源路径为空, src_mtime 记为 0");
|
||||
return 0.0;
|
||||
}
|
||||
return readJson(cachePath);
|
||||
QFileInfo fi(path);
|
||||
if (!fi.exists()) {
|
||||
LogManager::instance()->logWarning(QString("[dirMtime] 源路径不存在: %1").arg(path));
|
||||
return 0.0;
|
||||
}
|
||||
if (fi.isFile()) {
|
||||
double m = static_cast<double>(fi.lastModified().toSecsSinceEpoch());
|
||||
LogManager::instance()->logInfo(QString("[dirMtime] 文件 %1 -> mtime=%2").arg(path).arg(m, 0, 'f', 3));
|
||||
return m;
|
||||
}
|
||||
double latest = 0.0;
|
||||
qint64 count = 0;
|
||||
QDirIterator it(path, QDir::Files | QDir::Readable, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
++count;
|
||||
double m = static_cast<double>(it.fileInfo().lastModified().toSecsSinceEpoch());
|
||||
if (m > latest) {
|
||||
latest = m;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
// 未扫到任何文件(空目录/无权限/遍历异常): 回退为目录自身 mtime,
|
||||
// 避免 src_mtime 记为 0 导致缓存失效判断始终认为"源已变更"。
|
||||
latest = static_cast<double>(fi.lastModified().toSecsSinceEpoch());
|
||||
LogManager::instance()->logWarning(QString("[dirMtime] 目录 %1 未扫到文件, 回退为目录自身 mtime=%2")
|
||||
.arg(path).arg(latest, 0, 'f', 3));
|
||||
return latest;
|
||||
}
|
||||
LogManager::instance()->logInfo(QString("[dirMtime] 目录 %1 扫描 %2 个文件 -> 最新 mtime=%3")
|
||||
.arg(path).arg(count).arg(latest, 0, 'f', 3));
|
||||
return latest;
|
||||
}
|
||||
}
|
||||
|
||||
bool MetadataProcessor::writeCache(const QJsonObject& cache)
|
||||
{
|
||||
QString cachePath = QDir(m_uf2touft3Path).filePath("metadata_cache.json");
|
||||
return writeJson(cachePath, cache);
|
||||
}
|
||||
// 将缓存库 (name -> data JSON 字符串) 行集还原为 QJsonObject(key -> 子对象/子数组)
|
||||
// srcMtime: 源目录最新修改时间(多源时调用方相加), 写入 _meta.src_mtime 供缓存失效判断。
|
||||
|
||||
void MetadataProcessor::syncJsonToCache(const QString& jsonPath, bool isUft3)
|
||||
void MetadataProcessor::saveRowsFromJson(const QString& db, const QString& table,
|
||||
const QJsonObject& resultJson, double srcMtime)
|
||||
{
|
||||
if (!DataCache::instance()->initDatabase()) {
|
||||
LogManager::instance()->logError("元数据同步缓存失败 - 数据库初始化失败");
|
||||
QMap<QString, QString> rows;
|
||||
for (auto it = resultJson.begin(); it != resultJson.end(); ++it) {
|
||||
QJsonObject val = it.value().toObject();
|
||||
rows[it.key()] = QString::fromUtf8(QJsonDocument(val).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
if (rows.isEmpty()) {
|
||||
LogManager::instance()->logWarning(QString("元数据落库跳过 - %1.%2 为空").arg(db, table));
|
||||
return;
|
||||
}
|
||||
QJsonObject json = readJson(jsonPath);
|
||||
if (json.isEmpty()) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("元数据同步缓存跳过 - 读取为空: %1").arg(jsonPath));
|
||||
// 注意: 此处不可调用 DataCache::initDatabase() 做前置可行性判断。
|
||||
// initDatabase() 是"只读探测"(库文件不存在时返回 false), 若在此依赖它,
|
||||
// 首次元数据更新(缓存库尚不存在)会被误判为失败而跳过落库, 导致只生成 0KB 空库、
|
||||
// 需要第二次更新才有数据。写入路径直接调用 saveDataset 即可, 它内部会
|
||||
// 自动创建 cache 目录、DB 文件与数据表。
|
||||
LogManager::instance()->logInfo(QString("元数据落库 %1.%2 共 %3 行, src_mtime=%4")
|
||||
.arg(db, table).arg(rows.size()).arg(srcMtime, 0, 'f', 3));
|
||||
if (!DataCache::instance()->saveDataset(db, table, rows, srcMtime)) {
|
||||
LogManager::instance()->logError(QString("元数据落库失败: %1.%2").arg(db, table));
|
||||
return;
|
||||
}
|
||||
bool ok = isUft3 ? DataCache::instance()->saveUFT3Config(json)
|
||||
: DataCache::instance()->saveUF20Config(json);
|
||||
if (!ok) {
|
||||
LogManager::instance()->logError(
|
||||
QString("元数据同步缓存失败 - 写入%1失败").arg(isUft3 ? "UFT3" : "UF20"));
|
||||
return;
|
||||
}
|
||||
// 记录当前工程路径指纹,供启动比对(详见 configreaderbase.cpp)
|
||||
QStringList parts = {m_uf20Path, m_ufAcct20Path, m_uftDbSettPath,
|
||||
m_uft30PubPath, m_uft30CbptransPath, m_uft30Path};
|
||||
QStringList norm;
|
||||
for (const QString& p : parts) norm.append(PathUtils::normPath(p));
|
||||
DataCache::instance()->saveProjectPaths({{"fingerprint", norm.join("|")}});
|
||||
LogManager::instance()->log(
|
||||
QString("元数据同步缓存成功 - %1 已写入 config_cache.db").arg(isUft3 ? "UFT3" : "UF20"));
|
||||
}
|
||||
|
||||
bool MetadataProcessor::processUf2Interfaces(const QString& basePath)
|
||||
@@ -299,17 +317,12 @@ bool MetadataProcessor::processUf2Interfaces(const QString& basePath)
|
||||
ctx.fileFilterDict = filterDict;
|
||||
ctx.ufAcct20ProjectDir = m_ufAcct20Path;
|
||||
ctx.uftDbSett = (m_supportUftdbSett == "0") ? QString() : m_uftDbSettPath;
|
||||
QString custJsonPath = QDir(m_uf2touft3Path).filePath("cust.json");
|
||||
QString custJsonPath = Constants::custJson();
|
||||
ctx.custJson = readJson(custJsonPath);
|
||||
|
||||
Uf2Interface uf2;
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uf2.json");
|
||||
bool ok = processInterfaces(&uf2, ctx, m_uf20Path, outputPath, QString(), "uf2", false);
|
||||
// 生成成功后立即把新 json 同步进 config_cache.db(功能查询的数据源),
|
||||
// 避免“换机器/换路径重新生成元数据后,db 仍停留在旧工程数据”导致功能查询查不到
|
||||
if (ok) {
|
||||
syncJsonToCache(outputPath, false);
|
||||
}
|
||||
// 解析结果直接落库 uf20.uf2_interface, 不再生成中间 JSON(uf2.json)
|
||||
bool ok = processInterfaces(&uf2, ctx, m_uf20Path, "uf2", false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -327,47 +340,35 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath)
|
||||
ctx.moduleGenerationCheck = m_moduleGenerationCheck;
|
||||
|
||||
Uft3Interface uft3;
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uft3.json");
|
||||
QString transOutputPath = QDir(m_uf2touft3Path).filePath("uft3Trans.json");
|
||||
bool ok = processInterfaces(&uft3, ctx, m_uft30Path, outputPath, transOutputPath, "uft3", true);
|
||||
// 生成成功后立即把新 json 同步进 config_cache.db(功能查询的数据源),
|
||||
// 避免“换机器/换路径重新生成元数据后,db 仍停留在旧工程数据”导致功能查询查不到
|
||||
if (ok) {
|
||||
syncJsonToCache(outputPath, true);
|
||||
}
|
||||
// 解析结果直接落库 uft3.uft3_interface / uft3.uft3_transcode, 不再生成中间 JSON(uft3.json/uft3Trans.json)
|
||||
bool ok = processInterfaces(&uft3, ctx, m_uft30Path, "uft3", true);
|
||||
return ok;
|
||||
}
|
||||
|
||||
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);
|
||||
(void)entries; // 全量模式下扫描在 scanDir 内完成, entries 仅供增量模式遍历使用
|
||||
|
||||
QMap<QString, QString> moduleCodeMap = scanner->buildModuleCodeMap(dirPath, baseCtx);
|
||||
(void)moduleCodeMap;
|
||||
|
||||
QJsonObject oldJson = readJson(outputPath);
|
||||
QJsonObject oldTransJson = transOutputPath.isEmpty() ? QJsonObject() : readJson(transOutputPath);
|
||||
QJsonObject oldCache = readCache();
|
||||
|
||||
bool full = m_forceFull || oldJson.isEmpty()
|
||||
|| !oldCache.contains(cacheKey) || oldCache[cacheKey].toObject().isEmpty();
|
||||
// 不再落中间 JSON / __file_cache 表, 元数据更新改为每次全量重新扫描所有接口文件并落库覆盖。
|
||||
const QString& db = isUft3 ? "uft3" : "uf20";
|
||||
const QString interfaceTable = isUft3 ? Constants::Cache::TblUft3Interface : Constants::Cache::TblUf2Interface;
|
||||
|
||||
// 无增量基线(__file_cache 已移除), 始终全量重扫。
|
||||
QJsonObject resultJson;
|
||||
QJsonObject transCodeJson;
|
||||
QJsonObject newCache;
|
||||
|
||||
if (full) {
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QMap<QString, qint64> mtimeByPath;
|
||||
for (const FileEntry& e : entries) mtimeByPath[e.path] = e.mtime;
|
||||
|
||||
ScanResult scanResult = scanner->scanDir(dirPath, baseCtx);
|
||||
QMap<QString, InterfaceInfo> resultMap = scanResult.resultMap;
|
||||
QMap<QString, InterfaceInfo> transCodeMap = scanResult.transCodeMap;
|
||||
@@ -375,96 +376,29 @@ bool MetadataProcessor::processInterfaces(IInterfaceScanner* scanner,
|
||||
for (const QString& key : resultMap.keys()) {
|
||||
const InterfaceInfo& info = resultMap[key];
|
||||
resultJson[key] = isUft3 ? interfaceInfoToUFT3Json(info) : interfaceInfoToUF2Json(info);
|
||||
QJsonObject rec;
|
||||
rec["mtime"] = QString::number(mtimeByPath.value(info.path, 0));
|
||||
rec["key"] = key;
|
||||
rec["trans"] = false;
|
||||
newCache[info.path] = rec;
|
||||
}
|
||||
for (const QString& key : transCodeMap.keys()) {
|
||||
const InterfaceInfo& info = transCodeMap[key];
|
||||
transCodeJson[key] = interfaceInfoToUFT3Json(info);
|
||||
QJsonObject rec;
|
||||
rec["mtime"] = QString::number(mtimeByPath.value(info.path, 0));
|
||||
rec["key"] = key;
|
||||
rec["trans"] = true;
|
||||
newCache[info.path] = rec;
|
||||
}
|
||||
LogManager::instance()->log(QString("%1 全量扫描耗时: %2ms").arg(cacheKey).arg(timer.elapsed()));
|
||||
}
|
||||
|
||||
// 解析结果直接落库, 不再生成中间 JSON 文件
|
||||
// src_mtime 对齐 Python: UF2 接口源 = uf20 目录 + ufAcct20 目录(相加);
|
||||
// UFT3 接口源 = 传入的工程目录 dirPath(更新 UFT3 时即 uft30 目录)。
|
||||
const double srcMtime = isUft3 ? dirMtime(dirPath)
|
||||
: (dirMtime(m_uf20Path) + dirMtime(m_ufAcct20Path));
|
||||
if (isUft3) {
|
||||
saveRowsFromJson("uft3", Constants::Cache::TblUft3Interface, resultJson, srcMtime);
|
||||
if (!transCodeJson.isEmpty()) {
|
||||
saveRowsFromJson("uft3", Constants::Cache::TblUft3TransCode, transCodeJson, srcMtime);
|
||||
}
|
||||
} else {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QJsonObject oldCacheForVer = oldCache[cacheKey].toObject();
|
||||
QSet<QString> currentPaths;
|
||||
resultJson = oldJson;
|
||||
transCodeJson = oldTransJson;
|
||||
|
||||
for (const FileEntry& e : entries) {
|
||||
currentPaths.insert(e.path);
|
||||
QJsonObject rec = oldCacheForVer.value(e.path).toObject();
|
||||
bool changed = rec.isEmpty() || rec["mtime"].toString().toLongLong() != e.mtime;
|
||||
if (changed) {
|
||||
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) {
|
||||
if (transCodeJson.contains(oldKey)) transCodeJson.remove(oldKey);
|
||||
} else {
|
||||
if (resultJson.contains(oldKey)) resultJson.remove(oldKey);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
newCache[e.path] = crec;
|
||||
} else {
|
||||
newCache[e.path] = rec; // 未变化:直接沿用旧缓存记录
|
||||
}
|
||||
}
|
||||
|
||||
// 删除已从磁盘移除的文件对应的条目
|
||||
for (auto it = oldCacheForVer.begin(); it != oldCacheForVer.end(); ++it) {
|
||||
if (!currentPaths.contains(it.key())) {
|
||||
QJsonObject rec = it.value().toObject();
|
||||
QString oldKey = rec["key"].toString();
|
||||
bool oldTrans = rec["trans"].toBool();
|
||||
if (oldTrans) {
|
||||
if (transCodeJson.contains(oldKey)) transCodeJson.remove(oldKey);
|
||||
} else {
|
||||
if (resultJson.contains(oldKey)) resultJson.remove(oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
LogManager::instance()->log(QString("%1 增量扫描耗时: %2ms").arg(cacheKey).arg(timer.elapsed()));
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2Interface, resultJson, srcMtime);
|
||||
}
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) return false;
|
||||
if (!transOutputPath.isEmpty()) {
|
||||
if (!writeJson(transOutputPath, transCodeJson)) return false;
|
||||
}
|
||||
|
||||
QJsonObject merged = oldCache;
|
||||
merged[cacheKey] = newCache;
|
||||
if (!writeCache(merged)) {
|
||||
LogManager::instance()->logWarning(QString("%1 缓存写入失败(不影响本次结果)").arg(cacheKey));
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("%1 接口处理完成,结果已保存到: %2").arg(cacheKey).arg(outputPath));
|
||||
LogManager::instance()->log(QString("%1 接口处理完成,已落库: %2.%3").arg(cacheKey).arg(db, interfaceTable));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -488,16 +422,13 @@ bool MetadataProcessor::processUf2DataType()
|
||||
}
|
||||
resultJson[key] = typeJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uf2dataType.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UF2.0 数据类型处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 数据类型处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_datatype, 不再生成中间 JSON(uf2dataType.json)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2DataType, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 数据类型处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2DataType));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -509,18 +440,21 @@ bool MetadataProcessor::processUf2StdFields()
|
||||
|
||||
QString dirPath = m_uf20Path;
|
||||
|
||||
// 数据类型依赖从缓存库 uf20.uf2_datatype 读取, 不再读中间 JSON(uf2dataType.json)
|
||||
QMap<QString, QMap<QString, QString>> uf2datatypeMap;
|
||||
QString datatypeJsonPath = QDir(m_uf2touft3Path).filePath("uf2dataType.json");
|
||||
QJsonObject datatypeJson = readJson(datatypeJsonPath);
|
||||
for (const QString& key : datatypeJson.keys()) {
|
||||
QJsonObject typeObj = datatypeJson[key].toObject();
|
||||
QMap<QString, QString> typeInfo;
|
||||
for (const QString& attrKey : typeObj.keys()) {
|
||||
typeInfo[attrKey] = typeObj[attrKey].toString();
|
||||
if (DataCache::instance()->initDatabase()) {
|
||||
QMap<QString, QString> datatypeRows =
|
||||
DataCache::instance()->getAllRows("uf20", Constants::Cache::TblUf2DataType);
|
||||
for (auto it = datatypeRows.begin(); it != datatypeRows.end(); ++it) {
|
||||
QJsonObject typeObj = QJsonDocument::fromJson(it.value().toUtf8()).object();
|
||||
QMap<QString, QString> typeInfo;
|
||||
for (const QString& attrKey : typeObj.keys()) {
|
||||
typeInfo[attrKey] = typeObj[attrKey].toString();
|
||||
}
|
||||
uf2datatypeMap[it.key()] = typeInfo;
|
||||
}
|
||||
uf2datatypeMap[key] = typeInfo;
|
||||
}
|
||||
|
||||
|
||||
Uf2StdFields uf2StdFields;
|
||||
auto resultMap = uf2StdFields.loadData(dirPath, uf2datatypeMap);
|
||||
|
||||
@@ -533,16 +467,13 @@ bool MetadataProcessor::processUf2StdFields()
|
||||
}
|
||||
resultJson[key] = fieldJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uf2stdfield.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UF2.0 标准字段处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 标准字段处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_stdfield, 不再生成中间 JSON(uf2stdfield.json)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2StdField, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 标准字段处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2StdField));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -566,16 +497,122 @@ bool MetadataProcessor::processUf2ErrNumber()
|
||||
}
|
||||
resultJson[key] = errorJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uf2Error.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UF2.0 标准错误号处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_error, 不再生成中间 JSON(uf2Error.json)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2Error, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 标准错误号处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2Error));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataProcessor::processUf2Constants()
|
||||
{
|
||||
LogManager::instance()->log("========== 开始处理 UF2.0 常量 ==========");
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
Uf2Constants uf2Constants;
|
||||
auto resultMap = uf2Constants.loadData(m_uf20Path);
|
||||
|
||||
QJsonObject resultJson;
|
||||
for (const QString& key : resultMap.keys()) {
|
||||
const QMap<QString, QString>& info = resultMap[key];
|
||||
QJsonObject entry;
|
||||
for (const QString& attrKey : info.keys()) {
|
||||
entry[attrKey] = info[attrKey];
|
||||
}
|
||||
resultJson[key] = entry;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 标准错误号处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_constants, 不再生成中间 JSON(uf2constants.json)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2Constants, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 常量处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2Constants));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataProcessor::processUf2MenuFunc()
|
||||
{
|
||||
LogManager::instance()->log("========== 开始处理 UF2.0 菜单功能 ==========");
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
Uf2MenuAndFunction parser;
|
||||
auto resultMap = parser.loadData(m_uf20Path);
|
||||
|
||||
QJsonObject resultJson;
|
||||
for (const QString& key : resultMap.keys()) {
|
||||
const QMap<QString, QString>& info = resultMap[key];
|
||||
QJsonObject entry;
|
||||
for (const QString& attrKey : info.keys()) {
|
||||
entry[attrKey] = info[attrKey];
|
||||
}
|
||||
resultJson[key] = entry;
|
||||
}
|
||||
|
||||
// 解析结果只落库 uf20.uf2_menu_func(不再生成 uf2_menu 表与中间 JSON)。
|
||||
// Python 侧 schema 也只定义 uf2_menu_func 一个数据集, 两者保持对齐。
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2MenuFunc, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 菜单功能处理完成,共 %1 条,耗时: %2ms,已落库: uf20.%3")
|
||||
.arg(resultJson.size())
|
||||
.arg(timer.elapsed())
|
||||
.arg(Constants::Cache::TblUf2MenuFunc));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataProcessor::processUf2Module()
|
||||
{
|
||||
LogManager::instance()->log("========== 开始处理 UF2.0 模块 ==========");
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
Uf2Module parser;
|
||||
auto resultMap = parser.loadData(m_uf20Path);
|
||||
|
||||
QJsonObject resultJson;
|
||||
for (const QString& key : resultMap.keys()) {
|
||||
const QMap<QString, QString>& info = resultMap[key];
|
||||
QJsonObject entry;
|
||||
for (const QString& attrKey : info.keys()) {
|
||||
entry[attrKey] = info[attrKey];
|
||||
}
|
||||
resultJson[key] = entry;
|
||||
}
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_module, 不再生成中间 JSON(uf2module.json)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2Module, resultJson, dirMtime(m_uf20Path));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 模块处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2Module));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataProcessor::processUf2Table()
|
||||
{
|
||||
LogManager::instance()->log("========== 开始处理 UF2.0 表结构 ==========");
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
Uf2Table uf2Table;
|
||||
QString settPath = (m_supportUftdbSett == "0") ? QString() : m_uftDbSettPath;
|
||||
auto resultMap = uf2Table.loadData(m_uf20Path, settPath);
|
||||
|
||||
QJsonObject resultJson;
|
||||
for (auto it = resultMap.begin(); it != resultMap.end(); ++it) {
|
||||
resultJson[it.key()] = it.value();
|
||||
}
|
||||
|
||||
// 解析结果直接落库 uf20.uf2_table, 不再生成中间 JSON(uf2table.json)
|
||||
// src_mtime 对齐 Python: uf2_table 源 = uf20 目录 + 数据库设置目录(相加)
|
||||
saveRowsFromJson("uf20", Constants::Cache::TblUf2Table, resultJson,
|
||||
dirMtime(m_uf20Path) + dirMtime(m_uftDbSettPath));
|
||||
|
||||
LogManager::instance()->log(QString("UF2.0 表结构处理完成,耗时: %1ms,已落库: uf20.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUf2Table));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -599,16 +636,13 @@ bool MetadataProcessor::processUft3DataType()
|
||||
}
|
||||
resultJson[key] = typeJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uft3datatype.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UFT3.0 数据类型处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 数据类型处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uft3.uft3_datatype, 不再生成中间 JSON(uft3datatype.json)
|
||||
saveRowsFromJson("uft3", Constants::Cache::TblUft3DataType, resultJson, dirMtime(m_uft30PubPath));
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 数据类型处理完成,耗时: %1ms,已落库: uft3.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUft3DataType));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -620,18 +654,21 @@ bool MetadataProcessor::processUft3StdFields()
|
||||
|
||||
QString dirPath = m_uft30PubPath;
|
||||
|
||||
// 数据类型依赖从缓存库 uft3.uft3_datatype 读取, 不再读中间 JSON(uft3datatype.json)
|
||||
QMap<QString, QMap<QString, QString>> uf3datatypeMap;
|
||||
QString datatypeJsonPath = QDir(m_uf2touft3Path).filePath("uft3datatype.json");
|
||||
QJsonObject datatypeJson = readJson(datatypeJsonPath);
|
||||
for (const QString& key : datatypeJson.keys()) {
|
||||
QJsonObject typeObj = datatypeJson[key].toObject();
|
||||
QMap<QString, QString> typeInfo;
|
||||
for (const QString& attrKey : typeObj.keys()) {
|
||||
typeInfo[attrKey] = typeObj[attrKey].toString();
|
||||
if (DataCache::instance()->initDatabase()) {
|
||||
QMap<QString, QString> datatypeRows =
|
||||
DataCache::instance()->getAllRows("uft3", Constants::Cache::TblUft3DataType);
|
||||
for (auto it = datatypeRows.begin(); it != datatypeRows.end(); ++it) {
|
||||
QJsonObject typeObj = QJsonDocument::fromJson(it.value().toUtf8()).object();
|
||||
QMap<QString, QString> typeInfo;
|
||||
for (const QString& attrKey : typeObj.keys()) {
|
||||
typeInfo[attrKey] = typeObj[attrKey].toString();
|
||||
}
|
||||
uf3datatypeMap[it.key()] = typeInfo;
|
||||
}
|
||||
uf3datatypeMap[key] = typeInfo;
|
||||
}
|
||||
|
||||
|
||||
Uft3StdFields uft3StdFields;
|
||||
auto resultMap = uft3StdFields.loadData(dirPath, uf3datatypeMap);
|
||||
|
||||
@@ -644,16 +681,13 @@ bool MetadataProcessor::processUft3StdFields()
|
||||
}
|
||||
resultJson[key] = fieldJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uft3stdfields.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UFT3.0 标准字段处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 标准字段处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uft3.uft3_stdfield, 不再生成中间 JSON(uft3stdfields.json)
|
||||
saveRowsFromJson("uft3", Constants::Cache::TblUft3StdField, resultJson, dirMtime(m_uft30PubPath));
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 标准字段处理完成,耗时: %1ms,已落库: uft3.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUft3StdField));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -680,15 +714,12 @@ bool MetadataProcessor::processUft3Table()
|
||||
}
|
||||
resultJson[key] = tableJson;
|
||||
}
|
||||
|
||||
QString outputPath = QDir(m_uf2touft3Path).filePath("uft3Table.json");
|
||||
|
||||
if (!writeJson(outputPath, resultJson)) {
|
||||
LogManager::instance()->log(QString("UFT3.0 表结构处理失败,耗时: %1ms").arg(timer.elapsed()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 表结构处理完成,耗时: %1ms,结果已保存到: %2").arg(timer.elapsed()).arg(outputPath));
|
||||
|
||||
|
||||
// 解析结果直接落库 uft3.uft3_table, 不再生成中间 JSON(uft3Table.json)
|
||||
saveRowsFromJson("uft3", Constants::Cache::TblUft3Table, resultJson, dirMtime(m_uft30PubPath));
|
||||
|
||||
LogManager::instance()->log(QString("UFT3.0 表结构处理完成,耗时: %1ms,已落库: uft3.%2")
|
||||
.arg(timer.elapsed()).arg(Constants::Cache::TblUft3Table));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
bool processUf2StdFields();
|
||||
bool processUf2DataType();
|
||||
bool processUf2ErrNumber();
|
||||
bool processUf2Constants();
|
||||
bool processUf2MenuFunc();
|
||||
bool processUf2Module();
|
||||
bool processUf2Table();
|
||||
|
||||
bool processUft3StdFields();
|
||||
bool processUft3DataType();
|
||||
@@ -37,28 +41,28 @@ public:
|
||||
|
||||
private:
|
||||
bool isValidUtf8(const QByteArray& data);
|
||||
bool writeJson(const QString& filePath, const QJsonObject& jsonObject);
|
||||
QJsonObject readJson(const QString& filePath);
|
||||
QJsonObject readJson(const QString& filePath); // 仅用于读取输入文件 cust.json
|
||||
QJsonObject interfaceInfoToUFT3Json(const InterfaceInfo& info);
|
||||
QJsonObject interfaceInfoToUF2Json(const InterfaceInfo& info);
|
||||
|
||||
// 轻量增量缓存(仅记录 文件路径->修改时间(mtime)+该文件在 JSON 中的 key)
|
||||
QJsonObject readCache();
|
||||
bool writeCache(const QJsonObject& cache);
|
||||
// 把解析得到的 QJsonObject(key -> 子对象) 直接落库到双库对应表(name 列=key, data 列=子对象JSON)。
|
||||
// 不再生成任何中间 JSON 文件。db 选 "uf20"/"uft3", table 为 Constants::Cache 表名。
|
||||
// srcMtime 为源目录最新修改时间(多源时相加), 写入 _meta.src_mtime 供缓存失效判断。
|
||||
void saveRowsFromJson(const QString& db, const QString& table, const QJsonObject& resultJson,
|
||||
double srcMtime = 0.0);
|
||||
|
||||
// 统一处理入口:通过 IInterfaceScanner 策略多态处理,消除 UF2/UFT3 平行实现
|
||||
// 统一处理入口:通过 IInterfaceScanner 策略多态处理,消除 UF2/UFT3 平行实现。
|
||||
// 解析结果直接落双库(uf20.uf2_interface / uft3.uft3_interface / uft3.uft3_transcode)。
|
||||
bool processInterfaces(IInterfaceScanner* scanner,
|
||||
const ScanContext& baseCtx,
|
||||
const QString& dirPath,
|
||||
const QString& outputPath,
|
||||
const QString& transOutputPath,
|
||||
const QString& cacheKey,
|
||||
bool isUft3);
|
||||
|
||||
// 生成元数据后,把 json 同步进 config_cache.db(功能查询数据源)并刷新工程路径指纹;
|
||||
// isUft3 区分 UF2/UFT3 两套 cname 表
|
||||
void syncJsonToCache(const QString& jsonPath, bool isUft3);
|
||||
|
||||
// 注: C++ 直接解析源文件并落双库对应表(name 列=key, data 列=JSON 序列化字符串),
|
||||
// 落库路径 <exe>/cache/uf20_cache.db 与 <exe>/cache/uft3_cache.db(见 DataCache)。
|
||||
// 各 processUf2Xxx/processUft3Xxx 解析后调用 saveRowsFromJson 落库, 不再生成中间 JSON 文件。
|
||||
|
||||
QString m_basePath;
|
||||
QString m_uf2touft3Path;
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "uf2constants.h"
|
||||
#include "utils/Constants.h"
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QDir>
|
||||
|
||||
Uf2Constants::Uf2Constants(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>> Uf2Constants::loadData(const QString& dirPath)
|
||||
{
|
||||
QMap<QString, QMap<QString, QString>> resultMap;
|
||||
QString filePath = dirPath + Constants::MetaPaths::Uf2Constants;
|
||||
QFile file(filePath);
|
||||
if (!file.exists()) {
|
||||
return resultMap;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (reader.isStartElement() && reader.name().compare("constant", Qt::CaseInsensitive) == 0) {
|
||||
QXmlStreamAttributes attrs = reader.attributes();
|
||||
QString name = attrs.value("name").toString().trimmed();
|
||||
QString value = attrs.value("constant_value").toString().trimmed();
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QMap<QString, QString> entry;
|
||||
entry["cname"] = name;
|
||||
entry["value"] = value;
|
||||
resultMap[name] = entry; // key = constant_name
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return resultMap;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef UF2CONSTANTS_H
|
||||
#define UF2CONSTANTS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
// 解析 UF2.0 常量文件: 公共资源/constants.xml
|
||||
// key = constant_name, data = {cname, value}
|
||||
class Uf2Constants : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Uf2Constants(QObject *parent = nullptr);
|
||||
|
||||
QMap<QString, QMap<QString, QString>> loadData(const QString& dirPath);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "uf2menuandfunction.h"
|
||||
#include "utils/Constants.h"
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QDir>
|
||||
|
||||
Uf2MenuAndFunction::Uf2MenuAndFunction(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>> Uf2MenuAndFunction::loadData(const QString& dirPath)
|
||||
{
|
||||
QMap<QString, QMap<QString, QString>> resultMap;
|
||||
QString filePath = dirPath + Constants::MetaPaths::Uf2MenuFunc;
|
||||
QFile file(filePath);
|
||||
if (!file.exists()) {
|
||||
return resultMap;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
// 解析要点(对齐 Python uf2_menu_and_function.py):
|
||||
// 1) 不可使用 QXmlStreamReader::readElementText()。hsmenuitem 内含
|
||||
// extendsFields/entry/string 等复合嵌套元素, readElementText() 只适用于叶子节点,
|
||||
// 遇到复合元素会提前停止并吞掉 EndElement 令牌, 导致解析出 0 条。
|
||||
// 2) Python 的 xml_util.by_tag() 基于 elem.iter(), 是"递归查找所有后代",
|
||||
// 因此 childMenus/hsmenuitem 这类"嵌套菜单"也必须采集。
|
||||
// 故此处遇到 hsmenuitem/capabilityitem 时, 即使在某个 item 内部也要
|
||||
// 开启新的 item(用栈保存父 item 状态), 否则会漏掉嵌套菜单导致数据不全。
|
||||
struct ItemState {
|
||||
QString item; // "hsmenuitem" / "capabilityitem"
|
||||
QMap<QString, QString> entry;// 直接子元素名 -> 文本
|
||||
int depth = 0; // item 内部相对深度, item 自身为 0
|
||||
QString tag; // 当前直接子元素名
|
||||
QString text; // 当前直接子元素文本缓冲
|
||||
};
|
||||
|
||||
ItemState cur;
|
||||
bool inItem = false;
|
||||
QList<ItemState> stack; // 父 item 状态栈(支持嵌套)
|
||||
|
||||
auto isItemTag = [](const QString& t) {
|
||||
return t.compare("hsmenuitem", Qt::CaseInsensitive) == 0 ||
|
||||
t.compare("capabilityitem", Qt::CaseInsensitive) == 0;
|
||||
};
|
||||
|
||||
auto flush = [&]() {
|
||||
if (!inItem) return;
|
||||
QString macroName = cur.item.compare("hsmenuitem", Qt::CaseInsensitive) == 0
|
||||
? cur.entry.value("menuMacroName")
|
||||
: cur.entry.value("marcoDefine");
|
||||
if (!macroName.isEmpty()) {
|
||||
QMap<QString, QString> entry;
|
||||
if (cur.item.compare("hsmenuitem", Qt::CaseInsensitive) == 0) {
|
||||
entry["cName"] = cur.entry.value("menuCaption");
|
||||
entry["menuCode"] = cur.entry.value("menuCode");
|
||||
} else { // capabilityitem
|
||||
entry["cName"] = cur.entry.value("capabilityName");
|
||||
entry["menuCode"] = cur.entry.value("nowCapabilityCode");
|
||||
}
|
||||
entry["name"] = macroName; // 关键: 以宏名为 key
|
||||
resultMap[macroName] = entry;
|
||||
}
|
||||
// item 结束: 恢复父 item 状态(若存在)
|
||||
if (!stack.isEmpty()) {
|
||||
cur = stack.takeLast();
|
||||
} else {
|
||||
inItem = false;
|
||||
cur = ItemState();
|
||||
}
|
||||
};
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
QXmlStreamReader::TokenType token = reader.readNext();
|
||||
if (token == QXmlStreamReader::StartElement) {
|
||||
QString tag = reader.name().toString();
|
||||
if (isItemTag(tag)) {
|
||||
// 开启新 item: 若当前已在 item 内(嵌套菜单), 先压栈保存父状态
|
||||
if (inItem) {
|
||||
stack.append(cur);
|
||||
}
|
||||
inItem = true;
|
||||
cur = ItemState();
|
||||
cur.item = tag;
|
||||
} else if (inItem) {
|
||||
++cur.depth;
|
||||
if (cur.depth == 1) {
|
||||
cur.tag = tag;
|
||||
cur.text.clear();
|
||||
}
|
||||
}
|
||||
} else if (token == QXmlStreamReader::Characters) {
|
||||
if (inItem && cur.depth == 1) {
|
||||
cur.text += reader.text();
|
||||
}
|
||||
} else if (token == QXmlStreamReader::EndElement) {
|
||||
if (!inItem) continue;
|
||||
if (cur.depth == 1) {
|
||||
cur.entry[cur.tag] = cur.text.trimmed();
|
||||
cur.tag.clear();
|
||||
cur.text.clear();
|
||||
--cur.depth;
|
||||
} else if (cur.depth > 1) {
|
||||
--cur.depth;
|
||||
} else {
|
||||
// depth == 0 -> 当前 item 自身结束
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 正常 XML 已由 EndElement 收尾; 此处兜底处理未闭合的残留
|
||||
while (inItem) {
|
||||
flush();
|
||||
}
|
||||
file.close();
|
||||
return resultMap;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef UF2MENUANDFUNCTION_H
|
||||
#define UF2MENUANDFUNCTION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
// 解析 UF2.0 菜单与功能: 公共资源/menuandfunction.xml
|
||||
// 含 <hsmenuitem> 与 <capabilityitem>, key = macro_name, data = {cName, name, menuCode}
|
||||
// 只落库 uf20.uf2_menu_func 一张表(不再生成 uf2_menu), 对齐 Python 侧 schema 定义。
|
||||
// 解析须递归采集所有层级的 item(含 childMenus 下的嵌套 hsmenuitem), 否则数据不全。
|
||||
class Uf2MenuAndFunction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Uf2MenuAndFunction(QObject *parent = nullptr);
|
||||
|
||||
QMap<QString, QMap<QString, QString>> loadData(const QString& dirPath);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "uf2module.h"
|
||||
#include "utils/logmanager.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
Uf2Module::Uf2Module(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Uf2Module::scanDir(const QString& dirPath)
|
||||
{
|
||||
QDir dir(dirPath);
|
||||
if (!dir.exists()) {
|
||||
return;
|
||||
}
|
||||
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
for (const QFileInfo& info : entries) {
|
||||
if (info.isDir()) {
|
||||
scanDir(info.filePath());
|
||||
} else if (info.fileName().compare("module.xml", Qt::CaseInsensitive) == 0) {
|
||||
// 与 Python 端对齐: 跳过 "数据库" 子目录下的 module.xml
|
||||
if (info.path().replace('\\', '/').contains("/数据库/", Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
QFile file(info.filePath());
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
continue;
|
||||
}
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (reader.isStartElement() && reader.name().compare("info", Qt::CaseInsensitive) == 0) {
|
||||
QXmlStreamAttributes attrs = reader.attributes();
|
||||
// cname 取自 module.xml 所在目录名 (与 Python uf2_module.py 一致),
|
||||
// 而非 <info> 的 cname 属性 (该属性通常为空)。
|
||||
QString cname = QDir(info.path()).dirName();
|
||||
QString ename = attrs.value("ename").toString().trimmed();
|
||||
if (ename.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QMap<QString, QString> entry;
|
||||
entry["cname"] = cname;
|
||||
entry["path"] = info.path(); // 所在目录
|
||||
m_resultMap[ename] = entry; // key = ename
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QMap<QString, QString>> Uf2Module::loadData(const QString& dirPath)
|
||||
{
|
||||
m_resultMap.clear();
|
||||
scanDir(dirPath);
|
||||
return m_resultMap;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef UF2MODULE_H
|
||||
#define UF2MODULE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
// 解析 UF2.0 模块: 工程各目录下 module.xml
|
||||
// key = ename, data = {cname, path}
|
||||
class Uf2Module : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Uf2Module(QObject *parent = nullptr);
|
||||
|
||||
QMap<QString, QMap<QString, QString>> loadData(const QString& dirPath);
|
||||
|
||||
private:
|
||||
void scanDir(const QString& dirPath);
|
||||
QMap<QString, QMap<QString, QString>> m_resultMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "uf2table.h"
|
||||
#include "utils/logmanager.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
Uf2Table::Uf2Table(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString schemaToUser(const QString& schema)
|
||||
{
|
||||
static const QMap<QString, QString> map = {
|
||||
{"HS_USER_DATA", "hs_user"},
|
||||
{"HS_ASSET_DATA", "hs_asset"},
|
||||
{"HS_FUND_DATA", "hs_fund"},
|
||||
{"HS_SECU_DATA", "hs_secu"},
|
||||
{"HS_AUTH_DATA", "hs_auth"},
|
||||
{"HS_CRDT_DATA", "hs_crdt"},
|
||||
{"HS_OFUND_DATA", "hs_ofund"},
|
||||
{"HS_BOND_DATA", "hs_bond"},
|
||||
{"HS_FUTU_DATA", "hs_futu"},
|
||||
{"HS_DATA_DATA", "hs_data"},
|
||||
{"HS_HIS_DATA", "hs_his"},
|
||||
{"HS_CBS_DATA", "hs_cbs"},
|
||||
{"HS_CASH_DATA", "hs_cash"},
|
||||
{"HS_SETTPLUS_DATA", "hs_settplus"},
|
||||
{"HS_SETTARG_DATA", "hs_settarg"},
|
||||
};
|
||||
return map.value(schema, QString());
|
||||
}
|
||||
|
||||
void Uf2Table::scanDir(const QString& dirPath)
|
||||
{
|
||||
// dirPath 已是“数据库”目录(或其子目录), 直接遍历, 不再重复拼 “/数据库”
|
||||
QDir dbDir(dirPath);
|
||||
if (!dbDir.exists()) {
|
||||
return;
|
||||
}
|
||||
QFileInfoList entries = dbDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
for (const QFileInfo& info : entries) {
|
||||
if (info.isDir()) {
|
||||
scanDir(info.filePath());
|
||||
} else if (info.fileName().endsWith(".table_design", Qt::CaseInsensitive) &&
|
||||
info.fileName().compare("module.xml", Qt::CaseInsensitive) != 0) {
|
||||
QString tableCName = info.baseName(); // 文件名去掉扩展名
|
||||
QJsonObject obj = loadTable(info.filePath(), tableCName);
|
||||
QString tableName = obj.value("name").toString();
|
||||
if (!tableName.isEmpty()) {
|
||||
m_resultMap[tableName] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject Uf2Table::loadTable(const QString& filePath, const QString& tableCName)
|
||||
{
|
||||
QJsonObject result;
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString tableName;
|
||||
QString user;
|
||||
QJsonArray filedList;
|
||||
QJsonObject indexList;
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (!reader.isStartElement()) {
|
||||
continue;
|
||||
}
|
||||
QString tag = reader.name().toString();
|
||||
if (tag.compare("tablePrimaryInfo", Qt::CaseInsensitive) == 0) {
|
||||
QXmlStreamAttributes attrs = reader.attributes();
|
||||
tableName = attrs.value("tableName").toString().trimmed();
|
||||
user = schemaToUser(attrs.value("schema").toString().trimmed());
|
||||
} else if (tag.compare("tablefield", Qt::CaseInsensitive) == 0) {
|
||||
QXmlStreamAttributes attrs = reader.attributes();
|
||||
QString flag = attrs.value("flag").toString();
|
||||
if (!flag.contains("H", Qt::CaseInsensitive)) {
|
||||
filedList.append(attrs.value("name").toString().trimmed());
|
||||
}
|
||||
} else if (tag.compare("tableIndex", Qt::CaseInsensitive) == 0) {
|
||||
QXmlStreamAttributes attrs = reader.attributes();
|
||||
QString flag = attrs.value("flag").toString();
|
||||
if (!flag.contains("H", Qt::CaseInsensitive)) {
|
||||
QJsonObject idx;
|
||||
QString idxName = attrs.value("name").toString().trimmed();
|
||||
QStringList cols = attrs.value("columnNames").toString().split(",", Qt::SkipEmptyParts);
|
||||
QJsonArray colArr;
|
||||
for (const QString& c : cols) colArr.append(c.trimmed());
|
||||
idx["name"] = idxName;
|
||||
idx["filedList"] = colArr;
|
||||
idx["unique"] = (attrs.value("unique").toString().compare("true", Qt::CaseInsensitive) == 0);
|
||||
idx["flag"] = flag;
|
||||
indexList[idxName] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
|
||||
result["name"] = tableName;
|
||||
result["cname"] = tableCName;
|
||||
result["filedList"] = filedList;
|
||||
result["indexList"] = indexList;
|
||||
result["user"] = user;
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> Uf2Table::loadData(const QString& dirPath, const QString& settDirPath)
|
||||
{
|
||||
m_resultMap.clear();
|
||||
// 对齐 Python: 扫描 dirPath/数据库 与 settDirPath(整个工程, 仅取路径含 /数据库/ 的 .table_design)
|
||||
QDir dbDir(dirPath + "/数据库");
|
||||
if (dbDir.exists()) {
|
||||
scanDir(dbDir.path());
|
||||
}
|
||||
if (!settDirPath.isEmpty() && QDir::isAbsolutePath(settDirPath)) {
|
||||
scanDir(settDirPath);
|
||||
}
|
||||
return m_resultMap;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef UF2TABLE_H
|
||||
#define UF2TABLE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QJsonObject>
|
||||
|
||||
// 解析 UF2.0 表结构: 各工程目录下 数据库/*.table_design
|
||||
// key = tableName, data = {name, cname, filedList, indexList, user}
|
||||
class Uf2Table : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Uf2Table(QObject *parent = nullptr);
|
||||
|
||||
QMap<QString, QJsonObject> loadData(const QString& dirPath, const QString& settDirPath = QString());
|
||||
|
||||
private:
|
||||
void scanDir(const QString& dirPath);
|
||||
QJsonObject loadTable(const QString& filePath, const QString& tableCName);
|
||||
QMap<QString, QJsonObject> m_resultMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "metadatupdatepage.h"
|
||||
#include "utils/Constants.h"
|
||||
#include "utils/logmanager.h"
|
||||
#include "utils/datacache.h"
|
||||
#include "utils/business_strategy.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -185,11 +186,12 @@ void MetadataUpdatePage::showEvent(QShowEvent* event)
|
||||
|
||||
void MetadataUpdatePage::showUpdateItems()
|
||||
{
|
||||
QStringList uft3Items = {"标准字段", "数据类型", "表结构",
|
||||
// 注意: "数据类型" 需排在 "标准字段" 之前, 标准字段解析依赖数据类型(从缓存库读取)。
|
||||
QStringList uft3Items = {"数据类型", "标准字段", "表结构",
|
||||
"接口数据"};
|
||||
|
||||
QStringList uf20Items = {"标准字段", "数据类型", "标准错误号",
|
||||
"接口数据"};
|
||||
QStringList uf20Items = {"数据类型", "标准字段", "标准错误号",
|
||||
"接口数据", "常量", "菜单功能", "模块", "表结构"};
|
||||
|
||||
for (int i = 0; i < uft3Items.size(); ++i) {
|
||||
auto* cbx = new ElaCheckBox(uft3Items[i]);
|
||||
@@ -313,6 +315,14 @@ UpdateResult MetadataUpdatePage::runUpdateUf2(const QStringList& items)
|
||||
success = false;
|
||||
}
|
||||
interfaceUpdated = true;
|
||||
} else if (item == "常量") {
|
||||
processUf2Constants();
|
||||
} else if (item == "菜单功能") {
|
||||
processUf2MenuFunc();
|
||||
} else if (item == "模块") {
|
||||
processUf2Module();
|
||||
} else if (item == "表结构") {
|
||||
processUf2Table();
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
@@ -370,6 +380,26 @@ bool MetadataUpdatePage::processUf2Interface(const QString& basePath)
|
||||
return _processor->processUf2Interfaces(basePath);
|
||||
}
|
||||
|
||||
void MetadataUpdatePage::processUf2Constants()
|
||||
{
|
||||
_processor->processUf2Constants();
|
||||
}
|
||||
|
||||
void MetadataUpdatePage::processUf2MenuFunc()
|
||||
{
|
||||
_processor->processUf2MenuFunc();
|
||||
}
|
||||
|
||||
void MetadataUpdatePage::processUf2Module()
|
||||
{
|
||||
_processor->processUf2Module();
|
||||
}
|
||||
|
||||
void MetadataUpdatePage::processUf2Table()
|
||||
{
|
||||
_processor->processUf2Table();
|
||||
}
|
||||
|
||||
void MetadataUpdatePage::processUft3StdFields()
|
||||
{
|
||||
_processor->processUft3StdFields();
|
||||
@@ -432,6 +462,8 @@ void MetadataUpdatePage::onWorkerFinished()
|
||||
_processLabel->setText("正在更新数据缓存...");
|
||||
// 通过业务策略工厂按当前任务选择对应版本的配置重载,取代 UF20/UFT3 if/else
|
||||
createBusinessStrategy(m_currentTaskName)->reloadConfig();
|
||||
// 元数据落库后重建功能查询内存缓存, 使后续 UF20/UFT3 功能搜索走缓存而非全表扫描
|
||||
DataCache::instance()->refreshFunctionCache();
|
||||
}
|
||||
|
||||
onUpdateFinished(r.success, QString("%1更新完成").arg(m_currentTaskName));
|
||||
|
||||
@@ -61,6 +61,10 @@ private:
|
||||
void processUf2StdFields();
|
||||
void processUf2DataType();
|
||||
void processUf2ErrNumber();
|
||||
void processUf2Constants();
|
||||
void processUf2MenuFunc();
|
||||
void processUf2Module();
|
||||
void processUf2Table();
|
||||
bool processUf2Interface(const QString& basePath);
|
||||
|
||||
void processUft3StdFields();
|
||||
|
||||
+39
-6
@@ -35,6 +35,7 @@ namespace Paths {
|
||||
inline const QString ToolDir = "uf2touft3"; // 工具主目录
|
||||
inline const QString LogDir = "logs"; // 日志目录
|
||||
inline const QString CacheDir = "cache"; // 缓存目录
|
||||
inline const QString ConfigDir = "config"; // 配置文件目录(toolDir 下)
|
||||
inline const QString ChangeCodeDir = "change_code"; // 代码检索根目录(toolDir 下)
|
||||
inline const QString FunctionOutput = "function_output"; // 调用链/文件导出目录
|
||||
}
|
||||
@@ -44,18 +45,47 @@ namespace Paths {
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace ToolFiles {
|
||||
inline const QString Exe = "uf2touft3.exe";
|
||||
inline const QString ConfigIni = "config.ini";
|
||||
inline const QString ConfigIni = "config/config.ini";
|
||||
inline const QString Uf2Json = "uf2.json";
|
||||
inline const QString Uft3Json = "uft3.json";
|
||||
inline const QString Uft3Trans = "uft3Trans.json";
|
||||
inline const QString CustJson = "cust.json";
|
||||
inline const QString CustJson = "config/cust.json";
|
||||
inline const QString Cust2Json = "config/cust2.json";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cache 相关
|
||||
// 缓存库采用双库 + 键值表结构(对齐 Python src/store/schema.py),
|
||||
// C++ 直接解析业务工程源文件并写库(方向 A), 表结构统一为:
|
||||
// (name TEXT PRIMARY KEY, data TEXT), data 为 JSON 序列化字符串。
|
||||
// 另含 _meta 表(dataset TEXT PRIMARY KEY, row_count INTEGER, src_mtime REAL)。
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace Cache {
|
||||
inline const QString DbFile = "config_cache.db";
|
||||
// 双库文件(置于 cacheDir() 下)
|
||||
inline const QString Uf20DbFile = "uf20_cache.db";
|
||||
inline const QString Uft3DbFile = "uft3_cache.db";
|
||||
|
||||
// uf20 库表名
|
||||
inline const QString TblUf2Interface = "uf2_interface";
|
||||
inline const QString TblUf2Table = "uf2_table";
|
||||
inline const QString TblUf2Module = "uf2_module";
|
||||
inline const QString TblUf2StdField = "uf2_stdfield";
|
||||
inline const QString TblUf2DataType = "uf2_datatype";
|
||||
inline const QString TblUf2Error = "uf2_error";
|
||||
inline const QString TblUf2Constants = "uf2_constants";
|
||||
inline const QString TblUf2Menu = "uf2_menu";
|
||||
inline const QString TblUf2MenuFunc = "uf2_menu_func";
|
||||
|
||||
// uft3 库表名 (func_bak 由 Python 生成, 但本工具不读取)
|
||||
inline const QString TblUft3Interface = "uft3_interface";
|
||||
inline const QString TblUft3TransCode = "uft3_trans_code";
|
||||
inline const QString TblUft3Table = "uft3_table";
|
||||
inline const QString TblUft3StdField = "uft3_stdfield";
|
||||
inline const QString TblUft3DataType = "uft3_datatype";
|
||||
|
||||
// 功能查询用的接口表(本工具只用这两张表)
|
||||
inline const QString Uf20InterfaceTable = TblUf2Interface;
|
||||
inline const QString Uft3InterfaceTable = TblUft3Interface;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -70,6 +100,8 @@ namespace MetaPaths {
|
||||
inline const QString Uf2StdField = "/公共资源/stdfields.xml"; // UF2 标准字段文件
|
||||
inline const QString Uf2DataType = "/公共资源/datatypes.xml"; // UF2 数据类型文件
|
||||
inline const QString Uf2ErrNumber = "/公共资源/errNumbers.xml"; // UF2 错误号文件
|
||||
inline const QString Uf2Constants = "/公共资源/constants.xml"; // UF2 常量文件
|
||||
inline const QString Uf2MenuFunc = "/公共资源/menuandfunction.xml"; // UF2 菜单与功能文件
|
||||
// 文件扩展名
|
||||
inline const QString Uft3StructExt = ".uftstructure"; // UFT3 表结构文件扩展名
|
||||
}
|
||||
@@ -82,8 +114,9 @@ inline QString toolExe() { return toolDir() + "/" + ToolFiles::Exe; }
|
||||
inline QString configIni() { return toolDir() + "/" + ToolFiles::ConfigIni; }
|
||||
inline QString custJson() { return toolDir() + "/" + ToolFiles::CustJson; }
|
||||
inline QString logDir() { return QCoreApplication::applicationDirPath() + "/" + Paths::LogDir; }
|
||||
inline QString cacheDir() { return QCoreApplication::applicationDirPath() + "/" + Paths::CacheDir; }
|
||||
inline QString cacheDb() { return cacheDir() + "/" + Cache::DbFile; }
|
||||
inline QString cacheDir() { return toolDir() + "/" + Paths::CacheDir; } // bin/uf2touft3/cache
|
||||
inline QString uf20CacheDb() { return cacheDir() + "/" + Cache::Uf20DbFile; }
|
||||
inline QString uft3CacheDb() { return cacheDir() + "/" + Cache::Uft3DbFile; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 代码检索(CodeSearch)相关常量与路径
|
||||
@@ -95,7 +128,7 @@ namespace CodeSearch {
|
||||
}
|
||||
|
||||
inline QString changeCodeDir() { return toolDir() + "/" + Paths::ChangeCodeDir; } // 默认代码检索根目录
|
||||
inline QString changeCodeIni() { return toolDir() + "/" + QString::fromLatin1(CodeSearch::ChangeCodeIni); }
|
||||
inline QString changeCodeIni() { return toolDir() + "/" + Paths::ConfigDir + "/" + QString::fromLatin1(CodeSearch::ChangeCodeIni); }
|
||||
inline QString functionOutput() { return Paths::FunctionOutput; } // 调用链/文件导出目录名
|
||||
|
||||
} // namespace Constants
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "configmanager.h"
|
||||
#include "utils/Constants.h"
|
||||
#include "logmanager.h"
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
@@ -31,8 +32,7 @@ bool ConfigManager::isValidUtf8(const QByteArray& data)
|
||||
bool ConfigManager::loadConfig(const QString& basePath)
|
||||
{
|
||||
m_basePath = basePath;
|
||||
QString uf2touft3Path = QDir(basePath).filePath("uf2touft3");
|
||||
m_configFilePath = QDir(uf2touft3Path).filePath("config.ini");
|
||||
m_configFilePath = Constants::configIni();
|
||||
|
||||
LogManager::instance()->log(QString("========== 加载配置文件 =========="));
|
||||
LogManager::instance()->log(QString("配置文件路径: %1").arg(m_configFilePath));
|
||||
|
||||
+17
-111
@@ -6,19 +6,6 @@
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
|
||||
// 计算当前工程路径指纹(含盘符/路径变化即视为不同工程),用于识别
|
||||
// “换机器/换路径后旧缓存 db 仍非空”导致功能查询读到旧数据的情况
|
||||
static QString currentProjectPathFingerprint()
|
||||
{
|
||||
QStringList parts;
|
||||
const QStringList keys = {"uf20", "ufAcct20", "UftdbSett", "uft30pub", "uft30cbptrans", "uft30"};
|
||||
ConfigManager* cfg = ConfigManager::instance();
|
||||
for (const QString& k : keys) {
|
||||
parts.append(PathUtils::normPath(cfg->getValue("projectPath", k)));
|
||||
}
|
||||
return parts.join("|");
|
||||
}
|
||||
|
||||
bool ConfigReaderBase::doLoadAllConfig(const ConfigReaderTraits& traits)
|
||||
{
|
||||
m_failedFiles.clear();
|
||||
@@ -30,119 +17,38 @@ bool ConfigReaderBase::doLoadAllConfig(const ConfigReaderTraits& traits)
|
||||
return false;
|
||||
}
|
||||
|
||||
QString filePath = Constants::toolDir() + "/" + traits.mainFile;
|
||||
|
||||
// 元数据解析结果直接落双库(cache/uf20_cache.db / cache/uft3_cache.db),
|
||||
// 不再生成中间 JSON(uf2.json/uft3.json)。因此只需校验接口表非空即可加载。
|
||||
if (traits.hasConfig(traits.mainFile)) {
|
||||
// 缓存表非空:检测工程路径是否与上次写入时一致,
|
||||
// 不一致(如 A 电脑的缓存 db 打包给 B 电脑)则强制重新从 json 同步,
|
||||
// 避免功能查询读到旧工程的 cname 数据
|
||||
QMap<QString, QString> meta = DataCache::instance()->loadProjectPaths();
|
||||
const QString stored = meta.value("fingerprint");
|
||||
const QString current = currentProjectPathFingerprint();
|
||||
if (stored != current) {
|
||||
LogManager::instance()->log(QString("%1配置加载 - 工程路径已变化(旧:%2 新:%3),强制重新同步缓存")
|
||||
.arg(traits.logTag).arg(stored).arg(current));
|
||||
} else {
|
||||
m_loaded = true;
|
||||
DataCache::instance()->setDataLoaded(true);
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 记录当前工程路径指纹,供下次启动比对(详见 doLoadAllConfig 开头)
|
||||
DataCache::instance()->saveProjectPaths({{"fingerprint", currentProjectPathFingerprint()}});
|
||||
|
||||
m_loaded = true;
|
||||
DataCache::instance()->setDataLoaded(true);
|
||||
return true;
|
||||
// 接口表为空: 缓存库尚无数据, 需先执行元数据更新。
|
||||
LogManager::instance()->logError(
|
||||
QString("%1配置加载失败 - 接口表为空, 请先执行元数据更新").arg(traits.logTag));
|
||||
m_failedFiles.append(traits.mainFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
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()) {
|
||||
// 元数据更新完成后数据已直接落双库, 重新加载仅需校验接口表非空并刷新工程路径指纹。
|
||||
if (!DataCache::instance()->initDatabase()) {
|
||||
LogManager::instance()->logError(
|
||||
QString("%1配置重新加载失败 - 配置为空").arg(traits.logTag));
|
||||
QString("%1配置重新加载失败 - 数据库初始化失败").arg(traits.logTag));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!traits.saveConfig(mergedConfig)) {
|
||||
if (!traits.hasConfig(traits.mainFile)) {
|
||||
LogManager::instance()->logError(
|
||||
QString("%1配置重新加载失败 - 保存失败").arg(traits.logTag));
|
||||
QString("%1配置重新加载失败 - 接口表为空, 请先执行元数据更新").arg(traits.logTag));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogManager::instance()->log(
|
||||
QString("%1配置重新加载成功 - 数据源: cache/uf20_cache.db / cache/uft3_cache.db").arg(traits.logTag));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+339
-383
@@ -4,8 +4,11 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
@@ -17,9 +20,11 @@ DataCache::DataCache(QObject *parent) : QObject(parent), m_dataLoaded(false)
|
||||
|
||||
DataCache::~DataCache()
|
||||
{
|
||||
if (m_db.isOpen()) {
|
||||
m_db.close();
|
||||
}
|
||||
QMutexLocker locker(&m_connMutex);
|
||||
for (auto& c : m_uf20Db) { if (c.isOpen()) c.close(); }
|
||||
for (auto& c : m_uft3Db) { if (c.isOpen()) c.close(); }
|
||||
m_uf20Db.clear();
|
||||
m_uft3Db.clear();
|
||||
}
|
||||
|
||||
DataCache* DataCache::instance()
|
||||
@@ -30,135 +35,69 @@ DataCache* DataCache::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
QString DataCache::getDatabasePath()
|
||||
// 打开指定库连接(只读/读写均走此函数)。库文件不存在时仅创建 cache/ 目录并打开 SQLite,
|
||||
// 由 SQLite 自动生成空 DB 文件; _meta 表在 saveDataset 落库时按需创建, 此处不主动建表。
|
||||
QSqlDatabase DataCache::database(const QString& db) const
|
||||
{
|
||||
QString dir = Constants::cacheDir();
|
||||
QDir dbDir(dir);
|
||||
if (!dbDir.exists()) {
|
||||
if (!dbDir.mkpath(dir)) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 创建缓存目录失败: %1").arg(dir));
|
||||
const bool isUft3 = (db == "uft3");
|
||||
QHash<QThread*, QSqlDatabase>& conns = isUft3 ? m_uft3Db : m_uf20Db;
|
||||
|
||||
// QSqlDatabase 连接只能在创建它的线程中使用。元数据更新在 QtConcurrent 后台线程写库,
|
||||
// 功能查询在主线程读库, 因此按线程各自维护一条连接, 避免跨线程使用导致写入静默失败。
|
||||
QThread* self = QThread::currentThread();
|
||||
{
|
||||
// QHash 非线程安全, 主线程查询与后台线程落库会并发访问, 需加锁保护。
|
||||
// QSqlDatabase 为值语义, 取出副本后即可解锁, 后续 SQL 不持锁。
|
||||
QMutexLocker locker(&m_connMutex);
|
||||
auto it = conns.constFind(self);
|
||||
if (it != conns.constEnd() && it.value().isOpen()) {
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
return dbDir.absoluteFilePath(Constants::Cache::DbFile);
|
||||
}
|
||||
|
||||
bool DataCache::createTables()
|
||||
{
|
||||
QSqlQuery query(m_db);
|
||||
|
||||
query.exec("PRAGMA synchronous = OFF");
|
||||
query.exec("PRAGMA journal_mode = MEMORY");
|
||||
|
||||
QString createUF20Table = R"(
|
||||
CREATE TABLE IF NOT EXISTS uf20_cname_table (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cname TEXT UNIQUE NOT NULL,
|
||||
e_name TEXT,
|
||||
function_no TEXT,
|
||||
update_time TEXT NOT NULL
|
||||
)
|
||||
)";
|
||||
|
||||
QString createUFT3Table = R"(
|
||||
CREATE TABLE IF NOT EXISTS uft3_cname_table (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cname TEXT UNIQUE NOT NULL,
|
||||
e_name TEXT,
|
||||
function_no TEXT,
|
||||
update_time TEXT NOT NULL
|
||||
)
|
||||
)";
|
||||
|
||||
if (!query.exec(createUF20Table)) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 创建uf20_cname_table失败: %1").arg(query.lastError().text()));
|
||||
return false;
|
||||
QString path = isUft3 ? Constants::uft3CacheDb() : Constants::uf20CacheDb();
|
||||
QFileInfo fi(path);
|
||||
if (!fi.dir().exists() && !QDir().mkpath(fi.absolutePath())) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 创建缓存目录失败: %1").arg(fi.absolutePath()));
|
||||
return QSqlDatabase();
|
||||
}
|
||||
|
||||
if (!query.exec(createUFT3Table)) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 创建uft3_cname_table失败: %1").arg(query.lastError().text()));
|
||||
return false;
|
||||
// 连接名需全局唯一: 库名 + 线程地址
|
||||
QString connName = QString("%1_cache_connection_%2")
|
||||
.arg(isUft3 ? "uft3" : "uf20")
|
||||
.arg(reinterpret_cast<quintptr>(self), 0, 16);
|
||||
QSqlDatabase conn = QSqlDatabase::addDatabase("QSQLITE", connName);
|
||||
conn.setDatabaseName(path);
|
||||
if (!conn.open()) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 打开 %1 缓存库失败: %2")
|
||||
.arg(db, conn.lastError().text()));
|
||||
return QSqlDatabase();
|
||||
}
|
||||
|
||||
// 工程路径指纹表:记录上次写入 cname 表时使用的工程路径,
|
||||
// 用于在换机器/换路径场景下识别旧缓存
|
||||
QString createMetaTable = R"(
|
||||
CREATE TABLE IF NOT EXISTS cache_meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)
|
||||
)";
|
||||
if (!query.exec(createMetaTable)) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 创建cache_meta失败: %1").arg(query.lastError().text()));
|
||||
return false;
|
||||
{
|
||||
QMutexLocker locker(&m_connMutex);
|
||||
conns.insert(self, conn);
|
||||
}
|
||||
|
||||
LogManager::instance()->log("数据缓存 - 表创建成功");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataCache::saveProjectPaths(const QMap<QString, QString>& paths)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
QSqlQuery query(m_db);
|
||||
query.exec("DELETE FROM cache_meta");
|
||||
for (auto it = paths.begin(); it != paths.end(); ++it) {
|
||||
query.prepare("INSERT OR REPLACE INTO cache_meta (key, value) VALUES (?, ?)");
|
||||
query.addBindValue(it.key());
|
||||
query.addBindValue(it.value());
|
||||
if (!query.exec()) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 写入cache_meta失败: %1").arg(query.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QMap<QString, QString> DataCache::loadProjectPaths()
|
||||
{
|
||||
QMap<QString, QString> result;
|
||||
if (!m_db.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
QSqlQuery query(m_db);
|
||||
if (query.exec("SELECT key, value FROM cache_meta")) {
|
||||
while (query.next()) {
|
||||
result[query.value(0).toString()] = query.value(1).toString();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 已打开 %1 缓存库(线程 0x%2): %3")
|
||||
.arg(db).arg(reinterpret_cast<quintptr>(self), 0, 16).arg(path));
|
||||
return conn;
|
||||
}
|
||||
|
||||
bool DataCache::initDatabase()
|
||||
{
|
||||
// 已打开则直接返回,避免重复打印“数据库路径”(该函数启动期会被多次调用)
|
||||
if (QSqlDatabase::contains("qt_sql_default_connection")) {
|
||||
m_db = QSqlDatabase::database("qt_sql_default_connection");
|
||||
if (m_db.isOpen()) {
|
||||
return true;
|
||||
}
|
||||
// 纯只读探测: 仅当缓存库文件已存在时才打开, 不创建空库文件、不建 _meta 表。
|
||||
// 这样首次启动(尚无缓存库)时返回 false, 提示用户先执行元数据更新, 避免生成空的 uf20_cache.db。
|
||||
// 已打开则直接返回 true(启动期会被多次调用)。
|
||||
bool ok20 = QFile::exists(Constants::uf20CacheDb());
|
||||
bool ok3 = QFile::exists(Constants::uft3CacheDb());
|
||||
if (ok20 || ok3) {
|
||||
// 仅打开已存在的库文件, 避免为尚未生成的库创建空 DB 文件
|
||||
if (ok20) database("uf20");
|
||||
if (ok3) database("uft3");
|
||||
LogManager::instance()->log(QString("数据缓存 - 已加载缓存库: uf20=%1, uft3=%2")
|
||||
.arg(ok20 ? "是" : "否").arg(ok3 ? "是" : "否"));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString dbPath = getDatabasePath();
|
||||
LogManager::instance()->log(QString("数据缓存 - 数据库路径: %1").arg(dbPath));
|
||||
|
||||
m_db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
m_db.setDatabaseName(dbPath);
|
||||
|
||||
if (!m_db.open()) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 打开数据库失败: %1").arg(m_db.lastError().text()));
|
||||
QString drivers = QSqlDatabase::drivers().join(", ");
|
||||
LogManager::instance()->logError(QString("数据缓存 - 可用的数据库驱动: %1").arg(drivers));
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.exec("PRAGMA synchronous = OFF");
|
||||
query.exec("PRAGMA journal_mode = MEMORY");
|
||||
|
||||
LogManager::instance()->log("数据缓存 - 数据库打开成功");
|
||||
return createTables();
|
||||
LogManager::instance()->logWarning("数据缓存 - 缓存库不存在(uf20_cache.db / uft3_cache.db), 请先执行元数据更新");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DataCache::isDataLoaded() const
|
||||
@@ -171,340 +110,357 @@ void DataCache::setDataLoaded(bool loaded)
|
||||
m_dataLoaded = loaded;
|
||||
}
|
||||
|
||||
bool DataCache::saveUF20Config(const QJsonObject& data)
|
||||
// ---- 通用只读 API ----
|
||||
|
||||
bool DataCache::datasetExists(const QString& db, const QString& table) const
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
QSqlDatabase conn = database(db);
|
||||
if (!conn.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.exec("DELETE FROM uf20_cname_table");
|
||||
|
||||
QStringList cnames;
|
||||
QStringList eNames;
|
||||
QStringList functionNos;
|
||||
|
||||
for (auto it = data.begin(); it != data.end(); ++it) {
|
||||
if (it.value().isObject()) {
|
||||
QJsonObject obj = it.value().toObject();
|
||||
QString cname = obj.value("cname").toString();
|
||||
if (!cname.isEmpty()) {
|
||||
cnames.append(cname);
|
||||
eNames.append(obj.value("eName").toString());
|
||||
functionNos.append(obj.value("functionNo").toString());
|
||||
}
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT COUNT(*) FROM %1").arg(table));
|
||||
if (query.exec() && query.next()) {
|
||||
return query.value(0).toInt() > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QMap<QString, QString> DataCache::getAllRows(const QString& db, const QString& table) const
|
||||
{
|
||||
QMap<QString, QString> result;
|
||||
QSqlDatabase conn = database(db);
|
||||
if (!conn.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT name, data FROM %1").arg(table));
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
result[query.value(0).toString()] = query.value(1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (cnames.isEmpty()) {
|
||||
LogManager::instance()->logWarning("数据缓存 - UF20没有找到cname记录");
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DataCache::getRowData(const QString& db, const QString& table, const QString& key) const
|
||||
{
|
||||
QSqlDatabase conn = database(db);
|
||||
if (!conn.isOpen()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
m_db.transaction();
|
||||
|
||||
query.prepare("INSERT INTO uf20_cname_table (cname, e_name, function_no, update_time) VALUES (?, ?, ?, ?)");
|
||||
|
||||
QString updateTime = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
for (int i = 0; i < cnames.size(); ++i) {
|
||||
query.addBindValue(cnames[i]);
|
||||
query.addBindValue(eNames[i]);
|
||||
query.addBindValue(functionNos[i]);
|
||||
query.addBindValue(updateTime);
|
||||
query.exec();
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT data FROM %1 WHERE name = :k").arg(table));
|
||||
query.bindValue(":k", key);
|
||||
if (query.exec() && query.next()) {
|
||||
return query.value(0).toString();
|
||||
}
|
||||
|
||||
m_db.commit();
|
||||
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - UF20成功保存 %1 个cname记录,耗时: %2ms").arg(cnames.size()).arg(timer.elapsed()));
|
||||
return QString();
|
||||
}
|
||||
|
||||
int DataCache::rowCount(const QString& db, const QString& table) const
|
||||
{
|
||||
QSqlDatabase conn = database(db);
|
||||
if (!conn.isOpen()) {
|
||||
return 0;
|
||||
}
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT COUNT(*) FROM %1").arg(table));
|
||||
if (query.exec() && query.next()) {
|
||||
return query.value(0).toInt();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- 通用写入: 原子落库到双库对应表(对齐 Python store.save) ----
|
||||
// 步骤: 建临时表 t_<table>_new -> 写入 -> DROP 旧表 -> RENAME 临时表 -> 更新 _meta
|
||||
bool DataCache::saveDataset(const QString& db, const QString& table,
|
||||
const QMap<QString, QString>& rows, double srcMtime)
|
||||
{
|
||||
QSqlDatabase conn = database(db);
|
||||
if (!conn.isOpen()) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 写入 %1.%2 失败: 库未打开").arg(db, table));
|
||||
return false;
|
||||
}
|
||||
|
||||
QString tmpTable = "t_" + table + "_new";
|
||||
|
||||
if (!conn.transaction()) {
|
||||
LogManager::instance()->logError(QString("数据缓存 - 写入 %1.%2 开启事务失败").arg(db, table));
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery q(conn);
|
||||
// 临时表
|
||||
q.exec(QString("DROP TABLE IF EXISTS %1").arg(tmpTable));
|
||||
if (!q.exec(QString("CREATE TABLE %1 (name TEXT PRIMARY KEY, data TEXT)").arg(tmpTable))) {
|
||||
conn.rollback();
|
||||
LogManager::instance()->logError(QString("数据缓存 - 建临时表失败: %1").arg(q.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
// 用 bindValue(索引) 而非 addBindValue: 后者是"追加"绑定, 在循环里会不断累积,
|
||||
// 按索引赋值才是幂等的, 避免残留旧绑定值。
|
||||
q.prepare(QString("INSERT OR REPLACE INTO %1 (name, data) VALUES (?, ?)").arg(tmpTable));
|
||||
for (auto it = rows.begin(); it != rows.end(); ++it) {
|
||||
q.bindValue(0, it.key());
|
||||
q.bindValue(1, it.value());
|
||||
if (!q.exec()) {
|
||||
conn.rollback();
|
||||
LogManager::instance()->logError(QString("数据缓存 - 写入 %1.%2 行失败: %3")
|
||||
.arg(db, table, q.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 原子替换
|
||||
q.exec(QString("DROP TABLE IF EXISTS %1").arg(table));
|
||||
if (!q.exec(QString("ALTER TABLE %1 RENAME TO %2").arg(tmpTable, table))) {
|
||||
conn.rollback();
|
||||
LogManager::instance()->logError(QString("数据缓存 - 重命名 %1 失败: %2")
|
||||
.arg(table, q.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
// 更新 _meta
|
||||
q.exec("CREATE TABLE IF NOT EXISTS _meta (dataset TEXT PRIMARY KEY, row_count INTEGER, src_mtime REAL)");
|
||||
q.prepare("INSERT OR REPLACE INTO _meta (dataset, row_count, src_mtime) VALUES (?, ?, ?)");
|
||||
q.bindValue(0, table);
|
||||
q.bindValue(1, rows.size());
|
||||
q.bindValue(2, srcMtime);
|
||||
if (!q.exec()) {
|
||||
conn.rollback();
|
||||
LogManager::instance()->logError(QString("数据缓存 - 更新 _meta 失败: %1").arg(q.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!conn.commit()) {
|
||||
conn.rollback();
|
||||
LogManager::instance()->logError(QString("数据缓存 - 提交 %1.%2 失败").arg(db, table));
|
||||
return false;
|
||||
}
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 已写入 %1.%2 共 %3 行")
|
||||
.arg(db, table).arg(rows.size()));
|
||||
// 接口表落库后使功能查询内存缓存失效, 下次查询自动重建
|
||||
if (table == Constants::Cache::Uf20InterfaceTable) m_uf20FuncCacheValid = false;
|
||||
if (table == Constants::Cache::Uft3InterfaceTable) m_uft3FuncCacheValid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataCache::saveUFT3Config(const QJsonObject& data)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.exec("DELETE FROM uft3_cname_table");
|
||||
|
||||
QStringList cnames;
|
||||
QStringList eNames;
|
||||
QStringList functionNos;
|
||||
|
||||
for (auto it = data.begin(); it != data.end(); ++it) {
|
||||
if (it.value().isObject()) {
|
||||
QJsonObject obj = it.value().toObject();
|
||||
QString cname = obj.value("cname").toString();
|
||||
if (!cname.isEmpty()) {
|
||||
cnames.append(cname);
|
||||
eNames.append(obj.value("eName").toString());
|
||||
functionNos.append(obj.value("functionNo").toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cnames.isEmpty()) {
|
||||
LogManager::instance()->logWarning("数据缓存 - UFT3没有找到cname记录");
|
||||
return true;
|
||||
}
|
||||
|
||||
m_db.transaction();
|
||||
|
||||
query.prepare("INSERT INTO uft3_cname_table (cname, e_name, function_no, update_time) VALUES (?, ?, ?, ?)");
|
||||
|
||||
QString updateTime = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
for (int i = 0; i < cnames.size(); ++i) {
|
||||
query.addBindValue(cnames[i]);
|
||||
query.addBindValue(eNames[i]);
|
||||
query.addBindValue(functionNos[i]);
|
||||
query.addBindValue(updateTime);
|
||||
query.exec();
|
||||
}
|
||||
|
||||
m_db.commit();
|
||||
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - UFT3成功保存 %1 个cname记录,耗时: %2ms").arg(cnames.size()).arg(timer.elapsed()));
|
||||
return true;
|
||||
}
|
||||
// ---- 是否已缓存 ----
|
||||
|
||||
bool DataCache::hasUF20Config(const QString& fileName)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileName != "uf2.json") {
|
||||
return true;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
if (query.exec("SELECT COUNT(*) FROM uf20_cname_table")) {
|
||||
if (query.next()) {
|
||||
return query.value(0).toInt() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
Q_UNUSED(fileName);
|
||||
return datasetExists("uf20", Constants::Cache::Uf20InterfaceTable);
|
||||
}
|
||||
|
||||
bool DataCache::hasUFT3Config(const QString& fileName)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
return false;
|
||||
Q_UNUSED(fileName);
|
||||
return datasetExists("uft3", Constants::Cache::Uft3InterfaceTable);
|
||||
}
|
||||
|
||||
// ---- 功能查询: 从 *interface 表读取 data(JSON) 提取 cname/eName/functionNo ----
|
||||
|
||||
namespace {
|
||||
// 从单行 data(可能为数组或对象) 解析出 FunctionInfo
|
||||
QList<FunctionInfo> parseInterfaceData(const QString& dataJson)
|
||||
{
|
||||
QList<FunctionInfo> list;
|
||||
if (dataJson.isEmpty()) {
|
||||
return list;
|
||||
}
|
||||
QJsonDocument doc = QJsonDocument::fromJson(dataJson.toUtf8());
|
||||
QJsonArray arr;
|
||||
if (doc.isArray()) {
|
||||
arr = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
arr.append(doc.object());
|
||||
}
|
||||
for (const QJsonValue& v : arr) {
|
||||
if (!v.isObject()) continue;
|
||||
QJsonObject obj = v.toObject();
|
||||
FunctionInfo info;
|
||||
info.cname = obj.value("cname").toString();
|
||||
info.eName = obj.value("eName").toString();
|
||||
info.functionNo = obj.value("functionNo").toString();
|
||||
if (!info.cname.isEmpty()) {
|
||||
list.append(info);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
if (fileName != "uft3.json") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 懒加载并缓存指定库的解析后 FunctionInfo 列表。
|
||||
// 首次调用或缓存失效时, 全表读取 *interface 表并解析 JSON 构建列表, 之后直接返回内存副本,
|
||||
// 避免每次 search* / getAll*Cnames 都全表扫描 + 重解析所有 JSON 行。
|
||||
const QList<FunctionInfo>& DataCache::cachedFunctions(const QString& db) const
|
||||
{
|
||||
bool isUf20 = (db == "uf20");
|
||||
QList<FunctionInfo>& cache = isUf20 ? m_uf20FuncCache : m_uft3FuncCache;
|
||||
bool& valid = isUf20 ? m_uf20FuncCacheValid : m_uft3FuncCacheValid;
|
||||
if (valid) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
if (query.exec("SELECT COUNT(*) FROM uft3_cname_table")) {
|
||||
if (query.next()) {
|
||||
return query.value(0).toInt() > 0;
|
||||
|
||||
cache.clear();
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
if (isUf20) {
|
||||
// UF20 功能号全部落在 uf2_interface 一张表
|
||||
QMap<QString, QString> rows = getAllRows(db, Constants::Cache::Uf20InterfaceTable);
|
||||
for (auto it = rows.begin(); it != rows.end(); ++it) {
|
||||
cache.append(parseInterfaceData(it.value()));
|
||||
}
|
||||
} else {
|
||||
// UFT3 功能号分散在 uft3_interface (普通接口) 与 uft3_trans_code (转码) 两张表,
|
||||
// 必须合并才能覆盖全部 LS 转码功能号 (如 LS_证券周边_客户资金余额获取)。
|
||||
QStringList tables;
|
||||
tables << Constants::Cache::Uft3InterfaceTable
|
||||
<< Constants::Cache::TblUft3TransCode;
|
||||
for (const QString& t : tables) {
|
||||
QMap<QString, QString> rows = getAllRows(db, t);
|
||||
for (auto it = rows.begin(); it != rows.end(); ++it) {
|
||||
cache.append(parseInterfaceData(it.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
valid = true;
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 重建 %1 功能缓存 %2 条,耗时: %3ms")
|
||||
.arg(db).arg(cache.size()).arg(timer.elapsed()));
|
||||
return cache;
|
||||
}
|
||||
|
||||
void DataCache::refreshFunctionCache()
|
||||
{
|
||||
m_uf20FuncCacheValid = false;
|
||||
m_uft3FuncCacheValid = false;
|
||||
// 立即预热, 使后续查询走内存缓存
|
||||
cachedFunctions("uf20");
|
||||
cachedFunctions("uft3");
|
||||
LogManager::instance()->logInfo("数据缓存 - 功能查询缓存已重建");
|
||||
}
|
||||
|
||||
bool DataCache::checkUF20CnameExists(const QString& cname)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
QSqlDatabase conn = database("uf20");
|
||||
if (!conn.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT COUNT(*) FROM uf20_cname_table WHERE cname = :cname");
|
||||
query.bindValue(":cname", cname);
|
||||
|
||||
if (query.exec() && query.next()) {
|
||||
return query.value(0).toInt() > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT data FROM %1 WHERE name = :k").arg(Constants::Cache::Uf20InterfaceTable));
|
||||
query.bindValue(":k", cname);
|
||||
return query.exec() && query.next();
|
||||
}
|
||||
|
||||
QSet<QString> DataCache::getAllUF20Cnames()
|
||||
{
|
||||
QSet<QString> result;
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
if (query.exec("SELECT cname FROM uf20_cname_table")) {
|
||||
while (query.next()) {
|
||||
result.insert(query.value(0).toString());
|
||||
const QList<FunctionInfo>& all = cachedFunctions("uf20");
|
||||
for (const FunctionInfo& info : all) {
|
||||
if (!info.cname.isEmpty()) {
|
||||
result.insert(info.cname);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 获取UF20 Cname缓存,耗时: %1ms").arg(timer.elapsed()));
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 获取UF20 Cname缓存 %1 条").arg(result.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<FunctionInfo> DataCache::searchUF20Functions(const QString& keyword, const QString& field, bool exactMatch)
|
||||
{
|
||||
QList<FunctionInfo> result;
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
QString sql;
|
||||
QString keywordValue = exactMatch ? keyword : "%" + keyword + "%";
|
||||
QString op = exactMatch ? "= :keyword" : "LIKE :keyword";
|
||||
|
||||
if (field == "cname") {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uf20_cname_table
|
||||
WHERE cname %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
} else if (field == "function_no") {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uf20_cname_table
|
||||
WHERE function_no %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
} else {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uf20_cname_table
|
||||
WHERE cname %1 OR e_name %1 OR function_no %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
}
|
||||
|
||||
query.prepare(sql);
|
||||
query.bindValue(":keyword", keywordValue);
|
||||
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
FunctionInfo info;
|
||||
info.cname = query.value(0).toString();
|
||||
info.eName = query.value(1).toString();
|
||||
info.functionNo = query.value(2).toString();
|
||||
QString kw = keyword.trimmed();
|
||||
Qt::CaseSensitivity cs = exactMatch ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
|
||||
const QList<FunctionInfo>& all = cachedFunctions("uf20");
|
||||
for (const FunctionInfo& info : all) {
|
||||
bool hit = false;
|
||||
if (field == "cname") {
|
||||
hit = exactMatch ? (info.cname == kw) : info.cname.contains(kw, cs);
|
||||
} else if (field == "function_no") {
|
||||
hit = exactMatch ? (info.functionNo == kw) : info.functionNo.contains(kw, cs);
|
||||
} else if (field == "eName") {
|
||||
hit = exactMatch ? (info.eName == kw) : info.eName.contains(kw, cs);
|
||||
} else {
|
||||
hit = (info.cname.contains(kw, cs) ||
|
||||
info.eName.contains(kw, cs) ||
|
||||
info.functionNo.contains(kw, cs));
|
||||
}
|
||||
if (hit) {
|
||||
result.append(info);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DataCache::checkUFT3CnameExists(const QString& cname)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
QSqlDatabase conn = database("uft3");
|
||||
if (!conn.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT COUNT(*) FROM uft3_cname_table WHERE cname = :cname");
|
||||
query.bindValue(":cname", cname);
|
||||
|
||||
if (query.exec() && query.next()) {
|
||||
return query.value(0).toInt() > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
QSqlQuery query(conn);
|
||||
query.prepare(QString("SELECT data FROM %1 WHERE name = :k").arg(Constants::Cache::Uft3InterfaceTable));
|
||||
query.bindValue(":k", cname);
|
||||
return query.exec() && query.next();
|
||||
}
|
||||
|
||||
QSet<QString> DataCache::getAllUFT3Cnames()
|
||||
{
|
||||
QSet<QString> result;
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
if (query.exec("SELECT cname FROM uft3_cname_table")) {
|
||||
while (query.next()) {
|
||||
result.insert(query.value(0).toString());
|
||||
const QList<FunctionInfo>& all = cachedFunctions("uft3");
|
||||
for (const FunctionInfo& info : all) {
|
||||
if (!info.cname.isEmpty()) {
|
||||
result.insert(info.cname);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 获取UFT3 Cname缓存,耗时: %1ms").arg(timer.elapsed()));
|
||||
LogManager::instance()->logInfo(QString("数据缓存 - 获取UFT3 Cname缓存 %1 条").arg(result.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<FunctionInfo> DataCache::searchUFT3Functions(const QString& keyword, const QString& field, bool exactMatch)
|
||||
{
|
||||
QList<FunctionInfo> result;
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
QString sql;
|
||||
QString keywordValue = exactMatch ? keyword : "%" + keyword + "%";
|
||||
QString op = exactMatch ? "= :keyword" : "LIKE :keyword";
|
||||
|
||||
if (field == "cname") {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uft3_cname_table
|
||||
WHERE cname %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
} else if (field == "function_no") {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uft3_cname_table
|
||||
WHERE function_no %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
} else {
|
||||
sql = QString(R"(
|
||||
SELECT cname, e_name, function_no
|
||||
FROM uft3_cname_table
|
||||
WHERE cname %1 OR e_name %1 OR function_no %1
|
||||
ORDER BY cname
|
||||
)").arg(op);
|
||||
}
|
||||
|
||||
query.prepare(sql);
|
||||
query.bindValue(":keyword", keywordValue);
|
||||
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
FunctionInfo info;
|
||||
info.cname = query.value(0).toString();
|
||||
info.eName = query.value(1).toString();
|
||||
info.functionNo = query.value(2).toString();
|
||||
QString kw = keyword.trimmed();
|
||||
Qt::CaseSensitivity cs = exactMatch ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
|
||||
const QList<FunctionInfo>& all = cachedFunctions("uft3");
|
||||
for (const FunctionInfo& info : all) {
|
||||
bool hit = false;
|
||||
if (field == "cname") {
|
||||
hit = exactMatch ? (info.cname == kw) : info.cname.contains(kw, cs);
|
||||
} else if (field == "function_no") {
|
||||
hit = exactMatch ? (info.functionNo == kw) : info.functionNo.contains(kw, cs);
|
||||
} else if (field == "eName") {
|
||||
hit = exactMatch ? (info.eName == kw) : info.eName.contains(kw, cs);
|
||||
} else {
|
||||
hit = (info.cname.contains(kw, cs) ||
|
||||
info.eName.contains(kw, cs) ||
|
||||
info.functionNo.contains(kw, cs));
|
||||
}
|
||||
if (hit) {
|
||||
result.append(info);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataCache::clearCache()
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
return;
|
||||
// 缓存库由 C++ 元数据解析直接落库维护。此处仅重置内存状态并关闭连接, 不删除库文件。
|
||||
QMutexLocker locker(&m_connMutex);
|
||||
for (auto it = m_uf20Db.begin(); it != m_uf20Db.end(); ++it) {
|
||||
if (it.value().isOpen()) it.value().close();
|
||||
QSqlDatabase::removeDatabase(it.value().connectionName());
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.exec("DELETE FROM uf20_cname_table");
|
||||
query.exec("DELETE FROM uft3_cname_table");
|
||||
for (auto it = m_uft3Db.begin(); it != m_uft3Db.end(); ++it) {
|
||||
if (it.value().isOpen()) it.value().close();
|
||||
QSqlDatabase::removeDatabase(it.value().connectionName());
|
||||
}
|
||||
m_uf20Db.clear();
|
||||
m_uft3Db.clear();
|
||||
m_dataLoaded = false;
|
||||
// 失效功能查询内存缓存, 重新打开后首次查询自动重建
|
||||
m_uf20FuncCacheValid = false;
|
||||
m_uft3FuncCacheValid = false;
|
||||
LogManager::instance()->logInfo("数据缓存 - 已重置缓存连接(缓存库文件保留)");
|
||||
}
|
||||
|
||||
+63
-19
@@ -4,7 +4,13 @@
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QHash>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
struct FunctionInfo {
|
||||
QString cname;
|
||||
@@ -12,48 +18,86 @@ struct FunctionInfo {
|
||||
QString functionNo;
|
||||
};
|
||||
|
||||
// DataCache: 缓存库(uf20_cache.db / uft3_cache.db)由 C++ 元数据解析后直接落库维护,
|
||||
// 存放于 <exe>/cache/ 目录。物理表结构统一为 (name TEXT PRIMARY KEY, data TEXT),
|
||||
// data 为 JSON 序列化字符串, 对齐 Python 参考项目 src/store/schema.py。
|
||||
// 库文件不存在时 database() 会自动创建 cache/ 目录与 DB 文件, 无需预先运行 Python 工具。
|
||||
class DataCache : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static DataCache* instance();
|
||||
|
||||
|
||||
// 打开两个缓存库(uf20_cache.db / uft3_cache.db)。库不存在时自动创建, 成功打开任一库即返回 true。
|
||||
bool initDatabase();
|
||||
|
||||
bool isDataLoaded() const;
|
||||
void setDataLoaded(bool loaded);
|
||||
|
||||
bool saveUF20Config(const QJsonObject& data);
|
||||
bool saveUFT3Config(const QJsonObject& data);
|
||||
|
||||
bool hasUF20Config(const QString& fileName);
|
||||
bool hasUFT3Config(const QString& fileName);
|
||||
|
||||
// 工程路径指纹:记录上次成功写入 cname 表时所用工程路径,
|
||||
// 用于检测“换机器/换路径后旧缓存 db 仍非空”导致功能查询读到旧数据的问题
|
||||
bool saveProjectPaths(const QMap<QString, QString>& paths);
|
||||
QMap<QString, QString> loadProjectPaths();
|
||||
|
||||
// ---- 通用只读 API (db 选 "uf20" / "uft3", table 为 Constants::Cache 中表名) ----
|
||||
// 物理表结构统一为: (name TEXT PRIMARY KEY, data TEXT), 对齐 Python src/store/schema.py
|
||||
// 接口表是否存在且非空(用于“是否已缓存”判定)
|
||||
bool datasetExists(const QString& db, const QString& table) const;
|
||||
// 读取整张键值表: name -> data(JSON 字符串)
|
||||
QMap<QString, QString> getAllRows(const QString& db, const QString& table) const;
|
||||
// 读取单行 data
|
||||
QString getRowData(const QString& db, const QString& table, const QString& key) const;
|
||||
// 行数
|
||||
int rowCount(const QString& db, const QString& table) const;
|
||||
|
||||
// ---- 通用写入 API (C++ 解析源文件后落库到双库对应表) ----
|
||||
// rows: name -> data(JSON 字符串); 原子写(临时表 + RENAME)后更新 _meta
|
||||
bool saveDataset(const QString& db, const QString& table,
|
||||
const QMap<QString, QString>& rows, double srcMtime = 0.0);
|
||||
|
||||
// ---- 兼容旧调用方的占位实现 (配置由元数据解析直接落双库, 此处为空操作) ----
|
||||
bool saveUF20Config(const QJsonObject& data) { Q_UNUSED(data); return true; }
|
||||
bool saveUFT3Config(const QJsonObject& data) { Q_UNUSED(data); return true; }
|
||||
|
||||
// 是否已缓存对应版本配置(= 接口表非空)
|
||||
bool hasUF20Config(const QString& fileName = QString());
|
||||
bool hasUFT3Config(const QString& fileName = QString());
|
||||
|
||||
// ---- 功能查询 (从 *interface 表读取) ----
|
||||
bool checkUF20CnameExists(const QString& cname);
|
||||
QSet<QString> getAllUF20Cnames();
|
||||
QList<FunctionInfo> searchUF20Functions(const QString& keyword, const QString& field = "", bool exactMatch = false);
|
||||
|
||||
|
||||
bool checkUFT3CnameExists(const QString& cname);
|
||||
QSet<QString> getAllUFT3Cnames();
|
||||
QList<FunctionInfo> searchUFT3Functions(const QString& keyword, const QString& field = "", bool exactMatch = false);
|
||||
|
||||
|
||||
// 重新构建功能查询内存缓存(元数据落库后调用, 使后续查询走缓存而非全表扫描)
|
||||
void refreshFunctionCache();
|
||||
|
||||
void clearCache();
|
||||
|
||||
private:
|
||||
explicit DataCache(QObject *parent = nullptr);
|
||||
~DataCache();
|
||||
|
||||
bool createTables();
|
||||
QString getDatabasePath();
|
||||
|
||||
|
||||
// 取指定库连接。QSqlDatabase 连接只能在创建它的线程中使用,
|
||||
// 因此按线程分别建立并缓存连接(元数据更新在 QtConcurrent 后台线程执行,
|
||||
// 而查询在主线程执行, 共用同一连接会导致写入静默失败)。
|
||||
QSqlDatabase database(const QString& db) const;
|
||||
|
||||
// 懒加载并缓存指定库的解析后 FunctionInfo 列表(避免每次查询全表扫描+重解析)
|
||||
const QList<FunctionInfo>& cachedFunctions(const QString& db) const;
|
||||
|
||||
static DataCache* m_instance;
|
||||
QSqlDatabase m_db;
|
||||
// key = 线程指针, value = 该线程专用的数据库连接
|
||||
// QHash 非线程安全, 由 m_connMutex 保护(主线程查询与后台线程落库会并发访问)
|
||||
mutable QHash<QThread*, QSqlDatabase> m_uf20Db;
|
||||
mutable QHash<QThread*, QSqlDatabase> m_uft3Db;
|
||||
mutable QMutex m_connMutex;
|
||||
bool m_dataLoaded;
|
||||
|
||||
// 功能查询内存缓存(UF20/UFT3 各一份, 落库或清缓存后失效重建)
|
||||
mutable QList<FunctionInfo> m_uf20FuncCache;
|
||||
mutable QList<FunctionInfo> m_uft3FuncCache;
|
||||
mutable bool m_uf20FuncCacheValid;
|
||||
mutable bool m_uft3FuncCacheValid;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,5 +16,8 @@
|
||||
},
|
||||
"V1.5.0": {
|
||||
"release_notes": "1.解决转码遇到公共已转函数导致失败部分接口转码失败问题;"
|
||||
},
|
||||
"V1.6.0": {
|
||||
"release_notes": "1.转码逻辑优化;\n2.元数据更新逻辑优化;\n3.元数据更新存储修改为db存储模式;\n4.写日志异常修复"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user