SAFEQ — распределенная Fullstack-платформа для отслеживания игровых статусов игроков Dota 2 в режиме реального времени. Сервис решает проблему лимитов встроенного внутриигрового черного списка Valve, позволяя формировать расширенный персональный список игроков («додж-лист») с заметками, автоматически определять их активность (в поиске матча, в активной игре, оффлайн) и мгновенно информировать пользователя о входе нежелательного игрока в матчмейкинг.
Платформа полностью развернута в production-инфраструктуре и доступна онлайн:
Live Production URL: https://safeq.studio/
Дисклеймер о проприетарности: Исходный код ядра платформы, алгоритмы обхода защитных механизмов WAF и инфраструктурные конфигурации являются закрытой коммерческой разработкой. Данный публичный репозиторий представляет собой Engineering Case Study (System Design Showcase), раскрывающий концепцию проекта, архитектурную топологию и решения ключевых инженерных вызовов высоконагруженного скрейпинга.
Полноразмерный тактический дашборд с отображением агрегированных метрик активности, индикатором квоты отслеживаемых слотов (до 10 игроков), фильтрацией по статусам и контекстным меню быстрых переходов к внешней аналитике (Dotabuff, OpenDota, Stratz):
Интерфейс оптимизирован под экраны смартфонов для оперативной проверки игроков и статуса поиска прямо во время стадии драфта в клиенте игры:
- Персональный додж-лист с квотой до 10 слотов: Добавление игроков по 32-bit Dota Account ID с сохранением контекстных заметок и даты фиксации. В интерфейсе реализован динамический прогресс-бар квоты (Fair-Use Quota Management), защищающий инфраструктуру от исчерпания ресурсов хоста.
- Мониторинг статуса в реальном времени: Автоматическое определение текущего состояния игрока (
SEARCHING,PLAYING,OFFLINE) и времени завершения последнего матча. - Обход Cloudflare WAF: Распределенный парсинг аналитических сервисов (Dotabuff) через пул headless-инстансов FlareSolverr с автоматической ротацией сессий и поддержкой upstream-прокси.
- Очереди с приоритезацией задач: Архитектура на базе Redis и BullMQ с фоновым регулярным обновлением по расписанию и мгновенным высокоприоритетным парсингом при добавлении нового игрока.
- WebSocket-пуши (Socket.IO): Мгновенная доставка обновлений статусов подключенным клиентам с изоляцией по персональным комнатам пользователей.
- Безопасная аутентификация: Авторизация через Google OAuth 2.0 с выдачей криптографически подписанных stateless JWT-токенов.
- Тактический веб-интерфейс: Темная адаптивная тема интерфейса, мгновенный поиск, фильтрация по статусам, индикация квот и контекстные ссылки на внешнюю статистику (Dotabuff, OpenDota, Stratz).
| Слой | Технология | Назначение в проекте |
|---|---|---|
| Frontend | React 19 / TypeScript 5.7 | Клиентское SPA-приложение с декларативным UI |
| Frontend Tooling | Vite 8 / Tailwind CSS 3.4 | Оптимизированный бандлинг и тактическая темная тема |
| Backend Framework | NestJS 11 | Модульная корпоративная серверная архитектура |
| Database | PostgreSQL 16 | Реляционная СУБД с транзакционной целостностью |
| ORM | Prisma ORM 7 | Типобезопасная работа с БД и автоматические миграции |
| Distributed Queues | BullMQ 5 / Redis 7 | Асинхронные очереди с поддержкой приоритетов и ретраев |
| Anti-Bot Engine | FlareSolverr (4x инстанса) | Headless-браузеры Chromium для прохождения Cloudflare JS Challenge |
| Real-time Transport | Socket.IO 4.8 | Дуплексный транспорт с изоляцией комнат пользователей |
| Auth & Security | Passport.js (Google OAuth, JWT) | Безопасная аутентификация и защита маршрутов через Guards |
| Ingress / Web Server | Nginx Alpine | Reverse-proxy, SPA-роутинг и WebSocket connection upgrade |
| Orchestration | Docker / Docker Compose | Мультиконтейнерная изолированная инфраструктура |
Платформа спроектирована по сервисно-ориентированной модели с четким разграничением зон ответственности компонентов:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#181825', 'primaryTextColor': '#cdd6f4', 'primaryBorderColor': '#89b4fa', 'lineColor': '#f38ba8', 'clusterBkg': '#11111b', 'clusterBorder': '#89b4fa'}}}%%
flowchart TD
classDef client fill:#1e1e2e,stroke:#89b4fa,stroke-width:1px,color:#cdd6f4;
classDef gateway fill:#181825,stroke:#cba6f7,stroke-width:1px,color:#cdd6f4;
classDef core fill:#181825,stroke:#a6e3a1,stroke-width:1px,color:#cdd6f4;
classDef data fill:#181825,stroke:#f9e2af,stroke-width:1px,color:#cdd6f4;
classDef scraper fill:#181825,stroke:#f38ba8,stroke-width:1px,color:#cdd6f4;
classDef external fill:#181825,stroke:#eba0ac,stroke-width:1px,color:#cdd6f4;
subgraph ClientLayer ["Клиентский уровень"]
Browser["React 19 SPA (Vite / Tailwind)"]:::client
end
subgraph IngressLayer ["Шлюз и обратный прокси"]
Nginx["Nginx Reverse Proxy (:80 / :8080)"]:::gateway
end
subgraph AppLayer ["Серверное ядро (NestJS 11)"]
API["REST Controllers (Auth / Users / DodgeList)"]:::core
WS["Socket.IO EventsGateway (WebSockets)"]:::core
Cron["Parser Scheduler (Cron */2m)"]:::core
Worker["BullMQ Parser Worker (Concurrency: 4)"]:::core
end
subgraph StorageLayer ["Слой персистентности и очередей"]
PG[("PostgreSQL 16 (Prisma ORM)")]:::data
Redis[("Redis 7 (BullMQ Engine)")]:::data
end
subgraph ScrapingCluster ["Распределенный скрейпинг-кластер"]
FS1["FlareSolverr Instance 1 (:8191)"]:::scraper
FS2["FlareSolverr Instance 2 (:8192)"]:::scraper
FS3["FlareSolverr Instance 3 (:8193)"]:::scraper
FS4["FlareSolverr Instance 4 (:8194)"]:::scraper
ProxyPool["Rotating Upstream Proxies"]:::scraper
end
subgraph ExternalServices ["Внешние сервисы"]
GoogleAuth["Google OAuth 2.0"]:::external
Dotabuff["Целевой источник данных (Dotabuff)"]:::external
end
Browser -->|"HTTP REST & Статика"| Nginx
Browser <-->|"WebSocket WS/WSS"| Nginx
Nginx -->|"Проксирование API"| API
Nginx <-->|"Проксирование /socket.io"| WS
API -->|"Google OAuth Flow"| GoogleAuth
API -->|"CRUD операции"| PG
API -->|"Высокоприоритетный таск (Priority: 1)"| Redis
Cron -->|"Периодический пуш задач (Priority: 10)"| Redis
Redis -->|"Выдача задач воркеру"| Worker
Worker -->|"Балансировка Round-Robin"| FS1
Worker -->|"Балансировка Round-Robin"| FS2
Worker -->|"Балансировка Round-Robin"| FS3
Worker -->|"Балансировка Round-Robin"| FS4
FS1 & FS2 & FS3 & FS4 -->|"Egress-трафик через прокси"| ProxyPool
ProxyPool -->|"Обход Cloudflare WAF"| Dotabuff
Worker -->|"Сохранение статуса и времени матча"| PG
Worker -->|"Отправка события в комнату userId"| WS
WS -->|"Реактивный пуш в интерфейс"| Browser
Сервис Dotabuff защищен Cloudflare WAF, требующим исполнения JavaScript Challenge и валидации сигнатур браузера. Прямые HTTP-запросы мгновенно отклоняются кодом 403. Поднятие постоянных инстансов браузера (Puppeteer/Playwright) в Docker приводит к быстрой деградации памяти и аварийному завершению процессов по OOM-killer.
- Кластерная архитектура FlareSolverr: В Docker-сети развернуто 4 изолированных инстанса FlareSolverr, каждый из которых маршрутизирует трафик через собственный upstream-прокси.
- Балансировка Round-Robin: На уровне воркера реализован циклический опрос пула инстансов, что исключает точечную перегрузку одного IP-адреса.
- Управление сессиями и предотвращение OOM:
- На каждом инстансе создается именованная сессия (
dotabuff-session), проходящая JS Challenge один раз и сохраняющая cookies. - Установлен лимит
MAX_REQUESTS_PER_SESSION = 35. По достижении счетчика сессия принудительно перезапускается, очищая внутренние кэши движка Chromium. - Контейнерам выделен
shm_size: 256mи жесткий лимит RAM (deploy.resources.limits.memory: 768M), защищающий сервер от истощения системной памяти. - При возникновении таймаута инстанс временно исключается из балансировки через мьютекс
_recreatingUrlsи пересоздается без остановки общего пайплайна.
- На каждом инстансе создается именованная сессия (
Парсинг защищенных страниц через headless-браузер требует существенных вычислительных ресурсов: один запрос занимает от 4 до 7 секунд. Без строгого ограничения объема отслеживания неограниченный ввод игроков привел бы к насыщению очередей (Queue Saturation), задержкам обновления в десятки минут и перегрузке CPU/RAM хоста.
- Инженерное обоснование квоты в 10 слотов (
MAX_PLAYERS_LIMIT: 10):- Расчет пропускной способности: кластер из 4 нод FlareSolverr обрабатывает порядка 30–40 страниц в минуту.
- Лимит в 10 слотов на пользователя гарантирует, что 2-минутный регулярный цикл фонового сбора успевает опросить всех уникальных игроков из базы без отставания очереди.
- Многоуровневый контроль квоты:
- Бэкенд проверяет текущее количество записей перед созданием новой (
dodgeList.count >= limit) и возвращает структурированную ошибку с кодом 400. - Фронтенд визуализирует лимит через интерактивный прогресс-бар в карточке метрик дашборда ("Tracked: X/10") и блокирует форму добавления при исчерпании доступных слотов.
- Бэкенд проверяет текущее количество записей перед созданием новой (
- Двухуровневая модель приоритетов в BullMQ:
- Фоновый цикл (Bulk Sync) выполняется по расписанию раз в 2 минуты (
Cron */2 * * * *) с низким приоритетом (priority: 10). - Добавление игрока пользователем (On-Demand Sync) инициирует немедленную задачу с наивысшим приоритетом (
priority: 1), минуя общую очередь и возвращая статус в течение нескольких секунд.
- Фоновый цикл (Bulk Sync) выполняется по расписанию раз в 2 минуты (
Один и тот же игрок может отслеживаться несколькими независимыми пользователями. При смене статуса необходимо обеспечить моментальную доставку события только заинтересованным клиентам без утечки информации о чужих списках отслеживания.
- Изоляция комнат Socket.IO: При авторизации сокета по JWT сервер автоматически помещает клиента в комнату с идентификатором
userId. - Точечная отправка событий: Воркер обращается к шлюзу
EventsGatewayи пушит события строго в целевую комнату:this.server.to(userId).emit('player-status-changed', data). - Реактивный интерфейс: Кастомный React-хук
useSocketлокально обновляет стейт таблицы без повторного REST-запроса всей таблицы, сопровождая переход статуса анимацией строки и тост-уведомлением (react-hot-toast).
- Изоляция сетевого периметра: Порты PostgreSQL и Redis закрыты внутри Docker-сети и не доступны из внешней сети (порт Postgres привязан строго к
127.0.0.1для обслуживания локальных миграций). - Rate Limiting: Защита эндпоинтов API от брутфорса и DoS через
@nestjs/throttler(лимит 30 запросов в 10 секунд на IP-клиента). - Безопасный OAuth Flow: Google OAuth 2.0 с валидацией редиректа по белому списку
FRONTEND_URL(защита от Open Redirect атак) и выдачей stateless JWT. - Nginx Ingress: Единая входная точка, терминация соединений, кэширование статики и проксирование WebSocket соединений (
Connection "upgrade").
Схема данных оптимизирована под минимальный объем избыточности с гарантией целостности на уровне СУБД:
[User] (id, googleId, email, username, createdAt)
│
└── 1 : N ──┐
▼
[DodgeRecord] (id, dotaId, note, status, lastMatchTime, createdAt, userId)
* Composite Unique Constraint: @@unique([userId, dotaId])
- Ограничение
@@unique([userId, dotaId])исключает повторное добавление одного и того же игрока одним пользователем. - Перечисление
Status(OFFLINE,PLAYING,SEARCHING) типизировано на уровне PostgreSQL.
Платформа успешно функционирует в production-контуре. Дальнейшие этапы развития:
- Интеграция биллинговой системы для покупки расширенных пакетов квот (увеличение слотов с 10 до 50/100 игроков с динамическим масштабированием пула нод FlareSolverr).
- Разработка Telegram-бота для мгновенных push-уведомлений о старте поиска матча отслеживаемым игроком.
- Подключение дополнительных аналитических провайдеров и прямой интеграции со Steam Web API.


