docs: add partial updates design spec for VikonIntegration module
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
# VikonPartialUpdates — Частичные обновления модулей VIKON
|
||||
|
||||
## [S1] Проблема
|
||||
|
||||
Текущий модуль `VikonIntegration` поддерживает только полное обновление ядра модуля (`UpdateCoreAction`) и синхронизацию файлов через FM (`SyncFilesAction`). Частичные обновления (обновление отдельных частей модуля — common, struct, document...) не реализованы.
|
||||
|
||||
В `vikon_core` частичные обновления реализованы через 4-шаговый flow:
|
||||
1. Запрос генерации части на сервере
|
||||
2. Polling статуса генерации
|
||||
3. Проверка готовности
|
||||
4. Скачивание и применение
|
||||
|
||||
Нужно перенести эту логику в VikonIntegration с учётом архитектуры Porto.
|
||||
|
||||
---
|
||||
|
||||
## [S2] Обзор решения
|
||||
|
||||
**Backend-driven polling**: фронт отправляет один запрос `POST /update-part`, backend выполняет всю цепочку синхронно (request → polling → download → apply), возвращает результат. `HttpTask` уже устанавливает `set_time_limit(600)`, поэтому запрос может блокироваться до 10 минут — это достаточно для polling flow. Фронт показывает спиннер во время ожидания.
|
||||
|
||||
**Полная поддержка**: обычные части (atomic swap папки) + ABITUR (пофайловая синхронизация).
|
||||
|
||||
---
|
||||
|
||||
## [S3] Backend компоненты
|
||||
|
||||
### Новый Action: `UpdatePartAction`
|
||||
|
||||
**Расположение:** `app/Containers/VikonIntegration/Actions/UpdatePartAction.php`
|
||||
|
||||
**Зависимости:** `HttpTask`, `FilesystemTask`, `PollPartStatusTask`
|
||||
|
||||
**Интерфейс:**
|
||||
```php
|
||||
public function run(int $moduleId, string $part, string $accessToken): array
|
||||
```
|
||||
|
||||
**Алгоритм:**
|
||||
1. Валидация `moduleId` и `part` (whitelist из конфига)
|
||||
2. Запрос генерации: `POST pull_updates/requestGeneratePartByNewCoreJson` с `part`
|
||||
3. Получение `operation_identity` и `ttl`
|
||||
4. Polling статуса через `PollPartStatusTask` (`GET pull_updates/getStatusPartGenerationByNewCoreJson`)
|
||||
5. Проверка готовности: `POST pull_updates/checkPartGenerationByNewCoreResultJson`
|
||||
6. Скачивание ZIP: `GET pull_updates/downloadPartByNewCoreResult?operation_identity=...&part=...`
|
||||
7. Распаковка во временную директорию
|
||||
8. Определение moduleId части (по `$allowedFoldersInCoreByModule`)
|
||||
9. Применение:
|
||||
- **Обычная часть**: atomic swap всей папки
|
||||
- **ABITUR**: пофайловая синхронизация всех entry
|
||||
10. `cleanUnitCore` с исключениями
|
||||
11. Очистка temp файлов
|
||||
|
||||
**Возвращаемое значение:**
|
||||
```php
|
||||
['success' => true, 'message' => '...', 'synced_count' => int]
|
||||
```
|
||||
|
||||
### Новый Task: `PollPartStatusTask`
|
||||
|
||||
**Расположение:** `app/Containers/VikonIntegration/Tasks/PollPartStatusTask.php`
|
||||
|
||||
**Интерфейс:**
|
||||
```php
|
||||
public function run(string $operationIdentity, string $accessToken, int $ttl = 120): array
|
||||
```
|
||||
|
||||
**Алгоритм:**
|
||||
1. Отправка GET `pull_updates/getStatusPartGenerationByNewCoreJson` с `operation_identity`
|
||||
2. Если `status === "completed"` → возврат `['status' => 'completed']`
|
||||
3. Если `status === "failed"` → возврат `['status' => 'failed', 'error' => ...]`
|
||||
4. Если `status === "pending"` и время < `ttl` → sleep(3) и повтор
|
||||
5. Если timeout → возврат `['status' => 'timeout']`
|
||||
|
||||
**Конфигурация:**
|
||||
- Интервал polling: 3 секунды (configurable через конфиг)
|
||||
- Max attempts: `ceil(ttl / interval)` + 2 (запас)
|
||||
|
||||
### Модификация `FilesystemTask`
|
||||
|
||||
Добавить метод `atomicSwap()`:
|
||||
```php
|
||||
public function atomicSwap(
|
||||
string $newEntryPath,
|
||||
string $currentEntryPath,
|
||||
string $baseDir,
|
||||
int $moduleId
|
||||
): bool
|
||||
```
|
||||
|
||||
**Алгоритм:**
|
||||
1. Если `$currentEntryPath` существует:
|
||||
- Удалить `$currentEntryPath_new` если есть
|
||||
- Переместить `$newEntryPath` → `$currentEntryPath_new`
|
||||
- Удалить `$currentEntryPath_old` если есть
|
||||
- Переместить `$currentEntryPath` → `$currentEntryPath_old`
|
||||
- Переместить `$currentEntryPath_new` → `$currentEntryPath`
|
||||
2. Если не существует:
|
||||
- Переместить `$newEntryPath` → `$currentEntryPath`
|
||||
|
||||
### Модификация `VikonController`
|
||||
|
||||
Добавить метод:
|
||||
```php
|
||||
public function updatePart(UpdatePartRequest $request): JsonResponse
|
||||
```
|
||||
|
||||
**Маршрут:** `POST /dashboard/vikon-updates/update-part`
|
||||
|
||||
**Middleware:** `access-check`, `dashboard.auth`, `throttle:10,1`, `vikon.refresh`
|
||||
|
||||
---
|
||||
|
||||
## [S4] Frontend компоненты
|
||||
|
||||
### Модификация `Index.vue`
|
||||
|
||||
**Новые Props:**
|
||||
- `available_parts`: объект с доступными частями по модулям
|
||||
|
||||
**Новые UI-элементы:**
|
||||
- Выпадающий список частей для выбранного модуля
|
||||
- Кнопка "Обновить часть" рядом с каждым модулем
|
||||
- Прогресс-бар с логом во время обновления
|
||||
- Индикатор загрузки (спиннер) во время polling
|
||||
|
||||
**Data flow:**
|
||||
1. Пользователь выбирает модуль → показываются доступные части
|
||||
2. Пользователь нажимает "Обновить" → отправляется `POST /update-part`
|
||||
3. Фронт показывает спиннер/прогресс-бар пока backend выполняет цепочку
|
||||
4. Получает ответ → показывает результат (успех/ошибка)
|
||||
|
||||
---
|
||||
|
||||
## [S5] API эндпоинты
|
||||
|
||||
### `POST /dashboard/vikon-updates/update-part`
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"module_id": 1,
|
||||
"part": "common"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (success):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Часть 'common' успешно обновлена.",
|
||||
"operation_identity": "...",
|
||||
"synced_count": 15
|
||||
}
|
||||
```
|
||||
|
||||
**Response (error):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Ошибка при генерации части: timeout"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [S6] ABITUR special case
|
||||
|
||||
Для ABITUR (moduleId=2) в шаге 4 используется пофайловая синхронизация:
|
||||
|
||||
1. Сканирует содержимое распакованного ZIP
|
||||
2. Для каждого entry (файл/папка) делает atomic swap через `FilesystemTask::atomicSwap()`
|
||||
3. Исключает `files/` и `.htaccess` из `cleanUnitCore`
|
||||
4. Не создаёт `sveden/update/index.php` redirect (это только для SVEDEN)
|
||||
|
||||
---
|
||||
|
||||
## [S7] Ошибки и откат
|
||||
|
||||
| Шаг | Ошибка | Действие |
|
||||
|-----|--------|----------|
|
||||
| Запрос генерации | HTTP ошибка | Возврат ошибки клиенту |
|
||||
| Polling | Timeout (> ttl) | Возврат ошибки, очистка temp |
|
||||
| Проверка готовности | Часть не готова | Возврат ошибки |
|
||||
| Скачивание ZIP | HTTP ошибка | Возврат ошибки |
|
||||
| Распаковка | Невалидный ZIP | Возврат ошибки |
|
||||
| Atomic swap | Ошибка rename | `restoreUnitCoreAfterFail()` → откат через `_old` суффиксы |
|
||||
| cleanUnitCore | Ошибка удаления | Логирование, продолжение |
|
||||
|
||||
---
|
||||
|
||||
## [S8] Конфигурация
|
||||
|
||||
Добавить в `config/vikon.php`:
|
||||
|
||||
```php
|
||||
'parts' => [
|
||||
1 => ['common', 'struct', 'document', 'education', 'managers', 'employees', 'objects', 'paid_edu', 'budget', 'vacant', 'grants', 'inter', 'catering', 'eduStandarts', 'corruption', 'antiterrorism'],
|
||||
2 => ['abitur'],
|
||||
6 => ['general', 'structure', 'faq', 'procedures', 'results-and-reports', 'plans', 'survey'],
|
||||
],
|
||||
'poll_interval' => 3,
|
||||
'poll_max_attempts' => 50,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [S9] Зависимости
|
||||
|
||||
- Существующие: `HttpTask`, `FilesystemTask`, `ValidateTokenTask`
|
||||
- Новые: `PollPartStatusTask`, `UpdatePartAction`
|
||||
- Frontend: Vue 3 Composition API, Inertia.js
|
||||
|
||||
---
|
||||
|
||||
## [S10] Тестирование
|
||||
|
||||
1. **Unit-тест `PollPartStatusTask`**: mock HTTP, проверка polling loop
|
||||
2. **Unit-тест `FilesystemTask::atomicSwap()`**: проверка 3-шагового flow
|
||||
3. **Integration-тест `UpdatePartAction`**: mock API responses, проверка полного flow
|
||||
4. **Manual test**: подключение к реальному API VIKON, обновление одной части
|
||||
Reference in New Issue
Block a user