随着时间的增加,存储的历史记录也在不断增加,如果设备数量很多,存储间隔很短,不用多久,数据库中的记录就非常多,至少是百万级别起步,而且有些用户还是需要存储每一次的采集的数据,这数据量别说一年,就是一个月下来都是恐怖级别的,所以这就涉及到一个重要的需求,如何自动清理早期的不需要的数据,比如只保存最近10万条记录,或者保存最近30天的记录,这就需要安排个线程,在线程中打开数据库以后,每隔一段时间去查询记录数量,超过了设定的最大值,则按照时间顺序把早期的数据删除,其实就是执行一个sql语句。如果设置的是只存储最近30天的记录,则每隔一段时间执行删除sql语句,带上条件where 时间<(今天-30)。由于是在线程中打开的数据库,所以在线程中执行的sql语句都不会对主界面使用的数据库相关处理造成卡顿。
光有数据记录清理可能还是不够的,比如系统还不断的在存储报警图片或者其他数据文件,由于硬盘的大小有限,也需要一个机制做清理,尤其是对于视频监控系统尤为重要。由于和数据库记录清理功能类似,而且数据库清理线程99.99%的时间都是空余的,就算是到了需要清理的时候,也是一次性清理多条,也不会频繁的在清理中,为了不让这个线程闲着,直接也把清理文件的机制也放到了这个类,指定要监听的目录,指定最大的大小,每隔一段时间读取下大小,超过了则清理早期的文件。
设备监控模块,包括数据监控(表格形式展示)、设备面板(面板形式展示)、地图监控(地图形式展示)、曲线监控(曲线形式展示)。
数据查询模块,包括报警记录、运行记录、操作记录。
系统设置模块,包括基本设置、端口管理、控制器管理、探测器管理、报警联动、类型设置等。
其他设置模块,包括用户管理、地图管理、位置调整、组态设计、设备调试等。
国内站点:https://gitee.com/feiyangqingyun
国际站点:https://github.com/feiyangqingyun
体验地址:http://pan.baidu.com/s/1foas7ytSXh7gHOTDqDREjQ 提取码:axip 文件名:bin_iotsystem.zip。
int DbCleanThread::getCount() { int count = -1; if (!dbOk) { return count; } time.restart(); QString sql = QString("select count(%1) from %2").arg(countName).arg(tableName); QSqlQuery query(database); if (query.exec(sql)) { if (query.next()) { count = query.value(0).toInt(); QString msg = QString("(共 %1 条/用时 %2 秒)").arg(count).arg(getUseTime()); emit debug(QString("%1数据库获取记录行数%2").arg(dbFlag).arg(msg)); emit receiveCount(tableName, count, time.elapsed()); } } return count; } QStringList DbCleanThread::getCleanValue(int cleanCount) { QStringList list; if (!dbOk) { return list; } QSqlQuery query(database); query.setForwardOnly(true); QString sql = DbHelper::getSelectCountSql(dbType, tableName, whereColumnName, "", orderSql, cleanCount); if (query.exec(sql)) { while (query.next()) { list << query.value(0).toString(); } } return list; } void DbCleanThread::cleanData() { if (!dbOk) { return; } //首先查找总记录数,如果总记录数超过限制,则将超出的部分按照字段排序进行删除 int count = getCount(); int cleanCount = (count - maxCount); if (cleanCount < 100) { return; } time.restart(); //每次最大清理1000条数据 cleanCount = cleanCount > 1000 ? 1000 : cleanCount; //将要删除的数据指定字段集合查询出来 QStringList list = getCleanValue(cleanCount); if (list.count() == 0) { return; } //删除数据 QSqlQuery query(database); QString sql = QString("delete from %1 where %2 in(%3)").arg(tableName).arg(whereColumnName).arg(list.join(",")); dbOk = query.exec(sql); //qDebug() << TIMEMS << sql; QString msg = QString("(共 %1 条/用时 %2 秒)").arg(cleanCount).arg(getUseTime()); if (dbOk) { emit debug(QString("%1数据库清理数据成功%2").arg(dbFlag).arg(msg)); } else { QString text = database.lastError().text(); emit error(QString("%1数据库清理数据失败%2, 原因: %3").arg(dbFlag).arg(msg).arg(text)); qDebug() << TIMEMS << this->objectName() << text; } } void DbCleanThread::cleanPath() { if (dirPath.isEmpty()) { return; } //找出该文件夹下的所有文件夹 QDir dir(dirPath); if (!dir.exists()) { return; } //按照目录查找,过滤文件夹,按照文件名称排序 dir.setFilter(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); dir.setSorting(QDir::Name); QStringList list = dir.entryList(); //遍历所有目录,对所有文件大小相加得到总大小,文件就在文件夹下,不会再有子目录 qint64 size = 0; foreach (QString path, list) { QDir d(dirPath + "/" + path); QFileInfoList infos = d.entryInfoList(dirFileFilter); foreach (QFileInfo info, infos) { size += info.size(); } //转化成MB,超过预定大小自动删除第一个文件夹,跳出循环无需继续判断 int sizeMB = size / (1024 * 1024); if (sizeMB >= dirMaxSize) { //删除该目录下的所有文件 QString path = dirPath + "/" + list.at(0); QDir dir(path); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); QStringList files = dir.entryList(); foreach (QString file, files) { dir.remove(file); qDebug() << TIMEMS << "删除文件" << path << file; } //删除文件夹本身 dir.rmdir(path); QString msg = QString("(共 %1 个文件/用时 %2 秒)").arg(files.count()).arg(getUseTime()); emit debug(QString("%1数据库自动清理目录成功%2").arg(dbFlag).arg(msg)); break; } } } void DbCleanThread::deletePath(const QString &path) { QDir dir(path); #if (QT_VERSION >= QT_VERSION_CHECK(5,0,0)) //这个方法可以递归彻底删除文件夹 不管文件夹下是否有文件 比较暴力 //此方法慎用 必须指定明确的文件夹 不然删除默认的目录哭都来不及 网上多个人中招 dir.removeRecursively(); #else //循环遍历删除文件及文件夹 dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); QFileInfoList fileList = dir.entryInfoList(); foreach (QFileInfo fi, fileList) { if (fi.isFile()) { fi.dir().remove(fi.fileName()); } else { deletePath(fi.absoluteFilePath()); dir.rmpath(fi.absoluteFilePath()); } } //最后删除最外层的目录 dir.rmpath(path); #endif }