Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
268 lines
11 KiB
C++
268 lines
11 KiB
C++
#include "EventHandler.h"
|
||
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QJsonArray>
|
||
#include <QMap>
|
||
#include <QThread>
|
||
#include <QTimer>
|
||
|
||
#include "dao/AccountDAO.h"
|
||
#include "dao/ApplicationDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dao/EventDAO.h"
|
||
#include "db/ApplicationInfo.h"
|
||
#include "net/NetworkService.h"
|
||
#include "birbank/GetLastDaysHistoryBirbankScript.h"
|
||
#include "birbank/LoginAndCheckAccountsBirbankScript.h"
|
||
#include "ozon/GetLastTransactionsScript.h"
|
||
#include "ozon/LoginAndCheckAccountsScript.h"
|
||
#include "rshb/GetLastDaysHistoryScript.h"
|
||
#include "rshb/LoginAndCheckAccountsScript.h"
|
||
#include "toss/GetLastDaysHistoryTossScript.h"
|
||
#include "toss/LoginAndCheckAccountsTossScript.h"
|
||
#include "ziraat/GetLastDaysHistoryZiraatScript.h"
|
||
#include "ziraat/LoginAndCheckAccountsZiraatScript.h"
|
||
|
||
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName, const QJsonObject &data);
|
||
|
||
EventHandler::EventHandler(QObject *parent) : QObject(parent) {}
|
||
|
||
EventHandler::~EventHandler() = default;
|
||
|
||
void EventHandler::start() {
|
||
m_network = new NetworkService(this);
|
||
connect(m_network, &NetworkService::tasksReceived, this, &EventHandler::onTasksReceived);
|
||
|
||
m_pollTimer = new QTimer(this);
|
||
m_pollTimer->setInterval(5000);
|
||
connect(m_pollTimer, &QTimer::timeout, m_network, &NetworkService::getTasks);
|
||
m_pollTimer->start();
|
||
|
||
// Первый запрос сразу при старте
|
||
m_network->getTasks();
|
||
}
|
||
|
||
void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
||
if (!m_running) return;
|
||
|
||
// 1. Сохраняем новые задачи в БД
|
||
for (const auto &taskVal : tasks) {
|
||
const QJsonObject task = taskVal.toObject();
|
||
const QString bankName = task["bank_name"].toString();
|
||
const QString materialId = task["material_id"].isString()
|
||
? task["material_id"].toString()
|
||
: QString::number(task["material_id"].toInt());
|
||
const QString typeStr = task["type"].toString();
|
||
|
||
const EventType eventType = eventTypeFromString(typeStr);
|
||
|
||
if (eventType == EventType::Unknown) {
|
||
qWarning() << "[EventHandler] Unknown task type:" << typeStr;
|
||
continue;
|
||
}
|
||
|
||
// Пропускаем если уже есть активная запись с таким external_id
|
||
if (EventDAO::existsActiveByExternalId(materialId)) continue;
|
||
|
||
// Ищем аккаунт по material_id напрямую
|
||
const AccountInfo account = AccountDAO::findByMaterialId(materialId);
|
||
if (account.id == -1) {
|
||
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
||
QJsonObject data;
|
||
data["status"] = "error";
|
||
data["description"] = "Account not found for material_id: " + materialId;
|
||
sendTaskResult(typeStr, materialId, bankName, data);
|
||
continue;
|
||
}
|
||
|
||
EventInfo event;
|
||
event.status = EventStatus::Wait;
|
||
event.type = eventType;
|
||
event.externalId = materialId;
|
||
event.accountId = account.id;
|
||
event.deviceId = account.deviceId;
|
||
event.bankName = bankName;
|
||
event.timestamp = QDateTime::currentDateTimeUtc();
|
||
|
||
if (EventDAO::insertEvent(event) == -1) {
|
||
qCritical() << "[EventHandler] Failed to insert event, external_id:" << materialId;
|
||
}
|
||
}
|
||
|
||
// 2. Для каждого онлайн устройства без активного потока — запускаем следующую задачу
|
||
const QStringList onlineDevices = DeviceDAO::getOnlineDeviceCodes();
|
||
for (const QString &deviceId : onlineDevices) {
|
||
if (const QThread *thr = m_threads.value(deviceId, nullptr); thr && thr->isRunning()) continue;
|
||
|
||
const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(deviceId);
|
||
if (event.id == -1) continue;
|
||
|
||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, account.appCode);
|
||
if (app.status != "active") continue;
|
||
|
||
if (!EventDAO::updateEvent(event.id, EventStatus::InProgress, "")) {
|
||
qCritical() << "[EventHandler] Failed to set InProgress, event id:" << event.id;
|
||
continue;
|
||
}
|
||
|
||
startThreadForTask(account, event);
|
||
}
|
||
}
|
||
|
||
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName,
|
||
const QJsonObject &data) {
|
||
QJsonObject obj;
|
||
obj["type"] = type;
|
||
obj["material_id"] = materialId; // UUID string
|
||
obj["bank_name"] = bankName;
|
||
obj["data"] = data;
|
||
|
||
auto *thread = new QThread;
|
||
auto *networkService = new NetworkService;
|
||
networkService->moveToThread(thread);
|
||
networkService->setPayload(QJsonDocument(obj).toJson());
|
||
QObject::connect(thread, &QThread::started, networkService, &NetworkService::postTaskResult);
|
||
QObject::connect(networkService, &NetworkService::finished, thread, &QThread::quit);
|
||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||
thread->start();
|
||
}
|
||
|
||
static void sendEventStatus(const QString &deviceId, const QString &externalId, const int eventId,
|
||
const QString &status, const QString &error) {
|
||
QJsonObject obj;
|
||
obj["id"] = externalId;
|
||
obj["externalId"] = eventId;
|
||
obj["status"] = status;
|
||
obj["error"] = error;
|
||
|
||
auto *paymentThread = new QThread;
|
||
auto *networkService = new NetworkService;
|
||
networkService->moveToThread(paymentThread);
|
||
networkService->setDeviceId(deviceId);
|
||
networkService->setPayload(QJsonDocument(obj).toJson());
|
||
QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::postEventStatus);
|
||
QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit);
|
||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||
QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||
paymentThread->start();
|
||
}
|
||
|
||
void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QString &result) {
|
||
const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
|
||
|
||
if (!EventDAO::updateEvent(event.id, newStatus, result)) {
|
||
qCritical() << "[EventHandler] Failed to update event:" << event.id;
|
||
} else {
|
||
sendEventStatus(
|
||
event.deviceId,
|
||
event.externalId,
|
||
event.id,
|
||
eventStatusToString(newStatus),
|
||
result
|
||
);
|
||
}
|
||
|
||
if (const auto tm = m_timers.take(key)) {
|
||
tm->stop();
|
||
tm->deleteLater();
|
||
}
|
||
m_threads.remove(key);
|
||
}
|
||
|
||
void EventHandler::startThreadForTask(const AccountInfo &account, const EventInfo &event) {
|
||
const auto thr = new QThread(this);
|
||
const QString key = account.deviceId;
|
||
|
||
// Generic helper: подключает сигналы и переносит worker в поток
|
||
auto setup = [&](auto *worker) {
|
||
using W = std::remove_pointer_t<decltype(worker)>;
|
||
worker->moveToThread(thr);
|
||
connect(thr, &QThread::started, worker, &W::start);
|
||
connect(worker, &W::finished, thr, &QThread::quit);
|
||
connect(worker, &W::finished, worker, &QObject::deleteLater);
|
||
connect(thr, &QThread::finished, thr, &QObject::deleteLater);
|
||
connect(worker, &W::finishedWithResult, this,
|
||
[this, key, event](const QString &result) {
|
||
updateEventThread(key, event, result);
|
||
}
|
||
);
|
||
};
|
||
|
||
const QString &appCode = account.appCode;
|
||
const QString &deviceId = account.deviceId;
|
||
|
||
if (event.type == EventType::FetchProfile) {
|
||
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(deviceId, appCode));
|
||
else if (appCode == "birbank") setup(new LoginAndCheckAccountsBirbankScript(deviceId, appCode));
|
||
else if (appCode == "rshb") setup(new Rshb::LoginAndCheckAccountsScript(deviceId, appCode));
|
||
else if (appCode == "toss") setup(new LoginAndCheckAccountsTossScript(deviceId, appCode));
|
||
else if (appCode == "ziraat") setup(new LoginAndCheckAccountsZiraatScript(deviceId, appCode));
|
||
else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode;
|
||
}
|
||
|
||
if (event.type == EventType::FetchTransactions) {
|
||
if (appCode == "birbank") setup(new GetLastDaysHistoryBirbankScript(account));
|
||
else if (appCode == "ozon") setup(new Ozon::GetLastTransactionsScript(deviceId, appCode));
|
||
else if (appCode == "rshb") setup(new Rshb::GetLastDaysHistoryScript(account));
|
||
else if (appCode == "toss") setup(new GetLastDaysHistoryTossScript(account));
|
||
else if (appCode == "ziraat") setup(new GetLastDaysHistoryZiraatScript(account));
|
||
else qWarning() << "[EventHandler] FETCH_TRANSACTIONS: unknown appCode:" << appCode;
|
||
}
|
||
|
||
if (event.type == EventType::CreateTransaction) {
|
||
// TODO: реализовать CREATE_TRANSACTION
|
||
}
|
||
|
||
// Watchdog-таймер: 5 минут — убиваем зависший поток
|
||
const auto timer = new QTimer(this);
|
||
timer->setSingleShot(true);
|
||
connect(timer, &QTimer::timeout, this, [this, key]() {
|
||
qWarning() << "[EventHandler] Timeout for key" << key << "- killing stuck thread.";
|
||
if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) {
|
||
thread->requestInterruption();
|
||
if (!thread->wait(500)) {
|
||
qWarning() << "[EventHandler] Hard terminate for key" << key;
|
||
thread->terminate();
|
||
thread->wait();
|
||
}
|
||
}
|
||
if (const auto tm = m_timers.take(key)) {
|
||
tm->deleteLater();
|
||
}
|
||
m_threads.remove(key);
|
||
});
|
||
|
||
m_threads[key] = thr;
|
||
m_timers[key] = timer;
|
||
|
||
timer->start(5 * 60 * 1000);
|
||
thr->start();
|
||
}
|
||
|
||
void EventHandler::clearAll() {
|
||
for (QThread *thread : m_threads) {
|
||
if (thread->isRunning()) {
|
||
thread->requestInterruption();
|
||
thread->terminate();
|
||
thread->wait();
|
||
}
|
||
thread->deleteLater();
|
||
}
|
||
m_threads.clear();
|
||
|
||
for (QTimer *timer : m_timers) {
|
||
timer->stop();
|
||
timer->deleteLater();
|
||
}
|
||
m_timers.clear();
|
||
}
|
||
|
||
void EventHandler::stop() {
|
||
m_running = false;
|
||
if (m_pollTimer) m_pollTimer->stop();
|
||
clearAll();
|
||
}
|