目录
一、问题背景
在停车项目中对于场内的僵尸车程序每天都会进行统计输出一个excel表格记录到指定文件,同时第二天定期通过邮件的方式进行发送到管理员的邮箱中,从而进行人为干预;但是对于一些车场特殊僵尸车,可能存在人为删除僵尸车日报躲避管理行为,导致客户以为程序有问题从而引起客诉。
二、解决方案
对于该问题,最初将日报表统计生成和邮件发送两个步骤都推迟到第二天固定时刻,统计生成后直接邮件转发,搜索不到前天的日志在生成一次在发送;同时为了在监控某些重点厂库,在客诉时能拿出证据,后续对僵尸车日报文件夹采用QFileSystemWatcher 进行了监控,并记录在数据库的操作记录中。
2.1 QFileSystemWatcher
QFileSystemWatcher 是 Qt 框架中提供的一个类,用于监视文件系统中的文件和目录的变化。它允许你在文件或目录发生变化时接收通知,并可以用于监视文件的创建、删除、重命名以及内容修改等操作。这对于需要实时监控文件系统变化的应用程序是非常有用的。
QFileSystemWatcher
类的一些常用函数:
函数 | 描述 |
---|---|
| 构造函数,创建一个文件系统监视器对象。 |
| 添加要监视的文件或目录路径。 |
| 添加要监视的多个文件或目录路径。 |
| 移除要监视的文件或目录路径。 |
| 移除要监视的多个文件或目录路径。 |
| 检查监视器是否包含指定的文件或目录路径。 |
| 返回当前监视的文件路径列表。 |
| 返回当前监视的目录路径列表。 |
| 设置监视器的过滤器,用于指定要监视的事件类型。 |
| 返回监视器当前的过滤器设置。 |
| 信号,当监视的文件发生变化时发出。 |
| 信号,当监视的目录发生变化时发出。 |
这些函数允许你动态地添加或移除要监视的文件或目录,设置过滤器以确定要监视的事件类型,并连接相应的信号以处理文件系统的变化事件。
2.2 文件夹监控代码
根据操作系统 QFileSystemWatcher监控的文件夹和文件有一定限制,过多则会失效;项目中日报表文件夹仅存最新的30份,且结构简单没有子文件夹,因此考虑便捷性QFileSystemWatcher是个不错的选择;以下是监控文件夹的代码,整体是单例模式方便使用。
监控文件夹的单例模式类头文件代码:
#pragma once
#include <QObject>
#include <QMap>
#include <QString>
#include <QSet>
#include <QFileSystemWatcher>
#define FileSystemWatcherInstance MyFileSystemWatcher::instance()
class MyFileSystemWatcher:public QObject
{
Q_OBJECT
public:
void addWatchPath(QString path);
static MyFileSystemWatcher* instance();
//受到操作系统的限制,无法监控过多的文件数量,可能会导致性能下降或监控失败
MyFileSystemWatcher(const MyFileSystemWatcher&) = delete;
MyFileSystemWatcher& operator= (const MyFileSystemWatcher&) = delete;
public slots:
// 目录更新时调用
void directoryUpdated(const QString &path);
// 文件被修改时调用
void fileUpdated(const QString &path);
private:
explicit MyFileSystemWatcher(QObject *parent = 0);
private:
// QFileSystemWatcher变量
QFileSystemWatcher *m_pSystemWatcher;
//当前每个监控的内容目录列表
QMap<QString, QStringList> m_currentContentsMap;
};
单例模式部分代码:
#pragma execution_character_set("utf-8")
MyFileSystemWatcher::MyFileSystemWatcher(QObject *parent):QObject(parent)
{
m_pSystemWatcher = new QFileSystemWatcher();
}
MyFileSystemWatcher* MyFileSystemWatcher::instance()
{
// 单例
static MyFileSystemWatcher Instance;
return &Instance;
}
添加监控目录:
void MyFileSystemWatcher::addWatchPath(QString path)
{
// 连接QFileSystemWatcher的directoryChanged和fileChanged信号到相应的槽
connect(m_pSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryUpdated(QString)));
connect(m_pSystemWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fileUpdated(QString)));
// 添加监控路径
m_pSystemWatcher->addPath(path);
// 如果添加路径是一个目录,保存当前内容列表
QFileInfo file(path);
if (file.isDir())
{
const QDir dirw(path);
m_currentContentsMap[path] = dirw.entryList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
}
}
添加文件夹改动槽函数
// 任何监控的目录更新(添加、删除、重命名)则调用
void MyFileSystemWatcher::directoryUpdated(const QString &path)
{
cLogger("ISC")->warn(QString("目录更新: %1").arg(path).toStdString());
// 比较最新的内容和保存的内容找出区别(变化)
QStringList currEntryList = m_currentContentsMap[path];
const QDir dir(path);
QStringList newEntryList = dir.entryList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
QSet<QString> newDirSet = QSet<QString>::fromList(newEntryList);
QSet<QString> currentDirSet = QSet<QString>::fromList(currEntryList);
// 添加了文件
QSet<QString> newFiles = newDirSet - currentDirSet;
QStringList newFile = newFiles.toList();
// 文件已被移除
QSet<QString> deletedFiles = currentDirSet - newDirSet;
QStringList deleteFile = deletedFiles.toList();
// 更新当前设置
m_currentContentsMap[path] = newEntryList;
if (!newFile.isEmpty() && !deleteFile.isEmpty())
{
// 文件/目录重命名
if ((newFile.count() == 1) && (deleteFile.count() == 1))
{
cLogger("ISC")->warn(QString("文件重命名 %1 到 %2").arg(deleteFile.first()).arg(newFile.first()).toStdString());
}
}
else
{
// 添加新文件/目录至Dir
if (!newFile.isEmpty())
{
QString logStr;
for (int i = 0; i < newFile.size(); i++)
{
logStr.append(newFile[i]).append("\n");
}
cLogger("ISC")->warn(QString("新建文件或目录:%1").arg(logStr).toStdString());
}
// 从Dir中删除文件/目录
if (!deleteFile.isEmpty())
{
QString logStr;
m_pSystemWatcher->removePaths(deleteFile);
for (int i = 0; i < deleteFile.size(); i++)
{
logStr.append(deleteFile[i]).append("\n");
}
cLogger("ISC")->warn(QString("删除文件或目录:%1").arg(logStr).toStdString());
}
}
}
监控文件改动槽函数
void MyFileSystemWatcher::fileUpdated(const QString &path)
{
QFileInfo file(path);
QString strPath = file.absolutePath();
QString strName = file.fileName();
cLogger("ISC")->warn(QString("文件 %1 路径 %2 修改").arg(strName).arg(strPath).toStdString());
}
调用代码:
//...
string dirPathStr = R"(G:/ProjectCode/log)";
FileSystemWatcherInstance->addWatchPath(path.c_str())
//...
2.3 测试结果
三、注意事项
在windows系统中 ReadDirectoryChangesW函数可以监控子文件夹;在linux上inotify 可以监控文件的变动;这两者在各自系统中均有良好的性能,比Qt自带的封装具有更强大的功能,如果需要更为细致的文件监控,则需要通过操作系统的接口,Qt提供的接口对于小部分文件监控效果良好,且方便使用,但监控数量上限较低,文件过多容易失效。