# События и задачи [← Главная](index.md) Описание полного цикла задачи: от получения из API до завершения и отчёта. --- ## Ключевые файлы | Файл | Роль | |------|------| | `services/EventHandler.h/cpp` | Оркестратор: поллинг, сохранение в БД, запуск потоков | | `services/net/NetworkService.h/cpp` | HTTP-клиент: `getTasks()`, `postTaskResult()` | | `database/dao/EventDAO.h/cpp` | CRUD для таблицы `events` | | `models/db/EventInfo.h` | Модель задачи | --- ## Жизненный цикл задачи ``` Wait → InProgress → Complete ↘ Error ``` --- ## Схема работы ### 1. Получение задач (каждые 5 секунд) ``` QTimer (5s) └─→ NetworkService::getTasks() └─→ GET /api/v1/task/get_tasks (Bearer token) └─→ signal tasksReceived(QJsonArray) └─→ EventHandler::onTasksReceived() ``` Формат ответа API: ```json { "success": true, "data": [ { "bank_name": "ozon", "material_id": "c340cd2e-cefd-48d6-a813-c4be097ae493", "type": "FETCH_PROFILE", "body": {} }, { "bank_name": "birbank", "material_id": "a1b2c3d4-0000-0000-0000-000000000001", "type": "FETCH_TRANSACTIONS", "body": {} }, { "bank_name": "ozon", "material_id": "a1b2c3d4-0000-0000-0000-000000000002", "type": "CREATE_TRANSACTION", "body": { "amount": 1000, "currency": 643 } } ] } ``` > `material_id` — UUID строка. Поддерживается также числовой формат для совместимости (парсится через `task["material_id"].isString() ? toString() : QString::number(toInt())`). ### 2. Обработка задачи (`onTasksReceived`) ``` Для каждой задачи из API: ├─ existsActiveByExternalId(material_id)? │ YES → пропустить (дедупликация) │ NO → продолжить │ ├─ AccountDAO::getAccountsByAppCode(bank_name) │ найти аккаунт где materialId == material_id │ НЕ НАЙДЕН → sendTaskResult(status=error) и пропустить │ └─ EventDAO::insertEvent(status=Wait) ``` Маппинг типов задач: | Тип в API | `EventType` в коде | Скрипт | |-----------|--------------------|--------| | `FETCH_PROFILE` | `FetchProfile` | `Ozon::LoginAndCheckAccountsScript` | | `FETCH_TRANSACTIONS` | `FetchTransactions` | `GetLastDaysHistoryBirbankScript` | | `CREATE_TRANSACTION` | `CreateTransaction` | *(не реализован)* | ### 3. Запуск задач (тот же `onTasksReceived`) ``` Для каждого онлайн устройства: ├─ m_threads[deviceId] запущен? → пропустить │ (одно устройство = один поток одновременно) │ ├─ EventDAO::getFirstWaitEventByDeviceId(deviceId) │ самая старая Wait-задача (ORDER BY timestamp ASC) │ ├─ ApplicationDAO::getApplication() → статус == "active"? │ ├─ EventDAO::updateEvent(id, InProgress) │ └─ startThreadForTask(account, event) ``` ### 4. Выполнение скрипта (QThread) ``` QThread └─→ Script::start() ├─ ADB команды (adb shell, screenshot, xml dump) ├─ ScreenXmlParser (парсинг UI) ├─ Tesseract OCR (при необходимости) └─ emit finishedWithResult(error или "") └─→ EventHandler::updateEventThread() ``` > **Watchdog:** если скрипт не завершился за **5 минут** — поток принудительно завершается. ### 5. Завершение задачи (`updateEventThread`) ``` result == "" → EventDAO::updateEvent(Complete) result != "" → EventDAO::updateEvent(Error, message) └─→ sendEventStatus() (новый QThread) └─→ POST /api/v1/event/status ⚠️ устаревший эндпоинт, требует замены на task_result ``` --- ## Отправка результата задачи Используется `POST /api/v1/task/task_result` через `NetworkService::postTaskResult()`. ### При ненайденном аккаунте (material_id не в БД) ```json { "type": "FETCH_PROFILE", "material_id": 42, "bank_name": "ozon", "data": { "status": "error", "description": "Account not found for material_id: 42" } } ``` ### При успешном выполнении — FETCH_PROFILE ```json { "type": "FETCH_PROFILE", "material_id": 42, "bank_name": "ozon", "data": { "bank_account_id": "string", "currency": 643, "amount": 15000, "extra": {} } } ``` ### При успешном выполнении — FETCH_TRANSACTIONS ```json { "type": "FETCH_TRANSACTIONS", "material_id": 7, "bank_name": "birbank", "data": [ { "bank_transaction_id": "string", "amount": 500, "currency": 944, "transaction_type": "bank", "bank_participator_id": "string", "status": "completed", "extra": {}, "created_at": "2024-01-01T12:00:00" } ] } ``` ### При ошибке выполнения скрипта ```json { "type": "FETCH_PROFILE", "material_id": 42, "bank_name": "ozon", "data": { "status": "error", "description": "Cant open the home screen: emulator-5554 ozon" } } ``` --- ## Модель `EventInfo` ```cpp struct EventInfo { int id; // локальный ID в SQLite int externalId; // material_id из API int accountId; // FK → accounts QString deviceId; // ADB device id double amount; QString phone; QString bankName; QString comment; QByteArray screenshot; // последний скриншот экрана EventStatus status; // Wait / InProgress / Complete / Error EventType type; // FetchProfile / FetchTransactions / CreateTransaction QDateTime timestamp; QDateTime updateTime; QDateTime completeTime; }; ``` --- ## Дедупликация `EventDAO::existsActiveByExternalId(int externalId)` — проверяет наличие записи с `status IN ('wait', 'iPprogress')` для данного `external_id`. Гарантирует что одна и та же задача не попадёт в очередь дважды пока не завершится предыдущее выполнение. --- ## Ограничения - **1 поток на устройство** — пока устройство занято, остальные задачи ждут следующего поллинга - **Очерёдность** — внутри устройства задачи выполняются по `timestamp ASC` - **Интервал поллинга** — 5 секунд (`m_pollTimer->setInterval`) - **Пути API** — настраиваются в `config.ini`: - `net/pathGetTasks` (дефолт `/api/v1/task/get_tasks`) - `net/pathTaskResult` (дефолт `/api/v1/task/task_result`)