1
0
forked from BRT/arc
arc/services/EventHandler.cpp
slava afbf3a3afb Add Ozon pay_to_new_number and pay_to_old_number XML assets
Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
2026-03-01 09:30:59 +07:00

268 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 int 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();
}