- tablekit builds a filter row under the headers for every th with data-filter; distinct cell values become a select (label from data-filter-label), or a text substring filter with data-filter="text" - filters combine with the global table search; placeholder rows (colspan) are not filtered away; filter row follows the lab-focus column hiding - enabled on: matrix lab columns (by variant), defense table (ЛР, status with readable values), students (Основа, Пароль), lab assignment lists (Уровень, Источник, Сдача), labs list (Статус)
LMS
Микросервисная система для проведения практических занятий (Kotlin + Spring Boot + PostgreSQL 18).
Модули
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.
Требования
- 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.
Запуск в Docker (для сервера)
На сервере Java не нужна: всё собирается и работает в контейнерах (multi-stage
Dockerfile, сборка на JDK 21, запуск на JRE 21).
cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
./deploy/deploy.sh up # сборка + запуск (postgres, 5 сервисов)
./deploy/deploy.sh logs web
./deploy/deploy.sh ps
./deploy/deploy.sh down
Наружу публикуется только web на 127.0.0.1:8090; reverse proxy — nginx
(конфиг и полная инструкция по деплою на lms.our-place.ru в
deploy/DEPLOY.md и deploy/nginx/lms.our-place.ru.conf).
Наполнение данными
Порядок ввода данных (всё через админку): /setup → администратор; импорт групп
(.xls, логин = студенческий номер, временный пароль со сменой при первом входе);
импорт лабораторной ZIP'ом (lab.yaml + README.md + variants/*.md); «Распределить»
— случайная раздача по кругам в рамках уровня; оценки/защита — на странице группы;
учебные материалы — статьи или ZIP (chapters/…, доступны гостям).
Подробный пошаговый порядок — в deploy/IMPORT.md.
Текущий функционал
Открыть http://localhost:8090. При первом запуске создаётся аккаунт
администратора (/setup); далее — вход по логину и паролю.
- Доступ: JWT (RSA, ключи identity генерирует при первом старте и хранит в БД), проверка токена на gateway, роли ADMIN/STUDENT, CSRF на всех POST-формах, session fixation-защита; вход студента с обязательной сменой пароля.
- Группы и студенты: создание групп, импорт
.xls/.xlsx/.csv, ручное добавление студента (генерация пароля) и «Добавить существующего» (перевод из другой группы), «Отчислить из группы», выгрузка логинов XLSX/CSV, сброс пароля. - Лабораторные: импорт 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/); создание/переименование/ удаление глав, разделов, статей; перенос статей между разделами. - Студент: дашборд успеваемости (сдано, средний балл, лучшая/слабая оценка), карточки опубликованных лаб, страница лабы: условие, свой вариант, сдача файлов (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.