29 lines
2.5 KiB
Markdown
29 lines
2.5 KiB
Markdown
# Архитектура и бизнес-правила (Ticket System)
|
||
|
||
## 1. Базовые принципы
|
||
- **Стек:** FastAPI, SQLAlchemy 2.0 (asyncpg), PostgreSQL, Redis, RabbitMQ.
|
||
- **Модели данных:** Описаны в `backend/database/models.py`.
|
||
- **Ключевые сущности:** User, Tournament, Seat, Ticket.
|
||
|
||
## 2. Жизненный цикл билета (State Machine)
|
||
Статус билета (`TicketStatus` в таблице `tickets`) строго ограничен:
|
||
1. `AVAILABLE` — место свободно для бронирования.
|
||
2. `LOCKED` — место временно захвачено пользователем (15 минут на оплату).
|
||
3. `PAID` — оплата прошла успешно.
|
||
4. `SCANNED` — билет погашен на входе.
|
||
5. `REFUNDED` — возврат.
|
||
|
||
## 3. Сценарий А: Конкурентное бронирование (Критический путь)
|
||
Захват места должен исключать "состояние гонки" (race condition).
|
||
1. При POST-запросе на захват места (`/api/seats/{seat_id}/lock`), FastAPI обращается к Redis.
|
||
2. Пытается установить ключ `lock:seat:{seat_id}` с помощью `SETNX` и TTL 15 минут.
|
||
3. **Успех:** Если Redis вернул 1, обновляем статус билета в БД на `LOCKED` и привязываем `user_id`. Возвращаем HTTP 200.
|
||
4. **Отказ:** Если ключ уже существует, немедленно возвращаем HTTP 409 Conflict. БД не трогаем.
|
||
|
||
## 4. Сценарий Б: Асинхронная выдача билета (Idempotency)
|
||
Защита от двойных списаний и зависаний интерфейса при долгой генерации PDF.
|
||
1. Платежный шлюз присылает Webhook об успешной оплате.
|
||
2. FastAPI проверяет поле `idempotency_key` в таблице `tickets`. Если ключ уже обработан — игнорируем запрос.
|
||
3. Обновляем статус билета на `PAID`.
|
||
4. Отправляем событие `ticket_paid` (содержит `ticket_id` и данные пользователя) в очередь RabbitMQ. Отвечаем шлюзу HTTP 200.
|
||
5. Фоновый воркер забирает задачу, генерирует PDF (reportlab), грузит в MinIO (boto3) и сохраняет ссылку в БД. |