Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
238 lines
7.4 KiB
Markdown
238 lines
7.4 KiB
Markdown
# События и задачи
|
||
|
||
[← Главная](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::findByMaterialId(material_id)
|
||
│ прямой SELECT WHERE 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": "c340cd2e-cefd-48d6-a813-c4be097ae493",
|
||
"bank_name": "ozon",
|
||
"data": {
|
||
"status": "error",
|
||
"description": "Account not found for material_id: c340cd2e-cefd-48d6-a813-c4be097ae493"
|
||
}
|
||
}
|
||
```
|
||
|
||
### При успешном выполнении — FETCH_PROFILE
|
||
|
||
```json
|
||
{
|
||
"type": "FETCH_PROFILE",
|
||
"material_id": "c340cd2e-cefd-48d6-a813-c4be097ae493",
|
||
"bank_name": "ozon",
|
||
"data": {
|
||
"bank_account_id": "string",
|
||
"currency": 643,
|
||
"amount": 15000,
|
||
"extra": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
### При успешном выполнении — FETCH_TRANSACTIONS
|
||
|
||
```json
|
||
{
|
||
"type": "FETCH_TRANSACTIONS",
|
||
"material_id": "a1b2c3d4-0000-0000-0000-000000000001",
|
||
"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": "c340cd2e-cefd-48d6-a813-c4be097ae493",
|
||
"bank_name": "ozon",
|
||
"data": {
|
||
"status": "error",
|
||
"description": "Cant open the home screen: emulator-5554 ozon"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Модель `EventInfo`
|
||
|
||
```cpp
|
||
struct EventInfo {
|
||
int id; // локальный ID в SQLite
|
||
QString externalId; // material_id из API (UUID строка)
|
||
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(const QString &externalId)` — проверяет наличие записи
|
||
с `status IN ('wait', 'inprogress')` для данного `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`)
|