#include "DataUploader.h"
#include "DatabaseManager.h"
#include "Logger.h"
#include <QCryptographicHash>
#include <QSqlQuery>
#include <QSqlError>
DataUploader::DataUploader(QObject *parent)
: QObject(parent)
{
//从以前的文件加载保存的文件状态
loadFileStates();
//将目录更改信号连接
connect(&watcher, &QFileSystemWatcher::directoryChanged,
this, &DataUploader::onDirectoryChanged);
//添加状态上报定时器
statusTimer = new QTimer(this);
connect(statusTimer,&QTimer::timeout,this,&DataUploader::reportStatus);
statusTimer->start(5000);
}
bool DataUploader::isRecentFile(const QString& filePath)
{
QFileInfo fileInfo(filePath);
if(!fileInfo.exists())
{
return false;
}
QDateTime fileTime = fileInfo.lastModified();
QDateTime twoDaysAgo = QDateTime::currentDateTime().addDays(-2);
return fileTime >= twoDaysAgo;
}
void DataUploader::cleanupOldData()
{
QDateTime twoDaysAgo = QDateTime::currentDateTime().addDays(-2);
QString twoDaysAgoStr = twoDaysAgo.toString("yyyy-MM-dd HH:mm:ss");
Logger::instance().log(Logger::INFO, QString("开始清理超过两天的旧数据,清理时间点: %1").arg(twoDaysAgoStr));
//DatabaseManager的实例方法
QSqlDatabase db = DatabaseManager::instance().getDatabase();
if (!db.isOpen())
{
Logger::instance().log(Logger::ERROR, "数据库未连接,无法清理旧数据");
return;
}
QSqlQuery query(db);
//清理component_errors表
query.prepare("DELETE FROM component_errors WHERE timestamp < ?");
query.addBindValue(twoDaysAgoStr);
if (!query.exec())
{
Logger::instance().log(Logger::ERROR,
QString("清理 component_errors 表失败: %1").arg(query.lastError().text()));
}else
{
Logger::instance().log(Logger::INFO,
QString("已清理 %1 条 component_errors 记录").arg(query.numRowsAffected()));
}
//清理alarm_stats表
query.prepare("DELETE FROM alarm_stats WHERE timestamp < ?");
query.addBindValue(twoDaysAgoStr);
if(!query.exec())
{
Logger::instance().log(Logger::ERROR,
QString("清理 alarm_stats 表失败: %1").arg(query.lastError().text()));
}else
{
Logger::instance().log(Logger::INFO,
QString("已清理 %1 条 alarm_stats 记录").arg(query.numRowsAffected()));
}
//清理完成后关闭连接
QString connectionName = db.connectionName();
db.close();
QSqlDatabase::removeDatabase(connectionName);
}
void DataUploader::setWatchPath(const QString& path)
{
//设置目录路径
if(!watchPath.isEmpty())
{
watcher.removePath(watchPath);
}
watchPath = path;
//添加路径到监视程序,处理现有文件
if(!watcher.addPath(watchPath))
{
Logger::instance().log(Logger::ERROR,
QString("无法监视目录: %1").arg(watchPath));
}else
{
Logger::instance().log(Logger::INFO,
QString("成功监视目录: %1").arg(watchPath));
}
processFiles();
}
void DataUploader::reportStatus()
{
try {
if(DatabaseManager::instance().isConnected())
{
bool success = DatabaseManager::instance().updateProgramStatus(programName);
qDebug() << "状态上报" << (success ? "成功" : "失败");
if(success)
{
Logger::instance().log(Logger::DEBUG, "状态上报成功");
}else
{
Logger::instance().log(Logger::ERROR, "状态上报失败");
}
}
}catch (...)
{
qCritical() << "状态上报发生异常";
Logger::instance().log(Logger::WARNING, "状态上报发生异常");
}
}
void DataUploader::onDirectoryChanged(const QString& path)
{
//处理目录更改
Q_UNUSED(path);
Logger::instance().log(Logger::INFO, "检测到目录更改");
processFiles();
}
void DataUploader::processFiles()
{
QDir dir(watchPath);
QStringList files = dir.entryList(QDir::Files, QDir::Time);
Logger::instance().log(Logger::INFO,
QString("扫描目录 %1,找到 %2 个文件").arg(watchPath).arg(files.size()));
// 先收集需要处理的文件
QStringList filesToProcess;
{
//QMutexLocker locker(&m_dataMutex);
foreach(const QString &file, files)
{
QString filePath = dir.filePath(file);
Logger::instance().log(Logger::DEBUG,
QString("检查文件: %1").arg(filePath));
if(!isRecentFile(filePath))
{
Logger::instance().log(Logger::DEBUG,
QString("文件 %1 不是最近两天的,跳过").arg(filePath));
continue;
}
QString currentHash = calculateFileHash(filePath);
if(!fileStates.contains(filePath) ||
fileStates[filePath].fileHash != currentHash)
{
filesToProcess.append(filePath);
}
}
}
// 处理文件(无锁状态)
foreach(const QString &filePath, filesToProcess)
{
processFile(filePath);
// 更新文件状态(加锁)
QMutexLocker locker(&m_dataMutex);
updateFileState(filePath, 0, calculateFileHash(filePath));
}
// 保存状态(加锁)
QMutexLocker locker(&m_dataMutex);
saveFileStates();
}
void DataUploader::processFile(const QString &filePath)
{
Logger::instance().log(Logger::INFO,
QString("开始处理文件: %1").arg(filePath));
// 加锁保护所有共享数据结构
QMutexLocker locker(&m_dataMutex);
//重置状态
boardInfoMap.clear();
pendingComponentErrors.clear();
pendingAlarmStats.clear();
componentErrors.clear();
alarmStats.clear();
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
Logger::instance().log(Logger::ERROR,
QString("无法打开文件: %1, 错误: %2").arg(filePath).arg(file.errorString()));
return;
}
QTextStream in(&file);
while(!in.atEnd())
{
QString line = in.readLine();
QStringList parsedData = parseLogLine(line);
if(parsedData.isEmpty()) continue;
QString dataType = parsedData[0];
QString timestamp = parsedData.last(); //时间戳总是在最后
if(dataType == "COMPONENT_ERROR")
{
PendingError error;
error.component = parsedData[1];
error.alarmType = parsedData[2];
error.timestamp = timestamp;
pendingComponentErrors.append(qMakePair(error, timestamp));
}
else if(dataType == "BOARD_INFO")
{
QString boardName = parsedData[1];
QString inspectionId = parsedData[2];
boardInfoMap[timestamp] = boardName + "|" + inspectionId;
//尝试关联已暂存的数据
processPendingData(timestamp);
}
else if(dataType == "ALARM_STATS")
{
PendingStat stat;
stat.alarmType = parsedData[1];
stat.alarmName = parsedData[2];
stat.count = parsedData[3];
stat.timestamp = timestamp;
pendingAlarmStats.append(qMakePair(stat, timestamp));
}
}
//文件处理完成后,关联所有剩余暂存数据
processPendingData("");
//插入数据到数据库
if(!componentErrors.isEmpty())
{
DatabaseManager::instance().insertAOIData(componentErrors, "component_errors");
}
if(!alarmStats.isEmpty())
{
DatabaseManager::instance().insertAOIData(alarmStats, "alarm_stats");
}
}
void DataUploader::processPendingData(const QString& specificTimestamp)
{
//处理元件错误
QMutableListIterator<QPair<PendingError, QString>> compIt(pendingComponentErrors);
while(compIt.hasNext())
{
auto& item = compIt.next();
QString timestamp = item.second;
const PendingError& error = item.first;
if(!specificTimestamp.isEmpty() && timestamp != specificTimestamp)
{
continue;
}
QString boardInfo = findBoardInfo(timestamp);
if(!boardInfo.isEmpty())
{
QStringList parts = boardInfo.split("|");
QString record = QString("%1|%2|%3|%4|%5")
.arg(parts[0]) //boardName
.arg(parts[1]) //inspectionId
.arg(error.component)
.arg(error.alarmType)
.arg(error.timestamp);
componentErrors.append(record);
compIt.remove(); //安全删除当前元素
}
}
//正向遍历错误统计
QMutableListIterator<QPair<PendingStat, QString>> statIt(pendingAlarmStats);
while(statIt.hasNext())
{
auto& item = statIt.next();
QString timestamp = item.second;
const PendingStat& stat = item.first;
if(!specificTimestamp.isEmpty() && timestamp != specificTimestamp)
{
continue;
}
QString boardInfo = findBoardInfo(timestamp);
if(!boardInfo.isEmpty())
{
QStringList parts = boardInfo.split("|");
QString record = QString("%1|%2|%3|%4|%5|%6")
.arg(parts[0]) //boardName
.arg(parts[1]) //inspectionId
.arg(stat.alarmType)
.arg(stat.alarmName)
.arg(stat.count)
.arg(stat.timestamp);
alarmStats.append(record);
statIt.remove(); //安全删除当前元素
}
}
}
QString DataUploader::findBoardInfo(const QString& timestamp)
{
//QReadLocker locker(&m_dataLock); //卡在这里
//查找最接近的板信息(时间戳小于等于给定时间戳)
auto it = boardInfoMap.upperBound(timestamp);
if(it != boardInfoMap.begin())
{
--it;
return it.value();
}
return QString();
}
QStringList DataUploader::parseLogLine(const QString &line)
{
// 记录原始日志行
Logger::instance().log(Logger::DEBUG,
QString("解析日志行: %1").arg(line));
QStringList result;
//提取时间戳
QRegularExpression timeRegex("^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})");
QRegularExpressionMatch timeMatch = timeRegex.match(line);
QString timestamp = timeMatch.hasMatch() ? timeMatch.captured(1) : "";
//解析组件缺陷
if(line.contains("CreateRepairInspection Spot component Name ="))
{
//QRegularExpression regex("Name = \\s*(\\S+).*AlarmType =\\s*([^,]+)");
QRegularExpression regex("Name\\s*=\\s*(\\S+).*AlarmType\\s*=\\s*([^,]+)");
QRegularExpressionMatch match = regex.match(line);
if(match.hasMatch())
{
result << "COMPONENT_ERROR"
<< match.captured(1).trimmed() //component
<< match.captured(2).trimmed() //alarmType
<< timestamp;
}
}
//解析板信息行
if(line.contains("ExportBitmapManager.Export"))
{
//QRegularExpression regex("id = ([^,]+), boardName = ([^,]+)");
QRegularExpression regex("id\\s*=\\s*([^,]+),\\s*boardName\\s*=\\s*([^,]+)");
QRegularExpressionMatch match = regex.match(line);
if(match.hasMatch())
{
result << "BOARD_INFO"
<< match.captured(2).trimmed() //boardName
<< match.captured(1).trimmed() //inspectionId
<< timestamp;
}
}
//解析错误统计信息
if(line.contains("alarmType =") && line.contains("alarmName =") && line.contains("count =") && !line.contains("update"))
{
QRegularExpression regex("alarmType\\s*=\\s*([^,]+),\\s*alarmName\\s*=\\s*([^,]+),\\s*count\\s*=\\s*(\\d+)");
QRegularExpressionMatch match = regex.match(line);
if(match.hasMatch())
{
QString countStr = match.captured(3).trimmed();
int count = countStr.toInt();
if(count > 0)
{
result << "ALARM_STATS"
<< match.captured(1).trimmed() //alarmType
<< match.captured(2).trimmed() //alarmName
<< match.captured(3).trimmed() //count
<< timestamp;
}
}
}
if(!result.isEmpty()) {
Logger::instance().log(Logger::DEBUG,
QString("解析结果: %1").arg(result.join("|")));
}
return result;
}
QString DataUploader::extractValue(const QString &line, const QString &key)
{
//提取键值对正则表达式
QRegularExpression regex(QString("%1\\s*=\\s*([^\\s,]+)").arg(key));
QRegularExpressionMatch match = regex.match(line);
return match.hasMatch() ? match.captured(1).trimmed() : "";
}
QString DataUploader::calculateFileHash(const QString &filePath)
{
//计算文件的MD5哈希值
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly))
{
Logger::instance().log(Logger::ERROR, QString("无法打开文件计算哈希: %1").arg(filePath));
return QString();
}
QCryptographicHash hash(QCryptographicHash::Md5);
if(!hash.addData(&file))
{
Logger::instance().log(Logger::ERROR, QString("计算文件哈希失败: %1").arg(filePath));
return QString();
}
qDebug() << "哈希计算成功";
return hash.result().toHex();
}
void DataUploader::updateFileState(const QString &filePath, qint64 pos, const QString &hash)
{
//更新跟踪文件的状态
FileState state;
state.lastPos = pos;
state.fileHash = hash;
fileStates[filePath] = state;
}
void DataUploader::saveFileStates()
{
//文件状态保存到持久存储
state.beginGroup("FileStates");
for(auto it = fileStates.begin(); it != fileStates.end(); ++it)
{
state.setValue(it.key() + "/pos", it->lastPos);
state.setValue(it.key() + "/hash", it->fileHash);
}
state.endGroup();
}
void DataUploader::loadFileStates()
{
//加载文件状态
state.beginGroup("FileStates");
foreach(QString key, state.childKeys())
{
if(key.endsWith("/pos"))
{
QString filePath = key.left(key.length() - 4);
FileState fs;
fs.lastPos = state.value(key).toLongLong();
fs.fileHash = state.value(filePath + "/hash").toString();
fileStates[filePath] = fs;
}
}
state.endGroup();
}
为什么这个代码无法上传数据,只能上报状态呢,难道是哪里写的不对导致阻塞了吗