1
0
forked from BRT/arc
arc/services/EventHandler.cpp
trnsmkot 0a31b4d717 Implement Event Type Handling and Status Updates
Added handling for event types (Balance, Phone) and respective processing logic in EventHandler. Introduced support for tracking and updating event statuses both locally and through network APIs. Enhanced data integrity and clarity by expanding the EventInfo model and associated database management.
2025-06-03 22:02:29 +07:00

209 lines
6.8 KiB
C++

#include "EventHandler.h"
#include <QJsonObject>
#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 "rshb/CommonScript.h"
#include "rshb/GetLastDaysHistoryScript.h"
#include "rshb/LoginAndCheckAccountsScript.h"
#include "rshb/PayByPhoneScript.h"
EventHandler::EventHandler(QObject *parent) : QObject(parent) {
m_checkTimer = new QTimer(this);
m_checkTimer->setInterval(1000);
connect(m_checkTimer, &QTimer::timeout, this, &EventHandler::checkAndRestart);
}
EventHandler::~EventHandler() = default;
void EventHandler::start() {
m_devices = DeviceDAO::getOnlineDeviceCodes();
clearAll();
// Запустим сразу потоки для всех ключей
for (auto &key: m_devices) {
startThreadForKey(key);
}
m_checkTimer->start();
}
void EventHandler::checkAndRestart() {
if (!m_running) {
return;
}
m_devices = DeviceDAO::getOnlineDeviceCodes();
for (const auto &key: m_devices) {
const QThread *thr = m_threads.value(key, nullptr);
if (!thr || !thr->isRunning()) {
startThreadForKey(key);
}
}
}
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) {
if (!result.isEmpty()) {
if (!EventDAO::updateEvent(event.id, EventStatus::Error, result)) {
qCritical() << "Cant update event: " << event.id;
} else {
sendEventStatus(
event.deviceId,
event.externalId,
event.id,
eventStatusToString(EventStatus::Error),
result
);
}
} else {
if (!EventDAO::updateEvent(event.id, EventStatus::Complete, "")) {
qCritical() << "Cant update event: " << event.id;
} else {
sendEventStatus(
event.deviceId,
event.externalId,
event.id,
eventStatusToString(EventStatus::Complete),
""
);
}
}
if (const auto tm = m_timers.take(key)) {
tm->stop();
tm->deleteLater();
}
m_threads.remove(key);
}
void EventHandler::startThreadForKey(const QString &key) {
const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(key);
if (event.id == -1) {
return;
}
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
const ApplicationInfo app = ApplicationDAO::getApplication(event.deviceId, account.appCode);
if (app.status != "active") {
return;
}
if (!EventDAO::updateEvent(event.id, EventStatus::InProgress, "")) {
qCritical() << "Cant update event: " << event.id;
return;
}
const auto thr = new QThread(this);
// qDebug() << "Start thread for key" << eventTypeToString(event.type);
if (event.type == EventType::Balance) {
auto *worker = new GetLastDaysHistoryScript(account);
worker->moveToThread(thr);
connect(thr, &QThread::started, worker, &GetLastDaysHistoryScript::start);
connect(worker, &GetLastDaysHistoryScript::finished, thr, &QThread::quit);
connect(worker, &GetLastDaysHistoryScript::finished, worker, &QObject::deleteLater);
connect(thr, &QThread::finished, thr, &QObject::deleteLater);
connect(worker, &GetLastDaysHistoryScript::finishedWithResult, this,
[this, key, event](const QString &result) {
updateEventThread(key, event, result);
}
);
}
if (event.type == EventType::Phone) {
auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount);
worker->moveToThread(thr);
connect(thr, &QThread::started, worker, &PayByPhoneScript::start);
connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit);
connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater);
connect(thr, &QThread::finished, thr, &QObject::deleteLater);
connect(worker, &PayByPhoneScript::finishedWithResult, this,
[this, key, event](const QString &result) {
updateEventThread(key, event, result);
}
);
}
// Таймер на 5 минут — если не успел finish(), убить и перезапустить
const auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this, key]() {
qWarning() << "Timeout for key" << key << "- killing stuck thread.";
if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) {
thread->requestInterruption(); // мягкое прерывание
if (!thread->wait(500)) {
qWarning() << "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;
m_checkTimer->stop();
clearAll();
}