Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
333 lines
12 KiB
C++
333 lines
12 KiB
C++
#include "DatabaseManager.h"
|
||
#include <QCoreApplication>
|
||
#include <QSqlError>
|
||
#include <QSqlQuery>
|
||
#include <QDebug>
|
||
#include <QDir>
|
||
#include <QSettings>
|
||
#include <QThread>
|
||
|
||
DatabaseManager::DatabaseManager() = default;
|
||
|
||
DatabaseManager::~DatabaseManager() {
|
||
QMutexLocker locker(&mutex);
|
||
|
||
for (auto it = connectionPool.begin(); it != connectionPool.end(); ++it) {
|
||
QSqlDatabase::removeDatabase(it.key());
|
||
}
|
||
qDebug() << "Все соединения закрыты";
|
||
}
|
||
|
||
DatabaseManager &DatabaseManager::instance() {
|
||
static DatabaseManager instance;
|
||
return instance;
|
||
}
|
||
|
||
QSqlDatabase DatabaseManager::database() {
|
||
QMutexLocker locker(&mutex);
|
||
QString name = connectionName();
|
||
|
||
// Создаем новое соединение, если его ещё нет
|
||
if (!connectionPool.contains(name)) {
|
||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", name);
|
||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||
|
||
QString dbPath = settings.value("db/dbPath").toString();
|
||
if (dbPath.startsWith("~")) {
|
||
dbPath.replace(0, 1, QDir::homePath());
|
||
}
|
||
|
||
// qDebug() << settings.value("db/dbPath").toString();
|
||
// db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
|
||
db.setDatabaseName(dbPath);
|
||
|
||
if (!db.open()) {
|
||
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
|
||
}
|
||
|
||
connectionPool[name] = db;
|
||
|
||
// Автоматическое удаление соединения при завершении потока
|
||
QObject::connect(QThread::currentThread(), &QThread::finished, [this, name]() {
|
||
QMutexLocker cLocker(&mutex);
|
||
if (connectionPool.contains(name)) {
|
||
connectionPool.remove(name);
|
||
QSqlDatabase::removeDatabase(name);
|
||
qDebug() << "Соединение" << name << "удалено";
|
||
}
|
||
});
|
||
}
|
||
|
||
return connectionPool[name];
|
||
}
|
||
|
||
QString DatabaseManager::connectionName() {
|
||
return QString("connection_%1").arg(reinterpret_cast<quintptr>(QThread::currentThreadId()));
|
||
}
|
||
|
||
|
||
static bool columnExists(QSqlQuery &q, const QString &table, const QString &column) {
|
||
q.exec(QString("SELECT COUNT(*) FROM pragma_table_info('%1') WHERE name='%2'")
|
||
.arg(table, column));
|
||
return q.next() && q.value(0).toInt() > 0;
|
||
}
|
||
|
||
void DatabaseManager::initSchema() {
|
||
QSqlDatabase db = database();
|
||
QSqlQuery q(db);
|
||
|
||
// Читаем текущую версию схемы
|
||
q.exec("PRAGMA user_version");
|
||
const int version = q.next() ? q.value(0).toInt() : 0;
|
||
|
||
// ── Версия 1: начальная схема + api_id для devices ────────────────────────
|
||
if (version < 1) {
|
||
db.transaction();
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS devices (
|
||
id TEXT PRIMARY KEY,
|
||
api_id TEXT,
|
||
status TEXT,
|
||
name TEXT,
|
||
android TEXT,
|
||
screen_width INTEGER,
|
||
screen_height INTEGER,
|
||
battery INTEGER,
|
||
update_time DATETIME,
|
||
timestamp DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||
image BLOB
|
||
))");
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS applications (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
device_id TEXT,
|
||
pin_code TEXT,
|
||
pin_code_checked TEXT DEFAULT 'no',
|
||
name TEXT,
|
||
code TEXT,
|
||
package TEXT,
|
||
status TEXT DEFAULT 'off',
|
||
install TEXT DEFAULT 'no',
|
||
comment TEXT,
|
||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||
UNIQUE (device_id, code)
|
||
))");
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS accounts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
device_id TEXT,
|
||
app_code TEXT,
|
||
last_numbers TEXT,
|
||
description TEXT,
|
||
amount REAL,
|
||
currency TEXT DEFAULT '',
|
||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||
status TEXT DEFAULT 'active',
|
||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||
UNIQUE (device_id, app_code, last_numbers)
|
||
))");
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
account_id INTEGER,
|
||
external_id INTEGER,
|
||
amount REAL,
|
||
fee REAL,
|
||
status TEXT,
|
||
type TEXT,
|
||
phone TEXT,
|
||
name TEXT,
|
||
short_description TEXT,
|
||
updated_short_description TEXT,
|
||
description TEXT,
|
||
updated_description TEXT,
|
||
bank_name TEXT,
|
||
bank_time TEXT,
|
||
bank_tr_external_id TEXT,
|
||
update_time DATETIME,
|
||
complete_time DATETIME,
|
||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
|
||
))");
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
account_id INTEGER,
|
||
external_id INTEGER,
|
||
device_id TEXT,
|
||
amount REAL,
|
||
phone TEXT,
|
||
bank_name TEXT,
|
||
comment TEXT,
|
||
status TEXT,
|
||
type TEXT,
|
||
update_time DATETIME,
|
||
complete_time DATETIME,
|
||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||
UNIQUE (external_id)
|
||
))");
|
||
|
||
// Добавляем api_id в devices если ещё нет (для существующих БД)
|
||
if (!columnExists(q, "devices", "api_id")) {
|
||
q.exec("ALTER TABLE devices ADD COLUMN api_id TEXT");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 1");
|
||
qDebug() << "[DB] Migration 1 applied";
|
||
}
|
||
|
||
// ── Версия 2: currency в accounts + переименование app_name → app_code ───
|
||
if (version < 2) {
|
||
db.transaction();
|
||
|
||
// Добавляем currency если ещё нет (существующие БД без этого поля)
|
||
if (!columnExists(q, "accounts", "currency")) {
|
||
q.exec("ALTER TABLE accounts ADD COLUMN currency TEXT DEFAULT ''");
|
||
}
|
||
|
||
// Переименовываем app_name → app_code (SQLite 3.25+)
|
||
if (columnExists(q, "accounts", "app_name")) {
|
||
q.exec("ALTER TABLE accounts RENAME COLUMN app_name TO app_code");
|
||
if (q.lastError().isValid()) {
|
||
qWarning() << "[DB] Migration 2: rename app_name failed:" << q.lastError().text();
|
||
}
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 2");
|
||
qDebug() << "[DB] Migration 2 applied";
|
||
}
|
||
|
||
// ── Версия 3: phone и full_name в applications ────────────────────────────
|
||
if (version < 3) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "applications", "phone")) {
|
||
q.exec("ALTER TABLE applications ADD COLUMN phone TEXT DEFAULT ''");
|
||
}
|
||
if (!columnExists(q, "applications", "full_name")) {
|
||
q.exec("ALTER TABLE applications ADD COLUMN full_name TEXT DEFAULT ''");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 3");
|
||
qDebug() << "[DB] Migration 3 applied";
|
||
}
|
||
|
||
// ── Версия 4: card_number в accounts ──────────────────────────────────────
|
||
if (version < 4) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "accounts", "card_number")) {
|
||
q.exec("ALTER TABLE accounts ADD COLUMN card_number TEXT DEFAULT ''");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 4");
|
||
qDebug() << "[DB] Migration 4 applied";
|
||
}
|
||
|
||
// ── Версия 5: bank_profile_id в applications ──────────────────────────────
|
||
if (version < 5) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "applications", "bank_profile_id")) {
|
||
q.exec("ALTER TABLE applications ADD COLUMN bank_profile_id TEXT DEFAULT ''");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 5");
|
||
qDebug() << "[DB] Migration 5 applied";
|
||
}
|
||
|
||
// ── Версия 6: currency и amount в applications ─────────────────────────────
|
||
if (version < 6) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "applications", "currency")) {
|
||
q.exec("ALTER TABLE applications ADD COLUMN currency TEXT DEFAULT ''");
|
||
}
|
||
if (!columnExists(q, "applications", "amount")) {
|
||
q.exec("ALTER TABLE applications ADD COLUMN amount REAL DEFAULT 0");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 6");
|
||
qDebug() << "[DB] Migration 6 applied";
|
||
}
|
||
|
||
// ── Версия 7: material_id в accounts ──────────────────────────────────────
|
||
if (version < 7) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "accounts", "material_id")) {
|
||
q.exec("ALTER TABLE accounts ADD COLUMN material_id TEXT DEFAULT ''");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 7");
|
||
qDebug() << "[DB] Migration 7 applied";
|
||
}
|
||
|
||
// ── Версия 8: receipt_image в transactions ──────────────────────────────────
|
||
if (version < 8) {
|
||
db.transaction();
|
||
|
||
if (!columnExists(q, "transactions", "receipt_image")) {
|
||
q.exec("ALTER TABLE transactions ADD COLUMN receipt_image BLOB");
|
||
}
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 8");
|
||
qDebug() << "[DB] Migration 8 applied";
|
||
}
|
||
|
||
// ── Версия 9: таблица banks ───────────────────────────────────────────────
|
||
if (version < 9) {
|
||
db.transaction();
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
alias TEXT UNIQUE,
|
||
name TEXT,
|
||
nspk_name TEXT
|
||
))");
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 9");
|
||
qDebug() << "[DB] Migration 9 applied";
|
||
}
|
||
|
||
// ── Версия 10: таблица app_logs ───────────────────────────────────────────
|
||
if (version < 10) {
|
||
db.transaction();
|
||
|
||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
source TEXT NOT NULL DEFAULT '',
|
||
device_id TEXT NOT NULL DEFAULT '',
|
||
message TEXT NOT NULL,
|
||
screenshot BLOB,
|
||
timestamp TEXT NOT NULL
|
||
))");
|
||
|
||
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||
|
||
db.commit();
|
||
q.exec("PRAGMA user_version = 10");
|
||
qDebug() << "[DB] Migration 10 applied";
|
||
}
|
||
}
|
||
|
||
void DatabaseManager::closeConnection() {
|
||
QMutexLocker locker(&mutex);
|
||
if (const QString name = connectionName(); connectionPool.contains(name)) {
|
||
connectionPool.remove(name);
|
||
QSqlDatabase::removeDatabase(name);
|
||
qDebug() << "Соединение" << name << "удалено вручную";
|
||
}
|
||
}
|