9.1 KiB
9.1 KiB
Архитектура Main Server (Ruby on Rails)
Документ описывает архитектуру главного сервера проекта «Ruby Sea Battle», структуру компонентов Rails-приложения, модель безопасности и схему взаимодействия с клиентом и игровым сервером.
1. Роль Main Server в общей системе
Система «Ruby Sea Battle» состоит из трёх ключевых узлов:
- Frontend (React): SPA-интерфейс в браузере.
- Main Server (Ruby on Rails): центральный сервер бизнес-логики, аккаунтов и матчмейкинга.
- Game Server (Go): сервер игровых комнат в реальном времени по WebSocket.
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]. - При отсутствии или невалидности токена клиенту возвращается стандартизированный ответ:
со статусом
{ "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.
- Алгоритм: HMAC-SHA256 (
MatchmakingService:- Инкапсулирует вызовы Redis Lua-скриптов (
PAIR_SCRIPT,FINALIZE_SCRIPT,CANCEL_SCRIPT). - Выполняет создание матчей, подписание игровых токенов и бродкаст уведомлений через
MatchmakingChannel.
- Инкапсулирует вызовы Redis Lua-скриптов (
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:
- Игровой сервер Go не имеет прямого доступа к базе данных пользователей Main Server.
- При подключении к Game Server игрок предъявляет
game_token. - Game Server валидирует подпись токена тем же общим секретом
SECRET_KEY_BASE, сверяетmatch_idи идентификатор подключившегося игрока (player1_idилиplayer2_id). - Токен действует всего 120 секунд, что исключает возможность повторного использования после начала игры.