diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 888a6fd..43304c8 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,45 +1,37 @@ -# XC_VM — Архитектурный план реорганизации `/src` +# XC_VM — Архитектура ## Содержание 0. [Стратегические цели](#0-стратегические-цели) -1. [Диагноз текущего состояния](#1-диагноз-текущего-состояния) -2. [Архитектурный стиль и принципы](#2-архитектурный-стиль-и-принципы) -3. [Целевая структура `/src`](#3-целевая-структура-src) -4. [Описание компонентов](#4-описание-компонентов) -5. [Карта миграции: откуда → куда](#5-карта-миграции-откуда--куда) -6. [Система модулей](#6-система-модулей) -7. [Границы ядра и модулей](#7-границы-ядра-и-модулей) -8. [Варианты сборки: MAIN vs LoadBalancer](#8-варианты-сборки-main-vs-loadbalancer) -9. [Транзакции и производительность](#9-транзакции-и-производительность) -10. [Правила для контрибьюторов](#10-правила-для-контрибьюторов) +1. [Архитектурный стиль и принципы](#1-архитектурный-стиль-и-принципы) +2. [Структура `/src`](#2-структура-src) +3. [Описание компонентов](#3-описание-компонентов) +4. [Система модулей](#4-система-модулей) +5. [Варианты сборки: MAIN vs LoadBalancer](#5-варианты-сборки-main-vs-loadbalancer) +6. [Транзакции и производительность](#6-транзакции-и-производительность) -> **План миграции** (фазы 0–15, стратегия рисков, порядок выполнения) вынесен в [MIGRATION.md](MIGRATION.md). +> **План миграции** (фазы, стратегия рисков, порядок выполнения) — см. [MIGRATION.md](MIGRATION.md). +> **Правила для контрибьюторов** — см. [CONTRIBUTING.md](CONTRIBUTING.md). --- ## 0. Стратегические цели -### 0.1. Зачем это всё - | # | Цель | Приоритет | Метрика успеха | |---|------|-----------|----------------| | 1 | **Контрибьюторская доступность** | 🔴 Критический | PHP-разработчик среднего уровня делает первый PR за 2 часа, не зная DDD | | 2 | **Поддерживаемость** | 🔴 Критический | Типичное изменение (добавить поток, изменить лимиты) < 1 час без риска сайд-эффектов | | 3 | **Изоляция отказов** | 🔴 Критический | Баг в admin-панели не ломает стриминг. Баг в модуле не ломает ядро | -| 4 | **Multi-node** | 🟡 Важный | LB-сервер собирается из подмножества koda. Streaming-путь не загружает admin-логику | +| 4 | **Multi-node** | 🟡 Важный | LB-сервер собирается из подмножества кода. Streaming-путь не загружает admin-логику | | 5 | **Open-core коммерция** | 🟡 Важный | Коммерческие модули подключаются без модификации ядра. Удаление модуля не ломает систему | | 6 | **Тестируемость** | 🟢 Поддерживающий | Любой сервис можно тестировать, подставив mock зависимости | -### 0.2. Чем это НЕ является +### Чем это НЕ является -- **Не переписывание с нуля.** Итеративный рефакторинг. Каждый шаг обратимо совместим. -- **Не DDD.** Нет Event Sourcing, CQRS, Aggregate Root, Domain Events. Простой паттерн: Controller → Service → Repository. -- **Не академический проект.** Архитектура оптимизирована под понятность и предсказуемость, а не под теоретическую чистоту. +- **Не DDD.** Нет Event Sourcing, CQRS, Aggregate Root. Простой паттерн: Controller → Service → Repository. +- **Не академический проект.** Архитектура оптимизирована под понятность, а не под теоретическую чистоту. -### 0.3. Принцип принятия решений - -Каждое архитектурное решение проходит три фильтра: +### Принцип принятия решений 1. **Контрибьютор поймёт за 5 минут?** → если нет — упростить 2. **Не ломает streaming hot path?** → если ломает — отклонить @@ -49,51 +41,11 @@ --- -## 1. Диагноз текущего состояния +## 1. Архитектурный стиль и принципы -### Масштаб: 382 PHP-файла, ~199 000 строк +### 1.1. Формализация -### Критические проблемы - -| # | Проблема | Где проявляется | Влияние | -|---|----------|-----------------|---------| -| 1 | **God-объекты** | `CoreUtilities` (4847 стр.), `admin_api.php` (6981 стр.), `admin.php` (4448 стр.) | Невозможно изменить одну подсистему без риска сломать другую | -| 2 | **Дублирование bootstrap** | `www/constants.php` vs `www/stream/init.php` — одни и те же ~70 `define()`, функции ошибок, flood-check | Каждое изменение нужно делать в двух местах | -| 3 | **Fork-дублирование** | `CoreUtilities` vs `StreamingUtilities` — идентичные свойства, `init()`, `cleanGlobals()` | Баги исправляются в одном классе и остаются в другом | -| 4 | **Глобальные переменные как шина данных** | `global $db`, `$rSettings`, `$rServers`, `$rUserInfo` в каждом файле | Невозможно тестировать, невозможно изолировать | -| 5 | **SQL в presentation-слое** | Каждая admin-страница делает `$db->query()` прямо в HTML | Бизнес-логика неразделима от отображения | -| 6 | **Один include = побочные эффекты** | `require admin.php` запускает сессию, создаёт БД, init 3 классов, определяет 50 констант | Нет возможности подключить часть системы без всей | -| 7 | **Admin = Reseller copy-paste** | `reseller/` — упрощённый клон `admin/` с тем же header/footer/session | Правки в одном не попадают в другой | -| 8 | **Goto-лейблы** | `includes/cli/monitor.php` содержит `goto label235`, `label592` | Следы обфускации, нечитаемый control flow | -| 9 | **Inline data** | Массивы стран/MAC-типов/разрешений по 150+ строк прямо в `admin.php` | Данные переплетены с логикой инициализации | -| 10 | **God-cron** | `crons/servers.php` — мониторинг, перезапуск демонов, статистика, очистка | Разные по частоте и природе задачи в одном файле | - -### Граф зависимостей (текущий) - -``` -admin/*.php ──────┐ -reseller/*.php ───┤──→ includes/admin.php ──→ CoreUtilities (4847) -includes/api/ ────┘ │ ├── Database - │ ├── Redis - ├──→ admin_api.php (6981) - ├──→ reseller_api.php (1204) - └──→ constants.php (~70 define()) - -www/stream/*.php ──→ www/stream/init.php ──→ StreamingUtilities (1992) - │ ├── Database (тот же) - │ └── Redis (тот же) - └── constants (ДУБЛИКАТ) -``` - -**Центральная точка отказа: `includes/admin.php`** — каждый контекст исполнения (admin, reseller, crons, API) проходит через этот один файл 4448 строк. - ---- - -## 2. Архитектурный стиль и принципы - -### 2.0. Формализация архитектурного стиля - -**Это: структурированный монолит с предсказуемой организацией по контексту.** +**Структурированный монолит с предсказуемой организацией по контексту.** Не DDD. Не Hexagonal. Не Clean Architecture. Не микросервисы. Простой PHP-монолит, разложенный по контекстам с минимумом абстракций. @@ -112,9 +64,7 @@ public/Controllers/Admin/ └── StreamController.php # Принять запрос → вызвать Service → отдать ответ ``` -Вот и всё. Три файла на контекст. Контрибьютор видит `/Stream/` — и знает где менять. - -#### Правила зависимостей (просто) +#### Правила зависимостей ``` Controller → Service → Repository → Database @@ -130,17 +80,9 @@ Controller → Service → Repository → Database | `streaming/` | `core/` (subset), `domain/` (read-only queries) | `public/`, `modules/` | | `modules/` | `domain/` (Service, Repository), `core/` | Другие модули (без явной зависимости), `public/`, `streaming/` | -### 2.1. Инверсия зависимостей +### 1.2. Strict Constructor Injection -Код верхнего уровня (UI, CLI, HTTP endpoints) зависит от абстракций ядра, а не от конкретных реализаций. Ядро не знает о существовании модулей. - -### 2.2. Единая точка bootstrap — множественные контексты - -Вместо дублированных `init.php` / `constants.php` / `stream/init.php` — один bootstrap с конфигурируемым набором загружаемых сервисов. - -### 2.3. Strict Constructor Injection — запрет Service Locator - -**Правило:** `ServiceContainer` используется **ТОЛЬКО** на этапе bootstrap (composition root). +`ServiceContainer` используется **ТОЛЬКО** на этапе bootstrap (composition root). После bootstrap все зависимости передаются **через конструктор**. Ни один сервис не вызывает `$container->get()` внутри своих методов. ```php @@ -151,490 +93,226 @@ class StreamService { private EventDispatcher $events, private FileLogger $logger ) {} - - public function create(array $data): int { - // Использует $this->repository, $this->events, $this->logger - // НЕ вызывает ServiceContainer - } } -// ✅ ПРАВИЛЬНО — composition root (bootstrap.php): -$container->register('stream.service', function($c) { - return new StreamService( - $c->get('stream.repository'), - $c->get('event.dispatcher'), - $c->get('logger.file') - ); -}); - // ❌ ЗАПРЕЩЕНО — Service Locator внутри сервиса: class StreamService { public function create(array $data): int { $db = ServiceContainer::getInstance()->get('db'); // ← АНТИПАТТЕРН - $logger = ServiceContainer::getInstance()->get('logger'); // ← АНТИПАТТЕРН } } ``` **Исключения (временные, фаза миграции):** -- Legacy-код в `includes/` может обращаться к контейнеру напрямую через proxy-методы -- Каждое такое обращение помечается `// @legacy-container — убрать в Фазе 8` -- В Фазе 8 все legacy-обращения к контейнеру заменяются на constructor injection +Legacy-код в `includes/` может обращаться к контейнеру напрямую. Каждое такое обращение помечается `// @legacy-container`. -### 2.4. Консолидация мелких классов — запрет «один класс = один файл» вслепую +### 1.3. Консолидация мелких классов -**Правило:** Не создавать отдельный файл/класс, если в нём < 150 строк **и** < 5 публичных методов. Маленькие классы живут в одном файле с родственным классом. +Не создавать отдельный файл/класс, если в нём < 150 строк **и** < 5 публичных методов. -**Зачем:** Избежать взрыва файлов с классами по 1-2 функции. 44 файла по 40 строк — хуже одного бога на 4000, но не намного лучше для навигации. - -**Конкретные правила:** - -| Ситуация | Что делать | Пример | -|----------|-----------|--------| -| Repository < 5 методов | Объединить Service + Repository в один файл | `EpgService.php` содержит и бизнес-логику, и SQL | -| Контекст = 1 Service с 1-2 методами | Влить в ближайший родственный контекст | `TicketService::submit()` → `UserService::submitTicket()` | -| Отдельный «Validator» с 1 методом | Сделать private-методом в Service | `HMACValidator::validate()` → `HMACService::validate()` | -| Отдельный «Sync» с 1 методом | Влить в Service того же контекста | `DeviceSync::sync()` → `MagService::syncLineDevices()` | -| Service + Repository > 300 строк суммарно | Разделить в отдельные файлы | `StreamService` (700 стр.) + `StreamRepository` (400 стр.) = раздельно | +| Ситуация | Что делать | +|----------|-----------| +| Repository < 5 методов | Объединить Service + Repository в один файл | +| Контекст = Service с 1-2 методами | Влить в ближайший родственный контекст | +| Отдельный «Validator» с 1 методом | Сделать private-методом в Service | +| Service + Repository > 300 строк суммарно | Разделить в отдельные файлы | **Когда МОЖНО создавать отдельный файл:** - Класс > 150 строк - Класс имеет ≥ 5 публичных методов -- Класс используется из 3+ разных контекстов (например `ConnectionTracker`) +- Класс используется из 3+ разных контекстов - Интерфейс для DI (`CacheInterface`, `LoggerInterface`) -```php -// ✅ ПРАВИЛЬНО — маленькие Service+Repository в одном файле: -// domain/Epg/EpgService.php -class EpgRepository { - public function __construct(private Database $db) {} - public function findById(int $id): ?array { ... } - public function getStreamEpg(int $streamId): array { ... } -} +### 1.4. Нет Entity-классов -class EpgService { - public function __construct(private EpgRepository $repo) {} - public function process(array $data): int { ... } - public function getChannelEpg(int $channelId): array { ... } -} +Данные передаются как `array`. Это PHP — массивы проще и понятнее, чем анемичные DTO-объекты. -// ❌ НЕПРАВИЛЬНО — два файла по 40 строк: -// domain/Epg/EpgRepository.php (40 строк, 3 метода) -// domain/Epg/EpgService.php (50 строк, 2 метода) -``` +### 1.5. Модуль = изолированная директория -### 2.5. Границы через интерфейсы, а не через `global` - -Каждый сервис получает зависимости через конструктор. Ни один компонент не использует `global`. - -### 2.6. Модуль = изолированная директория - -Модуль — это директория с известным контрактом. Его можно удалить, и система продолжит работать (деградируя в функциональности, но не падая). - -### 2.7. Open-core без лицензионных ограничений в ядре - -Ядро (`core/`) полностью свободно. Модули (`modules/`) могут быть как open-source, так и коммерческими. Ядро не содержит проверок лицензий, шифрования, или скрытых ограничений. Лицензирование — это задача отдельного опционального модуля расширений. +Модуль — это директория с известным контрактом. Его можно удалить, и система продолжит работать (деградируя в функциональности, но не падая). Ядро (`core/`) не содержит проверок лицензий, шифрования или скрытых ограничений. --- -## 3. Целевая структура `/src` +## 2. Структура `/src` ``` src/ -├── bootstrap.php # Единый bootstrap: require_once подключения, DI container, config -├── constants.php # Все path/version/status константы (один файл) +├── autoload.php # PSR-подобный автозагрузчик (class map) +├── bootstrap.php # Единый bootstrap: DI container, config, контексты +├── console.php # CLI entry point (php console.php cron:*, cmd:*) +├── service # Bash: управление демонами +├── update # Bash: процесс обновления │ -├── core/ # ═══ ЯДРО (стабильный, свободный, минимальный) ═══ -│ ├── Config/ -│ │ ├── ConfigLoader.php # .ini → массив, кэширование, env-override -│ │ └── PathResolver.php # Все пути системы (заменяет 70 define()) -│ │ -│ ├── Database/ -│ │ ├── Database.php # Базовая PDO-обёртка (перемещён из includes/) -│ │ ├── DatabaseHandler.php # Менеджер БД — расширенная обёртка с reconnect, bulk ops -│ │ ├── QueryBuilder.php # Конструктор запросов вместо raw SQL в UI -│ │ └── Migration.php # Миграции (извлечено из `status`) -│ │ -│ ├── Cache/ -│ │ ├── CacheInterface.php # Контракт для любого кэш-драйвера -│ │ ├── FileCache.php # Текущий igbinary file cache -│ │ └── RedisCache.php # Redis-обёртка -│ │ -│ ├── Auth/ -│ │ ├── SessionManager.php # Единый менеджер сессий (admin + reseller) -│ │ ├── Authenticator.php # Логин/пароль/api_key -│ │ └── Authorization.php # Проверка прав ($rPermissions → RBAC) -│ │ -│ ├── Http/ -│ │ ├── Request.php # Обёртка над $_GET/$_POST (заменяет cleanGlobals) -│ │ ├── Response.php # JSON/HTML ответ -│ │ ├── Router.php # Маршрутизация вместо switch($rAction) -│ │ └── Middleware/ -│ │ ├── FloodProtection.php # Rate limiting (извлечено из constants.php) -│ │ ├── IpWhitelist.php # IP-фильтрация -│ │ └── CorsMiddleware.php -│ │ -│ ├── Process/ -│ │ ├── ProcessManager.php # Управление процессами (kill, ps, isRunning) -│ │ ├── DaemonRunner.php # Запуск PHP-демонов с PID-файлами -│ │ └── CronLock.php # Файловые блокировки кронов (checkCron) -│ │ -│ ├── Logging/ -│ │ ├── LoggerInterface.php # Контракт -│ │ ├── FileLogger.php # Файловое логирование -│ │ └── DatabaseLogger.php # panel_logs / login_logs / activity -│ │ -│ ├── Events/ -│ │ ├── EventDispatcher.php # Простой event bus -│ │ └── EventInterface.php # Контракт события -│ │ -│ ├── Container/ -│ │ └── ServiceContainer.php # Минимальный DI-контейнер -│ │ -│ └── Util/ -│ ├── GeoIP.php # Извлечено из CoreUtilities -│ ├── NetworkUtils.php # IP-операции, CIDR, subnet matching -│ ├── TimeUtils.php # secondsToTime(), timezone helper -│ ├── Encryption.php # AES, token generation -│ └── ImageUtils.php # Resize, thumbnail, upload +├── core/ # ═══ ЯДРО (инфраструктурные сервисы) ═══ +│ ├── Auth/ # SessionManager, Authenticator, Authorization, BruteforceGuard +│ ├── Backup/ # BackupService +│ ├── Cache/ # CacheInterface, FileCache, RedisCache +│ ├── Config/ # AppConfig, ConfigLoader, ConfigReader, Paths, Binaries, +│ │ # DomainResolver, SettingsManager, SettingsRepository +│ ├── Container/ # ServiceContainer (DI) +│ ├── Database/ # Database (PDO), DatabaseHandler, MigrationRunner +│ ├── Diagnostics/ # DiagnosticsService +│ ├── Error/ # ErrorHandler, ErrorCodes +│ ├── Events/ # EventDispatcher, EventInterface +│ ├── GeoIP/ # GeoIPService +│ ├── Http/ # Request, Response, Router, CurlClient, RequestGuard, +│ │ # RequestManager, Middleware/ +│ ├── Init/ # LegacyInitializer +│ ├── Logging/ # LoggerInterface, FileLogger, DatabaseLogger +│ ├── Module/ # ModuleInterface, ModuleLoader +│ ├── Process/ # ProcessManager, Multithread, Thread +│ ├── Util/ # Encryption, GeoIP, ImageUtils, NetworkUtils, +│ │ # StreamUtils, SystemInfo, TimeUtils +│ └── Validation/ # InputValidator │ ├── domain/ # ═══ БИЗНЕС-ЛОГИКА (сервисы и репозитории) ═══ -│ ├── Stream/ -│ │ ├── StreamService.php # Бизнес-логика + оркестрация -│ │ ├── StreamRepository.php # SQL-запросы (из admin_api.php) -│ │ ├── ChannelService.php # Каналы: create, massEdit, order -│ │ ├── CategoryService.php # Категории + CategoryRepository (< 150 стр. = один файл) -│ │ ├── StreamProcess.php # FFmpeg, kill, restart -│ │ ├── StreamMonitor.php # Мониторинг потока (из cli/monitor.php) -│ │ ├── ConnectionTracker.php # Redis: подключения, heartbeat -│ │ ├── StreamSorter.php # Сортировки и форматирование -│ │ ├── PlaylistGenerator.php # Генерация M3U/EPG плейлистов -│ │ ├── CronGenerator.php # Генерация кронов -│ │ └── M3UParser.php # Парсинг M3U -│ │ -│ ├── Vod/ -│ │ ├── MovieService.php # + MovieRepository (< 300 стр. суммарно = один файл) -│ │ ├── SeriesService.php # + SeriesRepository (< 300 стр. суммарно = один файл) -│ │ └── EpisodeService.php # process, massEdit, massDelete -│ │ -│ ├── Line/ -│ │ ├── LineService.php # + LineRepository (раздельно — > 300 стр. суммарно) -│ │ ├── LineRepository.php -│ │ └── PackageService.php # + PackageRepository (один файл, < 150 стр.) -│ │ -│ ├── Device/ -│ │ ├── MagService.php # + DeviceSync::syncLineDevices (влито) -│ │ └── EnigmaService.php -│ │ -│ ├── User/ -│ │ ├── UserService.php # + ProfileService::editAdminProfile (влито) -│ │ │ # + TicketService::submit (влито) -│ │ ├── UserRepository.php # getUserInfo, getUser, getRegisteredUsers... -│ │ └── GroupService.php # + GroupRepository (один файл, < 150 стр.) -│ │ -│ ├── Server/ -│ │ ├── ServerRepository.php -│ │ ├── ServerService.php # Мониторинг, health-check -│ │ └── SettingsService.php # edit, editBackup, editCacheCron (влито из domain/Settings/) -│ │ -│ ├── Bouquet/ -│ │ └── BouquetService.php # + BouquetRepository + BouquetMapper (один файл, < 300 стр.) -│ │ -│ ├── Epg/ -│ │ ├── EPG.php # XML-парсер EPG (XmlStringStreamer), MAIN-only -│ │ └── EpgService.php # + EpgRepository (один файл, < 300 стр.) -│ │ -│ ├── Auth/ -│ │ ├── AuthService.php # CodeService + HMACService + HMACValidator (объединены) -│ │ └── AuthRepository.php # CodeRepository + HMACRepository (объединены) -│ │ -│ └── Security/ -│ └── BlocklistService.php # + BlocklistRepository (один файл, < 300 стр.) +│ ├── Auth/ # AuthService, AuthRepository +│ ├── Bouquet/ # BouquetService +│ ├── Device/ # MagService, EnigmaService +│ ├── Epg/ # EPG (XML-парсер), EpgService +│ ├── Line/ # LineService, LineRepository, PackageService +│ ├── Security/ # BlocklistService +│ ├── Server/ # ServerService, ServerRepository, SettingsService +│ ├── Stream/ # StreamService, StreamRepository, StreamProcess, +│ │ # ChannelService, CategoryService, ConnectionTracker, +│ │ # StreamSorter, PlaylistGenerator, +│ │ # ProfileService, ProviderService, RadioService, +│ │ # StreamConfigRepository +│ ├── User/ # UserService, UserRepository, GroupService +│ └── Vod/ # MovieService, SeriesService, EpisodeService │ ├── streaming/ # ═══ СТРИМИНГ-ДВИЖОК (hot path) ═══ │ ├── StreamingBootstrap.php # Лёгкий init для стриминг-контекста -│ ├── Auth/ -│ │ ├── TokenAuth.php # HMAC/token парсинг (из auth.php) -│ │ ├── StreamAuth.php # Проверка доступа к потоку -│ │ └── DeviceLock.php # Привязка к устройству -│ │ -│ ├── Delivery/ -│ │ ├── LiveDelivery.php # Раздача live (из live.php) -│ │ ├── VodDelivery.php # Раздача VOD (из stream/vod.php) -│ │ ├── TimeshiftDelivery.php # Timeshift -│ │ └── SegmentReader.php # Чтение TS-сегментов -│ │ -│ ├── Balancer/ -│ │ └── LoadBalancer.php # Распределение + RedirectStrategy (один файл, < 150 стр.) -│ │ -│ ├── Protection/ -│ │ ├── RestreamDetector.php # Антипиратская защита -│ │ ├── ConnectionLimiter.php # Лимит подключений -│ │ └── GeoBlock.php # Блокировка по стране/ISP/IP/UA -│ │ -│ ├── Codec/ -│ │ ├── FFmpegCommand.php # Построение FFmpeg-команд (из CoreUtilities) -│ │ ├── TranscodeProfile.php # Профили транскодирования -│ │ └── TsParser.php # Парсер MPEG-TS (текущий ts.php) -│ │ -│ └── Health/ -│ ├── DivergenceDetector.php # Мониторинг качества -│ └── BitrateTracker.php # Отслеживание bitrate/FPS +│ ├── Auth/ # StreamAuth, StreamAuthMiddleware +│ ├── Balancer/ # ProxySelector +│ ├── Codec/ # FFmpegCommand, FFprobeRunner, FfmpegPaths +│ ├── Delivery/ # HLSGenerator, OffAirHandler, SegmentReader, +│ │ # SignalSender, StreamRedirector +│ ├── Health/ # ProcessChecker +│ ├── Lifecycle/ # ShutdownHandler +│ └── Protection/ # ConnectionLimiter │ -├── public/ # ═══ ТОЧКИ ВХОДА — HTTP (UI, API) ═══ -│ ├── index.php # Единая точка входа (front controller) -│ │ +├── public/ # ═══ HTTP ТОЧКИ ВХОДА ═══ +│ ├── index.php # Единый front controller +│ ├── routes/ # admin.php, reseller.php, player.php │ ├── Controllers/ -│ │ ├── Admin/ -│ │ │ ├── BaseAdminController.php -│ │ │ ├── DashboardController.php -│ │ │ ├── StreamController.php -│ │ │ ├── LineController.php -│ │ │ ├── VodController.php -│ │ │ ├── ServerController.php -│ │ │ ├── SettingsController.php -│ │ │ ├── UserController.php -│ │ │ ├── BouquetController.php -│ │ │ ├── EpgController.php -│ │ │ └── ... (111 контроллеров, по одному на страницу) -│ │ │ -│ │ ├── Reseller/ -│ │ │ ├── BaseResellerController.php -│ │ │ ├── DashboardController.php -│ │ │ ├── LineController.php -│ │ │ └── ... (22 контроллера) -│ │ │ -│ │ └── Api/ # (planned) -│ │ ├── AdminApiController.php -│ │ ├── ResellerApiController.php -│ │ ├── PlayerApiController.php # Публичный player API -│ │ └── InternalApiController.php # Межсерверный API (текущий www/api.php) -│ │ +│ │ ├── Admin/ # 96 контроллеров (BaseAdminController + по одному на страницу) +│ │ ├── Reseller/ # 30 контроллеров +│ │ ├── Api/ # 9 контроллеров (Admin, Reseller, Player, Internal, Enigma2...) +│ │ └── Player/ # 13 контроллеров │ ├── Views/ -│ │ ├── layouts/ -│ │ │ ├── admin.php # Header + footer шаблон для admin -│ │ │ └── footer.php # Единый footer -│ │ │ -│ │ ├── admin/ -│ │ │ ├── dashboard.php -│ │ │ ├── streams/ -│ │ │ │ ├── list.php -│ │ │ │ ├── edit.php -│ │ │ │ └── view.php -│ │ │ ├── lines/ -│ │ │ ├── vod/ -│ │ │ ├── servers/ -│ │ │ └── settings/ -│ │ │ -│ │ ├── reseller/ -│ │ │ ├── dashboard.php -│ │ │ └── ... -│ │ │ -│ │ └── partials/ -│ │ ├── modals.php -│ │ ├── topbar.php -│ │ └── table.php -│ │ -│ └── routes/ -│ ├── admin.php -│ ├── reseller.php -│ └── api.php +│ │ ├── admin/ # 149 шаблонов +│ │ ├── reseller/ # 24 шаблона +│ │ ├── player/ # 7 шаблонов +│ │ └── layouts/ # admin.php, footer.php, player/, reseller/ +│ └── assets/ # admin/, player/, reseller/ (CSS/JS/images/fonts) │ -├── player/ # Встроенный web-плеер -│ └── ... (PHP + JS + CSS) -│ -├── cli/ # ═══ (PLANNED) CLI ТОЧКИ ВХОДА ═══ -│ ├── Commands/ -│ │ ├── StartupCommand.php # cli/startup.php -│ │ ├── WatchdogCommand.php # cli/watchdog.php -│ │ ├── MonitorCommand.php # cli/monitor.php -│ │ ├── CacheHandlerCommand.php # cli/cache_handler.php -│ │ ├── QueueCommand.php # cli/queue.php -│ │ ├── SignalsCommand.php # cli/signals.php -│ │ ├── ScannerCommand.php # cli/scanner.php -│ │ ├── MigrateCommand.php # Из status -│ │ └── ToolsCommand.php # Из tools (rescue, access, ports и т.д.) -│ └── CronJobs/ -│ ├── StreamsCron.php -│ ├── ServersCron.php -│ ├── CacheCron.php -│ ├── EpgCron.php -│ ├── CleanupCron.php -│ ├── BackupsCron.php -│ ├── StatsCron.php -│ ├── LogsCron.php # lines_logs + streams_logs -│ ├── VodCron.php -│ └── TmdbCron.php +├── cli/ # ═══ CLI ТОЧКИ ВХОДА ═══ +│ ├── CommandInterface.php +│ ├── CommandRegistry.php +│ ├── CronTrait.php +│ ├── DaemonTrait.php +│ ├── migration_logic.php +│ ├── Commands/ # 24 команды (Monitor, Watchdog, Startup, Scanner, Queue...) +│ └── CronJobs/ # 21 крон (StreamsCronJob, ServersCronJob, CacheCronJob...) │ ├── modules/ # ═══ ОПЦИОНАЛЬНЫЕ МОДУЛИ ═══ -│ ├── ministra/ # Ministra/Stalker middleware (текущий ministra/) -│ │ ├── module.json # Manifest: name, version, dependencies -│ │ ├── MinistraModule.php # Точка входа модуля (implements ModuleInterface) -│ │ ├── PortalHandler.php # Диспетчер type+action (15 обработчиков) -│ │ ├── PortalHelpers.php # Хелперы портала (19 статических методов) -│ │ └── assets/ -│ │ -│ ├── plex/ # Plex integration (текущий settings_plex, plex_add) -│ │ ├── module.json -│ │ ├── PlexModule.php -│ │ ├── PlexService.php -│ │ └── config/ -│ │ -│ ├── tmdb/ # TMDB Integration -│ │ ├── module.json -│ │ ├── TmdbModule.php -│ │ ├── TmdbService.php -│ │ ├── TmdbCron.php -│ │ ├── TmdbPopularCron.php -│ │ └── lib.php # proxy → includes/libs/tmdb.php -│ │ -│ ├── watch/ # Watch/Recording (текущие watch, record файлы) -│ │ ├── module.json -│ │ ├── WatchModule.php -│ │ ├── WatchService.php -│ │ ├── RecordingService.php -│ │ ├── WatchController.php -│ │ ├── WatchCron.php -│ │ ├── WatchItem.php -│ │ └── views/ -│ │ ├── watch.php -│ │ ├── watch_scripts.php -│ │ ├── watch_add.php -│ │ ├── watch_add_scripts.php -│ │ ├── settings_watch.php -│ │ ├── settings_watch_scripts.php -│ │ ├── watch_output.php -│ │ ├── watch_output_scripts.php -│ │ ├── record.php -│ │ └── record_scripts.php -│ │ -│ ├── fingerprint/ # Fingerprint watermarking -│ │ ├── module.json -│ │ └── FingerprintModule.php -│ │ -│ ├── theft-detection/ # Anti-theft/restream detection -│ │ ├── module.json -│ │ └── TheftDetectionModule.php -│ │ +│ ├── ministra/ # Ministra/Stalker Portal middleware +│ ├── plex/ # Plex integration +│ ├── tmdb/ # TMDB metadata fetching +│ ├── watch/ # Watch/DVR recording +│ ├── fingerprint/ # Watermarking +│ ├── theft-detection/ # Anti-theft detection │ └── magscan/ # MAG device scanning -│ ├── module.json -│ └── MagscanModule.php │ ├── infrastructure/ # ═══ СИСТЕМНАЯ ИНФРАСТРУКТУРА ═══ -│ ├── nginx/ -│ │ ├── NginxConfigGenerator.php # Генерация nginx.conf (из CoreUtilities) -│ │ ├── templates/ -│ │ │ ├── main.conf.tpl -│ │ │ ├── rtmp.conf.tpl -│ │ │ └── vhost.conf.tpl -│ │ └── NginxReloader.php -│ │ -│ ├── redis/ -│ │ └── RedisManager.php # Подключение, pipeline, pub/sub -│ │ -│ ├── service/ -│ │ ├── ServiceManager.sh # Текущий файл `service` (bash) -│ │ └── daemons.sh # Список демонов -│ │ -│ ├── install/ -│ │ ├── database.sql # Начальная схема -│ │ └── proxy.tar.gz -│ │ -│ └── bin/ # Внешние бинарники (FFmpeg, certbot, yt-dlp и т.д.) -│ ├── ffmpeg_bin/ -│ ├── certbot/ -│ ├── maxmind/ -│ ├── guess -│ ├── yt-dlp -│ └── network.py +│ ├── bootstrap/ # Функции/сессии для admin, reseller, player (facade layer) +│ ├── cache/ # CacheReader +│ ├── database/ # DatabaseFactory +│ ├── legacy/ # player_resize_body, reseller_api_actions и др. +│ ├── nginx/ # templates/ +│ ├── redis/ # RedisManager +│ └── service/ # (bash-скрипты демонов) │ -├── data/ # ═══ ДАННЫЕ ВРЕМЕНИ ВЫПОЛНЕНИЯ ═══ -│ ├── cache/ # Файловый кэш (igbinary) -│ ├── logs/ # Логи приложения -│ ├── tmp/ # Временные файлы (текущий tmp/) -│ ├── content/ # Медиа-контент (текущий content/) -│ │ ├── archive/ -│ │ ├── epg/ -│ │ ├── playlists/ -│ │ ├── streams/ -│ │ ├── video/ -│ │ └── vod/ -│ ├── backups/ # Резервные копии -│ └── signals/ # Сигнальные файлы (.gitkeep) +├── includes/ # ═══ LEGACY (в процессе удаления) ═══ +│ ├── admin.php # Legacy bootstrap — proxy к domain/core (Phase 15: удалить) +│ ├── reseller_api.php # Legacy API +│ ├── ts.php # Timeshift утилиты +│ ├── api/ # admin/table.php, reseller/table.php +│ ├── data/ # permissions.php +│ ├── libs/ # TMDb/, Translator, Logger, XmlStringStreamer и др. +│ └── python/ # PTN/, release.py +│ +├── resources/ # ═══ РЕСУРСЫ ═══ +│ ├── data/ # admin_constants.php +│ ├── langs/ # bg, de, en, es, fr, pt, ru (.ini) +│ └── libs/ # (зарезервировано) │ ├── config/ # ═══ КОНФИГУРАЦИЯ ═══ -│ ├── config.ini # Основной конфиг (DB, Redis, пути) │ ├── modules.php # Список включённых модулей -│ ├── routes.php # Таблица маршрутов -│ ├── plex/ │ └── rclone.conf │ -└── resources/ # ═══ РЕСУРСЫ ═══ - ├── langs/ # Переводы (текущие .ini файлы) - │ ├── en.ini - │ ├── ru.ini - │ ├── de.ini - │ ├── es.ini - │ ├── fr.ini - │ ├── bg.ini - │ └── pt.ini - ├── data/ - │ ├── countries.php # Массивы стран (извлечены из admin.php) - │ ├── timezones.php - │ ├── mac_types.php - │ └── error_codes.php # $rErrorCodes (извлечены из constants.php) - └── libs/ # Сторонние PHP-библиотеки - ├── MobileDetect.php - ├── XmlStringStreamer.php - ├── m3u/ - └── Dropbox.php +├── www/ # ═══ WEB ENTRY POINTS ═══ +│ ├── constants.php # Фасад обратной совместимости +│ ├── init.php # Legacy init +│ ├── index.html # Ministra portal HTML +│ ├── probe.php # FFprobe endpoint +│ ├── progress.php # Progress reporting +│ ├── images/ # Статические изображения +│ ├── admin/ # 7 entry-points (api.php, index.php, live.php...) +│ └── stream/ # 11 streaming endpoints (auth, live, vod, segment...) +│ +├── bin/ # Внешние бинарники (ffmpeg, certbot, yt-dlp, nginx, nginx_rtmp, redis, php...) +├── content/ # Медиа-контент (archive, created, delayed, epg, playlists, streams, video, vod) +├── backups/ # Резервные копии +├── signals/ # Сигнальные файлы +├── tmp/ # Временные файлы +├── migrations/ # SQL-миграции (001_update_crontab_filenames.sql...) +└── ministra/ # Ministra JS-файлы (отдаются nginx напрямую) ``` --- -## 4. Описание компонентов +## 3. Описание компонентов -### 4.1. `core/` — Ядро системы +### 3.1. `core/` — Ядро -**Ответственность:** Инфраструктурные сервисы, которые нужны любому контексту исполнения. Не содержит бизнес-логики. - -| Подкаталог | Что даёт | Что заменяет | -|------------|----------|--------------| -| `Config/` | Загрузка конфигурации, резолв путей | 70 `define()` из `constants.php` и `stream/init.php` | -| `Database/` | PDO-обёртка (Database + DatabaseHandler), query builder, миграции | `Database.php` + SQL-запросы из `status` | -| `Cache/` | Унифицированный кэш с двумя драйверами | Разбросанные `igbinary_serialize` + ad-hoc Redis | -| `Auth/` | Единая авторизация для admin и reseller | `session.php` × 2, `API::processLogin()`, `ResellerAPI::processLogin()` | -| `Http/` | Абстракция запросов, роутинг, middleware | `cleanGlobals()` × 2, `switch($rAction)`, flood-check в constants.php | -| `Process/` | Управление PID, демонами, блокировками | `shell_exec('ps aux')`, `posix_kill()`, `checkCron()` разбросанные по файлам | -| `Logging/` | Унифицированное логирование | `CoreUtilities::saveLog()`, `StreamingUtilities::clientLog()`, `Logger::init()` | -| `Events/` | Event bus для связи без жёстких зависимостей | Прямые вызовы между компонентами | -| `Container/` | DI-контейнер (composition root only — см. §2.3) | `global $db`, статические `CoreUtilities::$rSettings` | -| `Util/` | Утилиты без состояния | Функции разбросанные по CoreUtilities, admin.php | +Инфраструктурные сервисы для любого контекста исполнения. Не содержит бизнес-логики. **Ключевое правило:** `core/` не знает о существовании `domain/`, `streaming/`, `modules/`, `public/`. Зависимости направлены только внутрь ядра. -### 4.2. `domain/` — Бизнес-логика +| Подкаталог | Что даёт | +|------------|----------| +| `Config/` | Загрузка конфигурации, резолв путей, управление настройками | +| `Database/` | PDO-обёртка (Database + DatabaseHandler), миграции | +| `Cache/` | Унифицированный кэш (File + Redis) через `CacheInterface` | +| `Auth/` | Единая авторизация (admin + reseller), RBAC, brute-force защита | +| `Http/` | Абстракция запросов, роутинг, middleware pipeline | +| `Process/` | Управление PID, потоками (Multithread, Thread) | +| `Logging/` | Унифицированное логирование (File + Database) | +| `Events/` | Event bus для модульных хуков | +| `Container/` | DI-контейнер (composition root only — §1.2) | +| `Error/` | Обработчик ошибок, коды ошибок | +| `GeoIP/` | Гео-определение по IP | +| `Util/` | Утилиты без состояния (Encryption, Network, Time, Image, Stream) | +| `Validation/` | Валидация входных данных | +| `Module/` | Загрузчик модулей, `ModuleInterface` | -**Ответственность:** Сервисы и репозитории, организованные по контекстам. Каждый контекст (Stream, Vod, Line, User...) — отдельная директория. +### 3.2. `domain/` — Бизнес-логика -**Паттерн для каждого контекста:** - -``` -domain/Stream/ - ├── StreamService.php # Бизнес-логика + оркестрация (валидация, транзакции, side-effects) - ├── StreamRepository.php # SQL-запросы (SELECT/INSERT/UPDATE/DELETE) - └── StreamProcess.php # Специфичные системные операции (ffmpeg, kill, PID) -``` +Сервисы и репозитории, организованные по контекстам. **Правила:** +1. **Service** = вся бизнес-логика контекста (валидация, транзакции, side-effects) +2. **Repository** = только SQL. Принимает массивы, возвращает массивы +3. Если Repository < 5 методов и < 150 строк — живёт в одном файле с Service (§1.3) -1. **Service** = вся бизнес-логика контекста. Валидация, транзакции, вызов Infrastructure (nginx, ffmpeg), логирование. Один Service на контекст, при росте можно разбить. -2. **Repository** = только SQL. Принимает массивы, возвращает массивы. Никакой логики. Никаких side-effects. -3. **Нет Entity-классов.** Данные передаются как `array`. Это PHP — массивы проще и понятнее, чем аномичные DTO-объекты. -4. **Консолидация мелких классов (§2.4).** Если Repository < 5 методов и < 150 строк — он живёт в одном файле с Service. Мелкие контексты (Ticket, Settings) вливаются в родственный контекст, а не создают отдельную директорию. +> **Текущее состояние:** Часть domain-классов уже использует constructor injection (как показано ниже). Другая часть (StreamService, StreamRepository и др.) ещё работает через `global $db` и статические методы — миграция в процессе. ```php -// ✅ ПРАВИЛЬНО — Service делает всё: +// ═══ ЦЕЛЕВОЙ ПАТТЕРН (новый код) ═══ + +// Service делает всё: class StreamService { public function __construct( private StreamRepository $repository, @@ -642,14 +320,11 @@ class StreamService { private FileLogger $logger, private Database $db ) {} - + public function create(array $data): int { - // Валидация if (empty($data['stream_source'])) { throw new \InvalidArgumentException('Source required'); } - - // Транзакция $this->db->beginTransaction(); try { $id = $this->repository->insert($data); @@ -658,133 +333,121 @@ class StreamService { $this->db->rollBack(); throw $e; } - $this->logger->log('stream', "Created stream {$id}"); return $id; } } -// ✅ ПРАВИЛЬНО — Repository = только SQL: +// Repository = только SQL: class StreamRepository { public function __construct(private Database $db) {} - public function findById(int $id): ?array { return $this->db->row("SELECT * FROM streams WHERE id = ?", [$id]); } - - public function insert(array $data): int { - $this->db->query("INSERT INTO streams ...", $data); - return $this->db->lastInsertId(); - } } ``` -**Откуда берётся код:** -- `admin_api.php` (6981 строк) → разбивается на ~10 Service + ~6 Repository (§2.4 — мелкие Repository влиты в Service) -- `admin.php` → процедурные `getUserInfo()`, `getSeriesList()` → в Repository -- Оркестрация (validate + save + nginx + cache + log) → в Service +```php +// ═══ LEGACY-ПАТТЕРН (ещё не мигрировано) ═══ +// StreamService, StreamRepository — статические методы + global $db +// Миграция на constructor injection запланирована (см. MIGRATION.md) -**Зависимости:** `domain/` зависит от `core/` (Database, Cache, Events). Не зависит от `public/` или `modules/`. +class StreamRepository { + public static function getById($rID) { + global $db; + $db->query('SELECT * FROM `streams` WHERE `id` = ?;', $rID); + return $db->fetchRecord(); + } +} +``` -### 4.3. `streaming/` — Стриминг-движок +### 3.3. `streaming/` — Стриминг-движок -**Ответственность:** Весь hot path доставки видео. Выделен отдельно от `domain/` потому что: +Весь hot path доставки видео. Выделен отдельно от `domain/` по причинам: - Критичен к производительности (нельзя загружать всю бизнес-логику) -- Имеет собственный лёгкий bootstrap -- Работает на уровне байтов/сегментов, а не на уровне CRUD +- Имеет собственный лёгкий bootstrap (`StreamingBootstrap.php`) +- Работает на уровне байтов/сегментов, а не CRUD -#### Изоляция streaming - -Streaming — **отдельный контекст исполнения** с минимальными зависимостями: +**Изоляция:** ``` streaming/ зависит от: ✅ core/ (Database, Redis, Logging, GeoIP, Encryption, NetworkUtils) - ✅ domain/ (Repository — только SELECT-запросы: потоки, серверы, букеты) - + ✅ domain/ (Repository — только SELECT-запросы) + ❌ НЕ зависит от: - public/ (контроллеры) - - modules/ (всё) + - modules/ - domain/*Service.php (бизнес-мутации) ``` -**Главное правило:** streaming вызывает **только read-методы** Repository. Запись данных (PID, stats, connections) — через собственные классы (`ConnectionTracker`, `BitrateTracker`), а не через Domain Services. +Streaming вызывает **только read-методы** Repository. Запись — через собственные классы (`ConnectionTracker`). -**Откуда берётся код:** -- `StreamingUtilities.php` (1992 стр.) → распределяется по подкаталогам -- `www/stream/auth.php` (800 стр.) → `Auth/TokenAuth.php` + `Auth/StreamAuth.php` + `Protection/` -- `www/stream/live.php` (708 стр.) → `Delivery/LiveDelivery.php` -- `includes/cli/monitor.php` (565 стр.) → `domain/Stream/StreamMonitor.php` (с рефакторингом goto) -- `CoreUtilities` методы FFmpeg → `Codec/FFmpegCommand.php` -- `ts.php` → `Codec/TsParser.php` +### 3.4. `public/` — HTTP точки входа -### 4.4. `public/` — Точки входа +Получение запроса → вызов Service → формирование ответа. Никакой бизнес-логики. -**Ответственность:** Получение запроса, вызов Service, формирование ответа. Никакой бизнес-логики. - -**HTTP:** - Один front controller `public/index.php` + `Router` -- Контроллеры вызывают **Service** из `domain/` для мутаций и **Repository** для чтения -- Шаблоны (`Views/`) — чистый HTML с минимумом PHP-вставок +- Контроллеры вызывают **Service** для мутаций, **Repository** для чтения +- Шаблоны (`Views/`) — HTML с минимумом PHP-вставок - Admin и Reseller — разные контроллеры, общие шаблоны -```php -// ✅ ПРАВИЛЬНО — контроллер вызывает Service: -class StreamController { - public function __construct( - private StreamService $streamService, - private StreamRepository $streamRepo - ) {} - - public function create(Request $request): void { - $id = $this->streamService->create($request->all()); - Response::json(['id' => $id]); - } - - public function list(): void { - $streams = $this->streamRepo->getAll(); - include VIEW_PATH . '/admin/streams/list.php'; - } -} +### 3.5. `cli/` — CLI точки входа -// ❌ ЗАПРЕЩЕНО — контроллер содержит бизнес-логику: -class StreamController { - public function create(Request $request): void { - // Валидация, транзакции, nginx — это задача Service - $this->db->beginTransaction(); - $this->streamRepo->insert(...); // ← fat controller - $this->nginx->reload(); - $this->db->commit(); - } -} -``` - -**CLI:** -- Каждый демон/крон — отдельный Command-класс +- Каждый демон/крон — отдельный Command/CronJob класс +- Все вызовы через `console.php` (например `php console.php cron:streams`) - Общая инициализация через `bootstrap.php` + `ServiceContainer` -- Файл `service` (bash) → `infrastructure/service/ServiceManager.sh` -**Откуда берётся код:** -- Каждый `admin/*.php` (100+ файлов) → Controller + View. Пример: `admin/streams.php` → `Controllers/Admin/StreamController.php` + `Views/admin/streams/list.php` -- `admin/header.php` (675 стр.) + `admin/footer.php` (805 стр.) → `Views/layouts/admin.php` + выделение JS в файлы `assets/` -- `admin/post.php` (1946 стр.) → обработчики форм распределяются по контроллерам -- `crons/*.php` → `cli/CronJobs/`, каждый крон — один файл (**crons/ удалён**, Phase 12.8) -- `includes/cli/*.php` → `cli/Commands/` (**includes/cli/ удалён**, Phase 12.6) +### 3.6. `modules/` — Опциональные модули -### 4.5. `modules/` — Опциональные модули +Каждый модуль — самодостаточная директория с `module.json` + класс, реализующий `ModuleInterface`. -**Ответственность:** Дополнительные функции, которые не являются частью ядра. Каждый модуль — самодостаточная директория. +| Модуль | Назначение | +|--------|-----------| +| `ministra/` | Ministra/Stalker Portal middleware | +| `plex/` | Plex integration | +| `tmdb/` | TMDB metadata fetching | +| `watch/` | Watch/DVR recording | +| `fingerprint/` | Watermarking | +| `theft-detection/` | Anti-theft detection | +| `magscan/` | MAG device scanning | -**Контракт модуля:** +### 3.7. `includes/` — Legacy-код (в процессе удаления) + +Оставшийся legacy-код, который ещё не полностью мигрирован: +- `admin.php` — legacy bootstrap (proxy к `domain/` и `core/`) +- `reseller_api.php` — legacy API-обработчик +- `libs/` — сторонние библиотеки (TMDb, Translator, XmlStringStreamer) + +**Статус:** Удаление запланировано в Phase 15 (см. MIGRATION.md). + +### 3.8. Bootstrap — контексты инициализации + +`bootstrap.php` предоставляет `XC_Bootstrap` с четырьмя контекстами: + +| Контекст | Что загружает | Для чего | +|----------|--------------|----------| +| `CONTEXT_MINIMAL` | autoload + constants + config + Logger | Скрипты, которым нужны только пути и конфиг | +| `CONTEXT_CLI` | + Database + LegacyInitializer (+ Redis опционально) | Cron-задачи, CLI-скрипты | +| `CONTEXT_STREAM` | + Database (lightweight, cached) | Стриминг-эндпоинты (hot path) | +| `CONTEXT_ADMIN` | + Database + LegacyInitializer + Redis + API + ResellerAPI + Translator + MobileDetect + session | Админ/реселлер-панель | + +--- + +## 4. Система модулей + +### 4.1. Контракт модуля ```php interface ModuleInterface { public function getName(): string; public function getVersion(): string; - public function boot(ServiceContainer $container): void; // Регистрация сервисов - public function registerRoutes(Router $router): void; // Свои маршруты - public function registerCrons(): array; // Свои кроны - public function getEventSubscribers(): array; // Подписки на события + public function boot(ServiceContainer $container): void; + public function registerRoutes(Router $router): void; + public function registerCommands(CommandRegistry $registry): void; + public function getEventSubscribers(): array; + public function install(): void; + public function uninstall(): void; } ``` @@ -793,260 +456,83 @@ interface ModuleInterface { { "name": "ministra", "version": "1.0.0", - "description": "Ministra/Stalker Portal middleware", - "requires_core": ">=2.0", - "dependencies": [] + "description": "Ministra portal integration module", + "requires_core": ">=2.0" } ``` -**Текущий код → модули:** +### 4.2. Жизненный цикл -| Модуль | Источник | Почему модуль, а не ядро | -|--------|----------|-------------------------| -| `ministra/` | `src/ministra/` (2155+ строк, десятки JS-файлов) | Сторонний портал, не всем нужен | -| `plex/` | `settings_plex.php`, `plex_add.php`, `crons/plex.php`, `config/plex/` | Интеграция с внешним сервисом | -| `tmdb/` | `includes/libs/TMDb/`, `crons/tmdb.php`, `crons/tmdb_popular.php` | Внешний API для метаданных | -| `watch/` | `watch.php`, `watch_add.php`, `watch_output.php`, `settings_watch.php`, `crons/watch.php` | Запись/DVR — расширенная функция | -| `fingerprint/` | `admin/fingerprint.php` | Водяные знаки — enterprise-функция | -| `theft-detection/` | `admin/theft_detection.php` | Антипиратство — enterprise-функция | -| `magscan/` | `admin/magscan_settings.php` | Сканирование устройств | +1. Модуль размещается в `modules/{name}/` +2. `ModuleLoader` сканирует `modules/*/module.json` +3. `config/modules.php` проверяется на overrides (`enabled => false`) +4. `boot(ServiceContainer)` → регистрация сервисов +5. `registerRoutes(Router)` → HTTP-маршруты +6. `registerCommands(CommandRegistry)` → CLI-команды и кроны +7. `getEventSubscribers()` → подписки на события +8. Установка: `install()` — создание таблиц, начальные данные +9. Удаление: `uninstall()` → убрать из `modules.php` → удалить папку -### 4.6. `infrastructure/` — Системный слой - -**Ответственность:** Взаимодействие с ОС: nginx, redis, bash-скрипты, внешние бинарники. - -**Откуда берётся код:** -- `CoreUtilities` → генерация nginx-конфигурации → `nginx/NginxConfigGenerator.php` -- `bin/nginx/`, `bin/php/`, `bin/redis/` → `bin/` (бинарники как есть) -- `bin/daemons.sh` → `service/daemons.sh` -- `service` → `service/ServiceManager.sh` -- `bin/install/database.sql` → `install/database.sql` - -### 4.7. `data/` — Runtime-данные - -**Ответственность:** Всё, что генерируется при эксплуатации и не является кодом. - -Заменяет текущие: `tmp/`, `content/`, `backups/`, `signals/`. - -### 4.8. `config/` — Конфигурация - -**Ответственность:** Всё, что администратор может изменить без правки кода. - -### 4.9. `resources/` — Статические ресурсы и данные - -**Ответственность:** Переводы, данные-справочники (страны, таймзоны), сторонние PHP-библиотеки. - -**Откуда берётся код:** -- `includes/langs/*.ini` → `langs/` ✅ ВЫПОЛНЕНО -- Inline-массивы стран из `admin.php` → `data/countries.php` -- `$rErrorCodes` из `constants.php` → `data/error_codes.php` -- `includes/libs/*` → `libs/` - ---- - -## 5. Карта миграции: откуда → куда - -### 5.1. God-объект `CoreUtilities.php` (4847 строк) - -| Метод/блок | Целевой файл | -|------------|-------------| -| `init()`, config loading | `core/Config/ConfigLoader.php` | -| `$db`, `getDatabase()` | `core/Database/DatabaseHandler.php` | -| `getCache()`, `setCache()` | `core/Cache/FileCache.php` | -| Redis operations | `core/Cache/RedisCache.php`, `infrastructure/redis/RedisManager.php` | -| `cleanGlobals()`, `parseIncomingRecursively()` | `core/Http/Request.php` | -| `startMonitor()`, `stopStream()`, `startMovie()` | `domain/Stream/StreamService.php` | -| `isStreamRunning()`, `isMonitorRunning()` | `core/Process/ProcessManager.php` | -| FFmpeg command building | `streaming/Codec/FFmpegCommand.php` | -| GeoIP lookup | `core/Util/GeoIP.php` | -| Nginx config generation | `infrastructure/nginx/NginxConfigGenerator.php` | -| Image resize/upload | `core/Util/ImageUtils.php` | -| Encryption (AES/token) | `core/Util/Encryption.php` | -| `saveLog()` | `core/Logging/FileLogger.php` | - -### 5.2. God-объект `admin_api.php` (6981 строк) - -| Метод/блок | Целевой файл | -|------------|-------------| -| `processStream()` | `domain/Stream/StreamService.php` | -| `processMovie()` | `domain/Vod/MovieService.php` | -| `processSerie()` | `domain/Vod/SeriesService.php` | -| `processEpisode()` | `domain/Vod/EpisodeService.php` | -| `processLine()` | `domain/Line/LineService.php` | -| `processMAG()` | `domain/Device/MagService.php` | -| `processEnigma()` | `domain/Device/EnigmaService.php` | -| `processServer()` | `domain/Server/ServerService.php` | -| `processBouquet()` | `domain/Bouquet/BouquetService.php` (+ Repository + Mapper — один файл) | -| `processUser()` | `domain/User/UserService.php` (+ submit ticket, editProfile) | -| `processGroup()` | `domain/User/GroupService.php` (+ GroupRepository — один файл) | -| `processCode()` | `domain/Auth/AuthService.php` (+ Code + HMAC — объединены) | -| `processLogin()` | `core/Auth/Authenticator.php` | -| `processSettings()` | `domain/Server/SettingsService.php` (влито из domain/Settings/) | -| `massEditStreams()` | `domain/Stream/StreamService::massEdit()` | -| `checkMinimumRequirements()` | Валидация в каждом Service | - -### 5.3. God-bootstrap `admin.php` (4448 строк) - -| Блок | Целевой файл | -|------|-------------| -| `session_start()`, session config | `core/Auth/SessionManager.php` | -| 50+ `define()` статусов | `constants.php` | -| `Database` creation | `core/Container/ServiceContainer.php` | -| `CoreUtilities::init()` | `bootstrap.php` | -| `$rCountryCodes`, `$rCountries` | `resources/data/admin_constants.php` | -| `$rPermissions` | `resources/data/admin_constants.php` → `core/Auth/Authorization.php` | -| `getUserInfo()` | `domain/User/UserRepository.php` | -| `getSeriesList()`, `updateSeries()` | `domain/Vod/SeriesService.php` (Repository влит — §2.4) | -| `secondsToTime()` | `core/Util/TimeUtils.php` | -| `hasPermissions()` | `core/Auth/Authorization.php` | -| Mobile detect init | `core/Http/Middleware/` (если нужно) | -| Translator init | `bootstrap.php` → `ServiceContainer` | - -### 5.4. Страницы admin/ (100+ файлов) - -**Паттерн трансформации:** - -``` -БЫЛО: - admin/streams.php (один файл = SQL + HTML + JS) - -СТАЛО: - public/Controllers/Admin/StreamController.php — маршрутизация - domain/Stream/StreamRepository.php — данные - public/Views/admin/streams/list.php — шаблон -``` - -### 5.5. Дублирование admin/ ↔ reseller/ - -``` -БЫЛО: - admin/header.php (675 строк) + reseller/header.php (284 строки) - admin/footer.php (805 строк) + reseller/footer.php - admin/session.php + reseller/session.php - admin/functions.php + reseller/functions.php - -СТАЛО: - public/Views/layouts/admin.php — единый layout - public/Views/layouts/footer.php — единый footer - core/Auth/SessionManager.php — единый менеджер сессий - core/Auth/Authorization.php — RBAC определяет, что видит пользователь -``` - -### 5.6. Стриминг-путь - -``` -БЫЛО: - www/stream/init.php (дублированный bootstrap) - www/stream/auth.php (800 строк: auth + block + token + balance) - www/stream/live.php (708 строк: token + ondemand + delivery + heartbeat) - StreamingUtilities.php (1992 строки: форк CoreUtilities) - -СТАЛО: - streaming/StreamingBootstrap.php — лёгкий init без дублирования - streaming/Auth/TokenAuth.php — парсинг токенов - streaming/Auth/StreamAuth.php — проверка доступа - streaming/Protection/GeoBlock.php — блокировки (IP/ISP/UA/Country) - streaming/Delivery/LiveDelivery.php — раздача контента - streaming/Health/BitrateTracker.php — мониторинг качества - core/Cache/* — общий кэш (не дублируется) - core/Database/DatabaseHandler.php — общая БД (не дублируется) -``` - ---- - -## 6. Система модулей - -### 6.1. Жизненный цикл модуля - -``` -1. Модуль размещается в modules/{name}/ -2. Добавляется в config/modules.php -3. При bootstrap: ServiceContainer сканирует modules.php -4. Для каждого модуля: require module.json → проверка зависимостей → boot() -5. Модуль регистрирует: сервисы, маршруты, кроны, event-подписки -6. Удаление: убрать из modules.php → (опционально) удалить папку -``` - -### 6.2. Правила изоляции +### 4.3. Правила изоляции ``` ✅ Модуль МОЖЕТ: - Использовать сервисы из core/ через constructor injection - - Вызывать Service из domain/ для бизнес-операций - - Использовать Repository из domain/ для чтения данных + - Вызывать Service/Repository из domain/ - Регистрировать свои маршруты, команды, кроны - - Подписываться на события-хуки ядра - - Иметь свои assets, views, конфиги + - Подписываться на события ядра + - Иметь свои views, assets, конфиги ❌ Модуль НЕ МОЖЕТ: - Модифицировать файлы core/ или domain/ - - Напрямую обращаться к базе данных мимо Repository - - Зависеть от другого модуля без явной декларации в module.json + - Обращаться к БД мимо Repository + - Зависеть от другого модуля без декларации в module.json - Переопределять маршруты или сервисы ядра - - Добавлять ограничения лицензирования в ядро ``` -### 6.3. Расширяемость через события-хуки +### 4.4. События-хуки События используются **только для модульных хуков**, а не внутри обычного CRUD. -**Когда использовать события:** -- Модуль хочет отреагировать на действие ядра (например, запись DVR при запуске потока) -- Ядро не должно знать о модуле - -**Когда НЕ использовать:** -- Внутри CRUD-операций (создал поток → обновил nginx) — это прямой вызов в Service -- Между ядерными компонентами (это магия, которую сложно отладить) - -```php -// ✅ ПРАВИЛЬНО — события для модульных хуков: -// Ядро публикует: -$dispatcher->dispatch(new StreamStartedEvent($streamId)); - -// Модуль watch подписывается: -class WatchModule implements ModuleInterface { - public function getEventSubscribers(): array { - return [ - StreamStartedEvent::class => [WatchRecorder::class, 'onStreamStarted'], - ]; - } -} - -// ❌ НЕПРАВИЛЬНО — события внутри CRUD: -class StreamService { - public function create(array $data): int { - $id = $this->repo->insert($data); - // НЕ нужно dispatch StreamCreatedEvent для обновления nginx. - // Просто вызвать $this->nginx->reload() напрямую. - $this->nginx->reload(); - return $id; - } -} -``` - -### 6.4. Коммерческие модули (будущее) - -Коммерческие модули — обычные модули в `modules/`, но: -- Доставляются отдельно (не в основном репозитории) -- Могут содержать собственную проверку лицензии **внутри себя** -- Ядро **не содержит** кода проверки лицензии — если модуль удалён, система работает - -``` -modules/ -├── ministra/ ← open-source, в основном репо -├── plex/ ← open-source, в основном репо -├── enterprise-analytics/ ← коммерческий, отдельный репо -│ ├── module.json -│ ├── License.php ← проверка лицензии ВНУТРИ модуля -│ └── AnalyticsModule.php -└── advanced-security/ ← коммерческий, отдельный репо -``` +- **Когда использовать:** Модуль хочет отреагировать на действие ядра (DVR-запись при запуске потока) +- **Когда НЕ использовать:** Внутри CRUD-операций — это прямой вызов в Service --- -## 7. Границы ядра и модулей +## 5. Варианты сборки: MAIN vs LoadBalancer -### 7.1. Пакетная диаграмма зависимостей +### 5.1. Два артефакта из одной кодовой базы + +| Артефакт | Назначение | Что включает | Что исключает | +|----------|-----------|--------------|---------------| +| **MAIN** | Основной сервер (admin + streaming) | Всё содержимое `src/` | Ничего | +| **LoadBalancer (LB)** | Стриминг-сервер без управления | Только стриминг + инфраструктура | Админ-панель, модули, player | + +**Принцип:** LB — подмножество MAIN. Код не форкается, а фильтруется при сборке. + +### 5.2. Конфигурация сборки + +LB собирается из следующих директорий и файлов (определены в `Makefile`): + +**Директории (`LB_DIRS`):** `bin`, `cli`, `config`, `content`, `core`, `domain`, `includes`, `infrastructure`, `resources`, `signals`, `streaming`, `tmp`, `www` + +**Root-файлы (`LB_ROOT_FILES`):** `autoload.php`, `bootstrap.php`, `console.php`, `service`, `update` + +**Исключения (`LB_DIRS_TO_REMOVE`):** `bin/install`, `bin/redis`, `bin/nginx/conf/codes`, `includes/api`, `includes/libs/resources`, `domain/User`, `domain/Device`, `domain/Auth`, `resources/langs`, `resources/libs` + +**Отдельные файлы, удаляемые из LB (`LB_FILES_TO_REMOVE`):** `includes/admin.php`, `includes/reseller_api.php`, `www/probe.php`, `config/rclone.conf`, ряд CLI-команд и CronJob'ов (admin-only), `domain/Epg/EPG.php` + +**Полностью исключены из LB:** `public/`, `modules/`, `ministra/` + +### 5.3. Правила для разработки с учётом LB + +1. Код в `domain/`, используемый через streaming, **не должен** тянуть admin-only зависимости +2. При добавлении новых root-директорий — обновить `LB_DIRS` в Makefile +3. `domain/` частично нужен LB — нельзя целиком исключать, только admin-specific поддомены +4. Все модули — это admin-функциональность, в LB не попадают +5. Проверка сборки: `make new && make lb` + +### 5.4. Пакетная диаграмма зависимостей ``` ┌──────────────┐ @@ -1059,335 +545,50 @@ modules/ │ domain/ │ │streaming/│ │ modules/ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ - │ depends on │ depends on │ depends on - ▼ ▼ ▼ + └────────────┼────────────┘ + ▼ ┌──────────────────────────────────┐ - │ core/ │ ← Config, DB, Cache, Auth, Process, - │ │ Logging, Events, Container, Util + │ core/ │ └──────────────────────────────────┘ │ ▼ ┌──────────────────────────────────┐ - │ infrastructure/ │ ← nginx, redis, bin, service + │ infrastructure/ │ └──────────────────────────────────┘ ``` **Стрелки всегда направлены вниз.** Ни один нижний слой не знает о верхних. -**Исключение:** `streaming/` — горизонтальный слой на уровне `domain/`, но с read-only доступом к domain/Repository. У него свой лёгкий entry path (см. §4.3). - -### 7.2. Что входит в ядро, а что нет - -| В ядро (`core/` + `domain/`) | В модули (`modules/`) | -|-------------------------------|----------------------| -| Управление потоками (CRUD, запуск, остановка) | Ministra portal | -| Управление подписками (линии, пакеты) | Plex integration | -| Управление VOD (фильмы, сериалы) | TMDb metadata fetching | -| Авторизация и RBAC | Fingerprint watermarking | -| Серверная инфраструктура | Theft detection | -| EPG (базовый импорт) | MAG device scanning | -| Bouquet management | Watch/DVR recording | -| Стриминг-движок | Advanced analytics (будущее) | -| API (admin, reseller, player) | White-label theming (будущее) | - --- -### 5.1 includes/ — Устаревшие файлы +## 6. Транзакции и производительность -``` -includes/ -├── admin.php # ~150 глобальных функций -> proxy к domain/*/Service.php -├── admin_api.php # ~80 методов класса AdminAPI -> proxy к domain/*/Service.php -├── reseller_api.php # ~9 методов класса ResellerAPI -├── CoreUtilities.php # ~135 статических методов -> proxy к core/ и domain/ -├── StreamingUtilities.php # ~66 статических методов -> proxy к streaming/ -├── ts.php # Timeshift утилиты -├── api/ # API-обработчики -│ ├── admin/ -│ └── reseller/ -├── cli/ # CLI-скрипты (monitor, balancer, watchdog и т.д.) -├── data/ # Статические данные (permissions) -├── langs/ # Файлы локализации (en.ini, ru.ini, de.ini, ...) -├── libs/ # Сторонние библиотеки -│ ├── Logger.php -│ ├── AsyncFileOperations.php -│ ├── Dropbox.php -│ ├── TMDb/ -│ └── ... -└── python/ # Python-скрипты -``` - -**Статус**: Все 150 функций `admin.php` и 135 методов `CoreUtilities` уже являются proxy-обёртками, делегирующими в `domain/` и `core/`. После завершения миграции файлы будут удалены. - -### 5.2 admin/ — Легаси admin-страницы - -~120 PHP-файлов — старые admin-страницы, которые содержали SQL+HTML в одном файле. **Заменены** на `public/Controllers/Admin/` + `public/Views/admin/`. - -### 5.3 reseller/ — Легаси reseller-страницы - -~35 PHP-файлов — аналогично admin/, но для реселлерской панели. **Заменены** на `public/Controllers/Reseller/` + `public/Views/reseller/`. - -### 5.4 ministra/ — Stalker Portal JS - -JavaScript-файлы для поддержки Stalker Portal (MAG-устройства). Не PHP — отдаются nginx напрямую. Сохраняется как есть. - -### 5.5 player/ — Встроенный web-плеер - -Мини-приложение (PHP + JS + CSS) для веб-отображения потоков. Имеет собственные `header.php`, `footer.php`, `functions.php`. Сохраняется как есть до отдельного рефакторинга. - ---- - -## 8. Варианты сборки: MAIN vs LoadBalancer - -### 8.1. Два артефакта из одной кодовой базы - -Система собирается в два варианта из одного `src/`: - -| Артефакт | Назначение | Что включает | Что исключает | -|----------|-----------|--------------|---------------| -| **MAIN** | Основной сервер (admin + streaming) | Всё содержимое `src/` | Ничего | -| **LoadBalancer (LB)** | Стриминг-сервер без управления | Только стриминг + инфраструктура | Админ-панель, реселлер, player, ministra, admin-only модули | - -**Ключевой принцип:** LB — это подмножество MAIN. Код не форкается, а фильтруется при сборке. - -### 8.2. Проблема LB-сборки (РЕШЕНО ✅ — Phase 9.1) - -Старый `Makefile` собирал LB из фиксированного списка `LB_FILES`, который не включал новые архитектурные директории. После фаз 0–8 это означало, что LB-сборка не содержала `core/`, `domain/`, `streaming/`, `infrastructure/`, `resources/`, а также `autoload.php` и `bootstrap.php`. - -**Решение (Phase 9.1):** `LB_FILES` разделён на `LB_DIRS` + `LB_ROOT_FILES`, добавлены все необходимые директории и admin-only исключения. Подробности — §8.3. - -| Директория / файл | Нужен LB? | Статус | -|----|---|---| -| `autoload.php` | ✅ ДА | ✅ Добавлен в `LB_ROOT_FILES` | -| `bootstrap.php` | ✅ ДА | ✅ Добавлен в `LB_ROOT_FILES` | -| `core/` | ✅ ДА | ✅ Добавлен в `LB_DIRS` | -| `domain/` | ⚠ ЧАСТИЧНО | ✅ В `LB_DIRS` + исключения `User/`, `Device/`, `Auth/` | -| `streaming/` | ✅ ДА | ✅ Добавлен в `LB_DIRS` | -| `infrastructure/` | ✅ ДА | ✅ Добавлен в `LB_DIRS` | -| `resources/` | ⚠ ЧАСТИЧНО | ✅ В `LB_DIRS` + исключения `langs/`, `libs/` | -| `public/` | ❌ НЕТ | ✅ Корректно отсутствует | -| `modules/` | ❌ НЕТ | ✅ Корректно отсутствует | -| `admin/` | ❌ НЕТ | ✅ Корректно отсутствует | -| `ministra/` | ❌ НЕТ | ✅ Корректно отсутствует | -| `player/` | ❌ НЕТ | ✅ Корректно отсутствует | -| `reseller/` | ❌ НЕТ | ✅ Корректно отсутствует | - -### 8.3. Реализованная конфигурация Makefile ✅ - -#### `LB_DIRS` + `LB_ROOT_FILES` (вместо единого `LB_FILES`) - -```makefile -# Директории (копируются через git ls-files | grep "^src/$dir/") -LB_DIRS := bin config content core crons domain includes \ - infrastructure resources signals streaming tmp www - -# Root-файлы (копируются через git ls-files --error-unmatch) -LB_ROOT_FILES := autoload.php bootstrap.php service status update -``` - -> **Почему раздельно:** grep `"^src/$item/"` с trailing `/` никогда не матчит файлы (`autoload.php`, `service`, `status`, `update`). Поэтому root-файлы копируются отдельным циклом через `git ls-files --error-unmatch`. - -#### `LB_DIRS_TO_REMOVE` (admin-only директории) - -```makefile -LB_DIRS_TO_REMOVE := \ - bin/install \ - bin/redis \ - bin/nginx/conf/codes \ - includes/api \ - includes/libs/resources \ - domain/User \ - domain/Device \ - domain/Auth \ - resources/langs \ - resources/libs -``` - -#### `lb_copy_files` — два цикла - -1. **Директории:** `for lb_item in $(LB_DIRS)` → `git ls-files | grep "^src/$$lb_item/"` → copy -2. **Root-файлы:** `for root_file in $(LB_ROOT_FILES)` → `git ls-files --error-unmatch "src/$$root_file"` → copy - -#### `lb_update_copy_files` — каскадная проверка - -```bash -# Для каждого changed file: -for lb_item in $(LB_DIRS); do # 1. проверка по директории - grep -q "^$$lb_item/" -done -for root_file in $(LB_ROOT_FILES); do # 2. проверка по root-файлу - [ "$$rel_path" = "$$root_file" ] -done -``` - -#### `set_permissions` — новые директории - -```makefile -# core/ domain/ streaming/ infrastructure/ resources/ → dirs:755, files:644 -@for arch_dir in core domain streaming infrastructure resources; do \ - if [ -d "$(TEMP_DIR)/$$arch_dir" ]; then \ - find "$(TEMP_DIR)/$$arch_dir" -type d -exec chmod 755 {} +; \ - find "$(TEMP_DIR)/$$arch_dir" -type f -exec chmod 644 {} +; \ - fi; \ -done - -# autoload.php, bootstrap.php → 644 -chmod 0644 $(TEMP_DIR)/autoload.php 2>/dev/null || true -chmod 0644 $(TEMP_DIR)/bootstrap.php 2>/dev/null || true -``` - -### 8.4. Что LB использует — карта зависимостей - -``` -www/stream/*.php ──→ bootstrap.php (CONTEXT_STREAM) - │ - ├──→ autoload.php (class map) - ├──→ core/Config/ (пути, конфигурация) - ├──→ core/Database/ (PDO-обёртка) - ├──→ core/Cache/ (Redis + File кэш) - ├──→ core/Auth/ (BruteforceGuard) - ├──→ core/Http/ (Request, RequestGuard) - ├──→ core/Process/ (ProcessManager) - ├──→ core/Logging/ (FileLogger, DatabaseLogger) - ├──→ core/Util/ (GeoIP, NetworkUtils, Encryption) - ├──→ core/Error/ (ErrorHandler, ErrorCodes) - │ - ├──→ streaming/Auth/ (TokenAuth, StreamAuth, DeviceLock) - ├──→ streaming/Delivery/ (LiveDelivery, VodDelivery, SegmentReader) - ├──→ streaming/Balancer/ (LoadBalancer, RedirectStrategy) - ├──→ streaming/Protection/(RestreamDetector, ConnectionLimiter, GeoBlock) - ├──→ streaming/Codec/ (FFmpegCommand, TsParser) - │ - ├──→ domain/Stream/ (StreamProcess, ConnectionTracker, StreamSorter) - ├──→ domain/Server/ (ServerRepository) - ├──→ domain/Bouquet/ (BouquetMapper — для плейлистов) - ├──→ domain/Vod/ (для VOD-доставки) - │ - ├──→ infrastructure/redis/(RedisManager) - └──→ resources/data/ (error_codes.php) - -console.php cron:* ──→ bootstrap.php (CONTEXT_CLI) - └──→ те же зависимости + domain/Stream/* - -> **Примечание:** `crons/*.php` удалены (Phase 12.8). Все вызовы идут через `console.php cron:{name}`. -``` - -### 8.5. Правила для разработки с учётом LB - -1. ~~**Proxy-методы в `CoreUtilities`/`StreamingUtilities`** должны быть безопасны для LB~~ — **РЕШЕНО:** god-объекты удалены (Phase 8), все зависимости через прямые вызовы. - -2. **Makefile обновлён синхронно с миграцией** ✅ — `LB_DIRS` включает все новые директории. При добавлении новых root-директорий — обновлять `LB_DIRS`. - -3. **Тестирование LB-сборки** — после каждой фазы миграции нужно проверять: - ```bash - make new && make lb - # Убедиться, что все нужные файлы присутствуют - # Убедиться, что admin-only файлы отсутствуют - ``` - -4. **domain/ частично нужен LB** — нельзя целиком исключать `domain/`. Исключаются только admin-specific поддомены (`User/`, `Ticket/`, `Device/`). - -5. **modules/ полностью исключается из LB** — все текущие модули (ministra, plex, tmdb, watch, fingerprint, theft-detection, magscan) — это admin-функциональность. В будущем, если появится LB-specific модуль, его нужно будет явно добавить. - -### 8.6. Makefile обновлён ✅ (Phase 9.1) - -Все 5 изменений применены: -1. `LB_FILES` → `LB_DIRS` (14 директорий) + `LB_ROOT_FILES` (5 файлов) -2. `lb_copy_files` — добавлен второй цикл для root-файлов -3. `lb_update_copy_files` — каскадная проверка (dirs → root files) -4. `LB_DIRS_TO_REMOVE` — 6 новых admin-only исключений -5. `set_permissions` — корректные права на `core/`, `domain/`, `streaming/`, `infrastructure/`, `resources/`, `autoload.php`, `bootstrap.php` - ---- - -## 9. Транзакции и производительность - -### 9.1. Кто управляет транзакциями +### 6.1. Кто управляет транзакциями **Правило:** Транзакцией управляет **Service**. Контроллер и Repository не открывают транзакции. -``` -Controller ──→ Service ──→ Repository + Infrastructure - │ - ├── beginTransaction() - ├── ... бизнес-операции ... - ├── commit() - └── (rollback при исключении) -``` +| Контекст | Транзакция | Кто управляет | +|----------|-----------|---------------| +| **Admin CRUD** | Одна операция = одна транзакция | Service | +| **Mass edit** | Весь batch = одна транзакция | Service | +| **Import** | Chunk по 100 записей | Service | +| **Cron** | Каждая итерация = отдельная транзакция | CronJob | +| **Streaming** | Нет транзакций | — (hot path не мутирует через транзакции) | +### 6.2. Внешние процессы -### 9.2. Паттерн транзакции +Операция «создать поток + запустить ffmpeg + обновить nginx» — не атомарна. FFmpeg/nginx — внешние процессы. -```php -class StreamService { - public function massEdit(array $streamIds, array $changes): int { - $this->db->beginTransaction(); - try { - $affected = 0; - foreach ($streamIds as $id) { - $this->repository->update($id, $changes); - $affected++; - } - $this->db->commit(); - return $affected; - } catch (\Throwable $e) { - $this->db->rollBack(); - $this->logger->log('error', "Mass edit failed: " . $e->getMessage()); - throw $e; - } - } -} -``` +**Паттерн:** DB-операции в транзакции → внешние процессы после commit → при сбое обновить статус (`status = 'error'`). -### 9.3. Границы по контексту +### 6.3. Два режима работы -| Контекст | Транзакция | Кто управляет | Пример | -|----------|-----------|---------------|--------| -| **Admin CRUD** | Одна операция = одна транзакция | Service | `StreamService::create()` | -| **Mass edit** | Весь batch = одна транзакция | Service | `StreamService::massEdit()` | -| **Import** | Chunk по 100 записей | Service | `StreamService::importM3U()` | -| **Cron** | Каждая итерация = отдельная транзакция | CronJob | `ServersCron::processServer()` | -| **Streaming** | ❌ Нет транзакций | — | Hot path не мутирует через транзакции | +| Режим | Путь | Частота | Допустимая latency | +|-------|------|---------|-------------------| +| **Hot path** (streaming) | `www/stream/*.php` | ~10K–100K req/min | < 50ms p99 | +| **Cold path** (admin) | `public/index.php`, API | ~1–100 req/min | < 500ms p99 | -### 9.4. Внешние процессы - -Операция «создать поток + запустить ffmpeg + обновить nginx» — не атомарна. FFmpeg/nginx — внешние процессы, откатить нельзя. - -**Паттерн:** -1. DB-операции — в транзакции -2. Внешние процессы — после commit, с обработкой ошибок -3. При сбое внешнего процесса — обновить статус в БД (`status = 'error'`) - -```php -class StreamService { - public function start(int $streamId): void { - // 1. DB: обновить статус - $this->repository->updateStatus($streamId, 'starting'); - - // 2. Внешние процессы (вне транзакции) - try { - $pid = $this->processManager->startFFmpeg($streamId); - $this->repository->updatePid($streamId, $pid); - $this->repository->updateStatus($streamId, 'running'); - } catch (\Throwable $e) { - $this->repository->updateStatus($streamId, 'error'); - $this->logger->log('error', "Start failed: {$e->getMessage()}"); - throw $e; - } - } -} -``` - -### 9.5. Два режима работы системы - -| Режим | Путь | Частота | Допустимая latency | Загрузка | -|-------|------|---------|-------------------|----------| -| **Hot path** (streaming) | `www/stream/*.php` | ~10K–100K req/min | < 50ms p99 | Минимальный bootstrap, никаких модулей | -| **Cold path** (admin) | `admin/*.php`, API | ~1–100 req/min | < 500ms p99 | Полный bootstrap, все модули | - -### 9.6. Бюджет hot path - -Streaming-запрос (`auth.php` → `live.php`) должен уложиться в: +### 6.4. Бюджет hot path ``` Bootstrap: < 5ms (autoload + constants + DB) @@ -1399,101 +600,4 @@ Total: < 30ms (target) | < 50ms (max) ``` **НЕЛЬЗЯ загружать в hot path:** Router, EventDispatcher с подписчиками, ServiceContainer полный boot. -**МОЖНО:** Database (persistent), Redis (single connection), GeoIP (mmap), streaming/*. - -### 9.7. Async / Queue (будущее) - -Длинные операции (ffprobe, tmdb, mass import) — кандидаты на async: - -``` -HTTP → Service → Queue Job (Redis LPUSH) → Worker (cron) → Result -``` - -Без RabbitMQ/Kafka — простая Redis-очередь через `LPUSH`/`BRPOP`. - ---- - -## 10. Правила для контрибьюторов - -> Этот раздел — краткая выжимка для тех, кто не читал весь документ. Подробности — в CONTRIBUTING.md. - -### 10.1. Как добавить новый эндпоинт (admin) - -1. Найти контекст в `domain/` (например `domain/Stream/`) -2. Добавить метод в `StreamService.php` (бизнес-логика) -3. Добавить SQL-метод в `StreamRepository.php` (если нужен новый запрос) -4. Добавить action в Controller или `admin_api.php` → вызов Service -5. `php -l` на изменённые файлы - -### 10.2. Как добавить новую таблицу - -1. Добавить миграцию в `infrastructure/install/` -2. Создать Repository в `domain/{Context}/` с CRUD-методами -3. Зарегистрировать в `autoload.php` - -### 10.3. Как НЕ сломать streaming - -- **Никогда** не менять `www/stream/*.php` без явного ревью -- **Никогда** не добавлять `require` тяжёлых классов в streaming bootstrap -- Не использовать EventDispatcher / Router / ServiceContainer в hot path -- Проверять latency: `< 50ms p99` - -### 10.4. Правила PR - -1. Один PR = одна фича/багфикс. Не смешивать рефакторинг с фичами. -2. `php -l` на все изменённые файлы перед пушем. -3. Если трогаете `core/` или `streaming/` — обязательный ревью. -4. proxy-методы помечать `// @legacy-proxy — убрать в Фазе 8`. -5. Новые классы регистрировать в `autoload.php`. - -### 10.5. Простые правила (cheat sheet) - -``` -✗ Не пиши SQL в Controller → Пиши в Repository -✗ Не пиши логику в Controller → Пиши в Service -✗ Не добавляй global → Используй constructor injection -✗ Не трогай streaming без ревью → hot path = священная территория -✗ Не модифицируй core/ из модулей → Модуль вызывает, не меняет -``` - ---- - -## Приложение: Один bootstrap вместо трёх - -`bootstrap.php` предоставляет класс `XC_Bootstrap` с четырьмя контекстами инициализации: - -| Контекст | Что загружает | Для чего | -|----------|--------------|----------| -| `CONTEXT_MINIMAL` | autoload + constants + Logger | Скрипты, которым нужны только пути | -| `CONTEXT_CLI` | + Database + CoreUtilities | Cron-задачи, CLI-скрипты | -| `CONTEXT_STREAM` | + Database + StreamingUtilities (кэш) | Стриминг-эндпоинты (hot path) | -| `CONTEXT_ADMIN` | + Database + CoreUtilities + API + ResellerAPI + Translator + session + Redis | Админ/реselлер-панель | - -```php -// public/index.php — admin/reseller entry point -require_once '/home/xc_vm/bootstrap.php'; -XC_Bootstrap::defineStatusConstants(); -XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN); -// $db, CoreUtilities, API, ResellerAPI, Translator — всё готово -``` - -```php -// public/stream.php — streaming entry point (lightweight) -require_once '/home/xc_vm/bootstrap.php'; -XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_STREAM); -// Только Database + StreamingUtilities, без admin_api, без Translator -``` - -```php -// cli/CronJobs/StreamsCron.php — cron -require_once '/home/xc_vm/bootstrap.php'; -XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_CLI, [ - 'cached' => true, - 'redis' => true, - 'process' => 'XC_VM[Streams]' -]); -// Database + CoreUtilities + Redis, без session и admin API -``` - -Один автозагрузчик. Один набор констант. Каждая точка входа вызывает `boot()` с нужным контекстом. -Новые классы миграции находятся автоматически через `XC_Autoloader` — без ручного `require_once`. +**МОЖНО:** Database (persistent), Redis (single connection), GeoIP (mmap), `streaming/*`.