devops
This commit is contained in:
278
DEVOPS.md
Normal file
278
DEVOPS.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# РАЗРАБОТКА И ВНЕДРЕНИЕ ОТКАЗОУСТОЙЧИВОЙ МУЛЬТИТЕНАНТНОЙ ИНФРАСТРУКТУРЫ ДЛЯ СИСТЕМЫ УПРАВЛЕНИЯ УНИВЕРСИТЕТСКИМ РАСПИСАНИЕМ «МАГИСТР»
|
||||
|
||||
**Диссертационное исследование и отчет о проделанной инженерной работе в качестве DevOps-архитектора проекта**
|
||||
|
||||
---
|
||||
|
||||
## ВВЕДЕНИЕ
|
||||
|
||||
Современные информационные системы для образовательных учреждений требуют строгого соблюдения требований к безопасности, изоляции данных различных организаций (университетов), гибкости масштабирования и высокой наблюдаемости (observability). В рамках разработки проекта **«Магистр»** — системы управления расписанием учебных занятий — передо мной встала задача проектирования и построения инфраструктуры, способной обслуживать десятки университетов в рамках единого прикладного контура при условии жесткой изоляции их баз данных.
|
||||
|
||||
В качестве DevOps-инженера проекта я спроектировал и реализовал архитектурное решение, объединяющее технологии аппаратной виртуализации, оркестрации контейнеров, автоматизации конфигурации, распределенного мониторинга и непрерывной интеграции. В данном документе подробно описаны теоретические предпосылки, практическая реализация и архитектурные решения, внедренные мной в продакшн-окружение проекта.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 1. СИСТЕМНАЯ АРХИТЕКТУРА И КОНЦЕПЦИЯ МУЛЬТИТЕНАНТНОСТИ
|
||||
|
||||
### 1.1 Выбор паттерна изоляции данных
|
||||
При проектировании мультитенантных (multi-tenant) систем классически выделяют три подхода к организации баз данных:
|
||||
1. **Shared Database & Shared Schema**: Все клиенты используют общие таблицы, записи разделяются по полю `tenant_id`. Подход дешев в обслуживании, но несет колоссальные риски утечки данных из-за ошибок в SQL-запросах приложения и не позволяет разграничивать физический доступ к базам.
|
||||
2. **Shared Database & Separate Schemas**: Одна СУБД, но разные логические схемы для каждого клиента. Обеспечивает умеренную изоляцию, но сохраняет единую точку отказа и общие аппаратные ресурсы СУБД.
|
||||
3. **Database-per-Tenant (Выбранный подход)**: Каждый клиент (университет) владеет собственной физически изолированной базой данных, расположенной на выделенном сервере или виртуальной машине.
|
||||
|
||||
Для проекта «Магистр» мной был выбран и реализован паттерн **Database-per-Tenant**. Это обусловлено спецификой клиентов (высшие учебные заведения), которые согласно законодательству (например, ФЗ-152 «О персональных данных») обязаны хранить свои данные локально или требовать полной изоляции конфиденциальной информации. Кроме того, данный подход позволяет:
|
||||
* Исключить влияние высокой нагрузки одного университета (например, в период составления сессий) на доступность системы для других вузов («синдром шумного соседа»).
|
||||
* Выполнять индивидуальное резервное копирование и обслуживание БД каждого университета без остановки всей платформы.
|
||||
* Переносить базы данных вузов на их собственные физические серверы в локальных сетях (on-premise), сохраняя при этом работу приложения в центральном облаке.
|
||||
|
||||
### 1.2 Архитектурная схема движения трафика
|
||||
Ниже представлена разработанная мной схема прохождения сетевых запросов и агрегации телеметрии в системе:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Client[Пользовательский браузер] -->|HTTPS: tenant.zuev.company| Caddy[Caddy Reverse Proxy]
|
||||
Caddy -->|HTTP Round-Robin| Ingress[Traefik Ingress Controller]
|
||||
|
||||
subgraph K3s Cluster
|
||||
Ingress -->|Route /*| Frontend[Frontend Pods x2 Apache]
|
||||
Ingress -->|Route /api/*| Backend[Backend Pods x1-x2 Spring Boot]
|
||||
Backend -->|K8s API: Get/Patch CM| K8sAPI[Kubernetes API Server]
|
||||
BackendConfig[ConfigMap: tenants-config] -.->|Mounted JSON| Backend
|
||||
end
|
||||
|
||||
subgraph Proxmox VE Infrastructure
|
||||
Backend -->|JDBC Connection| VM1[(VM 1: DBMS MSU<br>PostgreSQL 16)]
|
||||
Backend -->|JDBC Connection| VM2[(VM 2: DBMS SWSU<br>PostgreSQL 16)]
|
||||
|
||||
VM1 -.->|Metrics OTLP/gRPC| Collector1[OTel Collector VM1]
|
||||
VM2 -.->|Metrics OTLP/gRPC| Collector2[OTel Collector VM2]
|
||||
end
|
||||
|
||||
subgraph Observability Hub
|
||||
Backend -->|Logs & Traces OTLP| SigNoz[SigNoz APM Server]
|
||||
Collector1 -->|Postgres Metrics| SigNoz
|
||||
Collector2 -->|Postgres Metrics| SigNoz
|
||||
end
|
||||
|
||||
classDef cluster fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
|
||||
classDef vm fill:#efebe9,stroke:#4e342e,stroke-width:2px;
|
||||
classDef monitor fill:#efe8ff,stroke:#512da8,stroke-width:2px;
|
||||
class K3s Cluster cluster;
|
||||
class Proxmox VE Infrastructure vm;
|
||||
class Observability Hub monitor;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 2. АППАРАТНАЯ ВИРТУАЛИЗАЦИЯ НА БАЗЕ PROMVOX VE
|
||||
|
||||
Для размещения баз данных клиентов и вспомогательных инфраструктурных служб мной был развернут гипервизор **Proxmox Virtual Environment (PVE)** на выделенных серверных мощностях.
|
||||
|
||||
### 2.1 Конфигурация виртуализации и изоляции ресурсов
|
||||
Каждая база данных университета разворачивается в выделенной виртуальной машине (KVM), что гарантирует жесткую изоляцию на уровне ядра ОС и гипервизора.
|
||||
* **Сетевой уровень**: В Proxmox настроена виртуальная коммутация (Linux Bridge `vmbr0`). Сетевые интерфейсы виртуальных машин баз данных вынесены в приватную подсеть, изолированную от внешней сети. Доступ к ним имеет только подсеть кластера Kubernetes и управляющая машина администратора.
|
||||
* **Дисковая подсистема**: Использовано локальное хранилище на базе ZFS с зеркалированием (RAID-10 на SSD-накопителях). Это обеспечивает:
|
||||
* Высокую скорость операций ввода-вывода (IOPS), критически важную для СУБД при транзакционных нагрузках.
|
||||
* Механизм мгновенных снимков (snapshots) для безопасного проведения обновлений.
|
||||
* Аппаратное сжатие данных (LZ4) без потери производительности, снижающее износ накопителей.
|
||||
* **Резервное копирование**: Интегрирован **Proxmox Backup Server (PBS)**. Каждую ночь выполняются инкрементальные бэкапы виртуальных машин с дедупликацией на уровне блоков данных, что минимизирует нагрузку на сеть и диски, позволяя восстановить любую БД на любой момент времени за последние 30 дней.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 3. ОРКЕСТРАЦИЯ КОНТЕЙНЕРНОЙ ИНФРАСТРУКТУРЫ В KUBERNETES (K3s)
|
||||
|
||||
В качестве платформы оркестрации приложений был выбран **K3s** — сертифицированный дистрибутив Kubernetes от Rancher, оптимизированный под средние и малые нагрузки, обладающий минимальными накладными расходами на RAM/CPU для Control Plane, но сохраняющий полную совместимость с upstream-спецификациями Kubernetes API.
|
||||
|
||||
### 3.1 Архитектура манифестов и структура ресурсов
|
||||
Мной была разработана декларативная структура ресурсов, развернутая в изолированном пространстве имен `magistr` (манифест `namespace.yaml`):
|
||||
|
||||
* **Frontend (`frontend.yaml`)**:
|
||||
* Реализован в виде `Deployment` с 2 репликами для обеспечения высокой доступности (HA) и возможности бесшовного обновления rolling-update.
|
||||
* В качестве базового образа контейнера применен легковесный веб-сервер Apache HTTPd (`httpd:alpine`).
|
||||
* Для балансировки и внутреннего доступа настроен `Service` типа ClusterIP, слушающий порт 80.
|
||||
|
||||
* **Backend (`backend.yaml`)**:
|
||||
* `Deployment` с 1-2 репликами (детали балансировки конфигурации описаны ниже).
|
||||
* Для сборки образов используется multi-stage сборка Maven (JDK 17) и запуск под управлением `eclipse-temurin:17-jre-alpine`.
|
||||
* Интегрирован Java-агент OpenTelemetry для автоматического инструментирования трассировки и логов.
|
||||
* Для связи с Ingress настроен ClusterIP-сервис на порту 8080.
|
||||
|
||||
* **Ingress (`ingress.yaml`)**:
|
||||
* В роли Ingress-контроллера выступает стандартный для K3s **Traefik**.
|
||||
* Мной настроены строгие правила маршрутизации по доменным именам (например, `magistr.zuev.company` для тестового окружения, `n8n.zuev.company` и т.д.). Маршрутизация по путям разделяет трафик: запросы к API (`/api`) проксируются на бэкенд-сервис, а запросы к статическим ресурсам и страницам (`/`) — на фронтенд-сервис.
|
||||
|
||||
### 3.2 Реализация динамической мультитенантности через K8s API
|
||||
Одним из наиболее сложных этапов проектирования стала организация бесшовного добавления новых университетов без перезапуска бэкенда и изменения исходного кода приложения.
|
||||
|
||||
1. **Хранение конфигурации**:
|
||||
Список подключений к базам данных университетов хранится в JSON-формате (`tenants.json`) внутри Kubernetes `ConfigMap` с именем `tenants-config`.
|
||||
|
||||
2. **Проблема Read-Only монтирования**:
|
||||
По умолчанию Kubernetes монтирует ConfigMap как файловую систему в режиме Read-Only. Это исключало возможность для Java-приложения перезаписывать `tenants.json` при запросах от администратора на добавление нового тенанта.
|
||||
|
||||
3. **Решение с использованием `initContainer` и `emptyDir`**:
|
||||
Я спроектировал схему с временным перезаписываемым томом (`emptyDir`):
|
||||
* В спецификацию пода backend добавлен `initContainer` на базе образа `busybox`.
|
||||
* При старте пода `initContainer` монтирует ConfigMap `tenants-config` и копирует файл `tenants.json` во временный раздел `emptyDir` (путь `/config`).
|
||||
* Основной контейнер `backend` монтирует этот же `emptyDir` в режиме Read-Write.
|
||||
|
||||
4. **K8s RBAC и динамическое сохранение**:
|
||||
Чтобы предоставить бэкенду возможность сохранять изменения конфигурации обратно в кластер, мной были написаны манифесты авторизации (`rbac.yaml`):
|
||||
* Создан `ServiceAccount` `backend-sa` и назначен поду бэкенда.
|
||||
* Описана Kubernetes `Role` `backend-configmap-role`, дающая права только на операции `get` и `patch` для ресурса `configmaps` с конкретным именем `tenants-config` (принцип наименьших привилегий).
|
||||
* Создан `RoleBinding` `backend-configmap-binding`, связывающий сервис-аккаунт с этой ролью.
|
||||
|
||||
В коде бэкенда класс `ConfigMapUpdater` через Kubernetes Java Client отправляет PATCH-запрос к API-серверу при добавлении нового тенанта, обновляя ConfigMap в самом кластере.
|
||||
|
||||
5. **Репликация и синхронизация подов**:
|
||||
Для обеспечения консистентности данных в условиях работы нескольких реплик бэкенда:
|
||||
* Класс `TenantConfigWatcher` запускает фоновую задачу, которая каждые 30 секунд сверяет MD5-хеш локального файла `tenants.json` с версией из ConfigMap.
|
||||
* При обнаружении расхождения файл перезаписывается, DataSource-инстансы пересоздаются в памяти приложения на лету, исключая рассинхронизацию подов.
|
||||
* *Примечание*: В ходе тестирования для минимизации задержек и исключения эффекта "мигания" данных на время отладки количество реплик backend было ограничено до 1, с последующим переходом на полноценный StatefulSet/Shared Storage при росте количества нод.
|
||||
|
||||
### 3.3 Повышение отказоустойчивости бэкенда (Resiliency)
|
||||
При запуске бэкенда в облаке возникала проблема: если база данных одного из университетов становилась недоступной (например, из-за сетевого сбоя или регламентных работ), пул подключений HikariCP падал с ошибкой при инициализации бинов, отправляя под бэкенда в циклическую перезагрузку (`CrashLoopBackOff`), что делало недоступной систему для *всех* остальных вузов.
|
||||
|
||||
Для предотвращения этого каскадного сбоя я внедрил комплекс защитных механизмов:
|
||||
* **H2 Fallback**: В сборку (`pom.xml`) добавлена СУБД H2 in-memory. Если при старте ConfigMap пуст, Spring Boot инициализирует пустой JPA-контекст на базе H2, что позволяет приложению успешно пройти фазу запуска.
|
||||
* **HikariCP Resiliency**: В конфигурации источников данных задано свойство `setInitializationFailTimeout(-1)`. Это заставляет HikariCP игнорировать отсутствие связи с целевой БД при старте приложения, продолжая попытки подключения в фоновом режиме.
|
||||
* **Явное указание диалекта Hibernate**: Принудительно задан `org.hibernate.dialect.PostgreSQLDialect` в `TenantDataSourceConfig.java`. Это избавило Hibernate от необходимости выполнять тестовый запрос к БД для автоматического определения диалекта на ранних этапах инициализации контекста.
|
||||
* **Автоматическое создание таблиц (`DataInitializer`)**: Для новых БД вузов полностью автоматизирован процесс наката структуры. При обнаружении новой базы Java-код проверяет наличие таблицы `users` и, в случае ее отсутствия, самостоятельно применяет SQL-скрипт инициализации (`init.sql`), включая создание дефолтных справочников и учетной записи суперадминистратора.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 4. АВТОМАТИЗАЦИЯ КОНФИГУРАЦИИ СУБД С ПОМОЩЬЮ ANSIBLE
|
||||
|
||||
Для развертывания и администрирования баз данных университетов на удаленных виртуальных машинах Proxmox мной был спроектирован и реализован комплексный плейбук **Ansible** с модульной структурой ролей.
|
||||
|
||||
### 4.1 Структура и функционал Ansible-ролей
|
||||
Вся конфигурация целевого сервера разбита на 6 последовательных этапов (плейбук `site.yml`):
|
||||
|
||||
1. **`common` (Базовая подготовка и Hardening ОС)**:
|
||||
* Создание системного пользователя `deploy` с беспарольным доступом к `sudo` (через валидацию файла `/etc/sudoers.d/deploy` утилитой `visudo`).
|
||||
* Установка системных утилит (`curl`, `git`, `htop`, `chrony` для синхронизации времени по NTP).
|
||||
* **SSH Hardening**: Изменение стандартного SSH-порта на `2222`, отключение авторизации по паролям, запрет входа для пользователя `root`, ограничение таймаута сессий.
|
||||
* Адаптация под различные дистрибутивы ОС: написаны отдельные сценарии для семейств Debian/Ubuntu/Astra Linux (пакетный менеджер `apt`), RedHat/РЕД ОС (`dnf`) и ALT Linux (кастомная обертка для `apt-get`).
|
||||
|
||||
2. **`docker` (Среда контейнеризации)**:
|
||||
* Автоматическое добавление официальных GPG-ключей и репозиториев Docker CE.
|
||||
* Установка Docker Engine, CLI и Compose плагина.
|
||||
* Включение системной службы `docker` и добавление пользователя `deploy` в группу `docker` для беспарольного запуска контейнеров.
|
||||
|
||||
3. **`firewall` (Сетевая безопасность)**:
|
||||
* Настройка межсетевого экрана UFW (для Debian-based систем) или firewalld (для RedHat и ALT Linux).
|
||||
* Полная блокировка входящего трафика по умолчанию.
|
||||
* Разрешение входящих пакетов только на порт SSH (`2222`) и порт PostgreSQL (`5432`). Реализована поддержка ограничения доступа к порту `5432` только для IP-адресов нод Kubernetes-кластера (`firewall_postgresql_allowed_ips`).
|
||||
|
||||
4. **`postgresql` (Контейнеризированная СУБД)**:
|
||||
* Генерация файлов конфигурации `docker-compose.yml` и `.env` на основе шаблонов Jinja2.
|
||||
* Развертывание PostgreSQL 16 на базе легковесного Alpine-образа.
|
||||
* Физическое монтирование каталога данных БД с хост-системы (`/opt/magistr/postgres/data`), предотвращающее потерю данных при пересоздании контейнера.
|
||||
* Ограничение системных ресурсов контейнера (limits: memory: 512M) и тюнинг параметров PostgreSQL (`shared_buffers=256MB`, оптимизация пула соединений, логирование медленных запросов `log_min_duration_statement=1000`).
|
||||
|
||||
5. **`backup` (Резервное копирование на уровне БД)**:
|
||||
* Установка на хост автоматического bash-скрипта бэкапа.
|
||||
* Настройка задачи `cron` (запуск ежедневно в 03:00).
|
||||
* Скрипт осуществляет горячий дамп базы через `docker exec pg_dump`, архивирует его с помощью `gzip` и сохраняет в локальный каталог `/opt/magistr/backups/`.
|
||||
* Реализован алгоритм автоматической ротации: удаление архивных файлов старше заданного количества дней (по умолчанию 14 дней).
|
||||
|
||||
6. **`monitoring` (Сбор телеметрии хоста и БД)**:
|
||||
* Развертывание контейнера OpenTelemetry Collector на целевом сервере БД.
|
||||
* Конфигурирование коллектора на сбор метрик производительности PostgreSQL и ОС (процессор, память, диски) и их отправку на единый сервер SigNoz по OTLP/HTTP.
|
||||
|
||||
### 4.2 Управление секретами: Ansible Vault
|
||||
Для исключения попадания паролей администраторов БД в систему контроля версий (Git) все чувствительные переменные (например, `vault_postgresql_password`) вынесены в файл `group_vars/databases/vault.yml` и зашифрованы алгоритмом AES-256 с помощью **Ansible Vault**.
|
||||
|
||||
Для бесшовной интеграции в CI/CD пароль расшифрования считывается из локального файла `.vault_pass` на управляющей машине, доступ к которому ограничен правами `chmod 600`.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 5. НЕПРЕРЫВНАЯ ИНТЕГРАЦИЯ И ДОСТАВКА (CI/CD)
|
||||
|
||||
В рамках оптимизации внутренних процессов разработки и деплоя мной была развернута и настроена инфраструктура непрерывной интеграции на базе **Gitea** и **Gitea Actions**.
|
||||
|
||||
### 5.1 Автоматизация сборки (CI)
|
||||
В репозитории проекта создан workflow-манифест `.gitea/workflows/docker-build.yaml`. При каждом пуше изменений в ветку `main` запускается конвейер:
|
||||
1. **Checkout**: Загрузка актуального исходного кода проекта на ранер.
|
||||
2. **Setup Buildx**: Инициализация Docker Buildx для оптимизации кэширования слоев.
|
||||
3. **Login to Registry**: Аутентификация во встроенном реестре контейнеров Gitea Container Registry (`git.zuev.company`) с использованием сервисного токена `ZUEV_TOKEN` (права `write:package`).
|
||||
4. **Build & Push**: Параллельная сборка Docker-образов для бэкенда и фронтенда с тегом `latest` и отправка их в приватный реестр.
|
||||
|
||||
### 5.2 Доставка в кластер (CD)
|
||||
Для авторизации нод K3s в приватном реестре Gitea мной был создан секрет типа `docker-registry` в пространстве имен `magistr`:
|
||||
```bash
|
||||
kubectl create secret docker-registry gitea-registry \
|
||||
--docker-server=gitea.zuev.company \
|
||||
--docker-username=Zuev \
|
||||
--docker-password=${ZUEV_TOKEN} \
|
||||
--namespace=magistr
|
||||
```
|
||||
Этот секрет ассоциирован со спецификациями деплоев в `backend.yaml` и `frontend.yaml` через директиву `imagePullSecrets`. Обновление приложений в кластере после завершения сборки образов выполняется путем контролируемого перезапуска подов:
|
||||
```bash
|
||||
kubectl rollout restart deployment backend -n magistr
|
||||
kubectl rollout restart deployment frontend -n magistr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 6. ВХОДНОЙ ПРОКСИ-СЕРВЕР НА БАЗЕ CADDY PROXY
|
||||
|
||||
Для маршрутизации внешнего трафика и защиты соединений на входе в инфраструктуру развернут веб-сервер **Caddy**.
|
||||
|
||||
### 6.1 Преимущества Caddy и управление SSL/TLS
|
||||
В отличие от классического Nginx, Caddy был выбран благодаря:
|
||||
* Встроенной интеграции с удостоверяющими центрами Let's Encrypt и ZeroSSL. При добавлении нового поддомена вуза (например, `swsu.zuev.company`) Caddy автоматически запрашивает, валидирует через DNS/HTTP-вызовы и устанавливает TLS-сертификат, а также следит за его продлением.
|
||||
* Лаконичному синтаксису конфигурации (`Caddyfile`).
|
||||
* Полноценной поддержке протокола HTTP/3 «из коробки».
|
||||
|
||||
### 6.2 Конфигурация балансировки
|
||||
Внешний Caddy-сервер принимает запросы ко всем поддоменам `*.zuev.company` и перенаправляет их на ноды кластера K3s, выполняя роль внешнего балансировщика нагрузки (L4/L7 Load Balancer):
|
||||
```caddyfile
|
||||
*.zuev.company {
|
||||
reverse_proxy 192.168.1.104:80 192.168.1.105:80 192.168.1.106:80 {
|
||||
lb_policy round_robin
|
||||
lb_try_duration 5s
|
||||
lb_try_interval 250ms
|
||||
}
|
||||
}
|
||||
```
|
||||
Сетевые запросы распределяются по нодам K3s по алгоритму Round-Robin. В случае недоступности одной из нод, Caddy временно исключает ее из пула, обеспечивая отказоустойчивость инфраструктуры на сетевом уровне.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 7. СКВОЗНОЙ МОНИТОРИНГ И ОБСЕРВАБИЛИТИ (SigNoz + OpenTelemetry)
|
||||
|
||||
Для контроля здоровья системы и оперативного выявления аномалий мной была спроектирована и внедрена централизованная система мониторинга на базе APM-платформы **SigNoz** и стандартов **OpenTelemetry (OTel)**.
|
||||
|
||||
### 7.1 Сбор телеметрии Backend
|
||||
Java-приложение бэкенда запускается с подключением агента OpenTelemetry (`opentelemetry-javaagent.jar`). Это позволяет без изменения кода собирать:
|
||||
* **Трейсы (Traces)**: Сквозное прохождение HTTP-запросов через контроллеры, сервисы и JDBC-драйвер к базам данных.
|
||||
* **Метрики (Metrics)**: Метрики JVM (утилизация Heap, активность сборщика мусора GC, состояние потоков), метрики HTTP-запросов (интенсивность, латентность, коды ответов).
|
||||
* **Логи (Logs)**: Системные логи Logback экспортируются по протоколу OTLP напрямую в SigNoz.
|
||||
|
||||
### 7.2 Идентификация тенантов в мониторинге
|
||||
Для глубокого анализа производительности СУБД конкретных университетов критически важно разделять телеметрию по тенантам. Мной была реализована сквозная маркировка:
|
||||
* В бэкенде через Java-интерцептор `TenantInterceptor` при каждом запросе идентификатор тенанта помещается в контекст логирования SLF4J MDC (`MDC.put("tenant.id", tenant)`) и одновременно записывается в атрибуты текущего OpenTelemetry спана (`Span.current().setAttribute("tenant.id", tenant)`).
|
||||
* Благодаря переменной окружения `OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES=tenant.id`, OTel-агент автоматически парсит MDC и прикрепляет его к логам, отправляемым в SigNoz. Это позволяет администраторам фильтровать ошибки и строить графики нагрузки в разрезе каждого университета.
|
||||
|
||||
### 7.3 Мониторинг баз данных на хостах
|
||||
Для сбора детальной статистики с изолированных серверов баз данных:
|
||||
1. На хостах БД силами Ansible разворачивается OpenTelemetry Collector.
|
||||
2. В конфигурации коллектора (`otel-collector-config.yml.j2`) описывается ресивер `postgresql`, который подключается к локальной БД и считывает системные метрики (размер таблиц, cache hit ratio, количество транзакций, активные сессии, блокировки).
|
||||
3. К собираемым метрикам жестко прикрепляются ресурсные атрибуты хоста: `service.name=magistr-db-<tenant_domain>` и `university.name=<University Name>`.
|
||||
4. Метрики экспортируются в центральный коллектор SigNoz. В результате в интерфейсе SigNoz доступны кастомные дашборды JVM, PostgreSQL и HTTP, позволяющие оперативно локализовать проблемы с производительностью баз данных конкретных вузов.
|
||||
|
||||
---
|
||||
|
||||
## ЗАКЛЮЧЕНИЕ
|
||||
|
||||
Разработанная и внедренная мной DevOps-архитектура для проекта «Магистр» успешно решила задачи изоляции данных, отказоустойчивости и автоматизации администрирования.
|
||||
|
||||
### Ключевые результаты проделанной работы:
|
||||
1. **Изоляция данных**: Реализована физическая изоляция БД университетов на выделенных серверах (VM в Proxmox VE), соответствующая требованиям безопасности и законодательства.
|
||||
2. **Динамическое масштабирование**: Внедрен механизм динамического добавления тенантов без простоя системы (zero-downtime) за счет связки Spring Boot, K8s RBAC и ConfigMapWatcher.
|
||||
3. **Отказоустойчивость**: Минимизированы риски каскадных сбоев бэкенда за счет применения in-memory fallback баз данных и оптимизации параметров подключения HikariCP.
|
||||
4. **Автоматизация**: Создан универсальный Ansible-плейбук для быстрого ввода в эксплуатацию новых серверов БД (время развертывания «под ключ» сокращено до нескольких минут).
|
||||
5. **Наблюдаемость**: Реализован сквозной мониторинг логов, метрик и трассировок с детализацией по тенантам на базе стека OpenTelemetry + SigNoz.
|
||||
|
||||
Созданная инфраструктура обладает высокой степенью гибкости и готова к дальнейшему горизонтальному масштабированию по мере подключения новых высших учебных заведений к проекту «Магистр».
|
||||
@@ -1,248 +0,0 @@
|
||||
# Отчёт об изменениях по задачам научного руководителя
|
||||
|
||||
Дата: 2026-05-19
|
||||
|
||||
## Краткий итог
|
||||
|
||||
Выполнен первый цельный релиз архитектурной основы:
|
||||
|
||||
- добавлены роли `EDUCATION_OFFICE`, `DEPARTMENT` и `SCHEDULE_VIEWER`;
|
||||
- добавлена backend-проверка bearer-токена и ролей через `AuthorizationInterceptor` и `@RequireRoles`;
|
||||
- добавлен жизненный цикл справочников: `ACTIVE` / `ARCHIVED`;
|
||||
- физическое удаление ключевых сущностей заменено архивированием там, где это влияет на историю;
|
||||
- добавлена история переводов преподавателей между кафедрами;
|
||||
- добавлены точечные изменения расписания: перенос, отмена, замена;
|
||||
- добавлен расширенный read-only поиск расписания по аудитории, преподавателю, кафедре, группе, дисциплине, типу занятия, паре и чётности;
|
||||
- добавлены отчёты загруженности по преподавателям, аудиториям, кафедрам, парам и свободным аудиториям;
|
||||
- просмотр расписаний переведён с плоского списка на совмещённые матричные таблицы чётной/нечётной недели с разрезом по группам, преподавателям или аудиториям;
|
||||
- вкладка загруженности стала общей для аудиторий, преподавателей и кафедр;
|
||||
- интерфейсы кафедры и учебного отдела переведены в общий стиль админ-панели через role-based вкладки;
|
||||
- отдельная миграция `V2` удалена, изменения схемы внесены в `V1__init.sql`;
|
||||
- обновлена документация проекта.
|
||||
|
||||
## Backend
|
||||
|
||||
### Роли и авторизация
|
||||
|
||||
Поддерживаются роли:
|
||||
|
||||
- `ADMIN`;
|
||||
- `EDUCATION_OFFICE`;
|
||||
- `DEPARTMENT`;
|
||||
- `SCHEDULE_VIEWER`;
|
||||
- `TEACHER`;
|
||||
- `STUDENT`.
|
||||
|
||||
Добавлены:
|
||||
|
||||
- `AuthSessionService` — in-memory UUID-сессии;
|
||||
- `AuthContext` — текущий пользователь в `ThreadLocal`;
|
||||
- `AuthorizationInterceptor` — проверка `Authorization: Bearer ...`;
|
||||
- `@RequireRoles` — ограничение доступа на уровне контроллеров и методов;
|
||||
- `GET /api/auth/me` — текущий пользователь по токену.
|
||||
|
||||
Redirect после входа:
|
||||
|
||||
- `ADMIN` -> `/admin/`;
|
||||
- `EDUCATION_OFFICE` -> `/admin/#schedule-view`;
|
||||
- `DEPARTMENT` -> `/admin/#department-workspace`;
|
||||
- `SCHEDULE_VIEWER` -> `/admin/#schedule-view`;
|
||||
- `TEACHER` -> `/teacher/`;
|
||||
- `STUDENT` -> `/student/`.
|
||||
|
||||
### Миграция БД
|
||||
|
||||
Отдельная миграция `V2__roles_lifecycle_temporal_schedule.sql` удалена по текущему решению проекта.
|
||||
|
||||
Все изменения схемы внесены в:
|
||||
|
||||
```text
|
||||
backend/src/main/resources/db/migration/V1__init.sql
|
||||
```
|
||||
|
||||
`V1__init.sql` теперь сразу создаёт:
|
||||
|
||||
- lifecycle-поля к справочникам и пользователям;
|
||||
- `teacher_department_assignments`;
|
||||
- `subject_comments`;
|
||||
- version-поля к `schedule_rules`;
|
||||
- lock-поля к `schedule_rule_slots`;
|
||||
- `schedule_overrides`;
|
||||
- стартовых пользователей `учебный_отдел`, `кафедра_иб` и `просмотр_расписаний`.
|
||||
|
||||
### Архивирование вместо удаления
|
||||
|
||||
Архивирование добавлено для пользователей, аудиторий, оборудования, кафедр, специальностей, групп, подгрупп, дисциплин и правил расписания.
|
||||
|
||||
Архивные аудитории, преподаватели, группы и дисциплины запрещены для новых назначений. Историческое расписание при этом не теряет ссылки.
|
||||
|
||||
### История кафедр преподавателя
|
||||
|
||||
Добавлены endpoint:
|
||||
|
||||
- `GET /api/users/{id}/department-history`;
|
||||
- `POST /api/users/{id}/department-transfer`;
|
||||
- `GET /api/users/teachers/by-department/{departmentId}?date=YYYY-MM-DD`.
|
||||
|
||||
При переводе преподавателя старая запись закрывается, новая открывается с `valid_from`, а `users.department_id` обновляется как текущая кафедра.
|
||||
|
||||
### Расписание и загруженность
|
||||
|
||||
Добавлены:
|
||||
|
||||
- `GET /api/schedule/search`;
|
||||
- `GET /api/workload/teachers`;
|
||||
- `GET /api/workload/classrooms`;
|
||||
- `GET /api/workload/departments`;
|
||||
- `GET /api/workload/time-slots`;
|
||||
- `GET /api/workload/free-classrooms`;
|
||||
- `GET/POST/PUT/DELETE /api/edu-office/schedule/overrides`.
|
||||
|
||||
`GET /api/schedule` теперь использует общий слой поиска, поэтому учитывает точечные изменения расписания.
|
||||
|
||||
Роль `SCHEDULE_VIEWER` имеет доступ только к read-only просмотру расписаний и справочникам-фильтрам.
|
||||
|
||||
### Кабинет кафедры API
|
||||
|
||||
Добавлены:
|
||||
|
||||
- `GET /api/department/subjects`;
|
||||
- `POST /api/department/subjects/import`;
|
||||
- `GET /api/department/subjects/{subjectId}/comments`;
|
||||
- `POST /api/department/subjects/{subjectId}/comments`;
|
||||
- `GET /api/department/teachers`;
|
||||
- `GET /api/department/schedule`.
|
||||
|
||||
Для роли `DEPARTMENT` кафедра берётся из текущего пользователя. Для администратора можно выбрать кафедру в UI.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Единая панель по ролям
|
||||
|
||||
Отдельные интерфейсы `/department/` и `/edu-office/` заменены на redirect в общую админ-панель:
|
||||
|
||||
- `/department/` -> `/admin/#department-workspace`;
|
||||
- `/edu-office/` -> `/admin/#schedule-view`.
|
||||
|
||||
В `frontend/admin/js/main.js` добавлена фильтрация вкладок:
|
||||
|
||||
- `ADMIN` видит все вкладки;
|
||||
- `EDUCATION_OFFICE` видит просмотр расписаний, конструктор расписания, календарный график, загруженность, аудитории и оборудование;
|
||||
- `DEPARTMENT` видит кабинет кафедры и просмотр расписаний;
|
||||
- `SCHEDULE_VIEWER` видит только просмотр расписаний.
|
||||
|
||||
Backend не опирается только на скрытие вкладок. `AuthorizationInterceptor` проверяет bearer-токен для `/api/**`, а `@RequireRoles` ограничивает операции на контроллерах и методах. Для кафедры дополнительно закрыты обходные пути:
|
||||
|
||||
- общий `/api/subjects` оставлен для записи только администратору, кафедра загружает дисциплины через scoped `/api/department/subjects/import`;
|
||||
- `/api/teacher-subjects` для роли `DEPARTMENT` проверяет, что и преподаватель, и дисциплина относятся к кафедре текущего пользователя;
|
||||
- `SCHEDULE_VIEWER` имеет только read-only доступ к расписаниям и справочникам-фильтрам.
|
||||
|
||||
После браузерной проверки исправлено визуальное скрытие недоступных вкладок: `layout.css` теперь принудительно скрывает `.nav-item[hidden]` и скрытые пункты меню настроек, поэтому роли больше не видят чужие вкладки в sidebar.
|
||||
|
||||
### Просмотр расписаний
|
||||
|
||||
Добавлена вкладка:
|
||||
|
||||
```text
|
||||
frontend/admin/views/schedule-view.html
|
||||
frontend/admin/js/views/schedule-view.js
|
||||
```
|
||||
|
||||
Возможности:
|
||||
|
||||
- фильтры по одной дате в периоде, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия и чётности;
|
||||
- двухнедельный диапазон для таблиц автоматически строится от понедельника выбранной недели;
|
||||
- выбор разреза таблиц: автоматически, по группам, по преподавателям или по аудиториям;
|
||||
- совмещённые матричные таблицы найденных занятий: строки — пары, столбцы — дни недели;
|
||||
- нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, одинаковые занятия в обе недели схлопываются в цельную ячейку;
|
||||
- карточки занятий внутри ячеек с дисциплиной, группами, подгруппами, преподавателем, аудиторией, типом занятия, чётностью и ID слота.
|
||||
|
||||
### Загруженность
|
||||
|
||||
Вкладка `auditorium-workload` оставлена техническим tab id, но в UI называется `Загруженность`.
|
||||
|
||||
Возможности:
|
||||
|
||||
- выбор типа загруженности: аудитории, преподаватели или кафедры;
|
||||
- сводная матрица по выбранной дате: строки — выбранные сущности, столбцы — пары;
|
||||
- кафедральная матрица группирует занятия по кафедре преподавателя;
|
||||
- для аудиторий сохранены фильтры корпуса, вместимости и оборудования;
|
||||
- выбор конкретной аудитории, преподавателя или кафедры заменяет обзор двухнедельной таблицей по дням недели и времени;
|
||||
- чётная и нечётная недели показываются в одной ячейке: если состояние одинаковое, ячейка цельная, если отличается — делится вертикально.
|
||||
|
||||
### Кабинет кафедры
|
||||
|
||||
Добавлена вкладка:
|
||||
|
||||
```text
|
||||
frontend/admin/views/department-workspace.html
|
||||
frontend/admin/js/views/department-workspace.js
|
||||
```
|
||||
|
||||
Возможности:
|
||||
|
||||
- просмотр дисциплин кафедры;
|
||||
- загрузка дисциплин из списка `код; название`;
|
||||
- просмотр и добавление комментариев к дисциплинам;
|
||||
- просмотр преподавателей кафедры;
|
||||
- просмотр нагрузки преподавателей кафедры за период.
|
||||
|
||||
### Админка
|
||||
|
||||
Обновлено:
|
||||
|
||||
- создание пользователей теперь поддерживает роли `DEPARTMENT`, `EDUCATION_OFFICE` и `SCHEDULE_VIEWER`;
|
||||
- удаление пользователей заменено на архивирование;
|
||||
- вкладка аудиторий показывает архивные записи и умеет восстанавливать аудиторию;
|
||||
- удаление аудитории заменено на вывод из эксплуатации;
|
||||
- настройки временных слотов доступны `ADMIN` и `EDUCATION_OFFICE`.
|
||||
|
||||
## Документация
|
||||
|
||||
Обновлены:
|
||||
|
||||
- `AGENTS.md`;
|
||||
- `docs/README.md`;
|
||||
- `docs/API.md`;
|
||||
- `docs/DATABASE.md`;
|
||||
- `docs/BUSINESS_LOGIC.md`;
|
||||
- `docs/FRONTEND.md`;
|
||||
- `docs/ARCHITECTURE.md`.
|
||||
|
||||
## Проверки
|
||||
|
||||
Статически проверены новые и изменённые JS-файлы:
|
||||
|
||||
```bash
|
||||
node --check frontend/admin/js/main.js
|
||||
node --check frontend/admin/js/views/schedule-view.js
|
||||
node --check frontend/admin/js/views/auditorium-workload.js
|
||||
node --check frontend/admin/js/views/department-workspace.js
|
||||
node --check frontend/admin/settings/js/main.js
|
||||
node --check frontend/admin/js/api.js
|
||||
node --check frontend/admin/js/views/users.js
|
||||
node --check frontend/admin/js/views/classrooms.js
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Backend-компиляция проверяется через Docker Maven:
|
||||
|
||||
```bash
|
||||
docker run --rm -v /mnt/HDD/magistr/magistr/backend:/app -w /app maven:3.9-eclipse-temurin-17 mvn -q -DskipTests compile
|
||||
```
|
||||
|
||||
Результат: компиляция прошла успешно.
|
||||
|
||||
Единая `V1__init.sql` проверена на пустой PostgreSQL 16 внутри Docker: SQL применился успешно.
|
||||
|
||||
Локальный `mvn` в окружении отсутствует, поэтому используется Docker.
|
||||
|
||||
## Что осталось следующим этапом
|
||||
|
||||
Остались задачи, которые требуют отдельного цикла проработки:
|
||||
|
||||
- UI для просмотра истории переводов преподавателя в админке;
|
||||
- UI для списка и редактирования уже созданных `schedule_overrides`;
|
||||
- более строгая проверка конфликтов перед сохранением переносов;
|
||||
- полноценный импорт XLSX/CSV для кафедры вместо текстовой загрузки;
|
||||
- браузерный smoke-test и проверка ролевых вкладок после запуска `localhost:80`.
|
||||
@@ -1,288 +0,0 @@
|
||||
# Анализ структуры вкладок и предложения по UX/UI оптимизации системы Magistr
|
||||
|
||||
В данном документе представлен детальный анализ текущей структуры интерфейса административной панели (SPA) и настроек системы Magistr. Выявлены пересекающиеся области функционала, сформулированы предложения по их объединению, переносу в настройки или удалению, а также предложены новые функциональные модули и технические решения для оптимизации UX/UI и производительности.
|
||||
|
||||
---
|
||||
|
||||
## 1. Анализ текущей структуры и доступности вкладок
|
||||
|
||||
На данный момент в основном SPA-интерфейсе администратора (`/admin/`) зарегистрировано **13 вкладок**, а в дополнительном SPA настроек (`/admin/settings/`) — **2 вкладки**.
|
||||
|
||||
В зависимости от роли пользователя, сайдбар фильтруется следующим образом:
|
||||
|
||||
| Вкладка (ID) | Описание | Доступность по ролям |
|
||||
| :--- | :--- | :--- |
|
||||
| `users` | CRUD пользователей и архивирование | `ADMIN` |
|
||||
| `groups` | CRUD групп, подгрупп и назначений календарей | `ADMIN` |
|
||||
| `edu-forms` | Справочник форм обучения | `ADMIN` |
|
||||
| `profiles` | Управление профилями обучения специальностей | `ADMIN` |
|
||||
| `equipments` | Каталог оборудования | `ADMIN`, `EDUCATION_OFFICE` |
|
||||
| `classrooms` | Список аудиторий с привязкой оборудования | `ADMIN`, `EDUCATION_OFFICE` |
|
||||
| `subjects` | CRUD всех дисциплин и привязка преподавателей | `ADMIN` |
|
||||
| `department-workspace` | Рабочая область кафедры (дисциплины, импорт, нагрузка) | `ADMIN`, `DEPARTMENT` |
|
||||
| `schedule-view` | Просмотр расписаний (read-only) | `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER` |
|
||||
| `schedule` | Конструктор правил динамического расписания | `ADMIN`, `EDUCATION_OFFICE` |
|
||||
| `academic-calendar` | Календарные графики и Excel-редактор сетки | `ADMIN`, `EDUCATION_OFFICE` |
|
||||
| `auditorium-workload` | Матрицы и таблицы загруженности | `ADMIN`, `EDUCATION_OFFICE` |
|
||||
| `database` | Управление тенантами (БД клиентов) | `ADMIN` |
|
||||
| **Settings** (SPA) | Общие настройки, Управление временными слотами | `ADMIN`, `EDUCATION_OFFICE` (ссылка в подвале) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Пересечения функционала и проблемные зоны
|
||||
|
||||
### 2.1. Избыточное дробление академической структуры
|
||||
Для описания структуры обучения сейчас используются четыре отдельные вкладки:
|
||||
1. `departments-data` (Создание кафедры/специальности)
|
||||
2. `profiles` (Профили обучения специальностей)
|
||||
3. `edu-forms` (Формы обучения)
|
||||
4. `groups` (В блоке создания группы выбираются форма, кафедра, специальность и профиль)
|
||||
|
||||
**В чём проблема:**
|
||||
* **Разрыв контекста:** Чтобы завести новую специальность с профилями, администратору сначала нужно пойти во вкладку `departments-data`, создать специальность, затем перейти во вкладку `profiles`, выбрать в селекте эту специальность и добавить профили.
|
||||
* **Перегрузка меню:** 13 вкладок в сайдбаре для роли `ADMIN` — это слишком много. Пользователю трудно ориентироваться визуально.
|
||||
|
||||
### 2.2. Дублирование и раздвоение управления дисциплинами
|
||||
Управление дисциплинами разнесено между вкладками `subjects` (для администратора) и `department-workspace` (для кафедры).
|
||||
|
||||
**В чём проблема:**
|
||||
* Администратор во вкладке `subjects` видит плоскую таблицу всех дисциплин всех кафедр, но не имеет удобных инструментов фильтрации по кафедре.
|
||||
* Форма создания новой дисциплины во вкладке `subjects` требует ввода **числового ID кафедры вручную** (поле `new-subject-department` типа `number`). Это критическая UX-ошибка: администратор не должен помнить числовые ID БД наизусть.
|
||||
* Привязка преподавателей к дисциплинам (`teacher_subjects`) находится на вкладке `subjects`. При этом сама нагрузка преподавателей и список преподавателей кафедры отображаются на вкладке `department-workspace`. Логичнее было бы видеть, какие дисциплины ведёт преподаватель, непосредственно при работе с его нагрузкой или в едином справочнике преподавателей/кафедры.
|
||||
|
||||
### 2.3. Низкая частота использования справочника оборудования (`equipments`)
|
||||
Оборудование существует только как подчиненная сущность для аудиторий (чтобы указать, что в аудитории 101 есть 20 ПК и 1 проектор).
|
||||
|
||||
**В чём проблема:**
|
||||
* Выделение целой вкладки под простой список («Проектор», «ПК») не оправдано. Список оборудования меняется раз в несколько лет.
|
||||
* При этом привязка оборудования к аудиториям происходит на вкладке `classrooms`.
|
||||
|
||||
### 2.4. Перегруженность вкладки групп (`groups`)
|
||||
На вкладке групп на одном экране выводятся сразу три тяжелые формы и три таблицы:
|
||||
1. Создание/редактирование групп + таблица групп.
|
||||
2. Создание подгрупп для лабораторных + таблица подгрупп.
|
||||
3. Назначение календарного графика группе + таблица назначений.
|
||||
|
||||
**В чём проблема:**
|
||||
* Экран перегружен элементами управления.
|
||||
* Для работы с подгруппами или назначениями графиков конкретной группы пользователю приходится выбирать эту группу из длинного выпадающего списка в отдельной форме, вместо того чтобы выполнять действия в контексте строки этой группы в таблице.
|
||||
|
||||
### 2.5. Нахождение системной вкладки «База данных» в общем меню расписания
|
||||
Вкладка `database` отвечает за управление тенантами (добавление новых вузов-клиентов, создание для них БД и запуск Flyway миграций).
|
||||
|
||||
**В чём проблема:**
|
||||
* Это технический функционал уровня Super-Admin / DevOps. Он не имеет отношения к ежедневному планированию расписания. Нахождение его в одном ряду с расписанием и группами нарушает логику интерфейса.
|
||||
|
||||
---
|
||||
|
||||
## 3. Рекомендации по оптимизации структуры (Что убрать, объединить и перенести)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Было (13 вкладок в меню ADMIN)
|
||||
direction TB
|
||||
T1[database]
|
||||
T2[edu-forms]
|
||||
T3[departments-data]
|
||||
T4[profiles]
|
||||
T5[equipments]
|
||||
T6[classrooms]
|
||||
T7[groups]
|
||||
T8[users]
|
||||
T9[subjects]
|
||||
T10[department-workspace]
|
||||
T11[schedule]
|
||||
T12[schedule-view]
|
||||
T13[academic-calendar]
|
||||
T14[auditorium-workload]
|
||||
end
|
||||
|
||||
subgraph Стало (Сгруппированное меню + настройки)
|
||||
direction TB
|
||||
subgraph Основное меню SPA (Группы вкладок)
|
||||
direction TB
|
||||
DASH[1. Дашборд / Главная]
|
||||
|
||||
subgraph Модуль: Расписание
|
||||
SCH_ED[2. Конструктор расписания]
|
||||
SCH_VI[3. Просмотр расписаний]
|
||||
CAL[4. Календарный график]
|
||||
WORK[5. Загруженность]
|
||||
end
|
||||
|
||||
subgraph Модуль: Структура
|
||||
UNIV[6. Структура вуза<br/>Кафедры, Специальности, Профили]
|
||||
GRP[7. Группы и подгруппы]
|
||||
SUBJ[8. Дисциплины]
|
||||
end
|
||||
|
||||
subgraph Модуль: Ресурсы
|
||||
CLASS[9. Аудиторный фонд<br/>Аудитории + Оборудование]
|
||||
end
|
||||
|
||||
USR[10. Пользователи]
|
||||
end
|
||||
|
||||
subgraph SPA Настройки (Настройки системы)
|
||||
direction TB
|
||||
SET_GEN[Общие настройки]
|
||||
SET_TIME[Временные слоты сетка]
|
||||
SET_FORM[Формы обучения из edu-forms]
|
||||
SET_DB[Базы данных / Тенанты из database]
|
||||
end
|
||||
end
|
||||
|
||||
T1 -->|Перенести| SET_DB
|
||||
T2 -->|Перенести| SET_FORM
|
||||
T3 -->|Объединить| UNIV
|
||||
T4 -->|Объединить| UNIV
|
||||
T5 -->|Объединить| CLASS
|
||||
T6 -->|Объединить| CLASS
|
||||
T7 -->|Оптимизировать| GRP
|
||||
T8 -->|Оставить| USR
|
||||
T9 -->|Оптимизировать| SUBJ
|
||||
T10 -->|Сохранить для роли Кафедра| SUBJ
|
||||
T11 -->|Оставить| SCH_ED
|
||||
T12 -->|Оставить| SCH_VI
|
||||
T13 -->|Оставить| CAL
|
||||
T14 -->|Оставить| WORK
|
||||
```
|
||||
|
||||
### 3.1. Перенос в настройки (`/admin/settings/`)
|
||||
1. **База данных (`database`)**: Перенести в SPA настроек в раздел «Управление тенантами». Доступ должен быть только у роли `ADMIN`.
|
||||
2. **Формы обучения (`edu-forms`)**: Перенести в настройки. Данный справочник заполняется один раз и не требует оперативного доступа из главного меню.
|
||||
|
||||
### 3.2. Объединение вкладок
|
||||
1. **Объединить Кафедры, Специальности и Профили** в одну вкладку **«Структура вуза»** (`university-structure`):
|
||||
* Интерфейс делится на две вкладки внутри страницы: **«Кафедры»** и **«Специальности»**.
|
||||
* Управление профилями должно происходить прямо внутри списка специальностей. Например, в строке специальности в таблице добавляется кнопка «Профили», открывающая список профилей этой специальности в раскрывающейся строке (accordion) или модальном окне.
|
||||
2. **Объединить Аудитории (`classrooms`) и Оборудование (`equipments`)** в одну вкладку **«Аудиторный фонд»**:
|
||||
* Основной экран показывает список аудиторий.
|
||||
* Рядом располагается кнопка «Управление каталогом оборудования», которая открывает модальное окно с простым списком (добавить/удалить тип оборудования). Это избавит от необходимости держать под это отдельную вкладку в сайдбаре.
|
||||
|
||||
### 3.3. Реорганизация вкладки групп (`groups`)
|
||||
* **Оставить на вкладке только список групп** с возможностью фильтрации по курсу, кафедре и форме обучения.
|
||||
* **Вынести подгруппы и назначения календарей из глобальных форм:**
|
||||
* В строке таблицы для каждой группы добавить действия:
|
||||
* `[Подгруппы]` — открывает компактное модальное окно для быстрого создания/редактирования подгрупп (лабораторных) именно для этой группы.
|
||||
* `[Календарь]` — открывает модальное окно назначения календарного учебного графика на учебные годы для этой группы.
|
||||
* Это уберет лишние тяжелые формы с главной страницы и исключит необходимость выбора группы из селектов вручную.
|
||||
|
||||
### 3.4. Оптимизация Дисциплин (`subjects`) и Кабинета кафедры
|
||||
* **Исправить форму добавления дисциплины:** Заменить ручной числовой ввод ID кафедры (`new-subject-department`) на стандартный выпадающий список (выбор из загруженных кафедр).
|
||||
* **Связать привязку преподавателей с дисциплинами:** Вместо двух разрозненных таблиц на вкладке `subjects` («Все дисциплины» и «Привязки преподавателей»), объединить их:
|
||||
* В строке дисциплины выводить список привязанных преподавателей (в виде чипов/badge).
|
||||
* По клику на кнопку «Преподаватели» в строке дисциплины открывать модальное окно привязки/отвязки преподавателей для этой конкретной дисциплины.
|
||||
|
||||
---
|
||||
|
||||
## 4. Что необходимо добавить (Новый функционал)
|
||||
|
||||
### 4.1. Главная страница / Дашборд (`dashboard`)
|
||||
При входе в систему администратор и сотрудники учебного отдела должны видеть не сырые таблицы пользователей или расписания, а высокоуровневую аналитическую панель:
|
||||
* **Информационные карточки (Метрики):**
|
||||
* Всего активных групп / студентов.
|
||||
* Количество задействованных преподавателей.
|
||||
* Процент занятости аудиторного фонда на текущий день.
|
||||
* Количество запланированных пар на сегодня.
|
||||
* **Мониторинг расписания в реальном времени:**
|
||||
* Список идущих прямо сейчас пар (текущий временной слот).
|
||||
* Ближайшие свободные аудитории.
|
||||
* **Быстрый переход:** Кнопки быстрого создания правила расписания, добавления переноса/замены.
|
||||
|
||||
### 4.2. Центр предупреждений и конфликтов (Red Zone)
|
||||
В соответствии с планируемой бизнес-логикой (раздел 5 `docs/BUSINESS_LOGIC.md`), система должна автоматически валидировать расписание и предупреждать о коллизиях.
|
||||
Необходима отдельная вкладка или виджет на дашборде **«Конфликты расписания»**:
|
||||
* **Критические ошибки (Блокирующие генерацию):**
|
||||
* *Накладки преподавателей:* Преподаватель назначен в один и тот же временной слот в разные аудитории / к разным группам (кроме потоковых лекций).
|
||||
* *Накладки аудиторий:* Две разные пары стоят в одной аудитории в один слот.
|
||||
* **Предупреждения (Мягкие проверки):**
|
||||
* *Превышение вместимости:* Численность группы (или сумма подгрупп при совмещенных занятиях) превышает физическую вместимость аудитории.
|
||||
* *Недостаток оборудования:* Дисциплина (например, лабораторная по химии) требует определенного оборудования, которого нет в назначенной аудитории.
|
||||
* *Превышение нагрузки:* Преподаватель превысил недельный лимит часов.
|
||||
|
||||
---
|
||||
|
||||
## 5. Концепция новой структуры меню (Сайдбара)
|
||||
|
||||
Для уменьшения визуального шума сайдбар предлагается сделать **двухуровневым** (сворачиваемые группы) или четко разделить визуальными разделителями:
|
||||
|
||||
```
|
||||
[ Логотип Magistr ]
|
||||
---------------------------
|
||||
👤 Кабинет кафедры <-- (Показывается только для роли DEPARTMENT)
|
||||
📈 Дашборд <-- (Новый главный экран для ADMIN / EDU_OFFICE)
|
||||
|
||||
📁 РАСПИСАНИЕ
|
||||
🗓 Конструктор правил
|
||||
🗓 Точечные изменения <-- (Выделенный функционал оверрайдов/переносов)
|
||||
📅 Календарный график
|
||||
📊 Загруженность
|
||||
🔍 Просмотр расписаний
|
||||
|
||||
🗂 СУБЪЕКТЫ И РЕСУРСЫ
|
||||
🏫 Аудиторный фонд <-- (Объединенные Аудитории + Оборудование)
|
||||
🎓 Группы обучения <-- (Группы + модалки подгрупп/календарей)
|
||||
🏛 Структура вуза <-- (Кафедры + Специальности + Профили)
|
||||
📚 Дисциплины <-- (Дисциплины + модалки преподавателей)
|
||||
|
||||
⚙️ СИСТЕМА
|
||||
👥 Пользователи
|
||||
⚙️ Настройки <-- (Переход в настройки SPA: слоты, БД, формы обучения)
|
||||
---------------------------
|
||||
[ Профиль / Выход ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Идеи по оптимизации фронтенда и взаимодействия с API
|
||||
|
||||
### 6.1. Кэширование справочников на клиенте
|
||||
На данный момент при переключении почти каждой вкладки заново запрашиваются списки кафедр, специальностей, учебных годов и т.д.
|
||||
* **Решение:** Реализовать простой кэш-менеджер во фронтенд-клиенте (`api.js` или отдельный `cache.js`):
|
||||
```javascript
|
||||
const cache = new Map();
|
||||
export async function getCachedData(key, fetchPromise) {
|
||||
if (!cache.has(key)) {
|
||||
cache.set(key, await fetchPromise);
|
||||
}
|
||||
return cache.get(key);
|
||||
}
|
||||
export function clearCache(key) {
|
||||
if (key) cache.delete(key);
|
||||
else cache.clear();
|
||||
}
|
||||
```
|
||||
Это позволит избежать десятков дублирующих запросов при переключении между вкладками `groups`, `subjects`, `schedule`.
|
||||
|
||||
### 6.2. Ленивая инициализация вкладок (Lazy Loading)
|
||||
Сейчас в `main.js` импортируются все JS-модули вкладок одновременно при загрузке страницы:
|
||||
```javascript
|
||||
import { initUsers } from './views/users.js';
|
||||
import { initGroups } from './views/groups.js';
|
||||
// ...еще 10 импортов
|
||||
```
|
||||
* **Решение:** Перейти на динамический импорт модулей инициализации при переходе на конкретную вкладку:
|
||||
```javascript
|
||||
async function switchTab(tab) {
|
||||
// ...
|
||||
const viewModule = await import(`./views/${tab}.js`);
|
||||
viewModule.init();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Это уменьшит размер начального JS-бандла и ускорит первую загрузку страницы.
|
||||
|
||||
### 6.3. Единый реестр кастомных выпадающих списков
|
||||
В проекте реализованы кастомные селекты (выпадающие списки с поддержкой поиска и чекбоксов). При динамической подгрузке HTML-шаблонов часто требуется их повторная инициализация.
|
||||
* **Решение:** Создать глобальный шину событий (Event Bus) или MutationObserver, который автоматически подхватывает элементы с классами `.custom-select` или атрибутами `data-select` и инициализирует их без вызова ручного кода `initAllCustomDropdowns()` в каждом отдельном скрипте представления.
|
||||
|
||||
---
|
||||
|
||||
## Резюме
|
||||
|
||||
Предложенные изменения позволят:
|
||||
1. Сократить количество пунктов основного меню с **13** до **8-9** логически сгруппированных разделов.
|
||||
2. Исключить ручной ввод ID (заменив на селекты) и разгрузить перегруженные интерфейсы (выносом подгрупп и назначений в контекстные модальные окна групп).
|
||||
3. Повысить отзывчивость интерфейса за счет клиентского кэширования справочников и ленивой загрузки JS-модулей.
|
||||
4. Добавить полноценный Дашборд и Центр предупреждений о конфликтах, что выведет аналитические возможности системы на уровень премиум-класса.
|
||||
Reference in New Issue
Block a user