Files
magistr/docs/ARCHITECTURE.md
Zuev 491807cd94
All checks were successful
Build and Push Docker Images / build-and-push-backend (push) Successful in 17s
Build and Push Docker Images / build-and-push-frontend (push) Successful in 10s
Build and Push Docker Images / deploy-to-k8s (push) Successful in 1m57s
docs: Add comprehensive project documentation covering architecture, development, and APIs, and update AGENTS.md.
2026-03-22 02:49:13 +03:00

6.4 KiB
Raw Blame History

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

Общая схема

graph TD
    Client["🌐 Браузер"] -->|HTTPS| Caddy["Caddy Proxy"]
    Caddy -->|:80| Frontend["Frontend<br/>(Apache httpd:alpine)"]
    Caddy -->|/api/*| Backend["Backend<br/>(Spring Boot 3.2.5)"]
    
    Backend --> TenantRouter{"TenantRoutingDataSource"}
    TenantRouter -->|swsu.zuev.company| DB1["PostgreSQL<br/>swsu_db"]
    TenantRouter -->|mgu.zuev.company| DB2["PostgreSQL<br/>mgu_db"]
    TenantRouter -->|...| DBn["PostgreSQL<br/>tenant_n_db"]
    
    Backend -->|Метрики, Логи, Трейсы| OTel["OpenTelemetry Collector"]
    OTel --> SigNoz["SigNoz"]

Компоненты

Frontend (Apache httpd:alpine)

  • Тип: Статические файлы (HTML/CSS/JS)
  • Контейнер: httpd:alpine — лёгкий Apache HTTP Server
  • Порт: 80
  • Содержание: Три изолированных интерфейса — admin/, teacher/, student/
  • JS-модули: Vanilla JavaScript с ES6 Modules (import/export)

Backend (Spring Boot 3.2.5)

  • Тип: REST API сервер
  • Язык: Java 17
  • Порт: 8080 (внутренний)
  • ORM: Hibernate (JPA), ddl-auto=none
  • Миграции: Flyway (программный запуск при подключении тенанта)
  • Аутентификация: bcrypt (через BCryptPasswordEncoder), UUID-токены

PostgreSQL

  • Версия: postgres:alpine3.23
  • Локально: Одна БД app_db (тенант default)
  • Продакшн: Множество БД, по одной на каждый университет (тенант)

Caddy (реверс-прокси)

  • Расположение: ../caddy-proxy/
  • Назначение: TLS-терминация, маршрутизация запросов к backend/frontend
  • Домен: *.zuev.company

Мультитенантная архитектура

Ключевая особенность системы — изоляция данных каждого университета в отдельной БД PostgreSQL.

Принцип работы

sequenceDiagram
    participant Browser as Браузер
    participant Interceptor as TenantInterceptor
    participant Context as TenantContext
    participant Router as TenantRoutingDataSource
    participant DB as PostgreSQL
    
    Browser->>Interceptor: GET /api/users<br/>Host: swsu.zuev.company
    Interceptor->>Interceptor: resolveTenant("swsu.zuev.company") → "swsu"
    Interceptor->>Context: setCurrentTenant("swsu")
    Note over Interceptor: Проверка: hasTenant("swsu")?
    Interceptor-->>Browser: 404 если тенант не найден
    
    Note over Context,Router: Обработка запроса контроллером
    Router->>Router: determineCurrentLookupKey() → "swsu"
    Router->>DB: SQL запрос к swsu_db
    DB-->>Browser: Ответ с данными

Ключевые классы

Класс Назначение
TenantInterceptor Извлекает поддомен из заголовка Host и определяет тенант
TenantContext ThreadLocal-хранилище имени текущего тенанта
TenantRoutingDataSource Наследует AbstractRoutingDataSource, маршрутизирует запросы к нужной БД
TenantDataSourceConfig Загружает конфигурацию тенантов из JSON-файла, создаёт HikariCP пулы
TenantConfigWatcher Периодически (каждые 30 сек) перечитывает tenants.json, синхронизирует тенантов
ConfigMapUpdater Обновляет Kubernetes ConfigMap при добавлении/удалении тенанта через API
TenantConfig POJO с параметрами тенанта: name, domain, url, username, password

Определение тенанта

Логика определения тенанта по заголовку Host:

Host Результат
swsu.zuev.company swsu
mgu.zuev.company mgu
localhost default
localhost:8080 default
192.168.1.1 default

Конфигурация тенантов

Список тенантов хранится в JSON-файле:

  • Локально: backend/tenants.json
  • Продакшн: Kubernetes ConfigMap tenants-config, монтируется в /config/tenants.json

Формат:

[
  {
    "name": "ЮЗГУ",
    "domain": "swsu",
    "url": "jdbc:postgresql://db-host:5432/swsu_db",
    "username": "dbuser",
    "password": "dbpass"
  }
]

Жизненный цикл тенанта

  1. Добавление через API: POST /api/database/tenants → создаёт HikariCP пул → запускает Flyway миграции → обновляет ConfigMap
  2. Синхронизация подов: TenantConfigWatcher каждые 30 сек проверяет tenants.json → добавляет новые / удаляет отсутствующие тенанты
  3. Удаление: DELETE /api/database/tenants/{domain} → закрывает пул → обновляет ConfigMap

Fallback при отсутствии тенантов

Если при запуске нет ни одного настроенного тенанта:

  1. Проверяется наличие spring.datasource.url → создаётся тенант default
  2. Если datasource тоже нет → создаётся H2 in-memory заглушка для инициализации Spring JPA

Аутентификация

Система использует простую модель аутентификации без JWT или Spring Security фильтров:

  1. Клиент отправляет POST /api/auth/login с username и password
  2. Backend проверяет пароль через BCryptPasswordEncoder
  3. При успехе возвращается:
    • UUID-токен (для заголовка Authorization: Bearer)
    • Роль пользователя (ADMIN, TEACHER, STUDENT)
    • Redirect URL (/admin/, /teacher/, /student/)
  4. Токен хранится в localStorage на клиенте