88 lines
7.4 KiB
Markdown
88 lines
7.4 KiB
Markdown
# 2026-09-06: Архитектурные решения Слоя 2 и границы независимых API слоёв
|
||
|
||
## 1. Контекст и цели
|
||
Зафиксированы ключевые проектные и архитектурные решения по разработке Слоя 2 в репозитории `upload-platform` перед началом реализации:
|
||
1. Полная автономность и независимость каждого слоя как отдельного API.
|
||
2. Легкость интеграции обоих слоёв (Слой 1 + Слой 2) в целевые сервисы (`drhider`, `contractor`) без переделки исходного кода ядра.
|
||
3. Составлено подробное ТЗ и резюме в файле `LAYER2-RESUME.md`.
|
||
|
||
---
|
||
|
||
## 2. Архитектурные границы слоёв и API-контракты
|
||
|
||
### Слой 1: File Picker (v0.1.13 — готов и протестирован)
|
||
- **Зона ответственности**: выбор отдельных файлов, выбор папок (`webkitdirectory`), клиентская рекурсивная распаковка ZIP-архивов с помощью `fflate`, дедупликация, фильтрация по расширениям, проверка лимитов, отрисовка древовидного UI.
|
||
- **API-контракт**: метод `picker.getFiles()` возвращает плоский массив стандартных объектов `File`. Слой полностью изолирован от транспорта и бэкенда.
|
||
|
||
### Слой 2: Транзитная доставка через ВМ-буфер в RAM бэкенда (текущая задача)
|
||
- **Причина существования**: Ingress-контроллер k8s-кластера (`pythonk8s`) обрывает входящие запросы с телом более 64 КБ. Прямой `POST` больших файлов в кластер невозможен; исходящий трафик (egress) из кластера не ограничен.
|
||
- **Принципиальные решения**:
|
||
1. **Пофайловый транзит (Per-File Transit)** вместо накопления всей пачки в буфере:
|
||
- Файл $k$ отправляется браузером через `PUT` в буфер на ВМ.
|
||
- Браузер сразу отправляет `POST /api/upload_refs` бэкенду во Flask для одного файла $k$.
|
||
- Бэкенд забирает файл исходящим потоковым `GET` прямо в RAM сессии (`upload/backend/session/state.py`).
|
||
- Бэкенд немедленно отправляет `DELETE` на ВМ.
|
||
- Файл на ВМ удалён, буфер чист. Браузер переходит к файлу $k+1$.
|
||
- В любой момент времени в буфере на ВМ находится максимум один файл.
|
||
2. **RAM-only (хранение строго в памяти)**:
|
||
- Никакой записи на диск ни на бэкенде, ни на ВМ в постоянном режиме.
|
||
3. **Zero Hardcode**:
|
||
- Полная параметризация через конфигурацию (`vmUploadUrl` на фронтенде, `vmUploadPrefix` на бэкенде для обязательной SSRF-валидации).
|
||
4. **Автономный mock-буфер**:
|
||
- In-memory mock WebDAV (`PUT`, `GET`, `DELETE`) внутри тестового стенда `site/app.py` для автономного прогона pytest и Playwright без внешней сети.
|
||
- **API-контракт фронтенда**: функция `uploadFilesViaVm(files, options)` с колбэками прогресса по файлу/пачке, завершения файла и поддержкой отмены (`AbortController`).
|
||
- **API-контракт бэкенда**: Blueprint `upload_refs` и менеджер сессий `session` (создание сессии, потоковое вытягивание, хранение в RAM, TTL, мягкая отмена).
|
||
|
||
### Слой 3: Бизнес-логика потребителей (не входит в текущую задачу)
|
||
- Сервисы `drhider` (обфускация, LLM) и `contractor` (парсинг, diff договоров) забирают файлы из оперативной памяти сессии через `session.get_files(session_id)`.
|
||
- В `upload-platform` Слой 3 эмулируется тестовым callback/эндпоинтом, фиксирующим успешный приём файла из сессии для дальнейшей обработки.
|
||
|
||
---
|
||
|
||
## 3. Файловая структура Слоя 2 в `upload-platform`
|
||
|
||
```
|
||
upload-platform/
|
||
├── upload/
|
||
│ ├── frontend/
|
||
│ │ ├── index.js # Слой 1 (initFilePicker)
|
||
│ │ ├── table/ # UI Слоя 1
|
||
│ │ ├── zip/ # fflate
|
||
│ │ └── upload/ # Слой 2 (Фронтенд):
|
||
│ │ ├── put_to_vm.js # XHR PUT одного файла на буфер с прогрессом и abort
|
||
│ │ └── upload_via_vm.js # Пофайловый транзит (PUT -> upload_refs -> repeat)
|
||
│ └── backend/ # Слой 2 (Бэкенд):
|
||
│ ├── __init__.py
|
||
│ ├── upload_refs/
|
||
│ │ ├── __init__.py
|
||
│ │ ├── blueprint.py # Flask Blueprint (POST /api/upload_refs)
|
||
│ │ ├── pull_file.py # Исходящий потоковый GET с ретраями (httpx)
|
||
│ │ ├── safe_name.py # Санитизация имен файлов
|
||
│ │ └── config.py # Конфигурация по умолчанию
|
||
│ └── session/
|
||
│ ├── __init__.py
|
||
│ ├── state.py # In-memory хранилище сессий
|
||
│ ├── create_session.py
|
||
│ ├── add_file.py
|
||
│ ├── get_files.py
|
||
│ ├── store_result.py
|
||
│ ├── store_csv.py
|
||
│ ├── ttl.py
|
||
│ ├── cancel.py
|
||
│ └── cleanup.py
|
||
├── site/
|
||
│ ├── app.py # Flask demo + local RAM mock buffer
|
||
│ └── templates/index.html
|
||
├── tests/ # pytest + browser smoke
|
||
├── LAYER2-RESUME.md # Техническое задание и сводка
|
||
├── build.mjs
|
||
├── package.json
|
||
└── requirements.txt
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Зафиксированные факты инфраструктуры
|
||
1. **ВМ `5.172.178.213`**: Nginx WebDAV настроен на сброс на диск (`/var/www/drhider-upload/`, `/var/www/contracts-upload/`) с cron-очисткой каждые 5 мин (`find ... -mmin +30 -delete`). Для целевого прода требуется перевод в RAM/in-memory буфер.
|
||
2. **Кластер k8s**: неймспейсы `20a75175-a58c-49cb-b8fa-e86367b1a8dc` (`drhider`), `b4523aba-b5e6-40f1-be56-bb4d2509357c` (`contractor`), `0e108526-5bb2-4757-9499-3f0bac4f0e83` (`uploader-dev`).
|