#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 QMutex db_mutex; SQLiteDB(const QString& dbPath) : dbPath(dbPath) { connect(); } ~SQLiteDB() { disconnect(); }; // 所有公有方法加锁保护 bool execute(const QString& queryStr, const QVariantList& args = {}) { QMutexLocker lock(&db_mutex); 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(const QString& queryStr, const QVariantList& args = {}) { QMutexLocker lock(&db_mutex); 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 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(", ")); QSqlQuery query(db); bool res = query.exec(sql); return res; } 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_%';"; QSqlQuery query(db); if (!query.exec(sql)) { qDebug() << "getTables failed:" << query.lastError().text(); return tables; } while (query.next()) { tables.push_back(query.value(0).toString()); } return tables; } bool dropTable(const QString& tableName) { QMutexLocker lock(&db_mutex); QString sql = QString("DROP TABLE IF EXISTS %1").arg(tableName); return execute(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); QSqlQuery query(db); query.prepare(sql); if (!query.exec()) { qDebug() << "Add column failed:" << query.lastError().text(); 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(sql); return !result.isEmpty(); } bool _insert(const QString& tableName, const std::map& data) { QMutexLocker lock(&db_mutex); if (data.empty()) { qDebug() << "Insert failed: data is empty."; return false; } if (tableName.isEmpty()) { qDebug() << "Insert failed: tableName is empty."; return false; } QStringList columns, values; for (const auto& pair : data) { columns << pair.first; values << "'" + pair.second.toString().replace("'", "''") + "'"; } QString sql = QString("INSERT INTO \"%1\" (%2) VALUES (%3)") .arg(tableName) .arg(columns.join(", ")) .arg(values.join(", ")); qDebug() << "Generated SQL: " << sql; QSqlQuery query(db); if (!db.isOpen()) { qDebug() << "Database is not open!"; return false; } if (!query.exec(sql)) { qDebug() << "Insert failed:" << query.lastError().text(); return false; } return true; } bool _update(const QString& tableName, const std::map& data, const std::map& conditions) { QMutexLocker lock(&db_mutex); if (data.empty() || conditions.empty()) { qDebug() << "Update failed: data or conditions are empty."; return false; } QStringList setClauses; for (const auto& pair : data) { QString value = pair.second.toString().replace("'", "''"); setClauses << QString("%1 = '%2'").arg(pair.first).arg(value); } QStringList whereClauses; for (const auto& pair : conditions) { QString conditionValue = pair.second.toString().replace("'", "''"); whereClauses << QString("%1 = '%2'").arg(pair.first).arg(conditionValue); } QString sql = QString("UPDATE \"%1\" SET %2 WHERE %3") .arg(tableName) .arg(setClauses.join(", ")) .arg(whereClauses.join(" AND ")); qDebug() << "Generated SQL: " << sql; QSqlQuery query(db); if (!db.isOpen()) { qDebug() << "Database is not open!"; return false; } if (!query.exec(sql)) { qDebug() << "Update failed:" << query.lastError().text(); return false; } return true; } bool _delete(const QString& tableName, const std::map& conditions) { QMutexLocker lock(&db_mutex); if (conditions.empty()) { qDebug() << "Delete failed: conditions are empty."; return false; } QStringList whereClauses; for (const auto& pair : conditions) { whereClauses << QString("%1 = :%2").arg(pair.first).arg(pair.first + "_condition"); } QString sql = QString("DELETE FROM \"%1\" WHERE %2") .arg(tableName) .arg(whereClauses.join(" AND ")); QSqlQuery query(db); query.prepare(sql); for (const auto& pair : conditions) { query.bindValue(":" + pair.first + "_condition", QVariant::fromValue(pair.second)); } if (!query.exec()) { qDebug() << "Delete failed:" << query.lastError().text(); return false; } return true; } std::vector> _search(const QString& tableName, const QStringList& columns, const std::map& conditions = {}) { QMutexLocker lock(&db_mutex); QStringList selectColumns = columns.isEmpty() ? QStringList("*") : columns; QString sql = QString("SELECT %1 FROM \"%2\"").arg(selectColumns.join(", "), tableName); if (!conditions.empty()) { QStringList whereClauses; for (const auto& pair : conditions) { QString value = pair.second.toString().replace("'", "''"); whereClauses << QString("%1 = '%2'").arg(pair.first).arg(value); } sql += " WHERE " + whereClauses.join(" AND "); } qDebug() << "Generated SQL: " << sql; QSqlQuery query(db); if (!db.isOpen()) { qDebug() << "Database is not open!"; return {}; } if (!query.exec(sql)) { qDebug() << "Search failed:" << query.lastError().text(); return {}; } std::vector> results; while (query.next()) { std::map row; for (int i = 0; i < query.record().count(); ++i) { row[query.record().fieldName(i)] = query.value(i); } results.push_back(row); } return results; } QList> queryTabelData(const QString& tableName) { QMutexLocker lock(&db_mutex); QString sql = QString("SELECT * FROM \"%1\"").arg(tableName); return query(sql); } bool exportTableToCSV(const QString& tableName, const QString& filePath, std::mutex& _mutex) { QMutexLocker lock(&db_mutex); using namespace std::chrono; auto start = high_resolution_clock::now(); QString csvFileName = filePath; QString sql = QString("SELECT * FROM \"%1\"").arg(tableName); QSqlQuery query(db); if (!query.exec(sql)) { qCritical() << "Query execution failed: " << query.lastError().text(); return false; } if (!query.next()) { qDebug() << "No data found in table" << tableName; return false; } QFile file(csvFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCritical() << "Failed to open file" << csvFileName; return false; } QTextStream out(&file); QSqlRecord record = query.record(); QStringList columns; for (int i = 0; i < record.count(); ++i) { columns << record.fieldName(i); } out << columns.join(",") << "\n"; do { QStringList values; for (const auto& column : columns) { values << query.value(column).toString(); } out << values.join(",") << "\n"; } while (query.next()); file.close(); auto end = high_resolution_clock::now(); auto duration = duration_cast(end - start); qDebug() << csvFileName << "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; } QSqlQuery pragma(db); QString mode = enableWAL ? "WAL" : "DELETE"; if (!pragma.exec(QString("PRAGMA journal_mode=%1;").arg(mode))) { qWarning() << "Failed to set journal_mode:" << pragma.lastError().text(); return false; } qDebug() << "SQLite journal_mode set to" << mode; if (enableWAL) { pragma.exec("PRAGMA synchronous=NORMAL;"); } pragma.exec(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; { QSqlQuery pragma(db); if (!pragma.exec("PRAGMA journal_mode=WAL;")) { qWarning() << "Failed to enable WAL mode:" << pragma.lastError().text(); } else { qDebug() << "SQLite WAL mode enabled."; } pragma.exec("PRAGMA synchronous=NORMAL;"); pragma.exec("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):db(dbIn){ try{ m_json = readJsonFromPath("./sqldrivers/sqliteCfg.json"); for (auto& data : m_json["数据列表"]) { db_InitColumns.append({ QString::fromStdString(data),"TEXT" }); } for (auto& data : m_json["链接到UI表格数据列表"].items()) { csvAndChartLink[QString::fromStdString(data.key())] = QString::fromStdString(data.value()); } if (m_json["新增数据列表"].size() > 0) { for (auto& data : m_json["新增数据列表"]) { db_newCol.append({ QString::fromStdString(data),"TEXT" }); } m_json["新增数据列表"].clear(); saveJsonFromPath("./sqldrivers/sqliteCfg.json", m_json); } for (auto& data : m_json["判断NG依据数据列表"]) { isNgDepends.push_back(QString::fromStdString(data)); } } catch (const std::exception&){ std::cerr << "readJsonFromPath error" << std::endl; } init(); } ~CsvWriter(){ //stopAutoExport(); } std::mapcsvAndChartLink; SQLiteDB* db; void addData(QString snCode, std::map data) { if (snCode.isEmpty()) { qWarning() << "addtData: snCode is empty."; return; } if (data.empty()) { qWarning() << "addtData: data is empty."; return; } QMutexLocker locker(&queueLock); queue.enqueue(std::make_pair(snCode, data)); this->start(); } bool isNewData(const QString& snCode) { QMutexLocker locker(&db->db_mutex); auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码",snCode} }); return res.empty(); } bool isNgData(const QString& snCode) { QMutexLocker locker(&db->db_mutex); auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码",snCode} }); if (res.empty() == true) return false; for (auto& data : isNgDepends) { if (res[0][data].toString() == "NG") return true; } return false; } void run() override{ while (queue.size() > 0) { std::pair> val; { QMutexLocker locker(&queueLock); val = queue.dequeue(); } try { auto snCode = val.first; auto data = val.second; QString currentProcessCsvName = getCurrentProcessCsvName(); if (currentProcessCsvName != currentProcessCsvNameCache) { QMutexLocker locker(&db->db_mutex); if (!db->createTable(currentProcessCsvName, db_InitColumns)) { qWarning() << "addtData: Failed to create table:" << currentProcessCsvName; return; } currentProcessCsvNameCache = currentProcessCsvName; } QList tableColumns; { QMutexLocker locker(&db->db_mutex); QSqlQuery colQuery(db->db); QString colSql = QString("PRAGMA table_info(\"%1\")").arg(currentProcessCsvName); if (colQuery.exec(colSql)) { while (colQuery.next()) { tableColumns.append(colQuery.value(1).toString()); } } } for (const auto& kv : data) { if (!tableColumns.contains(kv.first)) { if (!db->addColumn(currentProcessCsvName, kv.first, "TEXT")) { qWarning() << "addtData: Failed to add column:" << kv.first << "to table:" << currentProcessCsvName; } } } std::vector> res; { res = db->_search(currentProcessCsvName, {}, { {"条码",snCode} }); } QString nowTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); bool opResult = false; if (res.empty()) { std::vector> lastRes; { lastRes = db->_search(getLastProcessCsvName(), {}, { {"条码",snCode} }); } if (lastRes.empty()) { std::map insertData = data; insertData["条码"] = snCode; insertData["创建时间"] = nowTime; insertData["修改时间"] = nowTime; opResult = db->_insert(currentProcessCsvName, insertData); if (!opResult) { qWarning() << "addtData: Insert failed for table:" << currentProcessCsvName << "snCode:" << snCode; } } else { std::map updateData = data; updateData["修改时间"] = nowTime; opResult = db->_update(getLastProcessCsvName(), updateData, { {"条码",snCode} }); if (!opResult) { qWarning() << "addtData: Update failed for table:" << getLastProcessCsvName() << "snCode:" << snCode; } } } else { std::map updateData = data; updateData["修改时间"] = nowTime; opResult = db->_update(getCurrentProcessCsvName(), updateData, { {"条码",snCode} }); if (!opResult) { qWarning() << "addtData: Update failed for table:" << getCurrentProcessCsvName() << "snCode:" << snCode; } } } catch (const std::exception& ex) { qCritical() << "addtData: Exception caught:" << ex.what(); } catch (...) { qCritical() << "addtData: Unknown exception caught."; } } } private: QMutex queueLock; QQueue>> queue; std::vector isNgDepends; QQueue> > addDataCacheQueue; 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"}, }; 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 (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 == true) dateStr = currentDateTime.addDays(-1).toString("yyyy-MM-dd"); else dateStr = currentDateTime.toString("yyyy-MM-dd"); if (isDayShift) { return (dateStr + "白班"); } else { return (dateStr + "夜班"); } } 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"); if (isDayShift) { return (dateStr + "夜班"); } else { return (dateStr + "白班"); } } void exportCsv(){ QString name = getLastProcessCsvName(); db->exportTableToCSV(name, "./csv/"+ name +".csv", _mutex); name = getCurrentProcessCsvName(); db->exportTableToCSV(name, "./csv/" + name + ".csv", _mutex); QString currentProcessCsvName = getCurrentProcessCsvName(); if (currentProcessCsvName != currentProcessCsvNameCache) if (!db->createTable(currentProcessCsvName, db_InitColumns)) { return; } else currentProcessCsvNameCache = currentProcessCsvName; } }; #pragma once