Перейти к основному содержимому

Архитектура системы

Эта страница — детальное погружение в устройство FeedbackPulse SaaS. Понимание архитектуры помогает диагностировать проблемы, расширять платформу и принимать обоснованные решения при развёртывании.


Технологический стек

УровеньТехнологияНазначение
BackendLaravel 12 (PHP 8.4+)Фреймворк приложения, маршрутизация, ORM, очередь
База данныхMySQL 8.0+ / MariaDB 10.6+Постоянное хранилище данных
FrontendBlade + Alpine.js + Tailwind CSSСерверный рендеринг с реактивными компонентами
ПлатежиStripe PHP SDK + PayPal REST APIБиллинг подписок
AIOpenAI GPT APIАнализ тональности, автотегирование, предложения ответов
АутентификацияLaravel SocialiteGoogle и GitHub OAuth
EmailLaravel Mail (SMTP)Транзакционные письма, дайджесты, отчёты
Реальное времяServer-Sent Events (SSE)Потоковая передача новых отзывов в реальном времени
Планировщик задачLaravel Scheduler (cron)Дайджесты, истечение пробного периода, хранение данных

Модель мультитенантности

FeedbackPulse использует модель мультитенантности с единой базой данных и общей схемой. Это означает:

  • Одна база данных обслуживает всех арендаторов
  • Каждая таблица арендатора содержит колонку tenant_id
  • Глобальный scope (TenantScope) автоматически фильтрует все запросы по текущему арендатору
  • Трейт (BelongsToTenant) автоматически заполняет tenant_id при создании записи и применяет scope

Как определяется арендатор

Когда поступает запрос, middleware ResolveTenant определяет текущего арендатора в следующем порядке:

  1. Поддоменacme.yourdomain.com → ищет арендатора с поддоменом "acme"
  2. Пользовательский доменfeedback.acmecorp.com → ищет подтверждённый домен в таблице tenant_domains
  3. Авторизованный пользователь — берёт tenant_id авторизованного пользователя

Для публичных страниц (например, /wall/acme-corp) арендатор определяется по URL-слагу непосредственно в контроллере (в обход middleware).

Изоляция данных

+------------------------------------------+
| База данных |
| |
| products (tenant_id = 1) -> Acme data |
| products (tenant_id = 2) -> TechCorp |
| products (tenant_id = 3) -> E-Commerce |
| |
| TenantScope гарантирует, что Acme видит |
| только строки с tenant_id = 1 |
+------------------------------------------+

Безопасность: Публичные контроллеры используют withoutGlobalScopes() для обхода tenant scope, а затем вручную фильтруют по tenant ID. Это сделано намеренно — публичные страницы должны отображать данные без авторизованной сессии.


Структура каталогов

feedbackpulse-saas/
+-- app/
| +-- Console/Commands/ # 7 artisan-команд (дайджесты, хранение, алерты)
| +-- Http/
| | +-- Controllers/
| | | +-- Admin/ # Контроллеры панели суперадмина
| | | +-- Auth/ # Вход, регистрация, 2FA, OAuth, имперсонация
| | | +-- Customer/ # Портал клиента
| | | +-- Public/ # Публичные страницы (стена, форма, дорожная карта, история изменений, хаб)
| | | +-- Tenant/ # Контроллеры дашборда арендатора
| | | +-- Webhooks/ # Обработчики webhook Stripe и PayPal
| | +-- Middleware/ # 15 пользовательских middleware
| +-- Mail/ # 7 классов рассылки
| +-- Models/ # 28 моделей Eloquent
| +-- Scopes/ # TenantScope (глобальный scope запросов)
| +-- Services/ # Бизнес-логика (AI, платежи, webhook и др.)
| +-- Traits/ # Трейт BelongsToTenant
| +-- Providers/ # Сервис-провайдеры
+-- config/ # 11 конфигурационных файлов Laravel
+-- database/
| +-- migrations/ # 35+ файлов миграций
| +-- seeders/ # Сидеры демонстрационных данных
+-- public/ # Корень веба (index.php, ресурсы, символическая ссылка на storage)
+-- resources/views/ # 86 Blade-шаблонов
| +-- admin/ # Представления суперадмина
| +-- auth/ # Представления авторизации (вход, регистрация, 2FA)
| +-- tenant/ # Представления дашборда арендатора
| +-- public/ # Представления публичных страниц
| +-- emails/ # Шаблоны писем
| +-- layouts/ # Шаблоны макетов (admin, tenant, guest, install)
| +-- partials/ # Общие компоненты (навигация, мета, командная палитра)
| +-- install/ # Представления веб-установщика
| +-- legal/ # Страницы конфиденциальности, условий, cookie
| +-- errors/ # Страницы ошибок (403, 404, 419, 429, 500)
| +-- landing/ # Части лендинговой страницы
+-- routes/
| +-- web.php # 416 строк веб-маршрутов
| +-- api.php # Маршруты API v2
+-- storage/ # Загрузки, кэш, сессии, логи
+-- bootstrap/ # Файлы начальной загрузки фреймворка

Роли пользователей

FeedbackPulse имеет четыре роли пользователей, хранящихся в колонке users.role:

РольДоступURL входа
superadminПолный контроль над платформой (/admin/*)/login
tenant_adminПолный контроль арендатора (/dashboard, /settings/*)/login
tenant_staffОграниченный доступ арендатора (без биллинга и удаления)/login
customerТолько портал клиента (/customer/dashboard)/customer/login

Иерархия ролей

superadmin
+-- Может имперсонировать любого tenant_admin
+-- tenant_admin
+-- Может приглашать tenant_staff
+-- Может управлять биллингом, настройками, командой
+-- tenant_staff
+-- Может управлять отзывами, кампаниями
+-- customer
+-- Может просматривать свои отзывы

Жизненный цикл запроса

Вот что происходит, когда запрос поступает в FeedbackPulse:

Запрос браузера
|
v
public/index.php
|
v
Laravel Kernel (стек middleware)
|
+-- EnsureInstalled -> Перенаправление на /install, если не настроено
+-- SecurityHeaders -> Добавление HSTS, CSP, X-Frame-Options
+-- VerifyCsrfToken -> Проверка CSRF-токена (кроме webhook)
+-- ResolveTenant -> Определение текущего арендатора
+-- Authenticate -> Проверка авторизации пользователя
+-- EnsureTenantAccess -> Проверка принадлежности пользователя арендатору
+-- EnsureTwoFactorVerified -> Проверка 2FA, если включена
+-- CheckPlanLimit -> Применение ограничений плана
|
v
Контроллер (обработка запроса)
|
v
Blade-представление (рендеринг HTML)
|
v
Ответ -> Браузер

Архитектура базы данных

Основные таблицы

ТаблицаУровеньНазначение
tenantsПлатформаАккаунты мультиарендаторов
usersПлатформаВсе аккаунты пользователей (все роли)
plansПлатформаПланы подписки
platform_settingsПлатформаГлобальная конфигурация ключ-значение
productsАрендаторПродукты для сбора обратной связи
feedback_campaignsАрендаторКонфигурации форм обратной связи
feedback_submissionsАрендаторОтдельные записи обратной связи
feedback_tagsАрендаторТеги (связь многие-ко-многим с отзывами)
roadmap_itemsАрендаторЭлементы канбан-доски дорожной карты
roadmap_votesАрендаторАнонимные голоса за элементы дорожной карты
feature_requestsАрендаторПредложения функций от сообщества
changelog_entriesАрендаторПримечания к выпускам продукта
team_membersАрендаторЗаписи членов команды
team_invitationsАрендаторОжидающие приглашения
api_keysАрендаторКлючи доступа к API
audit_logsПлатформаЖурнал аудита действий
notificationsПлатформаВнутренние уведомления
payment_eventsПлатформаСобытия webhook Stripe/PayPal
webhook_logsАрендаторЖурналы доставки исходящих webhook
data_deletion_requestsАрендаторЗапросы на удаление данных (GDPR)
landing_pagesПлатформаДанные конструктора лендингов
tenant_domainsАрендаторПривязки пользовательских доменов
referral_codesАрендаторРеферальные коды
referral_conversionsПлатформаОтслеживание реферальных конверсий
cron_logsПлатформаЖурналы выполнения запланированных задач

Ключевые индексы

Все таблицы арендаторов индексированы по (tenant_id, created_at) для оптимальной производительности запросов. Таблица feedback_submissions имеет дополнительные индексы по status, product_id, campaign_id и sentiment_label.


Архитектура безопасности

УровеньЗащита
ТранспортHTTPS обязателен, заголовки HSTS
АутентификацияBcrypt (12 раундов), опциональная 2FA (TOTP)
АвторизацияMiddleware на основе ролей + классы политик
CSRFCSRF-токены Laravel на всех формах
XSSАвтоэкранирование в Blade ({{ }})
SQL-инъекцииПараметризованные запросы Eloquent
Ограничение запросовТроттлинг по маршрутам (5–120 req/min)
Безопасность APISHA256-хэшированные ключи API, ограничения по арендатору
Безопасность webhookПроверка HMAC-подписи (Stripe), защита от SSRF
ДанныеКонфиденциальные настройки зашифрованы в хранилище
СессииЗашифрованные, HTTP-only, защищённые cookie
ЗаголовкиCSP, X-Frame-Options, X-Content-Type-Options

Следующие шаги