Files
eUi/include/sql/qSqlBase.h
T
2025-11-10 00:27:17 +08:00

696 lines
22 KiB
C++

#include <qfile.h>
#include <string>
#include <unordered_map>
#include <mutex>
#include <qdatetime.h>
#include <memory>
#include <qtextstream.h>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
#include <QtSql/QSqlRecord>
#include <QVariant>
#include <QDebug>
#include <QString>
#include <QList>
#include <QMap>
#include <qcoreapplication.h>
#include <qdir.h>
#include <qtimer.h>
#include <chrono>
#include <qobject.h>
#include <generalJsonParsing.hpp>
#include <iostream>
#include <fstream>
#include <qelapsedtimer.h>
#include <QQueue>
#include <quuid.h>
#include <global.h>
#include <qthread.h>
#include <qmutex.h>
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<QMap<QString, QVariant>> query_unsafe(const QString& queryStr, const QVariantList& args = {}) {
QList<QMap<QString, QVariant>> 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<QString, QVariant> 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<QMap<QString, QVariant>> query(const QString& queryStr, const QVariantList& args = {}) {
QMutexLocker lock(&db_mutex);
return query_unsafe(queryStr, args);
}
bool createTable(const QString& tableName, const QList<std::pair<QString, QString>>& 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<QString> getTables() {
QMutexLocker lock(&db_mutex);
std::vector<QString> 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<QMap<QString, QVariant>> result = query_unsafe(sql);
return !result.isEmpty();
}
// 添加批量操作支持,减少锁竞争
bool batchInsert(const QString& tableName, const std::vector<std::map<QString, QVariant>>& 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<QString, QVariant>& data) {
QMutexLocker lock(&db_mutex);
return _insert_unsafe(tableName, data);
}
private:
bool _insert_unsafe(const QString& tableName, const std::map<QString, QVariant>& 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<QString, QVariant>& data,
const std::map<QString, QVariant>& conditions) {
QMutexLocker lock(&db_mutex);
return _update_unsafe(tableName, data, conditions);
}
private:
bool _update_unsafe(const QString& tableName,
const std::map<QString, QVariant>& data,
const std::map<QString, QVariant>& 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<QString, QVariant>& conditions) {
QMutexLocker lock(&db_mutex);
return _delete_unsafe(tableName, conditions);
}
private:
bool _delete_unsafe(const QString& tableName, const std::map<QString, QVariant>& 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<std::map<QString, QVariant>> _search(const QString& tableName,
const QStringList& columns,
const std::map<QString, QVariant>& conditions = {}) {
QMutexLocker lock(&db_mutex);
return _search_unsafe(tableName, columns, conditions);
}
private:
std::vector<std::map<QString, QVariant>> _search_unsafe(const QString& tableName,
const QStringList& columns,
const std::map<QString, QVariant>& 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<std::map<QString, QVariant>> results;
for (const auto& row : result) {
std::map<QString, QVariant> rowMap;
for (auto it = row.begin(); it != row.end(); ++it) {
rowMap[it.key()] = it.value();
}
results.push_back(rowMap);
}
return results;
}
public:
QList<QMap<QString, QVariant>> 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<milliseconds>(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) : db(dbIn) {}
~CsvWriter() {
// 确保线程安全退出
if (isRunning()) {
quit();
wait(5000); // 等待最多5秒
}
}
std::map<QString, QString> csvAndChartLink;
SQLiteDB* db;
void addData(QString snCode, std::map<QString, QVariant> 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<QString, std::map<QString, QVariant>> 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<std::pair<QString, std::map<QString, QVariant>>> queue;
std::vector<QString> isNgDepends;
std::mutex _mutex;
int m_intervalMs = 10000;
QElapsedTimer timer;
QString currentProcessCsvNameCache;
nlohmann::json m_json;
QList<std::pair<QString, QString>> db_newCol = {};
QList<std::pair<QString, QString>> db_InitColumns = {
{"条码", "TEXT NOT NULL PRIMARY KEY"},
{"创建时间", "TEXT"},
{"修改时间", "TEXT"},
{"CT", "TEXT"},
};
void processData(const QString& snCode, const std::map<QString, QVariant>& 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<QString, QVariant> 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<QString, QVariant> updateData = data;
updateData["修改时间"] = nowTime;
if (!db->_update(getLastProcessCsvName(), updateData, { {"条码", snCode} })) {
qWarning() << "addData: Update failed for table:" << getLastProcessCsvName() << "snCode:" << snCode;
}
}
} else {
// 更新当前数据
std::map<QString, QVariant> 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<QString, QVariant>& data) {
// 获取当前表的列信息
QList<QString> 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<std::mutex> 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