System Architecture & Developer Guide
Версия: 3.0
1. Обзор Системы и Стек Технологий
Настоящая документация описывает архитектуру промышленного уровня для системы онлайн-тестирования, обучения и поведенческого прокторинга. Документ призван служить единым, авторитетным источником информации для всех специалистов, взаимодействующих с системой, от новых инженеров-разработчиков до инженеров по эксплуатации (DevOps), отвечающих за ее круглосуточную доступность и надежность.
1.1. Архитектура Высокого Уровня
Система спроектирована на базе Flask и реализует архитектурный паттерн "Фабрика приложений" (реализованный через функцию create_app в __init__.py). Следует особо подчеркнуть, что данный паттерн был выбран ввиду его фундаментальной гибкости. Он позволяет осуществлять отложенную инициализацию (lazy initialization) приложения и его расширений, что дает возможность динамически создавать экземпляры приложения с различными конфигурациями (например, для production, development или testing) путем внедрения различных объектов конфигурации. Этот подход также эффективно решает проблему циклических импортов (circular imports) на уровне Python, что является частой проблемой в крупных Flask-приложениях.
Архитектура демонстрирует строгое разделение обязанностей (Separation of Concerns) между компонентами, что способствует высокой модульности и упрощает сопровождение:
- Flask: Ядро приложения, оркестрирующее все компоненты и связывающее расширения.
- API (api/routes.py): Выделенный модуль (Blueprint), инкапсулирующий всю бизнес-логику. Он функционирует как headless-сервис, принимая и обрабатывая исключительно JSON-запросы, и полностью отделен от логики отображения.
- Web (web/routes.py): Отдельный модуль (Blueprint), чья ответственность строго ограничена аутентификацией пользователей (через Flask-Login) и отдачей статических HTML-оболочек (хостов для SPA), таких как results.html.
- CLI (commands.py): Набор утилит командной строки (интегрированных с Flask-Click) для администрирования и обслуживания (например, flask db upgrade), функционально отделенный от веб-процесса.
Система в полной мере использует модель взаимодействия Backend for Frontend (BFF):
- Клиент (браузер) первоначально получает базовую HTML-оболочку от web/routes.py, которая служит лишь точкой входа.
- Внутри этой оболочки загружается и выполняется клиентский JavaScript (например, приложение 117-test.html или админ-панель results.html), который берет на себя всю ответственность за пользовательскую логику и рендеринг интерфейса.
- Данный JS-код активно взаимодействует с защищенным JSON API (api/routes.py), отправляя данные (например, logEventToServer) и получая их для отображения.
- Параллельно, SocketIO обеспечивает постоянный двунаправленный канал связи. Бэкенд (после успешной транзакции save_results) использует Redis Pub/Sub для широковещательной рассылки события update_needed всем подключенным админ-панелям. Это позволяет им инициировать дельта-обновление (delta update) своего интерфейса без необходимости полной перезагрузки страницы, обеспечивая высокий уровень оперативности.
1.2. Технологический Стек
Детальный перечень компонентов стека и их роли в архитектуре системы:
|
Компонент
|
Технология
|
Назначение и Ключевые Файлы
|
|
Фреймворк
|
Flask, Flask-SocketIO
|
Легковесное ядро приложения, реализующее роутинг и обработку HTTP-запросов. Flask-SocketIO обеспечивает интеграцию real-time WebSocket-коммуникаций поверх Flask, используя модель событий.
|
|
База данных
|
Flask-SQLAlchemy (PostgreSQL)
|
ORM для взаимодействия с реляционной базой данных PostgreSQL. Абстрагирует SQL-запросы, позволяет определять модели данных в Python. Следует отметить использование специфичных для PostgreSQL функций, таких как sqlalchemy.dialects.postgresql.insert (предположительно, для реализации ON CONFLICT DO UPDATE в get_or_create_user) и пессимистичных блокировок (with_for_update() в document.py). Последнее является критическим механизмом для обеспечения целостности данных (data integrity) в условиях высоких конкурентных нагрузок, в частности, для предотвращения "гонки состояний" (race conditions) при генерации уникальных номеров документов.
|
|
Кэширование и Очереди
|
Redis
|
Высокопроизводительное in-memory K-V хранилище, выполняющее в системе четыре критически важные и раздельные роли. Эта многофункциональность централизует управление эфемерным состоянием, оптимизирует производительность и снижает количество внешних зависимостей.
1. Кэш приложения (Flask-Cache): Кэширование результатов ресурсоемких запросов к API, например, агрегированной статистики (get_filtered_stats).
2. Бэкенд для SocketIO (Message Queue): Ключевой компонент, обеспечивающий горизонтальную масштабируемость. Использует Redis Pub/Sub для передачи real-time сообщений между несколькими экземплярами (воркерами) Flask, что является обязательным требованием для production-окружения, работающего за балансировщиком нагрузки.
3. Ограничитель скорости (Flask-Limiter): Защита API-эндпоинтов от DoS-атак и брутфорса путем отслеживания частоты запросов с IP-адресов.
4. Кэширование безопасности: Отдельное, высокоскоростное кэширование проверенных API-ключей (в decorators.py) для минимизации обращений к БД (read-heavy) при каждом API-вызове.
|
|
Аутентификация
|
Гибридная (Flask-Login + decorators.py)
|
Гибкая, двухуровневая модель доступа, обслуживающая разные классы клиентов (человек vs. машина):
1. Flask-Login: Управляет традиционными веб-сессиями на основе httpOnly cookies для пользователей-людей (администраторов), использующих SPA-дашборд (results.html).
2. Кастомные API-ключи: Используются для программного (M2M) доступа (фронтенд 117-test.html, CI/CD скрипты) через декоратор @api_key_required. Декоратор @admin_required элегантно объединяет обе проверки (session OR key), позволяя одному и тому же защищенному эндпоинту быть доступным как для человека-администратора, так и для автоматизированного скрипта.
|
|
Валидация
|
Pydantic и Flask-WTF
|
Два специализированных, непересекающихся инструмента для решения различных задач валидации на разных уровнях:
1. Pydantic (result_schema.py): Обеспечивает строгую валидацию структуры данных (входящих JSON payload) на границе API. Гарантирует, что поле raw_data в базе данных всегда соответствует согласованной схеме SaveResultsRequest, защищая от некорректных или вредоносных данных (data sanitization) на входе.
2. Flask-WTF (forms.py): Обеспечивает валидацию пользовательского ввода (HTML-форм, как LoginForm) и, что более важно, предоставляет интегрированную CSRF-защиту для веб-сессий администратора.
|
|
Безопасность
|
Flask-WTF (CSRF), Flask-CORS, Flask-Limiter, X-CSRFToken
|
Многоуровневая эшелонированная защита (defense-in-depth):
CORS: Контролирует, какие домены (origins) имеют право обращаться к API.
Limiter: Защита от брутфорса и DoS-атак на уровне IP.
CSRF (WTF): Защита веб-сессий админа от атак межсайтовой подделки запроса (реализована через полный цикл: Flask-WTF генерирует токен, web/routes.py внедряет его в meta-tag, admin/api.js считывает его и отправляет в заголовке X-CSRFToken при каждом запросе).
XSS (escapeHtml): Санитизация вывода в ui.js админ-панели для предотвращения атак межсайтового скриптинга.
|
|
Мониторинг
|
Prometheus (prometheus_flask_exporter)
|
Сбор и экспорт production-метрик (как стандартных http_requests_total, так и кастомных TESTS_COMPLETED_TOTAL, ANALYSIS_DURATION_SECONDS) для системы мониторинга Prometheus. Это обеспечивает наблюдаемость (observability) системы и возможность проактивного обнаружения проблем.
|
|
Логирование
|
Структурированное JSON-логирование (logging.py)
|
Система логирования, соответствующая промышленным стандартам. Каждый лог пишется в формате JSON и обогащается request_id. Это позволяет системам агрегации логов (ELK Stack, Splunk, Graylog) легко парсить, индексировать и, что наиболее важно, коррелировать все события, связанные с одним конкретным HTTP-запросом (end-to-end tracing) для быстрой диагностики инцидентов.
|
|
Аналитика
|
fastdtw (Гибридная)
|
Алгоритм Dynamic Time Warping. Используется для сравнения временных последовательностей разной длины (траекторий движения мыши). Присутствует и на бэкенде (analytics.py) для глубокого серверного анализа, и на клиенте (admin/analysis.js), предоставляя администратору гибкий выбор (trade-off) между скоростью (мгновенный анализ на клиенте) и исчерпывающей точностью (более медленный, но авторитетный анализ на сервере).
|
|
Миграции БД
|
Flask-Migrate
|
Инструмент (обертка над Alembic) для управления изменениями схемы базы данных. Позволяет применять и откатывать миграции (flask db upgrade) при развертывании, обеспечивая версионирование структуры БД.
|
|
Клиентский PDF
|
jsPDF, html2canvas
|
Используются в 117-test.html для гибридной генерации PDF. html2canvas создает растровое изображение HTML-шаблона, а jsPDF упаковывает его в PDF-документ на стороне клиента. Это значительно снижает нагрузку на app сервис, освобождая его CPU и память от ресурсоемких задач рендеринга. Эта архитектура переносит нагрузку по генерации PDF на машину конечного пользователя.
|
|
Контейнеризация (DevOps)
|
Docker & Docker Compose
|
Система упакована в Docker-контейнеры для обеспечения изоляции зависимостей и воспроизводимости окружения. docker-compose.yml (динамически формируемый deploy.sh) оркестрирует запуск и связывание всех сервисов (postgres, redis, app, nginx) в единую изолированную сеть.
|
|
Веб-сервер (DevOps)
|
Nginx
|
Функционирует в роли High-Performance Reverse-Proxy и "фасада" всей системы. Он принимает весь внешний трафик, терминирует SSL (обслуживает HTTPS), раздает статические ассеты (JS, CSS, изображения, собранные flask collect) напрямую из static_data volume (не нагружая Flask), перенаправляет запросы к /api и /socket.io на app сервис (Gunicorn/Flask) и выполняет первый эшелон rate-limiting.
|
|
SSL (DevOps)
|
Certbot
|
Интегрирован в deploy.sh и docker-compose.yml (при флаге --use-letsencrypt). Автоматически получает и, что важно, автоматически обновляет SSL-сертификаты от Let's Encrypt через ACME-challenge.
|
|
Оркестрация (DevOps)
|
Bash (deploy.sh)
|
Интеллектуальный скрипт-оркестратор для "нулевого развертывания" (zero-touch deployment). Он не просто запускает сервисы (docker compose up), а выполняет полный цикл инициализации всей системы с нуля, включая установку Docker, динамическую генерацию всех конфигурационных файлов (.env, docker-compose.yml, nginx.conf), получение SSL-сертификатов и первоначальное наполнение БД.
|
|
Управление (DevOps)
|
Makefile
|
Сгенерированный deploy.sh интерфейс управления (CLI Dashboard). Предоставляет простой, высокоуровневый и запоминаемый интерфейс (make backup, make logs) как слой абстракции над более сложными и длинными docker compose и flask командами, что кардинально упрощает ежедневное обслуживание системы.
|
1.3. Диаграмма Последовательности (End-to-End Flow)
Следующая диаграмма последовательности (Mermaid) представляет собой центральный визуальный артефакт данной документации. Она детализирует полный жизненный цикл одного пользовательского теста, демонстрируя хронологическое взаимодействие всех девяти ключевых компонентов системы: от первоначального GET-запроса пользователя до финального real-time уведомления администратора.
Эта диаграмма имеет критическое значение для понимания того, как потоки данных (data flows) проходят через систему, как обрабатываются атомарные транзакции и как компоненты (БД, Кэш, Pub/Sub) взаимодействуют для обеспечения как целостности данных (data integrity), так и оперативности их отображения. Необходимо обратить внимание на три ключевых аспекта:
- Асинхронная проверка API-ключа, использующая кэш Redis для оптимизации.
- Атомарная транзакция save_results, включающая пессимистическую блокировку DocumentCounter для предотвращения "гонки состояний".
- Широковещательный механизм уведомления через Redis Pub/Sub, который отделяет логику API от real-time оповещения.
sequenceDiagram
actor User as Пользователь (Браузер)
participant Web as Веб-сервер (web/routes.py)
participant API as API-сервер (api/routes.py)
participant Auth as Декораторы (@api_key_required)
participant Cache as Redis (Кэш)
participant DB as БД (SQLAlchemy)
participant Socket as Сервер SocketIO
participant PubSub as Redis (Pub/Sub)
actor Admin as Администратор (Браузер)
%% --- Фаза 0: Администратор подключается (Заранее) ---
Admin->>+Socket: Подключение по WebSocket
Socket-->>-Admin: Соединение установлено
Note right of Admin: Админ ожидает событий
%% --- Фаза 1: Пользователь начинает тест ---
User->>+Web: GET /117test (или /index.html)
Web-->>-User: HTML-страница (117-test.html)
group Фаза 1: log_event('test_started')
User->>+API: POST /api/log_event (eventType: 'test_started', X-API-Key: ...)
%% --- Проверка API-ключа ---
API->>+Auth: @api_key_required
Auth->>+Cache: cache.get(api_key) ?
Cache-->>-Auth: (miss)
Auth->>+DB: ApiKey.query(key) ?
DB-->>-Auth: key_obj
Auth->>+Cache: cache.set(key_obj)
Auth->>+DB: UPDATE ApiKey SET usage_count...
Auth-->>-API: Доступ разрешен
%% --- Логика 'log_event' ---
API->>+DB: db.session.get(ResultMetadata, session_id) ?
DB-->>-API: None (Сессия не найдена)
API->>DB: INSERT ResultMetadata (session_id, test_type="pending")
API->>DB: INSERT ProctoringEvent (eventType='test_started')
API->>DB: db.session.commit()
API-->>-User: 200 OK
end
loop Пользователь проходит тест
User->>API: POST /api/log_event (eventType: 'focus_loss', ...)
%% (Повторяется Фаза 1, но сессия уже найдена)
API->>User: 200 OK
end
%% --- Фаза 2: Пользователь завершает тест ---
group Фаза 2: save_results
User->>+API: POST /api/save_results (JSON с результатами, X-API-Key: ...)
%% --- Проверка API-ключа (теперь из кэша) ---
API->>+Auth: @api_key_required
Auth->>+Cache: cache.get(api_key) ?
Cache-->>-Auth: key_obj (hit)
Auth->>+DB: UPDATE ApiKey SET usage_count...
Auth-->>-API: Доступ разрешен
%% --- Логика 'save_results' (Транзакция) ---
Note over API,DB: Начало транзакции
API->>API: Валидация JSON (SaveResultsRequest)
API->>DB: get_or_create_user() (INSERT... ON CONFLICT...)
DB-->>API: user_obj
API->>DB: get_or_create_fingerprint()
DB-->>API: fingerprint_obj
API->>DB: db.session.get(ResultMetadata, session_id)
DB-->>-API: 'pending' result_obj
API->>API: result.score = 95, result.raw_data = {...}
alt Тест пройден (score >= 80)
API->>DB: Вызов generate_document_number()
Note right of DB: Pessimistic Lock: \n SELECT... FOR UPDATE
DB->>DB: UPDATE DocumentCounter SET last_sequence_number += 1
DB-->>API: doc_number (e.g., "25/11-0001")
API->>API: result.document_number = "25/11-0001"
API->>DB: INSERT Certificate (...)
end
API->>DB: db.session.commit()
Note over API,DB: Фиксация транзакции
DB-->>API: Commit OK
%% --- Фаза 3: Уведомление (после Commit) ---
group Фаза 3: Уведомление Администратора
API->>Cache: cache.delete_memoized(get_results_api)
API->>Socket: socketio.emit("update_needed", ...)
Note right of Socket: Конфигурация message_queue\nперехватывает emit() и...
Socket->>+PubSub: PUBLISH "updates" ({"type": "update_needed"})
Note over PubSub,Admin: 'redis_subscriber' на ВСЕХ \nсерверах слушает канал "updates"
PubSub-->>+Socket: (Сообщение получено)
Socket->>Admin: emit("update_needed") (WebSocket push)
Socket-->>-PubSub: (Завершение)
end
API->>API: METRICS.TESTS_COMPLETED_TOTAL.inc()
API-->>-User: 201 Created ({"officialDocumentNumber": "25/11-0001"})
end
2. Ключевая Функциональность и Потоки Данных (Use Cases)
2.1. Поток 1: Сбор данных (Полный "Туннель" Пользователя)
Система реализует строго регламентированную 4-этапную последовательность ("туннель"), которую проходит пользователь. Эта последовательность спроектирована для обеспечения целостности данных и предотвращения обхода этапов обучения или сбора данных.
Этап 1: index.html (Сбор данных и Идентификация)
- Действие: Пользователь вводит свои идентификационные данные (ФИО, должность).
- Механизм: Клиентский JS (setupPersistentId) немедленно создает или получает два идентификатора: persistentUserId_infosec (хранится в cookie для долгосрочной идентификации браузера) и lastTesterId_infosec (в localStorage для краткосрочного хранения) [cite: index.html].
- Цель: persistentId функционирует в качестве "якоря" (постоянного идентификатора), к которому будут привязаны все будущие события этого пользователя. ФИО сохраняется в localStorage (lastUserInfo) для автоматической передачи на следующие этапы, избегая повторного ввода.
Этап 2: index-start.html (Портал и Юридическое Предупреждение)
- Действие: Пользователю отображается навигационный хаб с доступными учебными модулями.
- Механизм: Критически важно: Пользователю демонстрируется явное предупреждение (warning-box) о "непрерывном мониторинге и протоколировании", "поведенческом анализе" и "отслеживании фокуса" [cite: index-start.html].
- Цель: Соблюдение юридических требований (получение информированного согласия) и психологическая подготовка пользователя к тому, что его действия отслеживаются, что само по себе является превентивной мерой.
Этап 3: study-117.html (Обучение с Первичным Прокторингом)
- Действие: Пользователь изучает учебные материалы.
- Механизм:
- Защита: Скрипт немедленно проверяет localStorage.getItem('lastUserInfo'). При отсутствии данных (пользователь попытался обойти index.html), он принудительно возвращается на index.html [cite: study-117.html].
- Начало Прокторинга: Сразу после загрузки вызывается logEventToServer('study_started', ...), что инициирует создание предварительной (pending) записи в ResultMetadata (согласно Диаграмме Последовательности) [cite: study-117.html, routes.py].
- Сбор данных: Отслеживаются module_view_time (время на модуль) и scroll_depth_milestone (глубина прокрутки) для верификации фактического взаимодействия пользователя с контентом [cite: study-117.html].
- Защита контента: Активируются водяной знак (#watermark с ФИО), защита от PrintScreen (screenshot_attempt), печати (print_attempt), вызова контекстного меню и выделения текста [cite: study-117.html].
- Цель: Сбор верифицируемых данных о вовлеченности в обучение. Эти метрики (module_view_time, scroll_depth_milestone) позволяют бэкенд-логике (get_behavior_analysis) впоследствии коррелировать высокий балл с низким временем обучения, выявляя статистические аномалии.
Этап 4: 117-test.html (Тест и Финальный Сбор Метрик)
- Действие: Пользователь проходит тестирование.
- Механизм: Запускается полноценное JS-приложение PersonalDataTestApp [cite: 117-test.html].
- Активный Прокторинг (Перехват событий): Мгновенно отслеживаются focus_loss, screenshot_attempt (с активацией "вспышки" для порчи изображения) и print_attempt, которые немедленно отправляются через logEventToServer [cite: 117-test.html].
- Пассивный Сбор (Накопление данных): В фоновом режиме собираются поведенческие метрики: keyboardDynamics (ритм набора ФИО), mouseMovements (массив координат мыши с временными метками), latency (время на ответ), answerChanges (индикатор нерешительности) [cite: 117-test.html].
- Цель: Формирование детального, многоаспектного поведенческого портрета пользователя непосредственно во время экзамена для последующего глубокого анализа и выявления нетипичных паттернов.
Этап 5: POST /api/save_results (Атомарное Сохранение на Бэкенде)
- Действие: Фронтенд (через finishTest()) агрегирует единый JSON-объект payload и отправляет его на бэкенд [cite: 117-test.html].
- Механизм:
- Валидация: Pydantic (схема SaveResultsRequest) гарантирует, что payload имеет корректную и ожидаемую структуру [cite: result_schema.py, routes.py].
- Транзакция: Запускается атомарная транзакция в БД для обеспечения согласованности данных [cite: routes.py]:
- get_or_create_user (атомарный INSERT ... ON CONFLICT...).
- ResultMetadata обновляется из pending в финальное состояние (балл, raw_data и т.д.).
- При успешном прохождении теста: generate_document_number() блокирует (with_for_update()) таблицу DocumentCounter для предотвращения "гонки состояний" и генерации дублирующихся номеров [cite: document.py].
- Создается запись в Certificate.
- Уведомление: После успешного commit() транзакции, notify_clients_of_update публикует update_needed в Redis Pub/Sub [cite: websocket.py, routes.py].
- Цель: Обеспечение целостности и согласованности данных (принцип "все или ничего"), гарантирование уникальности номеров сертификатов (через with_for_update) и немедленное уведомление администраторов о новом результате.
Этап 6: 117-test.html (Генерация PDF на Клиенте)
- Действие: Пользователь загружает сертификат.
- Механизм: Бэкенд возвращает 201 Created c officialDocumentNumber. Фронтенд (generateAndDownloadOfficialPDF) использует jsPDF и html2canvas для рендеринга PDF в браузере, вставляя в него officialDocumentNumber и данные из PDF_SETTINGS (предоставленные бэкендом) [cite: 117-test.html].
- Цель: Снижение серверной нагрузки (перенос рендеринга PDF на клиента) и обеспечение немедленной доступности документа для пользователя.
2.2. Поток 2: Администрирование (Модуль SPA)
Данный компонент представляет собой полноценное SPA (Single Page Application), выступающее центром управления. Его работа оркестрируется main.js [cite: main.js].
- Инициализация и Безопасность:
- Действие: Администратор проходит аутентификацию.
- Механизм: Аутентификация проходит через /login (форма LoginForm), Flask устанавливает безопасную httpOnly cookie-сессию [cite: routesweb.py, forms.py]. Бэкенд рендерит единственный раз results.html, в который критически важно встроен meta- тег с csrf_token [cite: results.html].
- Цель: main.js инициализирует SPA. api.js настраивает socket.io (для обработки update_needed) и safeFetch [cite: main.js, api.js].
- Безопасность: safeFetch автоматически считывает csrf_token из meta- тега и добавляет его в заголовок X-CSRFToken каждого последующего API-запроса (включая POST, PUT, DELETE), обеспечивая сквозную CSRF-защиту для всего SPA [cite: api.js].
- Дашборд (dashboard-view):
- Действие: Просмотр общей статистики и последних результатов.
- Механизм: apiClient асинхронно запрашивает:
- /api/get_dashboard_stats (для информационных виджетов, например, "Средний балл") [cite: api.js].
- /api/get_results (для таблицы с результатами) [cite: api.js].
- Оптимизация: Таблица (ui.renderDataTable) использует серверную пагинацию (запрашивает только N записей), но гибридную фильтрацию: фильтры по статусу (например, "pending") уходят на сервер (для уменьшения объема передаваемых данных), а поиск по ФИО происходит мгновенно на клиенте (applyFiltersAndRender), что обеспечивает высокую отзывчивость интерфейса (UX) [cite: ui.js, main.js].
- Безопасность: ui.js использует хелпер escapeHtml для защиты от XSS при рендеринге данных, полученных от API [cite: ui.js].
- Детальное Сравнение (comparison-view):
- Действие: Глубокий анализ подозрительных сессий.
- Механизм:
- Администратор выбирает сессии (их ID сохраняются в state.js) [cite: state.js].
- apiClient запрашивает полные raw_data (fetchFullResultDetails) для этих сессий [cite: api.js].
- Администратору доступен чекбокс "Глубокий анализ на сервере", который предлагает компромиссный выбор (trade-off) между ресурсами [cite: results.html]:
- Не нажат (Клиент): analysis.js выполняет fastDTW и analyzeFingerprints непосредственно в браузере. Это быстро, приватно (данные не покидают браузер админа) и снижает нагрузку на сервер [cite: analysis.js].
- Нажат (Сервер): apiClient отправляет ID сессий на эндпоинт /api/analyze_mouse для серверного, более точного и ресурсоемкого анализа [cite: main.js, api.js, routes.py].
- Цель: Предоставление гибкого инструмента анализа, варьирующегося от быстрого просмотра до глубокого исследования инцидентов.
- Журнал Событий (Модальное окно):
- Действие: Просмотр таймлайна действий пользователя.
- Механизм: При активации "👁️", apiClient запрашивает /api/get_events/{sessionId} [cite: api.js].
- Цель: ui.renderEventLog отрисовывает удобный таймлайн всех событий (focus_loss, print_attempt, study_started), немедленно визуально выделяя нарушения для быстрой оценки инцидента [cite: ui.js].
- Управление Настройками (settings-view):
- Действие: Изменение системных настроек (например, название организации в PDF).
- Механизм: Реализован полный цикл "Чтение-Запись" [cite: main.js, api.js, ui.js]:
- Чтение: GET /api/settings (apiClient.fetchSettings) получает текущие настройки.
- Отображение: ui.renderSettingsForm заполняет HTML-форму.
- Запись: POST /api/settings (apiClient.saveSettings) отправляет обновленные данные (с X-CSRFToken).
- Цель: Предоставление администратору возможности оперативно (real-time) изменять конфигурацию системы (например, ФИО подписанта в PDF) без необходимости перезапуска серверного приложения или прямого вмешательства в БД.
3. Архитектура Фронтенда (Пользовательский Модуль Прокторинга)
Этот модуль функционирует как активный агент по сбору данных, а не как пассивный веб-сайт.
3.1. "Туннель" (Enforced User Flow)
Принудительная 4-этапная последовательность ("туннель") является архитектурным требованием для обеспечения целостности данных прокторинга (нельзя пройти тест, не пройдя обучение).
- index.html: Сбор ФИО, установка persistentId (якорь сессии).
- index-start.html: Портал и юридическое предупреждение о мониторинге (информированное согласие).
- study-117.html: Обучение + первый этап прокторинга (сбор study_started, scroll_depth для анализа вовлеченности).
- 117-test.html: Тестирование + второй этап прокторинга (сбор детальных поведенческих метрик).
Следует подчеркнуть, что каждый этап (начиная со 2-го) выполняет проверку localStorage, чтобы убедиться, что предыдущий был пройден. Попытка прямого доступа к study-117.html приведет к принудительному редиректу на index.html.
3.2. Система Прокторинга (117-test.html)
JS-приложение PersonalDataTestApp использует как психологические, так и технические методы для обеспечения честности теста.
Психологический Фактор (Активное Сдерживание):
- "Следящий Глаз" (#proctor-eye): Данный элемент представляет собой не статическую иконку, а интерактивный компонент, который отслеживает курсор мыши (updateProctorEye), имитирует поведение (моргает triggerEyeBlink, совершает саккады triggerEyeSaccade) [cite: 117-test.html].
- Реакция на нарушения: Он изменяет цвет на "желтый" (.warning) при blur и "красный" (.alert) при PrintScreen, предоставляя пользователю немедленную обратную связь о том, что его действие замечено. Это служит сдерживающим фактором [cite: 117-test.html].
Активный Мониторинг (Перехват событий):
- blur (Уход со вкладки): logEventToServer('focus_loss') [cite: 117-test.html].
- e.keyCode === 44 (PrintScreen): logEventToServer('screenshot_attempt') + немедленная белая "вспышка" (#flash-overlay), которая "засвечивает" и делает нечитаемым (портит) скриншот [cite: 117-test.html].
- beforeprint (Печать): logEventToServer('print_attempt') [cite: 117-test.html].
Пассивный Сбор (Поведенческие Метрики):
- keyboardDynamics: Сбор таймингов keydown/keyup при вводе ФИО для создания "клавиатурного почерка" [cite: 117-test.html].
- mouseMovements: Запись всего пути мыши ([[x, y, timestamp], ...]) на каждом вопросе для fastdtw анализа [cite: 117-test.html].
- latency: Время (в мс) от показа вопроса до ответа [cite: 117-test.html].
- answerChanges: Количество смен ответа (индикатор нерешительности) [cite: 117-test.html].
3.3. Сборка Payload и Генерация PDF
- Payload (Контракт с API): finishTest() агрегирует все эти собранные данные (userInfo, testResults, sessionMetrics, behavioralMetrics) в единый payload. Этот payload представляет собой критически важную структуру данных, которая полностью соответствует Pydantic-схеме бэкенда SaveResultsRequest. Он функционирует как "контракт" между фронтендом и бэкендом, обеспечивающий целостность данных [cite: 117-test.html, result_schema.py].
- Гибридный PDF: Опишите следующий гибридный процесс, оптимизирующий использование ресурсов:
- Бэкенд (через SystemSetting) предоставляет данные (название организации, ФИО подписантов) через PDF_SETTINGS, которые встраиваются в 117-test.html [cite: routesweb.py, 117-test.html].
- Бэкенд (через save_results) предоставляет officialDocumentNumber в JSON-ответе [cite: routes.py].
- Фронтенд (generateAndDownloadOfficialPDF) использует html2canvas для создания "снимка" скрытого HTML-шаблона (#pdf-official-blank-container) и jsPDF для упаковки его в PDF на клиенте [cite: 117-test.html].
- Выгода: Серверные ресурсы (CPU) не затрачиваются на рендеринг PDF; сервер лишь предоставляет данные.
3.4. Безопасность Фронтенда
- Упомяните наличие "мастер-пароля" (this.overridePassword = 'QmFzZTExNw==') для сброса попыток (showPasswordPrompt), который декодируется из Base64 в "Base117". Данная функция предназначена для администрирования (например, при тестировании в режиме "киоска" или для сброса сессии пользователя) [cite: 117-test.html].
4. Архитектура Фронтенда (Админ-Панель SPA)
Данный модуль представляет собой SPA, разработанное в соответствии с принципами разделения ответственности (SoC).
4.1. Модульная Архитектура ("Мозг")
Опишите 5-модульную архитектуру SPA, объясняя роль и обоснование каждого модуля:
- state.js (Сердце / Единый источник истины):
- Роль: Центральное, изолированное хранилище состояния. Хранит все эфемерные данные приложения (allLoadedResults, settings, currentPage).
- Принцип: Ни один другой модуль не имеет права напрямую изменять эти данные. Все изменения должны происходить только через экпортируемые функции-сеттеры (например, setCurrentPageResults). Этот подход обеспечивает предсказуемый и отлаживаемый (unidirectional) поток данных [cite: state.js].
- api.js (Руки / Слой данных):
- Роль: Единственный модуль, ответственный за взаимодействие с бэкендом. Инкапсулирует всю логику сетевого взаимодействия (loadInitialData, fetchFullResultDetails, saveSettings).
- Механизмы: Реализует safeFetch для автоматического добавления X-CSRFToken ко всем запросам. Инициализирует socket.io и обрабатывает события update_needed от сервера, вызывая, в свою очередь, соответствующие сеттеры из state.js [cite: api.js].
- ui.js (Лицо / Слой отображения):
- Роль: Декларативный механизм рендеринга, отвечающий за все изменения в DOM.
- Принцип: Является "глупым" (dumb) компонентом; он никогда не изменяет состояние и не вызывает API. Он реактивно читает данные только из state.js и на их основе детерминированно строит HTML (renderDashboardCharts, renderDataTable) [cite: ui.js].
- analysis.js (Мозг / Клиентская логика):
- Роль: Выполняет сложные, потенциально блокирующие вычисления в браузере, снижая нагрузку на сервер.
- Механизмы: analyzeFingerprints (группирует сессии по privacySafeHash и ищет аномалии) и fastDTW (полная клиентская реализация Dynamic Time Warping) [cite: analysis.js].
- main.js (Нервная система / Оркестратор):
- Роль: "Склеивает" все компоненты. Инициализирует приложение (DOMContentLoaded).
- Механизм: Содержит всех слушателей событий (клики, ввод). Он управляет потоком: Пользовательское Событие (клик) -> main.js -> apiClient.fetch() -> (Promise resolves) -> state.setter() -> ui.render() [cite: main.js].
4.2. Ключевые Механизмы Админ-Панели
- Безопасность (CSRF & XSS):
- CSRF: Полный цикл: 1) Flask рендерит csrf_token в meta- тег в results.html. 2) api.js при инициализации читает токен. 3) safeFetch автоматически вставляет его в заголовок X-CSRFToken каждого fetch-запроса (POST/PUT/DELETE), обеспечивая полную защиту [cite: results.html, api.js].
- XSS: ui.js использует хелпер escapeHtml для санитизации всех данных (например, ФИО) перед вставкой в innerHTML [cite: ui.js].
- Администратор может выбрать, где проводить анализ мыши:
- Клиент (быстро): Используется analysis.js (чекбокс "Глубокий анализ" снят). Идеально для быстрой проверки [cite: analysis.js].
- Сервер (точно): Используется POST /api/analyze_mouse (чекбокс установлен). Идеально для "расследования" [cite: main.js].
- "Умный" Рендеринг (DOM Diffing):
- ui.js (через updateTableRows) не перерисовывает всю таблицу (что вызвало бы "моргание" и потерю фокуса). Он применяет простую технику "DOM diffing": сравнивает новое состояние из state.js со старым и точечно добавляет, обновляет или удаляет только измененные <tr> элементы. Это обеспечивает высокую производительность и плавность UX [cite: ui.js].
- Анализ Отпечатков (Fingerprints):
- analysis.js (через analyzeFingerprints) выполняет группировку по privacySafeHash на клиенте для быстрого поиска аномалий [cite: analysis.js].
5. Анализ Ключевых Компонентов (Backend)
5.1. Модели Данных (models.py)
- User: Гибридная модель. Идентифицируется по persistent_id (пользователь) или по email/password_hash (администратор) [cite: models.py].
- ResultMetadata: Центральная таблица. Критически важное поле: raw_data (JSON). Функционирует в роли "data lake", храня весь payload с фронтенда. Этот подход (schema-on-read) позволяет в будущем добавлять новые виды анализа в админ-панели, не требуя миграции схемы БД, так как все "сырые" данные уже сохранены [cite: models.py].
- ProctoringEvent: Журнал всех событий (study_started, focus_loss и т.д.) [cite: models.py].
- SystemSetting: K-V хранилище для настроек админ-панели (включая PDF_SETTINGS), которые можно менять "на лету" через SPA [cite: models.py].
- ApiKey: Таблица для управления API-ключами, их правами (is_admin, is_allowed_endpoint) и аудитом (usage_count) [cite: models.py].
5.2. API Эндпоинты (api/routes.py)
- get_results_api и get_abandoned_sessions: Используют CTE (Common Table Expressions) в SQL для "бесшовного" объединения данных из ResultMetadata (завершенные) и ProctoringEvent (активные/брошенные) "на лету". Вся работа по агрегации выполняется на уровне БД, что значительно эффективнее, чем загружать два списка в Python и объединять их там [cite: routes.py].
- /analyze_mouse: "Тяжелый" эндпоинт, использующий Prometheus (ANALYSIS_DURATION_SECONDS) для мониторинга времени выполнения [cite: routes.py, metrics.py].
- Эндпоинты для Админ-Панели:
- GET /api/get_dashboard_stats: Для виджетов.
- GET /api/get_filtered_stats: Для сводных графиков статистики.
- GET /api/get_events/{sessionId}: Для таймлайна событий.
- GET /api/get_full_result/{sessionId}: Для детального анализа.
- GET /api/settings: Загрузка настроек.
- POST /api/settings: Сохранение настроек (защищено CSRF).
- GET /api/global_search: Для глобального поиска.
- GET /api/get_certificates: Для реестра аттестатов.
5.3. CLI-команды (commands.py)
- flask init-settings: Заполнение SystemSetting значениями по умолчанию (идемпотентно) [cite: commands.py].
- flask create-admin: Создание администратора [cite: commands.py].
- flask create-apikey / revoke-apikey: Управление ключами доступа [cite: commands.py].
- flask collect: Критически важная команда для Docker-развертывания. Собирает статику в STATIC_ROOT (/app/static), который монтируется как static_data volume и раздается напрямую Nginx. Это необходимо, так как Gunicorn/Flask в production не должен заниматься раздачей статики [cite: commands.py, deploy.sh].
5.4. Валидация и Метрики (result_schema.py, metrics.py)
- Валидация: Pydantic-схема SaveResultsRequest — это "контракт" и "брандмауэр" API. Он гарантирует, что API не примет некорректный payload, обеспечивая чистоту данных в raw_data [cite: result_schema.py].
- Метрики: [cite: metrics.py]
- TESTS_COMPLETED_TOTAL (Counter): Бизнес-метрика (успешные тесты).
- DOCUMENTS_GENERATED_TOTAL (Counter): Бизнес-метрика (сертификаты).
- ANALYSIS_DURATION_SECONDS (Histogram): Техническая метрика (производительность /api/analyze_mouse).
- ACTIVE_WEBSOCKET_CONNECTIONS (Gauge): Техническая метрика (количество админов онлайн).
5.5. Архитектура Безопасности (decorators.py)
- @admin_required (Гибридный): Позволяет эндпоинту быть доступным как для человека (веб-сессия current_user.is_authenticated), так и для скрипта (API-ключ key_obj.is_admin), что идеально для автоматизации и CI/CD [cite: decorators.py].
- @api_key_required (Умный): [cite: decorators.py]
- Кэширование в Redis: Проверенные ключи кэшируются на 5 минут (cache.set(..., timeout=300)). Подавляющее большинство запросов не вызывают ApiKey.query.
- Проверка прав (Scope): Проверяет is_allowed_endpoint. Это позволяет создавать ключи с гранулированными правами (например, API_KEY_FRONTEND_CLIENT может только вызывать log_event и save_results).
- Аудит: Атомарно инкрементирует usage_count для ключа.
- Интеграция с CSRF: Flask-WTF (csrf_token) -> results.html (meta- тег) -> api.js (safeFetch) -> X-CSRFToken заголовок [cite: decorators.py, results.html, api.js].
5.6. Механизм Real-time Обновлений (websocket.py)
- Проблема: Как Воркер А, получивший /save_results, уведомит админа, подключенного по WebSocket к Воркеру Б? Стандартный вызов socketio.emit работает только в рамках одного процесса.
- Решение (Redis Pub/Sub): [cite: websocket.py]
- api/routes.py вызывает notify_clients_of_update [cite: routes.py].
- Эта функция публикует (redis_client.publish("updates", ...)) JSON-сообщение в канал Redis.
- В каждом воркере Flask (запущенном Gunicorn) в фоне запущен redis_subscriber (из __init__.py) [cite: init.py].
- Этот подписчик слушает (pubsub.listen()) канал "updates".
- Получив сообщение (от любого воркера), он транслирует его своим локальным WebSocket-клиентам (socketio.emit(...)), которых слушает api.js админ-панели [cite: api.js].
- Вывод: Это стандартная и самая надежная архитектура для SocketIO в production, которая декаплирует логику API от логики оповещения и не требует "sticky sessions" на Nginx.
5.7. Анализ Поведения ("Секретный Соус")
- На стороне Бэкенда (analytics.py):
- Технология: fastdtw (Fast Dynamic Time Warping).
- Алгоритм: compare_mouse_trajectories. Это не простое сравнение X/Y [cite: analytics.py]:
- Изоляция: extract_initial_stroke (отсекает паузы).
- Сравнение: fastDTW (математическое "совмещение").
- Оценка: Вычисляется взвешенная "оценка схожести": Форма (60%), Масштаб (25%), Позиция (15%).
- На стороне Клиента (Пользователь, 117-test.html):
- Сбор данных: "Следящий Глаз" (психология), "Вспышка" (активный мониторинг), keyboardDynamics и mouseMovements (пассивный сбор) [cite: 117-test.html].
- На стороне Клиента (Админ, analysis.js):
- fastDTW (клиентская реализация) и analyzeFingerprints как инструменты, снижающие нагрузку на сервер [cite: analysis.js].
5.8. Вспомогательные Модули (Utilities)
- forms.py: LoginForm для Flask-WTF (валидация email, пароля) [cite: forms.py].
- validators.py: Простые проверки (validate_session_id) [cite: validators.py].
- sanitizers.py: sanitize_filename (использует unidecode для безопасной транслитерации кириллицы), используется для имен PDF, предотвращая атаки Path Traversal [cite: sanitizers.py].
6. Архитектурные Решения и Рекомендации по Эксплуатации
6.1. Сильные стороны (Design Rationale)
- Надежность и Целостность Данных:
- Атомарный "UPSERT" (get_or_create_user): Предотвращает дубликаты пользователей [cite: routes.py].
- Гарантия уникальности документов: Использование with_for_update() в document.py — критическое решение, предотвращающее 'гонки состояний' и гарантирующее, что два пользователя никогда не получат один и тот же номер сертификата [cite: document.py].
- Безопасность (Defense in Depth):
- Многоуровневая: Rate Limiting, 'умные' API-ключи с кэшированием, гибридные права @admin_required [cite: decorators.py].
- Клиент (Пользователь): Принудительный "туннель", защита от скриншотов/печати (#flash-overlay), водяные знаки [cite: 117-test.html, study-117.html].
- Клиент (Админ): Сквозная CSRF-защита (Flask meta-tag -> api.js X-CSRFToken) [cite: results.html, api.js] и XSS-защита (escapeHtml в ui.js) [cite: ui.js].
- Горизонтальная: Использование Redis для кэша и сессий позволяет запускать несколько экземпляров app сервиса за load balancer.
- Real-time в кластере: Паттерн Redis Pub/Sub (websocket.py) — ключевой элемент, позволяющий масштабировать SocketIO-воркеры [cite: websocket.py].
- Наблюдаемость (Observability):
- Prometheus: Кастомные бизнес- (TESTS_COMPLETED_TOTAL) и технические (ANALYSIS_DURATION_SECONDS) метрики [cite: metrics.py].
- Логирование: Структурированное JSON-логирование с request_id — это признанный стандарт (best practice) для эксплуатации, позволяющий в ELK/Splunk отследить весь путь одного запроса [cite: logging.py].
- "Умный" API и Фронтенд (Оптимизация):
- Эффективность БД: Выполнение сложной агрегации (get_results_api) на стороне БД (через CTE), а не в Python [cite: routes.py].
- Контракт API: Сборка payload на клиенте, идеально соответствующая Pydantic-схеме (result_schema.py) [cite: 117-test.html, result_schema.py].
- SPA-архитектура: Четкое разделение state, api, ui и analysis в админ-панели [cite: state.js, api.js, ui.js, analysis.js].
- Снижение нагрузки: Клиентский fastDTW и analyzeFingerprints в admin/analysis.js — это эффективное решение, переносящее вычисления на машину администратора [cite: analysis.js].
7. Развертывание и Управление (deploy.sh & Makefile)
Этот раздел описывает слой DevOps и Управления, являющийся неотъемлемой частью системы [cite: deploy.sh].
7.1. Философия Развертывания (deploy.sh)
deploy.sh — это скрипт-оркестратор "нулевого развертывания". Его задача — превратить неконфигурированный сервер с Docker в полностью рабочую, защищенную и настроенную систему.
- Интерактивный (--start): Для первой установки. Проводит администратора по шагам: задает вопросы (домен, пароли, email) и безопасно сохраняет их в .env.
- Неинтерактивный (--non-interactive): Для CI/CD и автоматизации. Читает все параметры из переменных окружения (например, F152Z_DB_PASSWORD).
- Интеллектуальные функции: Скрипт является самодостаточным. Он автоматически проверяет наличие Docker/sudo и может самостоятельно установить Docker (--install-docker), используя менеджер пакетов ОС.
7.2. Процесс "Умной" Инициализации (deploy.sh)
Скрипт выполняет пошаговый процесс, подчеркивая автоматизацию и идемпотентность (безопасность повторного выполнения).
- Проверка Системы: Проверяет sudo, curl, openssl, docker, docker-compose.
- Сбор Конфигурации: Либо в интерактивном режиме, либо из env-переменных.
- Динамическая Генерация prod.env: Создает .env файл, генерируя SECRET_KEY с помощью openssl rand.
- Динамическая Генерация docker-compose.yml: Создает docker-compose.yml, описывающий 4 сервиса: postgres, redis, app (образ из ghcr.io), nginx.
- Динамическая Генерация nginx/nginx.conf: Создает конфигурацию Nginx на основе SERVER_NAME.
- Автоматизация SSL:
- По умолчанию: Генерирует самоподписанный сертификат (openssl req...), чтобы Nginx мог запуститься.
- При флаге --use-letsencrypt:
- Запускает временный Nginx.
- Запускает docker run ... certbot/certbot certonly --webroot... для прохождения ACME-challenge.
- Автоматически добавляет сервис certbot в docker-compose.yml для авто-обновления.
- Перезапускает Nginx с боевым Let's Encrypt сертификатом.
- Запуск Сервисов: Выполняет docker compose up -d.
- Инициализация Приложения (через docker compose exec):
- flask db upgrade: Автоматическое применение миграций БД.
- flask create-admin: Создание администратора (идемпотентно, с проверкой .admin_created).
- flask create-apikey: Автоматически создает API_KEY_FRONTEND_CLIENT и дописывает его в prod.env. Это завершает цикл: фронтенд не сможет работать без ключа, а ключ генерируется при первом запуске бэкенда.
- flask collect: Сбор статических файлов в static_data volume для Nginx.
- Генерация "Панели Управления": Создает Makefile и вспомогательные .sh скрипты.
7.3. "Панель Управления" (Makefile)
Makefile (сгенерированный deploy.sh) — это основной интерфейс (CLI) для управления развернутым приложением. Он служит абстракцией, скрывая сложные docker compose exec и pg_dump команды.
- Управление сервисами: make up, make down, make restart, make status.
- Управление данными: make backup (вызов backup.sh), make restore (интерактивно), make migrate.
- Управление ключами: make create-apikey, make create-admin-apikey (интерактивное создание с записью в .env).
- Отладка: make logs, make logs-app, make shell (в app), make shell-db (psql), make shell-redis (redis-cli).
- Обслуживание: make update (вызов update.sh), make monitor (вызов monitor.sh), make destroy (полное удаление данных и volumes).
7.4. Вспомогательные Скрипты
- update.sh: (Генерируется) Инкапсулирует логику обновления: docker compose pull (скачать новые образы), docker compose up -d (перезапустить), flask db upgrade (применить миграции).
- backup.sh: (Генерируется) Инкапсулирует логику бэкапа: pg_dump базы данных (postgres) и tar всей конфигурации (.env, docker-compose.yml, nginx/).
- monitor.sh: (Генерируется) Упрощенный TUI-дашборд на основе docker stats и docker system df для "живого" мониторинга использования CPU/RAM/Диска контейнерами.