#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class SQLiteDB { public: QSqlDatabase db; // 改为递归互斥锁,解决嵌套调用问题 mutable QRecursiveMutex db_mutex; SQLiteDB(const QString& dbPath) : dbPath(dbPath) { connect(); } ~SQLiteDB() { disconnect(); }; // 添加内部无锁版本,避免嵌套锁问题 bool execute_unsafe(const QString& queryStr, const QVariantList& args = {}) { QSqlQuery query(db); query.prepare(queryStr); for (int i = 0; i < args.size(); ++i) { query.bindValue(i, args[i]); } if (!query.exec()) { qCritical() << "SQL error:" << query.lastError().text(); qCritical() << "Query:" << queryStr; return false; } return true; } QList> query_unsafe(const QString& queryStr, const QVariantList& args = {}) { QList> results; QSqlQuery query(db); query.prepare(queryStr); for (int i = 0; i < args.size(); ++i) { query.bindValue(i, args[i]); } if (!query.exec()) { qCritical() << "SQL error:" << query.lastError().text(); qCritical() << "Query:" << queryStr; return results; } while (query.next()) { QMap row; for (int i = 0; i < query.record().count(); ++i) { row.insert(query.record().fieldName(i), query.value(i)); } results.append(row); } return results; } // 公有方法保持加锁保护 bool execute(const QString& queryStr, const QVariantList& args = {}) { QMutexLocker lock(&db_mutex); return execute_unsafe(queryStr, args); } QList> query(const QString& queryStr, const QVariantList& args = {}) { QMutexLocker lock(&db_mutex); return query_unsafe(queryStr, args); } bool createTable(const QString& tableName, const QList>& columns) { QMutexLocker lock(&db_mutex); if (columns.empty()) { qDebug() << "Create table failed: columns are empty."; return false; } QStringList columnDefinitions; for (const auto& pair : columns) { columnDefinitions << QString("%1 %2").arg(pair.first).arg(pair.second); } QString sql = QString("CREATE TABLE IF NOT EXISTS \"%1\" (%2)") .arg(tableName) .arg(columnDefinitions.join(", ")); return execute_unsafe(sql); } std::vector getTables() { QMutexLocker lock(&db_mutex); std::vector tables; QString sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; auto result = query_unsafe(sql); for (const auto& row : result) { tables.push_back(row.value("name").toString()); } return tables; } bool dropTable(const QString& tableName) { QMutexLocker lock(&db_mutex); QString sql = QString("DROP TABLE IF EXISTS %1").arg(tableName); return execute_unsafe(sql); } bool addColumn(const QString& tableName, const QString& columnName, const QString& columnType) { QMutexLocker lock(&db_mutex); if (columnName.isEmpty() || columnType.isEmpty()) { qDebug() << "Add column failed: column name or type is empty."; return false; } QString sql = QString("ALTER TABLE \"%1\" ADD COLUMN \"%2\" %3").arg(tableName, columnName, columnType); if (!execute_unsafe(sql)) { qDebug() << "Add column failed"; return false; } qDebug() << "Column" << columnName << "added to table" << tableName; return true; } bool tableExists(const QString& tableName) { QMutexLocker lock(&db_mutex); QString sql = QString("SELECT name FROM sqlite_master WHERE type='table' AND name='%1';").arg(tableName); QList> result = query_unsafe(sql); return !result.isEmpty(); } // 添加批量操作支持,减少锁竞争 bool batchInsert(const QString& tableName, const std::vector>& dataList) { if (dataList.empty()) { qDebug() << "Batch insert failed: data is empty."; return false; } QMutexLocker lock(&db_mutex); // 开始事务 if (!execute_unsafe("BEGIN TRANSACTION;")) { return false; } bool allSuccess = true; for (const auto& data : dataList) { if (!_insert_unsafe(tableName, data)) { allSuccess = false; break; } } if (allSuccess) { execute_unsafe("COMMIT;"); } else { execute_unsafe("ROLLBACK;"); } return allSuccess; } bool _insert(const QString& tableName, const std::map& data) { QMutexLocker lock(&db_mutex); return _insert_unsafe(tableName, data); } private: bool _insert_unsafe(const QString& tableName, const std::map& data) { if (data.empty()) { qDebug() << "Insert failed: data is empty."; return false; } if (tableName.isEmpty()) { qDebug() << "Insert failed: tableName is empty."; return false; } QStringList columns, placeholders; QVariantList values; for (const auto& pair : data) { columns << pair.first; placeholders << "?"; values << pair.second; } QString sql = QString("INSERT INTO \"%1\" (%2) VALUES (%3)") .arg(tableName) .arg(columns.join(", ")) .arg(placeholders.join(", ")); return execute_unsafe(sql, values); } public: bool _update(const QString& tableName, const std::map& data, const std::map& conditions) { QMutexLocker lock(&db_mutex); return _update_unsafe(tableName, data, conditions); } private: bool _update_unsafe(const QString& tableName, const std::map& data, const std::map& conditions) { if (data.empty() || conditions.empty()) { qDebug() << "Update failed: data or conditions are empty."; return false; } QStringList setClauses, whereClauses; QVariantList values; for (const auto& pair : data) { setClauses << QString("%1 = ?").arg(pair.first); values << pair.second; } for (const auto& pair : conditions) { whereClauses << QString("%1 = ?").arg(pair.first); values << pair.second; } QString sql = QString("UPDATE \"%1\" SET %2 WHERE %3") .arg(tableName) .arg(setClauses.join(", ")) .arg(whereClauses.join(" AND ")); return execute_unsafe(sql, values); } public: bool _delete(const QString& tableName, const std::map& conditions) { QMutexLocker lock(&db_mutex); return _delete_unsafe(tableName, conditions); } private: bool _delete_unsafe(const QString& tableName, const std::map& conditions) { if (conditions.empty()) { qDebug() << "Delete failed: conditions are empty."; return false; } QStringList whereClauses; QVariantList values; for (const auto& pair : conditions) { whereClauses << QString("%1 = ?").arg(pair.first); values << pair.second; } QString sql = QString("DELETE FROM \"%1\" WHERE %2") .arg(tableName) .arg(whereClauses.join(" AND ")); return execute_unsafe(sql, values); } public: std::vector> _search(const QString& tableName, const QStringList& columns, const std::map& conditions = {}) { QMutexLocker lock(&db_mutex); return _search_unsafe(tableName, columns, conditions); } private: std::vector> _search_unsafe(const QString& tableName, const QStringList& columns, const std::map& conditions = {}) { QStringList selectColumns = columns.isEmpty() ? QStringList("*") : columns; QString sql = QString("SELECT %1 FROM \"%2\"").arg(selectColumns.join(", "), tableName); QVariantList values; if (!conditions.empty()) { QStringList whereClauses; for (const auto& pair : conditions) { whereClauses << QString("%1 = ?").arg(pair.first); values << pair.second; } sql += " WHERE " + whereClauses.join(" AND "); } auto result = query_unsafe(sql, values); std::vector> results; for (const auto& row : result) { std::map rowMap; for (auto it = row.begin(); it != row.end(); ++it) { rowMap[it.key()] = it.value(); } results.push_back(rowMap); } return results; } public: QList> queryTabelData(const QString& tableName) { QMutexLocker lock(&db_mutex); QString sql = QString("SELECT * FROM \"%1\"").arg(tableName); return query_unsafe(sql); } // 移除外部 mutex 参数,避免锁顺序问题 bool exportTableToCSV(const QString& tableName, const QString& filePath) { QMutexLocker lock(&db_mutex); using namespace std::chrono; auto start = high_resolution_clock::now(); QString sql = QString("SELECT * FROM \"%1\"").arg(tableName); auto result = query_unsafe(sql); if (result.isEmpty()) { qDebug() << "No data found in table" << tableName; return false; } QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCritical() << "Failed to open file" << filePath; return false; } QTextStream out(&file); // 写入表头 if (!result.isEmpty()) { QStringList columns = result.first().keys(); out << columns.join(",") << "\n"; // 写入数据 for (const auto& row : result) { QStringList values; for (const auto& column : columns) { values << row.value(column).toString(); } out << values.join(",") << "\n"; } } file.close(); auto end = high_resolution_clock::now(); auto duration = duration_cast(end - start); qDebug() << filePath << "EXPORT CSV COST: " << duration.count() << "ms"; return true; } bool setWALMode(bool enableWAL = true, int busyTimeoutMs = 3000) { QMutexLocker lock(&db_mutex); if (!db.isOpen()) { qWarning() << "Database is not open, cannot set WAL mode."; return false; } QString mode = enableWAL ? "WAL" : "DELETE"; if (!execute_unsafe(QString("PRAGMA journal_mode=%1;").arg(mode))) { qWarning() << "Failed to set journal_mode"; return false; } qDebug() << "SQLite journal_mode set to" << mode; if (enableWAL) { execute_unsafe("PRAGMA synchronous=NORMAL;"); } execute_unsafe(QString("PRAGMA busy_timeout=%1;").arg(busyTimeoutMs)); return true; } private: bool connect() { m_connectionName = QUuid::createUuid().toString(); if (!QFile::exists(dbPath)) { QFileInfo fi(dbPath); QDir().mkpath(fi.absolutePath()); qDebug() << "Database file does not exist. Creating a new database:" << dbPath; } db = QSqlDatabase::addDatabase("QSQLITE", m_connectionName); db.setDatabaseName(dbPath); if (!db.open()) { qCritical() << "Failed to open or create database:" << db.lastError().text(); return false; } qDebug() << "Connected to SQLite database:" << dbPath; // 设置 WAL 模式和其他 PRAGMA if (!execute_unsafe("PRAGMA journal_mode=WAL;")) { qWarning() << "Failed to enable WAL mode"; } else { qDebug() << "SQLite WAL mode enabled."; } execute_unsafe("PRAGMA synchronous=NORMAL;"); execute_unsafe("PRAGMA busy_timeout=3000;"); createTable("setting", { {"id","INTEGER PRIMARY KEY AUTOINCREMENT"} }); return true; } void disconnect() { QSqlDatabase::removeDatabase(m_connectionName); if (db.isOpen()) { db.close(); qDebug() << "Database closed."; } } QString m_connectionName; QString dbPath; }; class CsvWriter : public QThread { public: CsvWriter(SQLiteDB* dbIn, QList>externalInitDataVec = QList>()) : db(dbIn) { db_InitColumns.append(externalInitDataVec); } ~CsvWriter() { // 确保线程安全退出 if (isRunning()) { quit(); wait(5000); // 等待最多5秒 } } std::map csvAndChartLink; SQLiteDB* db; void addData(QString snCode, std::map data) { if (snCode.isEmpty()) { qWarning() << "addData: snCode is empty."; return; } if (data.empty()) { qWarning() << "addData: data is empty."; return; } { QMutexLocker locker(&queueLock); queue.enqueue(std::make_pair(snCode, data)); } if (!isRunning()) { start(); } } bool isNewData(const QString& snCode) { auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码", snCode} }); return res.empty(); } bool isNgData(const QString& snCode) { auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码", snCode} }); if (res.empty()) { return false; } for (const auto& data : isNgDepends) { if (res[0][data].toString() == "NG") { return true; } } return false; } protected: void run() override { while (true) { std::pair> val; // 从队列获取数据 { QMutexLocker locker(&queueLock); if (queue.isEmpty()) { break; } val = queue.dequeue(); } try { processData(val.first, val.second); } catch (const std::exception& ex) { qCritical() << "addData: Exception caught:" << ex.what(); } catch (...) { qCritical() << "addData: Unknown exception caught."; } } } private: // 统一锁顺序:先队列锁,再数据库锁 QMutex queueLock; QQueue>> queue; std::vector isNgDepends; std::mutex _mutex; int m_intervalMs = 10000; QElapsedTimer timer; QString currentProcessCsvNameCache; nlohmann::json m_json; QList> db_newCol = {}; QList> db_InitColumns = { {"条码", "TEXT NOT NULL PRIMARY KEY"}, {"创建时间", "TEXT"}, {"修改时间", "TEXT"}, {"CT", "TEXT"}, {"算法结果", "TEXT"}, {"AI检测结果", "TEXT"}, }; void processData(const QString& snCode, const std::map& data) { QString currentProcessCsvName = getCurrentProcessCsvName(); // 确保表存在 if (currentProcessCsvName != currentProcessCsvNameCache) { if (!db->createTable(currentProcessCsvName, db_InitColumns)) { qWarning() << "addData: Failed to create table:" << currentProcessCsvName; return; } currentProcessCsvNameCache = currentProcessCsvName; } // 检查并添加缺失的列 ensureColumns(currentProcessCsvName, data); QString nowTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); // 检查数据是否存在 auto res = db->_search(currentProcessCsvName, {}, { {"条码", snCode} }); if (res.empty()) { // 检查上一个班次的数据 auto lastRes = db->_search(getLastProcessCsvName(), {}, { {"条码", snCode} }); if (lastRes.empty()) { // 插入新数据 std::map insertData = data; insertData["条码"] = snCode; insertData["创建时间"] = nowTime; insertData["修改时间"] = nowTime; if (!db->_insert(currentProcessCsvName, insertData)) { qWarning() << "addData: Insert failed for table:" << currentProcessCsvName << "snCode:" << snCode; } } else { // 更新上一个班次的数据 std::map updateData = data; updateData["修改时间"] = nowTime; if (!db->_update(getLastProcessCsvName(), updateData, { {"条码", snCode} })) { qWarning() << "addData: Update failed for table:" << getLastProcessCsvName() << "snCode:" << snCode; } } } else { // 更新当前数据 std::map updateData = data; updateData["修改时间"] = nowTime; if (!db->_update(getCurrentProcessCsvName(), updateData, { {"条码", snCode} })) { qWarning() << "addData: Update failed for table:" << getCurrentProcessCsvName() << "snCode:" << snCode; } } } void ensureColumns(const QString& tableName, const std::map& data) { // 获取当前表的列信息 QList tableColumns; auto columnsResult = db->query("PRAGMA table_info(\"" + tableName + "\")"); for (const auto& row : columnsResult) { tableColumns.append(row.value("name").toString()); } // 添加缺失的列 for (const auto& kv : data) { if (!tableColumns.contains(kv.first)) { if (!db->addColumn(tableName, kv.first, "TEXT")) { qWarning() << "addData: Failed to add column:" << kv.first << "to table:" << tableName; } } } } void init() { std::lock_guard locker(_mutex); if (!db->createTable(getLastProcessCsvName(), db_InitColumns)) { qDebug() << "Failed to create table " << getLastProcessCsvName(); } currentProcessCsvNameCache = getCurrentProcessCsvName(); if (!db->createTable(currentProcessCsvNameCache, db_InitColumns)) { qDebug() << "Failed to create table " << currentProcessCsvNameCache; } for (const auto& data : db_newCol) { db->addColumn(getLastProcessCsvName(), data.first, data.second); db->addColumn(getCurrentProcessCsvName(), data.first, data.second); } timer.start(); } QString getCurrentProcessCsvName() { QDateTime currentDateTime = QDateTime::currentDateTime(); int hour = currentDateTime.time().hour(); int minute = currentDateTime.time().minute(); bool isDayShift = (hour > 8 || (hour == 8 && minute >= 30)) && (hour < 20 || (hour == 20 && minute <= 30)); bool isDayBefore = hour <= 8 || (hour == 8 && minute <= 29); QString dateStr; if (isDayBefore) { dateStr = currentDateTime.addDays(-1).toString("yyyy-MM-dd"); } else { dateStr = currentDateTime.toString("yyyy-MM-dd"); } return dateStr + (isDayShift ? "白班" : "夜班"); } QString getLastProcessCsvName() { QDateTime currentDateTime = QDateTime::currentDateTime(); int hour = currentDateTime.time().hour(); int minute = currentDateTime.time().minute(); bool isDayShift = (hour >= 8 && minute >= 30) && (hour <= 20 && minute <= 30); bool isDayBefore = hour <= 20 && minute <= 30; QDateTime lastShiftDateTime; if (isDayBefore) { lastShiftDateTime = currentDateTime.addDays(-1); } else { lastShiftDateTime = currentDateTime; } QString dateStr = lastShiftDateTime.toString("yyyy-MM-dd"); return dateStr + (isDayShift ? "夜班" : "白班"); } void exportCsv() { QString name = getLastProcessCsvName(); db->exportTableToCSV(name, "./csv/" + name + ".csv"); name = getCurrentProcessCsvName(); db->exportTableToCSV(name, "./csv/" + name + ".csv"); QString currentProcessCsvName = getCurrentProcessCsvName(); if (currentProcessCsvName != currentProcessCsvNameCache) { if (db->createTable(currentProcessCsvName, db_InitColumns)) { currentProcessCsvNameCache = currentProcessCsvName; } } } }; #pragma once