diff --git a/src/metadataupdate/metadataprocessor.cpp b/src/metadataupdate/metadataprocessor.cpp index 1091705..1b57044 100644 --- a/src/metadataupdate/metadataprocessor.cpp +++ b/src/metadataupdate/metadataprocessor.cpp @@ -11,6 +11,7 @@ #include "uft3table.h" #include "utils/logmanager.h" #include "utils/configmanager.h" +#include "utils/datacache.h" #include #include #include @@ -256,6 +257,35 @@ bool MetadataProcessor::writeCache(const QJsonObject& cache) return writeJson(cachePath, cache); } +void MetadataProcessor::syncJsonToCache(const QString& jsonPath, bool isUft3) +{ + if (!DataCache::instance()->initDatabase()) { + LogManager::instance()->logError("元数据同步缓存失败 - 数据库初始化失败"); + return; + } + QJsonObject json = readJson(jsonPath); + if (json.isEmpty()) { + LogManager::instance()->logWarning( + QString("元数据同步缓存跳过 - 读取为空: %1").arg(jsonPath)); + 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) { LogManager::instance()->log("========== 开始处理 UF2.0 接口 =========="); @@ -274,7 +304,13 @@ bool MetadataProcessor::processUf2Interfaces(const QString& basePath) Uf2Interface uf2; QString outputPath = QDir(m_uf2touft3Path).filePath("uf2.json"); - return processInterfaces(&uf2, ctx, m_uf20Path, outputPath, QString(), "uf2", false); + bool ok = processInterfaces(&uf2, ctx, m_uf20Path, outputPath, QString(), "uf2", false); + // 生成成功后立即把新 json 同步进 config_cache.db(功能查询的数据源), + // 避免“换机器/换路径重新生成元数据后,db 仍停留在旧工程数据”导致功能查询查不到 + if (ok) { + syncJsonToCache(outputPath, false); + } + return ok; } bool MetadataProcessor::processUft3Interfaces(const QString& basePath) @@ -293,7 +329,13 @@ bool MetadataProcessor::processUft3Interfaces(const QString& basePath) Uft3Interface uft3; QString outputPath = QDir(m_uf2touft3Path).filePath("uft3.json"); QString transOutputPath = QDir(m_uf2touft3Path).filePath("uft3Trans.json"); - return processInterfaces(&uft3, ctx, m_uft30Path, outputPath, transOutputPath, "uft3", true); + bool ok = processInterfaces(&uft3, ctx, m_uft30Path, outputPath, transOutputPath, "uft3", true); + // 生成成功后立即把新 json 同步进 config_cache.db(功能查询的数据源), + // 避免“换机器/换路径重新生成元数据后,db 仍停留在旧工程数据”导致功能查询查不到 + if (ok) { + syncJsonToCache(outputPath, true); + } + return ok; } bool MetadataProcessor::processInterfaces(IInterfaceScanner* scanner, diff --git a/src/metadataupdate/metadataprocessor.h b/src/metadataupdate/metadataprocessor.h index 3627c34..2d85350 100644 --- a/src/metadataupdate/metadataprocessor.h +++ b/src/metadataupdate/metadataprocessor.h @@ -54,6 +54,10 @@ private: const QString& transOutputPath, const QString& cacheKey, bool isUft3); + + // 生成元数据后,把 json 同步进 config_cache.db(功能查询数据源)并刷新工程路径指纹; + // isUft3 区分 UF2/UFT3 两套 cname 表 + void syncJsonToCache(const QString& jsonPath, bool isUft3); QString m_basePath; QString m_uf2touft3Path; diff --git a/src/pages/batchconvert/batchconvertpage.cpp b/src/pages/batchconvert/batchconvertpage.cpp index 79b8eb1..45fb120 100644 --- a/src/pages/batchconvert/batchconvertpage.cpp +++ b/src/pages/batchconvert/batchconvertpage.cpp @@ -26,6 +26,31 @@ #include "ElaProgressBar.h" #include "ElaText.h" +namespace { + +bool validateLsFunctionNames(QWidget *parent, const QStringList &functionNames) +{ + QStringList nonLsNames; + for (const QString &name : functionNames) { + const QString trimmedName = name.trimmed(); + if (!trimmedName.isEmpty() + && !trimmedName.startsWith("LS_") + && !nonLsNames.contains(trimmedName)) { + nonLsNames.append(trimmedName); + } + } + + if (nonLsNames.isEmpty()) { + return true; + } + + QMessageBox::warning(parent, "提示", + QString("不支持非LS业务转码:\n%1").arg(nonLsNames.join('\n'))); + return false; +} + +} + class StatusDelegate : public QStyledItemDelegate { public: @@ -264,6 +289,10 @@ void BatchConvertPage::onEditRow(int row) continue; } + if (!validateLsFunctionNames(this, QStringList{newName})) { + continue; + } + if (newName == oldName) { return; } @@ -303,7 +332,13 @@ void BatchConvertPage::onAddFunction() btnLayout->addWidget(cancelBtn); layout->addLayout(btnLayout); - connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept); + connect(okBtn, &QPushButton::clicked, &dialog, [this, &dialog, textEdit]() { + const QStringList funcNames = + textEdit->toPlainText().split('\n', Qt::SkipEmptyParts); + if (validateLsFunctionNames(this, funcNames)) { + dialog.accept(); + } + }); connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject); if (dialog.exec() != QDialog::Accepted) { diff --git a/src/utils/configreaderbase.cpp b/src/utils/configreaderbase.cpp index fb65adc..140e73b 100644 --- a/src/utils/configreaderbase.cpp +++ b/src/utils/configreaderbase.cpp @@ -2,9 +2,23 @@ #include "Constants.h" #include "datacache.h" #include "logmanager.h" +#include "configmanager.h" #include #include +// 计算当前工程路径指纹(含盘符/路径变化即视为不同工程),用于识别 +// “换机器/换路径后旧缓存 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(); @@ -19,9 +33,20 @@ bool ConfigReaderBase::doLoadAllConfig(const ConfigReaderTraits& traits) QString filePath = Constants::toolDir() + "/" + traits.mainFile; if (traits.hasConfig(traits.mainFile)) { - m_loaded = true; - DataCache::instance()->setDataLoaded(true); - return true; + // 缓存表非空:检测工程路径是否与上次写入时一致, + // 不一致(如 A 电脑的缓存 db 打包给 B 电脑)则强制重新从 json 同步, + // 避免功能查询读到旧工程的 cname 数据 + QMap 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; + } } QFile file(filePath); @@ -51,6 +76,9 @@ bool ConfigReaderBase::doLoadAllConfig(const ConfigReaderTraits& traits) return false; } + // 记录当前工程路径指纹,供下次启动比对(详见 doLoadAllConfig 开头) + DataCache::instance()->saveProjectPaths({{"fingerprint", currentProjectPathFingerprint()}}); + m_loaded = true; DataCache::instance()->setDataLoaded(true); return true; diff --git a/src/utils/datacache.cpp b/src/utils/datacache.cpp index 92444e7..982e7cb 100644 --- a/src/utils/datacache.cpp +++ b/src/utils/datacache.cpp @@ -78,24 +78,71 @@ bool DataCache::createTables() LogManager::instance()->logError(QString("数据缓存 - 创建uft3_cname_table失败: %1").arg(query.lastError().text())); return false; } - + + // 工程路径指纹表:记录上次写入 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; + } + LogManager::instance()->log("数据缓存 - 表创建成功"); return true; } +bool DataCache::saveProjectPaths(const QMap& 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 DataCache::loadProjectPaths() +{ + QMap 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; +} + bool DataCache::initDatabase() { - QString dbPath = getDatabasePath(); - LogManager::instance()->log(QString("数据缓存 - 数据库路径: %1").arg(dbPath)); - + // 已打开则直接返回,避免重复打印“数据库路径”(该函数启动期会被多次调用) if (QSqlDatabase::contains("qt_sql_default_connection")) { m_db = QSqlDatabase::database("qt_sql_default_connection"); if (m_db.isOpen()) { - LogManager::instance()->log("数据缓存 - 数据库已打开"); return true; } } - + + QString dbPath = getDatabasePath(); + LogManager::instance()->log(QString("数据缓存 - 数据库路径: %1").arg(dbPath)); + m_db = QSqlDatabase::addDatabase("QSQLITE"); m_db.setDatabaseName(dbPath); diff --git a/src/utils/datacache.h b/src/utils/datacache.h index 30fd9a8..66296da 100644 --- a/src/utils/datacache.h +++ b/src/utils/datacache.h @@ -28,6 +28,11 @@ public: bool hasUF20Config(const QString& fileName); bool hasUFT3Config(const QString& fileName); + + // 工程路径指纹:记录上次成功写入 cname 表时所用工程路径, + // 用于检测“换机器/换路径后旧缓存 db 仍非空”导致功能查询读到旧数据的问题 + bool saveProjectPaths(const QMap& paths); + QMap loadProjectPaths(); bool checkUF20CnameExists(const QString& cname); QSet getAllUF20Cnames(); diff --git a/src/utils/logmanager.cpp b/src/utils/logmanager.cpp index 099bfab..d719212 100644 --- a/src/utils/logmanager.cpp +++ b/src/utils/logmanager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,10 @@ LogManager* LogManager::m_instance = nullptr; LogManager::LogManager(QObject *parent) : QObject(parent) { + // 日志目录固定为 exe 所在目录下的 logs,尽早确定, + // 避免 ConfigManager::loadConfig 早于 initLogConfig 写日志时 m_logDir 仍为空, + // 导致日志落到当前工作目录(exe 根目录) + m_logDir = QCoreApplication::applicationDirPath() + "/logs"; } LogManager::~LogManager() @@ -37,21 +42,28 @@ LogManager* LogManager::instance() void LogManager::initLogFile() { - QString dir = m_logDir.isEmpty() ? QDir::currentPath() : m_logDir; - QDir logDir(dir); + QDir logDir(resolveLogDir()); if (!logDir.exists()) { logDir.mkpath("."); } - - m_logFilePath = logDir.absoluteFilePath(m_fileName); - + if (m_toFile) { cleanupByAge(); // 启动即清理过期日志 - ensureFileOpen(); + ensureFileOpen(); // 打开“当天日期-基础名”日志文件并设置 m_logFilePath } writeLog(LogLevel::Info, "日志系统初始化完成"); } +QString LogManager::resolveLogDir() const +{ + // 日志目录固定为 exe 目录/logs;即便 m_logDir 因故为空,也兜底到 exeDir/logs, + // 绝不落到不可控的当前工作目录 + if (!m_logDir.isEmpty()) { + return m_logDir; + } + return QCoreApplication::applicationDirPath() + "/logs"; +} + QString LogManager::getLogFilePath() const { return m_logFilePath; @@ -59,12 +71,22 @@ QString LogManager::getLogFilePath() const void LogManager::ensureFileOpen() { - if (m_logFile.isOpen()) { - return; + // 实际文件名带当天日期前缀:YYYYMMDD-<基础名>,如 20260812-change_code.log + const QString tag = QDate::currentDate().toString("yyyyMMdd"); + const QString filePath = QDir(resolveLogDir()) + .absoluteFilePath(QString("%1-%2").arg(tag, m_baseName)); + + // 跨天运行时,当前打开的文件不属于今天 -> 关闭旧文件,切换到今天的文件 + if (m_logFile.isOpen() && m_logFilePath != filePath) { + m_logFile.close(); } - m_logFile.setFileName(m_logFilePath); - if (!m_logFile.open(QIODevice::Append | QIODevice::Text)) { - qDebug() << "无法打开日志文件:" << m_logFilePath; + + if (!m_logFile.isOpen()) { + m_logFilePath = filePath; + m_logFile.setFileName(filePath); + if (!m_logFile.open(QIODevice::Append | QIODevice::Text)) { + qDebug() << "无法打开日志文件:" << filePath; + } } } @@ -103,10 +125,10 @@ void LogManager::cleanupByAge() if (m_maxAgeDays <= 0) { return; } - QFileInfo fi(m_logFilePath); - QDir dir(fi.absolutePath()); - QString base = fi.fileName(); - QFileInfoList list = dir.entryInfoList(QStringList() << (base + ".*"), QDir::Files); + QDir dir(resolveLogDir()); + // 匹配所有日期前缀的日志文件(含轮转 .N):*<基础名>* + QString base = "*" + m_baseName; + QFileInfoList list = dir.entryInfoList(QStringList() << base, QDir::Files); QDateTime now = QDateTime::currentDateTime(); for (const QFileInfo& f : list) { if (f.lastModified().daysTo(now) > m_maxAgeDays) { @@ -129,15 +151,11 @@ void LogManager::initLogConfig() else if (levelStr == "ERROR" || levelStr == "ERR") level = LogLevel::Error; m_logLevel = level; - // 2) 输出路径与文件名 - QString path = cc.value("log/path", QString()).toString().trimmed(); - if (!path.isEmpty() && !QDir::isAbsolutePath(path)) { - path = QCoreApplication::applicationDirPath() + "/" + QDir::fromNativeSeparators(path); - } - m_logDir = path; - m_fileName = cc.value("log/file", "change_code.log").toString().trimmed(); - if (m_fileName.isEmpty()) { - m_fileName = "change_code.log"; + // 2) 输出路径固定为:可执行文件所在目录 / logs(不再从 ini 读取,避免配置不一致) + m_logDir = QCoreApplication::applicationDirPath() + "/logs"; + m_baseName = cc.value("log/file", "change_code.log").toString().trimmed(); + if (m_baseName.isEmpty()) { + m_baseName = "change_code.log"; } // 3) 轮转与清理 diff --git a/src/utils/logmanager.h b/src/utils/logmanager.h index d4f918a..231f6e3 100644 --- a/src/utils/logmanager.h +++ b/src/utils/logmanager.h @@ -44,12 +44,13 @@ private: ~LogManager(); void writeLog(LogLevel level, const QString& message); - void ensureFileOpen(); // 确保日志文件已打开(未打开则打开) + void ensureFileOpen(); // 确保日志文件已打开(未打开则打开,跨天自动切换) void rotateFile(); // 超过 maxSize 时按 maxFiles 轮转 void cleanupByAge(); // 删除超过 maxAge 天的历史日志 QString captureStackTrace(); // 调用堆栈(caller=true 时) static qint64 parseSizeToBytes(const QString& s); // "10MB" -> 字节 static QString translateTimeFormat(const QString& fmt); // Go 布局 -> Qt 格式 + QString resolveLogDir() const; // 解析日志目录(空 -> 当前目录) static LogManager* m_instance; QFile m_logFile; @@ -59,7 +60,7 @@ private: bool m_toConsole = true; // 是否输出到控制台 bool m_toFile = true; // 是否写入日志文件 QString m_logDir; // 日志目录(空 -> 当前目录) - QString m_fileName = "change_code.log"; // 日志文件名 + QString m_baseName = "change_code.log"; // 日志基础文件名(实际文件名会带日期前缀 YYYYMMDD-) qint64 m_maxSizeBytes = 10LL * 1024 * 1024; // 单文件大小上限(字节),0=不限制 int m_maxFiles = 100; // 轮转保留文件数 int m_maxAgeDays = 30; // 保留天数,0=不限制