LMS
Система для проведения практических занятий на основе микросервисной архитектуры (Kotlin + Spring Boot + PostgreSQL 18).
Документация
UML- и архитектурные диаграммы (Mermaid) — в каталоге docs/:
- Архитектура — контекст, компоненты, модули, аутентификация;
- Развёртывание — Docker, nginx, тома, обновление;
- Модель данных и классов — диаграммы классов по сервисам и ER-модель;
- Диаграммы последовательностей — вход, импорт ЛР, раздача вариантов, сдача, защита, оценки, материалы;
- Диаграммы состояний — защита, публикация оценок и материалов, сдача, пароль.
Модули
libs/common-http— общий формат ошибок и обработчик исключений.libs/security-jwt— выпуск/проверка JWT (RSA) иPasswordEncoder.libs/markdown— серверный рендер markdown со спойлерами и санитизацией.libs/test-support— общая тестовая инфраструктура (Testcontainers,postgres:18).services/identity-service— аккаунты, студенты, группы, зачисления (схемаidentity).services/academic-service— курсы, статьи, лабораторные, уровни, варианты (схемаacademic).services/progress-service— уровни студентов, распределение вариантов, работы, оценки (схемаprogress).services/gateway— Spring Cloud Gateway, маршрутизация и проверка JWT.web— серверный веб-интерфейс (Thymeleaf + htmx), BFF.
Ключевое в текущей версии:
- роли
ADMIN/TEACHER/STUDENT, курсы и группы-когорты, повышение и жизненный цикл студента, профиль студента курса; - access JWT 15 минут + refresh 7 дней с ротацией/отзывом; web-сессия 7 дней sliding с абсолютным потолком 30 дней и автоматическим обновлением access;
- аудит с keyset-пагинацией, пакетная выдача лабораторных и групп курса;
- материалы и активы secure-by-default: файл без статьи доступен только
админу (инвентаризация
/admin/legacy-assets).
Требования
- JDK 21 (runtime) + Docker (для PostgreSQL 18). Gradle сам скачает toolchain 21 для сборки.
- Не запускать сервисы на JAVA_HOME=Java 25: на 25 ломается logback внутри
fat-jar (NoClassDefFoundError
ThrowableProxy), что роняет потоки Tomcat. Скриптrun.shиспользует JDK 21 автоматически.
Запуск
./gradlew bootJar -x test # собрать все сервисы
./run.sh start # поднять БД + все 5 сервисов на JDK 21
./run.sh status # проверить здоровье
./run.sh logs web # посмотреть лог сервиса
./run.sh stop | restart
Можно и без скрипта: docker compose up -d postgres, затем
/usr/lib/jvm/java-21-openjdk/bin/java -jar <jar> для каждого сервиса,
или ./gradlew :services:identity-service:bootRun и т.д. по одному.
Порты по умолчанию: gateway :8080, identity :8081, academic :8082,
progress :8083, web :8090. Интерфейс: http://localhost:8090.
Качество кода и тесты
./gradlew ktlintCheck detekt test # линтеры (строгий режим) + тесты
./gradlew evidenceCheck # LDD: P0-claims должны иметь зелёные свидетельства
./gradlew ktlintFormat # автоформат Kotlin
./scripts/ldd-smoke.sh # быстрый smoke полного стека (как CI workflow smoke)
./scripts/ldd-runtime.sh # runtime-smoke полного стека + scale load
./scripts/ldd-migration-upgrade.sh # upgrade с master-схемы
./scripts/ldd-mutation.sh # PIT: mutation strength критичных P0-компонентов
./scripts/ldd-browser.sh # Playwright E2E: Chromium/Firefox/WebKit
./scripts/ldd-release.sh # полный release gate: test/upgrade/mutation/runtime/E2E
./scripts/ci-preflight.sh --release # проверка окружения runner без установки
./scripts/ci-build-image.sh # прогретый CI-образ lms-ci:1 для Woodpecker
./scripts/legacy-assets.sh # инвентаризация бесхозных файлов на стенде (admin)
ktlintотвечает за формат,detekt— за качество (сложность, вложенность, ошибки). Оба подключены во всех Kotlin-модулях конвенциейlms.lintи ломают сборку при замечаниях.- Конфигурация:
.editorconfig,config/detekt/detekt.yml. - Gradle daemon фиксируется на JDK 21 (
gradle/gradle-daemon-jvm.properties): detekt 1.23 не работает на JDK 25. - Тестовая инфраструктура —
libs:test-support(JUnit 5, MockK, Testcontainerspostgres:18). - LDD — реестр проверяемых утверждений
docs/evidence/claims.json; P0 без зелёного теста валит./gradlew evidenceCheck. Runtime, migration, mutation, browser E2E и smoke — скрипты изscripts/; baseline —docs/evidence/baseline-2026-10-27.md. - CI merge gate —
.woodpecker/(Gitea + Woodpecker 3.x): workflowcheck(test ktlintCheck detekt evidenceCheck) иsmoke(scripts/ldd-smoke.sh) на каждом push/MR; полныйrelease—scripts/ldd-release.sh— на тег или вручную. Smoke/release идут на отдельной CI VM: Woodpecker Docker backend, nested dind, Java 21, Docker Compose v2, Python 3, Node и Chromium/Firefox/WebKit. Установка —deploy/WOODPECKER.md. Baseline: 58 claims, 368 тестов, 18/18 browser E2E.
Запуск в Docker (для сервера)
На сервере Java не нужна: всё собирается и работает в контейнерах (multi-stage
Dockerfile, сборка на JDK 21, запуск на JRE 21).
cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
./deploy/deploy.sh up # backup + сборка + запуск (postgres, 5 сервисов)
./deploy/deploy.sh logs web
./deploy/deploy.sh ps
./deploy/deploy.sh down
Перед обновлением deploy.sh up обязательно делает backup БД и файлов.
Ежедневное расписание, retention и восстановление — в deploy/BACKUP.md.
Наружу публикуется только web на 127.0.0.1:8090; reverse proxy — nginx
(конфиг и полная инструкция по деплою на lms.our-place.ru в
deploy/DEPLOY.md и deploy/nginx/lms.our-place.ru.conf).
Наполнение данными
Порядок ввода данных (всё через админку): /setup → администратор; курс
(bootstrap или «+ Новый курс»); импорт групп (.xls, логин = студенческий номер,
временный пароль со сменой при первом входе); импорт лабораторной ZIP'ом с
выбором курса (lab.yaml + README.md + variants/*.md); «Распределить»
— случайная раздача по кругам в рамках уровня; оценки/защита — на странице группы;
учебные материалы — статьи или ZIP (chapters/…, доступны гостям).
Подробный пошаговый порядок — в deploy/IMPORT.md.
Текущий функционал
Открыть http://localhost:8090. При первом запуске создаётся аккаунт
администратора (/setup); далее — вход по логину и паролю.
- Доступ: JWT (RSA, ключи identity генерирует при первом старте и хранит в БД),
проверка токена на gateway, роли ADMIN/TEACHER/STUDENT, CSRF на всех POST-формах,
session fixation-защита; вход студента с обязательной сменой пароля.
Access-токен живёт 15 минут (
LMS_ACCESS_TOKEN_TTL), refresh — 7 дней (LMS_REFRESH_TOKEN_TTL) с ротацией и отзывом; прямой API используетPOST /api/v1/auth/refresh. Web-сессияLMS_SESSION— 7 дней sliding (LMS_SESSION_TIMEOUT), access обновляется автоматически за 2 минуты до истечения; logout и смена пароля отзывают refresh. - Группы и студенты: группы-когорты (поток, курс, номер, степень, год набора),
импорт
.xls/.xlsx/.csvпо шаблонупоток/курс/номер/степень/год набора/ФИО/№/база, ручное добавление студента (генерация пароля) и «Добавить существующего» (перевод из другой группы), «Отчислить из группы», выгрузка логинов XLSX/CSV, сброс пароля, история членства. - Курсы: админ-страницы курсов (
/admin/courses), номерcourse_noдля URL, назначение преподавателей и привязка групп с историей; bootstrap переносит существующие лабораторные, главы и активные группы в курс «Промышленное программирование на языке C». Лабораторные привязаны к курсу: создание и импорт требуют выбора курса, лабу можно скопировать в другой курс. - Преподаватели и доступ:
/admin/teachersсоздаёт аккаунтыTEACHERс одноразовым паролем; преподаватель видит только назначенные курсы (/teacher), их группы, студентов и профили. Курсы и лабораторные фильтруются по роли, внутренние вызовы identity защищеныLMS_INTERNAL_TOKEN. - Профиль студента курса:
/teacher/courses/{id}/students/{id}и админский маршрут показывают сводку, оценки, комментарии с авторами, защиты и все попытки сдачи со скачиванием ZIP; студенту API отдаёт только его профиль и опубликованные данные. - Работа преподавателя: ведомость группы переиспользует
/admin/groups/{groupId}?courseId=...без админских контролов — оценки и комментарии, публикация, уровни/варианты и раздача, защиты (код/reset), скачивание попыток и ZIP группы; лабораторные/уровни/варианты своего курса, привязка/отвязка групп на/teacher/courses/{id}, авторские материалы на/teacher/courses/{id}/content. Чужой курс отклоняется на сервисах. - Аудит: чувствительные действия (профили, скачивания, оценки, защиты,
курсы, группы, материалы, преподаватели, жизненный цикл) пишутся в
identity.audit_events; админ смотрит/admin/auditс фильтром по действию, временем МСК и постраничным переходом «Показать ещё» (page/limit). - Повышение и жизненный цикл:
/admin/promotion— dry-run, применение и откат повышения (автоматический dry-run 31 августа МСК); на странице группы — академотпуск, возврат, отчисление, архив и восстановление; архив группы и подтверждение удаления логином/ФИО. Вход закрыт для выпущенных и архивных групп. - Лабораторные: импорт ZIP (
lab.yaml+README.md+variants/*.md+assets/); создание через UI с произвольным числом уровней; единая форма настроек с одной кнопкой «Сохранить всё»; статусы draft/published (клик по бейджу меняет); даты в московском времени. - Распределение вариантов (только на странице лабы): «Распределить» с выбором групп; раздача случайная и равномерная — студенты выбранных групп идут общей очередью, вариант берётся со своего уровня, пока в уровне есть невыданные — без повторов, затем выдаются наименее занятые; ручные закрепления сохраняются всегда. На странице лабы — список назначений по группам (уровень/вариант каждому), массовые операции чекбоксами, индикаторы «выдано/всего». Матрица на странице группы — просмотр (вариант, сдача) + оценки/защита, управление раздачей оттуда убрано.
- Индивидуальные задания (защита): на странице группы — назначение/изменение
задания, импорт CSV (2 или 3 колонки, с кавычками и
\n), выдача кода, «доп. задание: начать заново» после завершения защиты. - Материалы (
/learn, публичная читалка): главы → разделы → статьи в markdown (код, спойлеры, картинки; санитизация OWASP); импорт ZIP (chapters/N-глава/N-раздел/N-статья.md+assets/); создание/переименование/ удаление глав, разделов, статей; перенос статей между разделами. Видимость картинок наследуется от статьи; файл без привязки к статье отдаёт404обычным пользователям и доступен только админу, который получает список таких файлов черезGET /api/v1/content/legacy-assets. - Студент: дашборд успеваемости (сдано, средний балл, лучшая/слабая оценка), карточки опубликованных лаб, страница лабы: условие, свой вариант, сдача файлов (zip/.c, переотправка до дедлайна, история, скачивание последней), оценка и комментарии отдельным блоком, защита по одноразовому коду (30 мин).
- Публикация оценок: в группе по каждой лабе — кнопка «опубликовать»; студент видит оценку и комментарии только после публикации (публикуется вся группа).
- Комментарии к оценкам: общий (итоговый) комментарий по лабораторной + комментарии с тональностью (positive/negative/neutral); на странице студента — ненумерованными списками в трёх колонках, хранятся в БД, автоподсказки при вводе ранжируются по частоте использования.
- Архив сдач группы: одна кнопка «Скачать все работы» — ZIP со всеми последними решениями группы, разложенными по лабам и студентам.
- Материалы
/learnдоступны без входа; аккордеон глав: свёрнуты все, открыта последняя. - UI: светлая/тёмная тема (сохраняется в localStorage), модальные окна вместо
отдельных страниц, аккордеоны глав/разделов в
/learn, фавиконка, адаптивная вёрстка без inline-стилей.
Примечания
- JDK 21 обязателен для runtime. В связке Spring Boot 4.0.x + fat-jar logback
не находит
ThrowableProxy, и это падает при любом исключении в потоке ответа. Если после деплоя сервис вдруг «висит» на страницах (а/actuator/healthдаёт 200) — лечится перезапуском (./run.sh restart). - Даты хранятся в UTC (instant), а в UI конвертируются в Europe/Moscow
(
DateUtils); формы datetime-local отправляются в московском времени. - Капча/rate-limit на входе отсутствует — для учебного стендома оставлено; пароли студентов показываются один раз при выдаче.
lms.submissions.dir(default ./data/submissions) иlms.content.assets-dirдержите на постоянном томе при деплое.
Сборка и тесты
./gradlew build # сборка всех модулей
./gradlew check # тесты и проверки
Переменные окружения для БД: LMS_DB_URL, LMS_DB_USER, LMS_DB_PASSWORD.