#pragma once #include #include #include #include #include #include #include #include class QWidget; class NetworkService final : public QObject { Q_OBJECT public: explicit NetworkService(QObject *parent = nullptr); ~NetworkService() override; void setPayload(const QByteArray &json) { m_json = json; } void setDeviceId(const QString &deviceId) { m_deviceId = deviceId; } void addExtra(const QString &key, const QVariant &value); // Подвешивает QMessageBox с текстом ошибки от backend (HTTP 409 — "материал // заблокирован" и подобные) к ближайшему окну. Вызывать после `new NetworkService` // в UI-коде, до запуска треда. static void connectErrorDialog(QWidget *parent, NetworkService *net); public slots: void postAccountsData(); void postBankProfile(); void patchBankProfile(); void toggleBankProfile(); void postMaterial(); void patchMaterial(); void toggleMaterial(); void postDeviceScreenshot(); void getPayments(); void postAppStatus(); void postEventStatus(); void insertTransactionData(); void updateTransactionData(); void loginAndSetup(); void syncCurrentProfile(); // Включает периодическую синхронизацию статусов (60s). Должно быть вызвано // ОДИН раз для долгоживущего инстанса после firstPassDone от DeviceScreener. void startPeriodicSync(); // Обрабатывает переход устройства в офлайн (дебаунс-сигнал от DeviceScreener): // выключает bank_profile локально и на сервере. void onDeviceWentOffline(const QString &deviceId); // Обрабатывает переход устройства в онлайн: пушит текущее локальное // состояние профиля и материалов на сервер (локаль не меняем). void onDeviceWentOnline(const QString &deviceId); void createDevice(); void updateDevice(); void getTasks(const QStringList &materialIds = {}); void postTaskResult(); void postMonitoringLog(const QString &type, const QString &event, const QString &message, const QByteArray &image); void postMonitoringDump(const QString &text, const QByteArray &archive, const QByteArray &image, const QString &archiveFilename = QStringLiteral("task_dump.zip")); void start(); void stop(); void disableAllProfilesOnServer(); void deleteDeviceFromServer(); signals: void finished(); void finishedWithResult(int value); void tokenRefreshFailed(); void tasksReceived(QJsonArray tasks); // Эмитится при HTTP-ошибке PATCH-запроса (например, 409 'материал заблокирован'). // UI подписывается и показывает QMessageBox. void apiPatchError(int httpStatus, QString message); // healthy=false при kSyncFailureThreshold подряд неудачных тиков; healthy=true // при первом успехе после такой серии. consecutiveFailures даёт текущий счёт. void syncHealthChanged(bool healthy, int consecutiveFailures); private: QByteArray m_json; QString m_deviceId; QString m_token; QString m_accessToken; QString m_apiBase; QMap m_extraData = {}; bool m_running = true; bool m_tokenErrorShown = false; QString m_urlAccountUpdate; QString m_urlPaymentsGet; QString m_urlTransactionUpdate; QString m_urlAppStatusUpdate; QString m_urlTransactionInsert; QString m_urlEventStatusUpdate; QString m_pathAuthLogin; QString m_pathAuthRefresh; QString m_pathDesktopCreate; QString m_pathDevice; QString m_pathDeviceScreenshot; QString m_pathBankProfile; QString m_pathMaterial; QString m_pathGetTasks; QString m_pathTaskResult; QString m_pathMonitoringLog; QString m_pathMonitoringDump; QString m_monitoringBase; QString m_monitoringToken; QString m_pathCurrentProfile; QString m_pathProfileGetInfo; bool m_fetchingTasks = false; // Периодическая синхронизация bool m_periodicSyncEnabled = false; // включается только после firstPassDone std::atomic m_syncInProgress{false}; // защита от наложения тиков int m_consecutiveSyncFailures = 0; // счётчик подряд неуспешных тиков bool m_unhealthyEmitted = false; // чтобы не спамить syncHealthChanged static constexpr int kSyncFailureThreshold = 3; static constexpr int kPeriodicSyncMs = 60 * 1000; // Реестр мьютексов на пару (deviceId, bankProfileId) — общий для всех // инстансов NetworkService. Используется для сериализации PATCH-операций. static QMutex s_keyRegistryMutex; static QMap> s_keyMutexes; QNetworkAccessManager *m_manager; QJsonValue post(QHttpMultiPart *multiPart, const QString &path); QJsonArray get(const QString &path, const QMap ¶ms); QJsonValue getJson(const QString &path, bool withAuth = false); QJsonValue postJson(const QString &path, const QJsonObject &body, bool withAuth = false); QJsonValue patchJson(const QString &path, const QJsonObject &body, bool withAuth = false); QJsonValue deleteJson(const QString &path, bool withAuth = false); bool refreshToken(); bool ensureValidToken(); void scheduleNextRefresh(); void scheduleNextStatusSync(); bool runPeriodicSyncTick(); // делает один тик, возвращает true при успехе void syncDevicesFromServer(const QString &desktopId); void syncBankProfilesFromServer(); void syncBanksIfEmpty(); public: // Возвращает (лениво создавая) shared mutex для пары (deviceId, bankProfileId). // Используется через QMutexLocker для сериализации PATCH-ов по одному профилю. static QSharedPointer keyMutex(const QString &deviceId, const QString &bankProfileId); };