首頁 > 軟體

Qt物聯網管理平臺之實現自動清理早期資料功能

2022-07-11 22:04:59

一、前言

隨著時間的增加,儲存的歷史記錄也在不斷增加,如果裝置數量很多,儲存間隔很短,不用多久,資料庫中的記錄就非常多,至少是百萬級別起步,而且有些使用者還是需要儲存每一次的採集的資料,這資料量別說一年,就是一個月下來都是恐怖級別的,所以這就涉及到一個重要的需求,如何自動清理早期的不需要的資料,比如只儲存最近10萬條記錄,或者儲存最近30天的記錄,這就需要安排個執行緒,線上程中開啟資料庫以後,每隔一段時間去查詢記錄數量,超過了設定的最大值,則按照時間順序把早期的資料刪除,其實就是執行一個sql語句。如果設定的是隻儲存最近30天的記錄,則每隔一段時間執行刪除sql語句,帶上條件where 時間<(今天-30)。由於是線上程中開啟的資料庫,所以線上程中執行的sql語句都不會對主介面使用的資料庫相關處理造成卡頓。

光有資料記錄清理可能還是不夠的,比如系統還不斷的在儲存報警圖片或者其他資料檔案,由於硬碟的大小有限,也需要一個機制做清理,尤其是對於視訊監控系統尤為重要。由於和資料庫記錄清理功能類似,而且資料庫清理執行緒99.99%的時間都是空餘的,就算是到了需要清理的時候,也是一次性清理多條,也不會頻繁的在清理中,為了不讓這個執行緒閒著,直接也把清理檔案的機制也放到了這個類,指定要監聽的目錄,指定最大的大小,每隔一段時間讀取下大小,超過了則清理早期的檔案。

二、功能特點

2.1 軟體模組

裝置監控模組,包括資料監控(表格形式展示)、裝置面板(面板形式展示)、地圖監控(地圖形式展示)、曲線監控(曲線形式展示)。

資料查詢模組,包括報警記錄、執行記錄、操作記錄。

系統設定模組,包括基本設定、埠管理、控制器管理、探測器管理、報警聯動、型別設定等。

其他設定模組,包括使用者管理、地圖管理、位置調整、組態設計、裝置偵錯等。

2.2 基礎功能

  1. 裝置資料採集,支援串列埠、網路,串列埠可設定串列埠號、波特率,網路可設定IP地址、通訊埠。
  2. 每個埠支援採集週期時間,預設1秒鐘一個裝置。
  3. 支援設定通訊超時次數,預設3次。
  4. 支援最大重連時間,用於重新讀取離線的裝置。
  5. 控制器資訊,能夠新增控制器名稱,選擇控制器地址、控制器型號,設定該控制器下面的探測器數量。
  6. 探測器資訊,能夠新增位號、探測器型號、氣體種類、氣體符號、高報值、低報值、緩衝值、清零值、是否啟用、報警聲音、背景地圖、儲存週期、數值換算小數點位數、報警延時時間、報警的型別(HH,LL,HL)等。
  7. 型別管理可設定控制器型號、探測器型號、氣體種類、氣體符號等。
  8. 地圖支援匯入和刪除,所有的探測器在地圖上的位置可自由拖動儲存。
  9. 埠資訊、控制器資訊、探測器資訊、型別資訊、使用者資訊等,都支援匯入、匯出、匯出到excel、列印。
  10. 執行記錄、報警記錄、操作記錄,都支援多條件組合查詢,比如時間段、控制器、探測器等,所有記錄支援匯出到excel/pdf和列印。
  11. 執行記錄、報警記錄、操作記錄都可刪除指定時間範圍內的資料。
  12. 系統設定可選擇對應表最大儲存記錄數,自動清理早期資料,留出足夠的空間儲存重要的資料。
  13. 報警簡訊轉發,支援多個接收手機號碼,可設定傳送間隔,比如即時傳送或者6個小時傳送一次所有的報警資訊,簡訊內容過長,自動拆分多條簡訊。
  14. 報警郵件轉發,支援多個接收郵箱,可設定傳送間隔,比如即時傳送或者6個小時傳送一次所有的報警資訊,支援附件傳送。
  15. 設定軟體的中文標題、英文標題、logo路徑、版權所有等。
  16. 開關設定開機執行、報警聲音、自動登入、記住密碼等。
  17. 報警聲音可設定播放次數,介面風格樣式提供18套面板檔案選擇。
  18. 使用者管理,包括使用者許可權設定,不同使用者可以有不同模組的許可權。
  19. 使用者登入和使用者退出,可以記住密碼和自動登入,超過三次報錯提示並關閉程式。
  20. 四種監控模式,裝置面板監控、地圖監控、表格資料監控、曲線資料監控,可自由切換,四種模式下都實時展示採集到的資料,報警閃爍等。
  21. 報警繼電器聯動,一個位號可以跨串列埠聯動多個模組和繼電器號,支援多對多。

2.3 特色功能

  1. 通訊協定支援modbus_com、modbus_tcp_rtu,後期拓展mqtt等協定。
  2. 資料來源除了真實的硬體裝置採集,還可選資料庫採集,這樣使用者可以安排其他程式設計師比如java程式設計師將前端採集好的資料放到資料庫,本系統直接從資料庫採集即可。資料庫採集模式可以作為通用的系統使用,更適合多人多系統共同作業。
  3. 智慧跳過超時的裝置,加快對線上裝置的採集速度,當裝置數量很多的時候尤其有用。
  4. 對智慧跳過的超時的裝置,在設定的重連時間自動採集一次,以便探測裝置是否又重新上線。
  5. 每個探測器可控是否啟用,不啟用則不會採集,也不會在介面顯示,相當於執行階段臨時關閉。
  6. 探測器可設定緩衝值和報警延時時間,在該值附近波動產生的報警,不計入報警,只有持續處於報警值且超過報警延時時間才算真正報警,這樣可以規避很多波動導致的誤報。
  7. 探測器可設定儲存週期,按照設定的時間來儲存一條執行記錄,可以按照重要程度對重要性高的設定儲存週期短一些,不重要的設定大一些,這樣可以節省不少的儲存空間,也保證了重要的資料及時儲存。
  8. 探測器可設定清零值,在一些高精度高靈敏的裝置可能出廠的時候預設值未必是0,需要設定清零值來表示初始值。
  9. 探測器可設定小數點,用於計算後的真實資料控制小數點點位顯示,相當於除以10、除以100、除以1000,這樣大部分的探測器資料直接通過小數點位設定控制真實換算後的值,極個別的需要特殊轉換的可以在通訊協定中約定。
  10. 探測器報警的型別支援多種,有些裝置是高於某個值高報,低於某個值低報,而有些裝置是在最小值最大值範圍內是高報,低於最小值低報,高於最大值正常。這樣可以分情況處理,涵蓋各種報警型別。
  11. 原創資料匯入、匯出、印表機制,跨平臺不依賴任何元件,瞬間匯出資料。
  12. 匯出到excel的記錄支援所有excel、wps等表格檔案版本,不依賴excel等軟體。
  13. 高報顏色、低報顏色、正常顏色、預設值顏色等,都可以自由設定。
  14. 支援雲端資料同步,將本地採集到的資料實時同步到雲端。
  15. 支援網路轉發和網路接收,網路接收開啟後,軟體從udp接收資料進行解析。網路轉發支援多個目標IP,這樣就實現了本地採集的軟體,自由將資料轉到使用者端,隨時檢視採集到的資料。
  16. 自動記住使用者最後停留的介面以及其他設定資訊,重啟後自動應用。
  17. 報警自動切換到對應的地圖,探測器按鈕閃爍,表格資料對應顏色顯示。
  18. 雙擊探測器圖示,彈出對應探測器詳細資訊,可以根據需要客製化回控操作。
  19. 資料庫支援多種,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金倉等。
  20. 本地裝置採集到的資料實時上傳到雲端,以便手機APP或者web等其他方式提取。
  21. 自帶裝置模擬工具,支援不同型號的多個裝置資料模擬,同時還帶資料庫資料模擬,以便在沒有裝置的時候測試資料。
  22. 標準modbus協定,各種控制器型別、探測器型別、種類、符號等全部自定義,非常靈活和強大,通訊協定範例資料非常完整,通用各種modbus協定系統,適用於各種應用場景接入。
  23. 同時整合了串列埠通訊、網路通訊、資料庫通訊、資料匯入匯出列印、通訊協定解析、介面UI、全域性換膚等眾多元件和知識點,非常適合新手入門和進階。
  24. 支援xp、win7、win10、、win11、linux、mac、各種國產系統(UOS、中標麒麟、銀河麒麟等)、嵌入式linux等系統。
  25. 註釋完整,專案結構清晰,超級詳細完整的使用開發手冊,精確到每個程式碼檔案的功能說明,不斷持續迭代版本。

三、體驗地址

國內站點:https://gitee.com/feiyangqingyun

國際站點:https://github.com/feiyangqingyun

體驗地址:https://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
}

以上就是Qt物聯網管理平臺之實現自動清理早期資料功能的詳細內容,更多關於Qt自動清理資料的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com