add tests for controllers and ActionCable channels; improve main backend docs
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# Архитектура Main Server (Ruby on Rails)
|
||||
|
||||
Документ описывает архитектуру главного сервера проекта «Ruby Sea Battle», структуру компонентов Rails-приложения, модель безопасности и схему взаимодействия с клиентом и игровым сервером.
|
||||
|
||||
---
|
||||
|
||||
## 1. Роль Main Server в общей системе
|
||||
|
||||
Система «Ruby Sea Battle» состоит из трёх ключевых узлов:
|
||||
1. **Frontend (React):** SPA-интерфейс в браузере.
|
||||
2. **Main Server (Ruby on Rails):** центральный сервер бизнес-логики, аккаунтов и матчмейкинга.
|
||||
3. **Game Server (Go):** сервер игровых комнат в реальном времени по WebSocket.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client["React Frontend"]
|
||||
MainServer["Main Server (Rails 8)"]
|
||||
RedisStore[("Redis (Очереди и Pub/Sub)")]
|
||||
DB[(База данных SQLite/PostgreSQL)]
|
||||
GameServer["Game Server (Go)"]
|
||||
|
||||
Client -- "REST API (Auth, Profile)" --> MainServer
|
||||
Client -- "WebSocket /cable (Matchmaking)" --> MainServer
|
||||
MainServer -- "State & Lua Scripts" --> RedisStore
|
||||
MainServer -- "Пользователи и рейтинг" --> DB
|
||||
MainServer -. "JWT с общим секретом" .-> GameServer
|
||||
Client -- "WebSocket (Игровой процесс)" --> GameServer
|
||||
```
|
||||
|
||||
### Основные обязанности Main Server:
|
||||
- Регистрация и аутентификация пользователей с выдачей JWT.
|
||||
- Хранение профилей и аватарок игроков (через Active Storage).
|
||||
- Ведение базового рейтинга пользователей (`users.rating`).
|
||||
- Очередь матчмейкинга и подбор соперников в реальном времени через Action Cable и Redis.
|
||||
- Выпуск одноразовых билетов матча (`game_token`) для подключения к игровому серверу Go.
|
||||
|
||||
---
|
||||
|
||||
## 2. Структура слоёв приложения
|
||||
|
||||
Главный сервер построен на принципах Rails API-only приложения:
|
||||
|
||||
```
|
||||
app/
|
||||
├── channels/
|
||||
│ ├── application_cable/
|
||||
│ │ ├── channel.rb # Базовый класс каналов
|
||||
│ │ └── connection.rb # Аутентификация WebSocket по JWT
|
||||
│ └── matchmaking_channel.rb # Канал поиска соперника (start/stop/claim)
|
||||
├── controllers/
|
||||
│ ├── application_controller.rb # Базовый контроллер: проверка JWT и current_user
|
||||
│ └── api/
|
||||
│ ├── registrations_controller.rb # Регистрация и загрузка аватара
|
||||
│ ├── sessions_controller.rb # Вход, выход, профиль (/api/me)
|
||||
│ └── matchmaking_controller.rb # HTTP-отмена поиска
|
||||
├── jobs/
|
||||
│ ├── application_job.rb # Базовый класс фоновых задач
|
||||
│ └── matchmaking_sweep_job.rb # Периодическая проверка очереди (Solid Queue)
|
||||
├── models/
|
||||
│ ├── application_record.rb # Базовый класс моделей
|
||||
│ └── user.rb # Модель игрока (никнейм, пароль, рейтинг, аватар)
|
||||
└── services/
|
||||
├── jwt_service.rb # Кодирование и валидация JWT
|
||||
└── matchmaking_service.rb # Логика очереди и вызовы Lua в Redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Компоненты приложения
|
||||
|
||||
### 3.1. Контроллеры и фильтры аутентификации
|
||||
|
||||
Все защищённые маршруты наследуются от `ApplicationController`.
|
||||
- **`authenticate_request!`:** выполняет проверку токена перед каждым действием.
|
||||
- **Извлечение токена (`extract_token`):** поддерживает как заголовок `Authorization: Bearer <token>`, так и параметр строки запроса `params[:token]`.
|
||||
- При отсутствии или невалидности токена клиенту возвращается стандартизированный ответ:
|
||||
```json
|
||||
{ "error": "Unauthorized" }
|
||||
```
|
||||
со статусом `401 Unauthorized`.
|
||||
|
||||
### 3.2. Модель пользователя (`User`)
|
||||
|
||||
- Использует механизм `has_secure_password` (BCrypt).
|
||||
- **Нормализация никнейма:** автоматическая очистка крайних пробелов через `normalizes :nickname, with: ->(n) { n.strip }`.
|
||||
- **Валидации:**
|
||||
- Никнейм: обязателен, длина от 3 до 24 символов, регистронезависимая уникальность.
|
||||
- Пароль: минимум 6 символов.
|
||||
- Рейтинг: обязателен, значение по умолчанию в БД — `100`.
|
||||
- Аватар (`has_one_attached :avatar`): проверка допустимых типов файлов (`image/png`, `image/jpeg`, `image/gif`, `image/webp`).
|
||||
|
||||
### 3.3. Action Cable и WebSocket
|
||||
|
||||
- **Подключение (`ApplicationCable::Connection`):** при коннекте к `/cable?token=...` сокет проверяет подпись токена. Если токен валиден и пользователь найден в базе, сокет идентифицируется как `current_user`. В противном случае вызывается `reject_unauthorized_connection`.
|
||||
- **Канал `MatchmakingChannel`:**
|
||||
- При подписке клиент слушает индивидуальный стрим `matchmaking:<user_id>`.
|
||||
- Метод `claim` обеспечивает атомарную выдачу уже найденного матча, если клиент переподключился.
|
||||
- Действие `start` запускает подбор.
|
||||
- Действие `stop` или закрытие соединения (`unsubscribed`) отменяет поиск.
|
||||
|
||||
### 3.4. Сервисный слой
|
||||
|
||||
- **`JwtService`:**
|
||||
- Алгоритм: HMAC-SHA256 (`HS256`).
|
||||
- Секретный ключ: `Rails.application.secret_key_base`.
|
||||
- Стандартный срок действия токена авторизации: 7 дней.
|
||||
- Обработка исключений `JWT::DecodeError` и `JWT::ExpiredSignature` с возвратом `nil`.
|
||||
- **`MatchmakingService`:**
|
||||
- Инкапсулирует вызовы Redis Lua-скриптов (`PAIR_SCRIPT`, `FINALIZE_SCRIPT`, `CANCEL_SCRIPT`).
|
||||
- Выполняет создание матчей, подписание игровых токенов и бродкаст уведомлений через `MatchmakingChannel`.
|
||||
|
||||
### 3.5. Фоновые задачи (Background Processing)
|
||||
|
||||
- В качестве движка фоновых задач используется `Solid Queue` (на базе БД) с периодическим расписанием `config/recurring.yml`.
|
||||
- `MatchmakingSweepJob` запускается каждые 2 секунды (`MATCHMAKING_SWEEP_INTERVAL`) и вызывает `MatchmakingService.sweep` для постепенного расширения диапазона поиска и очистки таймаутов.
|
||||
|
||||
---
|
||||
|
||||
## 4. Модель токенов и безопасность
|
||||
|
||||
В системе используются два вида JWT:
|
||||
|
||||
| Параметр | User Auth Token | Game Match Token (`game_token`) |
|
||||
|----------|-----------------|---------------------------------|
|
||||
| **Назначение** | Авторизация запросов к REST API и Action Cable | Доступ к комнате матча на `game-server` |
|
||||
| **Payload** | `{ "user_id": 1, "exp": ... }` | `{ "match_id": "...", "player1_id": 1, "player2_id": 2, "game_server": "...", "exp": ... }` |
|
||||
| **Срок жизни** | 7 дней | `MATCHMAKING_TOKEN_TTL` (по умолчанию 120 сек) |
|
||||
| **Секрет подписи** | `SECRET_KEY_BASE` | `SECRET_KEY_BASE` (общий для Rails и Go) |
|
||||
| **Алгоритм** | HS256 | HS256 |
|
||||
|
||||
### Безопасность делегирования на Game Server:
|
||||
1. Игровой сервер Go не имеет прямого доступа к базе данных пользователей Main Server.
|
||||
2. При подключении к Game Server игрок предъявляет `game_token`.
|
||||
3. Game Server валидирует подпись токена тем же общим секретом `SECRET_KEY_BASE`, сверяет `match_id` и идентификатор подключившегося игрока (`player1_id` или `player2_id`).
|
||||
4. Токен действует всего 120 секунд, что исключает возможность повторного использования после начала игры.
|
||||
Reference in New Issue
Block a user