把数据存入sql数据库。qt,c++。

linkrecorddbmgr.h

c 复制代码
#pragma once

#include <QSqlDatabase>
#include <QSqlError>
#include <QDateTime>
#include <QByteArray>
#include <QString>
#include <QStringList>
#include <QList>
#include <QMap>
#include <QMutex>
#include <QMutexLocker>

// =============================================================================
// 数据结构
//   ------ 如果你的项目里已在别处定义,请删除本节,只保留类定义。
// =============================================================================

/// 一条链路质量记录
struct Record
{
    QString   sendDev;              ///< 发送方设备
    QString   recDev;               ///< 接收方设备(同时决定表名)
    double    score           = 0.0;
    double    packetErrorRate = 0.0;
    double    bitErrorRate    = 0.0;
    double    snr             = 0.0;
    double    sir             = 0.0;
    QDateTime datetime;
};

/// 一条原始报文记录
struct RecieveMsg
{
    QString    sendDev;
    QString    recDev;
    QByteArray msgBytes;            ///< 二进制报文
    QDateTime  datetime;
};

// =============================================================================
// LinkRecordDbMgr --- 数据链通信记录数据库管理器(单例)
//
//   * 每个接收设备一张质量表 <recDev>,一张报文表 <recDev>_msg;
//   * 所有 SQL 使用 prepare + bindValue,杜绝注入;
//   * 所有对外接口线程安全(内部 QMutex);
//   * 时间统一以 "yyyy-MM-dd HH:mm:ss" 存储;
// =============================================================================

class LinkRecordDbMgr
{
public:
    static LinkRecordDbMgr &getInstance();

    // -------- 初始化 --------
    QSqlError initDb();

    // -------- 查询 --------
    /// 返回: recDev -> [sendDev1, sendDev2, ...]
    QMap<QString, QStringList> getDevices();

    /// 查质量记录。
    /// @param recDev 为空时,遍历所有质量表(即"不限接收设备")。
    QList<Record> search(const QString &sendDev,
                         const QString &recDev,
                         const QDateTime &start,
                         const QDateTime &end);

    /// 查原始报文。
    /// @param recDev 必须非空(报文只按接收设备分表)。
    QList<RecieveMsg> searchMsg(const QString &sendDev,
                                const QString &recDev,
                                const QDateTime &start,
                                const QDateTime &end);

    // -------- 插入 --------
    QSqlError insertRecord(const Record &record);
    QSqlError insertMsg(const RecieveMsg &record);

    // -------- 删除 --------
    /// 删除 (sendDev, recDev) 的所有质量记录,表保留。
    bool removeRecord(const QString &sendDev, const QString &recDev);

    /// 清空该设备的全部质量记录与报文记录(只 DELETE,不 DROP)。
    /// @return 是否全部成功
    bool removeAllRecord(const QString &recDev);

private:
    LinkRecordDbMgr();
    ~LinkRecordDbMgr() = default;

    Q_DISABLE_COPY_MOVE(LinkRecordDbMgr)

    // -------- 内部工具 --------
    /// 安全引用表名/列名: 在两端加双引号,内部双引号转义为 ""
    static QString quoteIdentifier(const QString &ident);

    /// 时间格式化常量
    static QString fmtDateTime(const QDateTime &dt);

    /// 当前数据库里所有"质量表"(非 sqlite_sequence / 非 _msg 后缀)
    QStringList recordTables() const;

    /// 表是否存在(不区分大小写)
    bool tableExists(const QString &name) const;

    /// 确保质量表存在,不存在则创建(含索引)
    bool ensureRecordTable(const QString &recDev, QSqlError *err = nullptr);

    /// 确保报文表存在,不存在则创建(含索引)
    bool ensureMsgTable(const QString &recDev, QSqlError *err = nullptr);

private:
    QSqlDatabase m_db;
    mutable QMutex m_lock;
    QString      m_connName = "link_rec_record";
    QString      m_dbPath;
};

linkrecorddbmgr.cpp

cpp 复制代码
#include "linkrecorddbmgr.h"

#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QSqlQuery>
#include <QVariant>
#include <QDebug>
#include <algorithm>

namespace {

/// 时间字符串格式(SQLite 里按 TEXT 存储,按字典序即为时间序)
const char *kDtFmt = "yyyy-MM-dd HH:mm:ss";

} // namespace

// =============================================================================
// 单例 & 构造 / 析构
// =============================================================================

LinkRecordDbMgr &LinkRecordDbMgr::getInstance()
{
    static LinkRecordDbMgr inst;
    return inst;
}

LinkRecordDbMgr::LinkRecordDbMgr()
{
    // 构造即初始化;失败会在日志里打印,外部仍可通过 initDb() 重试。
    initDb();
}

// =============================================================================
// initDb
// =============================================================================

QSqlError LinkRecordDbMgr::initDb()
{
    // 1. 确保 DB 目录存在
    const QString appDir = QCoreApplication::applicationDirPath();
    const QString dbDir  = appDir + "/DB";
    QDir dir;
    if (!dir.exists(dbDir)) {
        if (!dir.mkpath(dbDir)) {
            return QSqlError("LinkRecordDbMgr", "mkpath failed: " + dbDir,
                             QSqlError::ConnectionError);
        }
    }

    m_dbPath = dbDir + "/link_rec_record.db";

    // 2. 复用或创建命名连接
    if (QSqlDatabase::contains(m_connName)) {
        m_db = QSqlDatabase::database(m_connName, /*open=*/false);
    } else {
        m_db = QSqlDatabase::addDatabase("QSQLITE", m_connName);
        m_db.setDatabaseName(m_dbPath);
    }

    // 3. 打开(如果尚未打开)
    if (!m_db.isOpen() && !m_db.open()) {
        const QSqlError e = m_db.lastError();
        qWarning() << "[LinkRecordDbMgr] open db failed:" << e.text();
        return e;
    }

    // 4. 打开外键(如有需要)与 WAL 模式(高并发推荐)
    QSqlQuery q(m_db);
    q.exec("PRAGMA foreign_keys = ON");
    q.exec("PRAGMA journal_mode = WAL");

    qDebug() << "[LinkRecordDbMgr] db ready at" << m_dbPath;
    return {};
}

// =============================================================================
// 查询: getDevices
// =============================================================================

QMap<QString, QStringList> LinkRecordDbMgr::getDevices()
{
    QMutexLocker locker(&m_lock);
    QMap<QString, QStringList> devMap;

    const QStringList tables = recordTables();
    for (const QString &recDev : tables) {
        QSqlQuery q(m_db);
        // 用 DISTINCT 让数据库去重,不再把整表取回来做 QSet
        const QString sql = QString("SELECT DISTINCT sendDev FROM %1")
                                .arg(quoteIdentifier(recDev));
        if (!q.exec(sql)) {
            qWarning() << "[LinkRecordDbMgr] getDevices failed on"
                       << recDev << ":" << q.lastError().text();
            continue;
        }
        QStringList sendDevs;
        while (q.next()) {
            sendDevs << q.value(0).toString();
        }
        devMap.insert(recDev, sendDevs);
    }
    return devMap;
}

// =============================================================================
// 查询: search(质量记录)
// =============================================================================

QList<Record> LinkRecordDbMgr::search(const QString &sendDev,
                                      const QString &recDev,
                                      const QDateTime &start,
                                      const QDateTime &end)
{
    QMutexLocker locker(&m_lock);
    QList<Record> rst;

    // 需要查的表集合:recDev 为空 → 全部质量表
    QStringList tables;
    if (!recDev.isEmpty()) {
        tables << recDev;
    } else {
        tables = recordTables();
    }

    const QString sStart = fmtDateTime(start);
    const QString sEnd   = fmtDateTime(end);

    for (const QString &table : tables) {
        if (!tableExists(table)) {
            continue;
        }

        QSqlQuery q(m_db);
        const QString sql =
            QString("SELECT sendDev, recDev, score, packetErrorRate, "
                    "       bitErrorRate, snr, sir, datetime "
                    "FROM %1 "
                    "WHERE sendDev = :sendDev "
                    "  AND datetime >= :start "
                    "  AND datetime <= :end "
                    "ORDER BY datetime ASC")
                .arg(quoteIdentifier(table));

        q.prepare(sql);
        q.bindValue(":sendDev", sendDev);
        q.bindValue(":start",   sStart);
        q.bindValue(":end",     sEnd);

        if (!q.exec()) {
            qWarning() << "[LinkRecordDbMgr] search failed on"
                       << table << ":" << q.lastError().text();
            continue;
        }

        while (q.next()) {
            Record r;
            r.sendDev         = q.value(0).toString();
            r.recDev          = q.value(1).toString();
            r.score           = q.value(2).toDouble();
            r.packetErrorRate = q.value(3).toDouble();
            r.bitErrorRate    = q.value(4).toDouble();
            r.snr             = q.value(5).toDouble();
            r.sir             = q.value(6).toDouble();
            r.datetime        = q.value(7).toDateTime();
            rst << r;
        }
    }

    // 多表合并后重新按时间排序(单表时此处开销极小)
    std::sort(rst.begin(), rst.end(),
              [](const Record &a, const Record &b) {
                  return a.datetime < b.datetime;
              });
    return rst;
}

// =============================================================================
// 查询: searchMsg(报文)
// =============================================================================

QList<RecieveMsg> LinkRecordDbMgr::searchMsg(const QString &sendDev,
                                             const QString &recDev,
                                             const QDateTime &start,
                                             const QDateTime &end)
{
    QMutexLocker locker(&m_lock);
    QList<RecieveMsg> rst;

    if (recDev.isEmpty()) {
        qWarning() << "[LinkRecordDbMgr] searchMsg: recDev is empty";
        return rst;
    }

    const QString table = recDev + "_msg";
    if (!tableExists(table)) {
        return rst;
    }

    QSqlQuery q(m_db);
    const QString sql =
        QString("SELECT sendDev, recDev, msgBytes, datetime "
                "FROM %1 "
                "WHERE sendDev = :sendDev "
                "  AND datetime >= :start "
                "  AND datetime <= :end "
                "ORDER BY datetime ASC")
            .arg(quoteIdentifier(table));

    q.prepare(sql);
    q.bindValue(":sendDev", sendDev);
    q.bindValue(":start",   fmtDateTime(start));
    q.bindValue(":end",     fmtDateTime(end));

    if (!q.exec()) {
        qWarning() << "[LinkRecordDbMgr] searchMsg failed on"
                   << table << ":" << q.lastError().text();
        return rst;
    }

    while (q.next()) {
        RecieveMsg m;
        m.sendDev   = q.value(0).toString();
        m.recDev    = q.value(1).toString();
        m.msgBytes  = q.value(2).toByteArray();
        m.datetime  = q.value(3).toDateTime();
        rst << m;
    }
    return rst;
}

// =============================================================================
// 插入: 质量记录
// =============================================================================

QSqlError LinkRecordDbMgr::insertRecord(const Record &record)
{
    QMutexLocker locker(&m_lock);

    if (record.recDev.isEmpty()) {
        return QSqlError("LinkRecordDbMgr", "recDev is empty",
                         QSqlError::StatementError);
    }

    // 1. 确保表存在
    QSqlError e;
    if (!ensureRecordTable(record.recDev, &e)) {
        return e;
    }

    // 2. 参数化插入
    QSqlQuery q(m_db);
    const QString sql =
        QString("INSERT INTO %1 "
                "(recDev, sendDev, score, packetErrorRate, "
                " bitErrorRate, snr, sir, datetime) "
                "VALUES (:recDev, :sendDev, :score, :packetErrorRate, "
                "        :bitErrorRate, :snr, :sir, :datetime)")
            .arg(quoteIdentifier(record.recDev));

    q.prepare(sql);
    q.bindValue(":recDev",          record.recDev);
    q.bindValue(":sendDev",         record.sendDev);
    q.bindValue(":score",           record.score);
    q.bindValue(":packetErrorRate", record.packetErrorRate);
    q.bindValue(":bitErrorRate",    record.bitErrorRate);
    q.bindValue(":snr",             record.snr);
    q.bindValue(":sir",             record.sir);
    q.bindValue(":datetime",        fmtDateTime(record.datetime));

    if (!q.exec()) {
        const QSqlError err = q.lastError();
        qWarning() << "[LinkRecordDbMgr] insertRecord failed:"
                   << err.text();
        return err;
    }
    return {};
}

// =============================================================================
// 插入: 报文
// =============================================================================

QSqlError LinkRecordDbMgr::insertMsg(const RecieveMsg &record)
{
    QMutexLocker locker(&m_lock);

    if (record.recDev.isEmpty()) {
        return QSqlError("LinkRecordDbMgr", "recDev is empty",
                         QSqlError::StatementError);
    }

    QSqlError e;
    if (!ensureMsgTable(record.recDev, &e)) {
        return e;
    }

    QSqlQuery q(m_db);
    const QString table = record.recDev + "_msg";
    const QString sql =
        QString("INSERT INTO %1 (recDev, sendDev, msgBytes, datetime) "
                "VALUES (:recDev, :sendDev, :msgBytes, :datetime)")
            .arg(quoteIdentifier(table));

    q.prepare(sql);
    q.bindValue(":recDev",    record.recDev);
    q.bindValue(":sendDev",   record.sendDev);
    q.bindValue(":msgBytes",  record.msgBytes);
    q.bindValue(":datetime",  fmtDateTime(record.datetime));

    if (!q.exec()) {
        const QSqlError err = q.lastError();
        qWarning() << "[LinkRecordDbMgr] insertMsg failed:" << err.text();
        return err;
    }
    return {};
}

// =============================================================================
// 删除
// =============================================================================

bool LinkRecordDbMgr::removeRecord(const QString &sendDev,
                                   const QString &recDev)
{
    QMutexLocker locker(&m_lock);

    if (recDev.isEmpty()) {
        return false;
    }
    if (!tableExists(recDev)) {
        return true;   // 表本来就不存在,视为"已删除"
    }

    QSqlQuery q(m_db);
    const QString sql = QString("DELETE FROM %1 "
                                "WHERE sendDev = :sendDev")
                            .arg(quoteIdentifier(recDev));
    q.prepare(sql);
    q.bindValue(":sendDev", sendDev);

    if (!q.exec()) {
        qWarning() << "[LinkRecordDbMgr] removeRecord failed:"
                   << q.lastError().text();
        return false;
    }
    return true;
}

bool LinkRecordDbMgr::removeAllRecord(const QString &recDev)
{
    QMutexLocker locker(&m_lock);

    if (recDev.isEmpty()) {
        return false;
    }

    bool ok = true;

    // 质量表:DELETE
    if (tableExists(recDev)) {
        QSqlQuery q(m_db);
        if (!q.exec(QString("DELETE FROM %1").arg(quoteIdentifier(recDev)))) {
            qWarning() << "[LinkRecordDbMgr] removeAllRecord(quality) failed:"
                       << q.lastError().text();
            ok = false;
        }
    }

    // 报文表:DELETE
    const QString msgTable = recDev + "_msg";
    if (tableExists(msgTable)) {
        QSqlQuery q(m_db);
        if (!q.exec(QString("DELETE FROM %1").arg(quoteIdentifier(msgTable)))) {
            qWarning() << "[LinkRecordDbMgr] removeAllRecord(msg) failed:"
                       << q.lastError().text();
            ok = false;
        }
    }

    // 回收磁盘空间(可选,数据量大时才有意义)
    if (ok) {
        QSqlQuery(m_db).exec("VACUUM");
    }
    return ok;
}

// =============================================================================
// 内部工具
// =============================================================================

QString LinkRecordDbMgr::quoteIdentifier(const QString &ident)
{
    QString s = ident;
    s.replace('"', "\"\"");       // 内部双引号转义为 ""
    return "\"" + s + "\"";
}

QString LinkRecordDbMgr::fmtDateTime(const QDateTime &dt)
{
    return dt.toString(kDtFmt);
}

QStringList LinkRecordDbMgr::recordTables() const
{
    QStringList rst;
    const QStringList all = m_db.tables();
    for (const QString &t : all) {
        if (t.compare("sqlite_sequence", Qt::CaseInsensitive) == 0) continue;
        if (t.endsWith("_msg", Qt::CaseInsensitive))               continue;
        rst << t;
    }
    return rst;
}

bool LinkRecordDbMgr::tableExists(const QString &name) const
{
    return m_db.tables().contains(name, Qt::CaseInsensitive);
}

bool LinkRecordDbMgr::ensureRecordTable(const QString &recDev, QSqlError *err)
{
    if (tableExists(recDev)) {
        return true;
    }

    const QString sql =
        QString("CREATE TABLE IF NOT EXISTS %1 ("
                "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
                "  sendDev TEXT NOT NULL,"
                "  recDev  TEXT NOT NULL,"
                "  score           DOUBLE,"
                "  packetErrorRate DOUBLE,"
                "  bitErrorRate    DOUBLE,"
                "  snr             DOUBLE,"
                "  sir             DOUBLE,"
                "  datetime        DATETIME"
                ")").arg(quoteIdentifier(recDev));

    QSqlQuery q(m_db);
    if (!q.exec(sql)) {
        if (err) *err = q.lastError();
        qWarning() << "[LinkRecordDbMgr] ensureRecordTable failed:"
                   << q.lastError().text();
        return false;
    }

    // 常用查询索引
    q.exec(QString("CREATE INDEX IF NOT EXISTS %1 "
                   "ON %2 (sendDev, datetime)")
               .arg(quoteIdentifier(recDev + "_idx_send_dt"),
                    quoteIdentifier(recDev)));
    return true;
}

bool LinkRecordDbMgr::ensureMsgTable(const QString &recDev, QSqlError *err)
{
    const QString table = recDev + "_msg";
    if (tableExists(table)) {
        return true;
    }

    const QString sql =
        QString("CREATE TABLE IF NOT EXISTS %1 ("
                "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
                "  sendDev  TEXT NOT NULL,"
                "  recDev   TEXT NOT NULL,"
                "  msgBytes BLOB,"
                "  datetime DATETIME"
                ")").arg(quoteIdentifier(table));

    QSqlQuery q(m_db);
    if (!q.exec(sql)) {
        if (err) *err = q.lastError();
        qWarning() << "[LinkRecordDbMgr] ensureMsgTable failed:"
                   << q.lastError().text();
        return false;
    }

    q.exec(QString("CREATE INDEX IF NOT EXISTS %1 "
                   "ON %2 (sendDev, datetime)")
               .arg(quoteIdentifier(table + "_idx_send_dt"),
                    quoteIdentifier(table)));
    return true;
}

使用示范

cpp 复制代码
auto &dbMgr = LinkRecordDbMgr::getInstance();

// 插入一条质量记录
Record r;
r.sendDev = "radio-001";
r.recDev  = "radio-002";
r.score = 95.5;  r.snr = 12.4;  r.sir = 20.1;
r.packetErrorRate = 0.001;  r.bitErrorRate = 1e-5;
r.datetime = QDateTime::currentDateTime();
dbMgr.insertRecord(r);

// 插入一条报文
RecieveMsg m;
m.sendDev  = "radio-001";
m.recDev   = "radio-002";
m.msgBytes = QByteArray::fromHex("01020304");
m.datetime = QDateTime::currentDateTime();
dbMgr.insertMsg(m);

// 查某对设备在某个时间段的记录
const auto list = dbMgr.search("radio-001", "radio-002",
                               QDateTime::currentDateTime().addDays(-1),
                               QDateTime::currentDateTime());
for (const auto &rec : list) {
    qDebug() << rec.datetime << rec.snr << rec.sir;
}

// 列出现有设备
const auto devMap = dbMgr.getDevices();
for (auto it = devMap.begin(); it != devMap.end(); ++it) {
    qDebug() << "recDev:" << it.key() << " senders:" << it.value();
}

// 清空 radio-002 的全部记录(表保留)
dbMgr.removeAllRecord("radio-002");
相关推荐
ManageEngineITSM1 小时前
工单优先级怎么定?影响×紧急度矩阵的设计与落地方法
java·大数据·运维·前端·数据库·变更管理
科研online1 小时前
数据资源:中国10年旅游流动网络数据集
数据库
智商网输送线配件1 小时前
电子制造零散耗材补货实战:小批量混批采购平台怎么选才不停机?
大数据·服务器·前端·数据库·自动化
小冯不疯2 小时前
集成中心架构:PULL × PUSH × 集成转换的三大引擎
数据库·架构
tryxr2 小时前
数据库如何通过 MVCC 实现隔离性
数据库·mysql·mvcc·隔离性
yeflx2 小时前
Ubuntu22.04安装Mysql Workbench
数据库·mysql·adb
PaperData2 小时前
1986-2025年《中国能源统计年鉴》全年份pdf+excel版
数据库·能源
SelectDB技术团队2 小时前
日志场景 Elasticsearch 替换实操:建表、索引、写入链路与查询改写的完整参数
大数据·数据库·数据仓库·elasticsearch·全文检索·日志分析·apache doris
百数平台2 小时前
低代码安全技术实践:表单 / 流程 / 报表三级权限体系设计解析
java·大数据·数据库