# NetworkService — полная карта API-вызовов [← Главная](../index.md) Все HTTP-вызовы, выполняемые приложением. Класс `NetworkService` (`services/net/NetworkService.h/cpp`). --- ## Аутентификация | Метод | Путь | Слот | Описание | |-------|------|------|----------| | POST | `/api/v1/auth/login` | `loginAndSetup()` | Вход по `api_key`, получение токенов | | POST | `/api/v1/auth/refresh_token` | `refreshToken()` (private) | Обновление access_token | Токены сохраняются в `config.ini`. Авто-рефреш за 30 минут до истечения — `scheduleNextRefresh()`. --- ## Desktop | Метод | Путь | Слот | Описание | |-------|------|------|----------| | POST | `/api/v1/desktop/` | `loginAndSetup()` | Создание desktop при первом запуске (если `desktop_id` не сохранён) | Ответ: сохраняет `desktop_id` и `profile_id` в `config.ini`. --- ## Device | Метод | Путь | Слот | Описание | |-------|------|------|----------| | POST | `/api/v1/device/` | `createDevice()` | Регистрация нового Android устройства | | PATCH | `/api/v1/device/{api_id}` | `updateDevice()` | Обновление статуса/батареи устройства | | POST | `/api/v1/device/add_screenshot/{api_id}` | `postDeviceScreenshot()` | Загрузка скриншота (`multipart/form-data`, `image/jpeg`) | `api_id` — UUID, получаемый при создании и сохраняемый в таблице `devices`. --- ## Profile / Material | Метод | Путь | Слот | Описание | |-------|------|------|----------| | POST | `/api/v1/profile/bank_profile` | `postBankProfile()` | Создать банк-профиль | | PATCH | `/api/v1/profile/bank_profile/{id}` | `patchBankProfile()` | Обновить банк-профиль | | POST | `/api/v1/profile/material` | `postMaterial()` | Создать материал (карту) | `postBankProfile` сохраняет `bank_profile_id` в таблице `applications`. `postMaterial` сохраняет `material_id` (UUID) в таблице `accounts`. --- ## Tasks | Метод | Путь | Слот | Auth | Описание | |-------|------|------|------|----------| | GET | `/api/v1/task/get_tasks` | `getTasks()` | Bearer | Получить очередь задач (поллинг каждые 5 сек) | | POST | `/api/v1/task/task_result` | `postTaskResult()` | Bearer | Отправить результат задачи | `getTasks()` — асинхронный (без QEventLoop), использует сигнал `tasksReceived(QJsonArray)`. `postTaskResult()` — вызывается из `sendTaskResult()` в `EventHandler` в отдельном QThread. --- ## Устаревшие эндпоинты (legacy) Используют старый `multipart/form-data` API с токеном в теле. Пути задаются в `config.ini` напрямую как полные URL. | Слот | Ключ в config.ini | Статус | |------|-------------------|--------| | `postEventStatus()` | `net/eventStatusUpdate` | ⚠️ устарел, используется в `updateEventThread` | | `postAppStatus()` | `net/appStatusUpdate` | используется при смене статуса приложения | | `insertTransactionData()` | `net/transactionInsert` | создание транзакции | | `updateTransactionData()` | `net/transactionUpdate` | обновление транзакции | | `getPayments()` | `net/paymentsGet` | ⚠️ старый поллинг задач (заменён на `getTasks`) | --- ## Конфигурация путей (`config.ini`) ```ini [net] apiBase = http://localhost:9999 pathAuthLogin = /api/v1/auth/login pathAuthRefresh = /api/v1/auth/refresh_token pathDesktopCreate = /api/v1/desktop/ pathDevice = /api/v1/device/ pathDeviceScreenshot = /api/v1/device/add_screenshot/ pathBankProfile = /api/v1/profile/bank_profile pathMaterial = /api/v1/profile/material pathGetTasks = /api/v1/task/get_tasks pathTaskResult = /api/v1/task/task_result ``` Если ключ отсутствует — используется значение по умолчанию (захардкожено в конструкторе `NetworkService`). --- ## Авторизация в запросах - **Bearer** — `Authorization: Bearer ` из `config.ini [common/access_token]` - **Legacy** — поле `token` в multipart-теле (из `config.ini [common/token]`) --- ## Таймауты и ретраи Каждый синхронный запрос крутится в локальном `QEventLoop` под охраной `QTimer`. Если таймер сработал раньше, чем `reply->finished` — это **client timeout**: запрос аборитится и повторяется заново. ### Параметры | Параметр | Значение | Где | |----------|----------|-----| | Лимит ожидания | **30 секунд** | `kDefaultTimeoutMs` — все запросы | | Лимит ожидания (dump) | **120 секунд** | `kDumpTimeoutMs` — только `postMonitoringDump` (большой zip-архив) | | Макс. попыток | **3** | `kMaxRequestAttempts` — 1 основная + 2 повтора | | Пауза между попытками | **0** | Ретрай идёт сразу после abort | Константы и helper `awaitReply()` — в анонимном namespace в `services/net/NetworkService.cpp`. ### Когда ретраим Ретрай срабатывает **только** при client timeout (наш `QTimer` сработал, reply не успел завершиться). На любых других исходах ретрая нет: | Исход | Ретрай? | Почему | |-------|---------|--------| | Client timeout (наш QTimer) | ✅ да | сеть моргнула — стоит попробовать | | HTTP 4xx (401/404/409/…) | ❌ нет | детерминированная ошибка, повтор не поможет | | HTTP 5xx | ❌ нет | сервер ответил — это бизнес-сбой, не транспортный | | `ConnectionRefused` / DNS / SSL | ❌ нет | повтор не починит инфраструктурную проблему | | `success: false` в JSON | ❌ нет | прикладная ошибка | После каждой неудачной попытки в лог пишется `Network timeout (POST) attempt N/3:
`; если все 3 попытки таймаутнулись — финальная строка `Timeout (POST) after 3 attempts:
` плюс запись в `GeneralLogDAO` (для `postJson`/`patchJson`). ### Покрытие методов | Метод | Ретрай | Примечание | |-------|--------|------------| | `postJson` | ✅ | | | `patchJson` | ✅ | | | `getJson` | ✅ | | | `deleteJson` | ✅ | | | `get(path, params)` | ✅ | используется `getPayments()` (legacy) | | `syncBanksIfEmpty()` инлайн-GET | ✅ | | | `postDeviceScreenshot()` | ✅ | multipart пересобирается на каждой попытке | | `postMonitoringLog()` | ✅ | multipart пересобирается на каждой попытке | | `postMonitoringDump()` | ✅ | timeout 120s; multipart пересобирается | | `post(QHttpMultiPart*, path)` | ❌ | multipart передаётся снаружи и удаляется вместе с `reply`; колл-сайты (`postEventStatus`/`insertTransactionData`/…) — legacy | | `getTasks()` | ❌ | асинхронный (без `QEventLoop`); таймаут просто аборитит reply | ### Идемпотентность ⚠️ POST-запросы, создающие ресурсы (`/desktop/`, `/device/`, `/profile/bank_profile`, `/profile/material`), при ретрае могут создать дубль, если первая попытка дошла до сервера, но ответ потерялся. Сейчас защиты от этого нет — полагаемся на то, что 3 timeout-а подряд это редкая ситуация. Если станет проблемой — добавлять идемпотентные ключи на стороне бэкенда.