docs: Add comprehensive project documentation covering architecture, development, and APIs, and update AGENTS.md.
This commit is contained in:
142
docs/ARCHITECTURE.md
Normal file
142
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 🏗 Архитектура системы
|
||||
|
||||
## Общая схема
|
||||
|
||||
```mermaid
|
||||
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.
|
||||
|
||||
### Принцип работы
|
||||
|
||||
```mermaid
|
||||
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`
|
||||
|
||||
Формат:
|
||||
```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` на клиенте
|
||||
Reference in New Issue
Block a user