优化转码添加LS的限制,优化缓存读取的问题
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "uft3table.h"
|
||||
#include "utils/logmanager.h"
|
||||
#include "utils/configmanager.h"
|
||||
#include "utils/datacache.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -2,9 +2,23 @@
|
||||
#include "Constants.h"
|
||||
#include "datacache.h"
|
||||
#include "logmanager.h"
|
||||
#include "configmanager.h"
|
||||
#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();
|
||||
@@ -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<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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
+53
-6
@@ -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<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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ public:
|
||||
|
||||
bool hasUF20Config(const QString& fileName);
|
||||
bool hasUFT3Config(const QString& fileName);
|
||||
|
||||
// 工程路径指纹:记录上次成功写入 cname 表时所用工程路径,
|
||||
// 用于检测“换机器/换路径后旧缓存 db 仍非空”导致功能查询读到旧数据的问题
|
||||
bool saveProjectPaths(const QMap<QString, QString>& paths);
|
||||
QMap<QString, QString> loadProjectPaths();
|
||||
|
||||
bool checkUF20CnameExists(const QString& cname);
|
||||
QSet<QString> getAllUF20Cnames();
|
||||
|
||||
+42
-24
@@ -3,6 +3,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <QDate>
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
#include <QFileInfo>
|
||||
@@ -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) 轮转与清理
|
||||
|
||||
@@ -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=不限制
|
||||
|
||||
Reference in New Issue
Block a user