RealHelpDesk API
REST API системы управления заявками. Предоставляет функционал для работы с порталами (тематическими пространствами), заявками, сообщениями, с возможностью прикреплять вложения.
Возможности
- Регистрация, авторизация, аутентификация, верификация email, восстановление пароля.
- Порталы: создание, редактирование, удаление; публичный доступ и доступ по списку доверенных пользователей.
- Заявки: создание, статусы, приоритеты, удаление, поиск по заявкам.
- Формирование сообщений, прикрепление к заявке файловых вложений (MinIO, отдача с поддержкой HTTP
Rangeдля не стабильных сетей). - Передача права владения порталом с подтверждением и полной историей состояний портала.
- Оповещения: email и in-app (SSE-канал, long polling — фолбэк).
- Ограничения: квоты пользователя, рейт-лимиты, капча.
Технологии
Базовый стек — Java 25 и Spring Boot 4.1.1 на PostgreSQL 18; инфраструктура
описана в Docker Compose. Ниже — полный список технологий и зависимостей
с версиями и решаемыми задачами. Источник версий для Java-зависимостей —
pom.xml (большинство версий задаёт BOM Spring Boot 4.1.1), для сервисов —
infrastructure/docker-compose.yaml.
Язык и платформа
| Технология | Версия | Решаемая задача |
|---|---|---|
| Java | 25 | Язык и целевой уровень компиляции (release=25) |
| Maven (Maven Wrapper) | 3.9.11 (3.3.4) | Сборка, тесты, плагины; wrapper фиксирует версию Maven для разработчиков и CI |
| Spring Boot | 4.1.1 | Каркас приложения, авто-конфигурация, management-контур |
| Spring Framework | 7.0.9 | DI-контейнер, WebMVC, транзакции, JDBC |
| Tomcat (embedded) | 11.0.25 | Servlet-контейнер REST API; патч 11.0.24→11.0.25 — требование trivy (CRITICAL/HIGH) |
| Jackson 2 (core / databind / annotations) | 2.21.7 | JSON: сериализация DTO, чтение тел запросов, JWT |
Jackson 3 (tools.jackson) |
3.1.7 | JSON для Actuator и внутренних конвертеров Boot 4 |
| SnakeYAML | 2.6 | YAML-конфигурации (Spring, OTel и др.) |
Все версии используемых библиотек — на странице
docs/dependency-versions.md.
Запуск
Стек поднимается в Docker; Java 25 и Maven нужны только для разработки.
Подготовка → .env (make env) → секреты в .env
├─ Dev: контейнеры (make dev-up) / хост из IDEA (make dev-up-deps + Debug)
├─ Prod: pull из ghcr.io → make prod-up
├─ Тесты: make verify (хост) / make e2e-all (Keycloak-контур, поднятый стек)
├─ Нагрузка: make load (k6)
└─ CI/CD: push → build → push-образ → deploy по тегу
Все сценарии — построчно, с командами, портами и проверками — на странице
docs/getting-started.md.
Архитектура
Структура пакетов (src/main/java/com/ukhanov/realhelpdesk):
com.ukhanov.realhelpdesk
├── core — общая инфраструктурная часть, не зависящая от предметной области
│ ├── annotation, config, controller, exception
│ ├── filter, http, log, mail, observability, pagination
│ ├── security (accesscontrol, handler, keycloak, limiter, localjwt, ratelimit, user)
│ └── storage
├── domain — доменная модель: сущности, репозитории и доменные правила
│ ├── portal, ticket, message, attachment, notification
└── feature — функциональные модули: REST-слой по областям применения
├── portalmanager, ticketmanager, messagemanager
├── attachmentmanager, notificationmanager, usermanager
Ошибки HTTP-слоя централизованно преобразуются в ответы формата RFC 7807
через GlobalExceptionHandler. Spring ProblemDetail формирует тело
application/problem+json с полями type, title, status, detail,
instance; timestamp, errors и машинный code провала капчи —
extension-поля, которые RFC 7807 разрешает добавлять.
Конфигурация
Несекретные настройки хранят разумные дефолты прямо в схеме
application*.properties — строки вида ${VAR:default}. Без дефолта
остались только секреты (JWT_SECRET, DB_PASSWORD, …) и специфичные для
среды хосты/URL: не заданная такая переменная приводит к отказу старта с
явным указанием причины, а не к молчаливой работе на чужих значениях.
Среды различаются наборами профилей Spring и compose-файлов.
Подробнее: docs/configuration.md
Аутентификация
Упор сделан на JWT. Токен передаётся в http-only cookie, refresh-токен
хранится в БД как SHA-256 хеш и проходит ротацию; отзыв (логаут, смена
пароля) применяется немедленно — за счёт версии токенов, не дожидаясь
истечения срока. Профиль keycloak переключает проверку подписи на внешний
OAuth2-провайдер, модель токена и cookie-контур при этом сохраняются.
Подробнее: docs/authentication.md
Правила доступа
Упор сделан на JWT: личность и роль извлекаются из токена на каждом запросе,
а проверка прав выполняется на сервере в момент обработки — аннотациями
@PreAuthorize над сервисами валидации доступа. Роль всегда читается из БД,
поэтому блокировка пользователя или её понижение вступают в силу сразу, не
дожидаясь истечения токена.
Порталы имеют три уровня доступа: владелец, доверенный пользователь,
публичный. Заявки — две политики: ALL_USERS (все, кому доступен портал) и
CREATOR_AND_PORTAL_USERS (автор и пользователи портала). Отказ отдаётся как
403, чужая или отсутствующая сущность — как 404, чтобы не раскрывать
факт существования записи.
Подробнее: docs/access-control.md
Оповещения
In-app оповещения доходят по двум каналам: основной — SSE, фолбэк —
long polling. Оба используют общий курсор afterId и отдают одни и те же
строки, поэтому клиент может переключиться между ними без потери событий.
Дополнительно уходят email-уведомления, а повторы напоминаний доставляет
фоновый планировщик.
Подробнее: docs/notifications.md
Рейт-лимиты
Ограничения считаются отдельно по клиентскому IP и по эндпоинту; превышение
отдаёт 429 с заголовком Retry-After. Счётчики живут в памяти процесса и
сбрасываются при рестарте, пороги задаются переменными окружения.
Подробнее: docs/rate-limits.md
Наблюдаемость
Три сигнала: метрики (Micrometer → Mimir), трейсы (OTel → Tempo) и логи с
traceId/spanId (→ Loki). Стек наблюдаемости поднимается отдельно от
приложения, приложение от него не зависит. Алерты обеспечены runbooks.
Управляющий порт
Приложение слушает управляющий порт 8081 (MANAGEMENT_PORT) — отдельно от
основного 8080. Правила безопасности (ManagementSecurityConfiguration):
| Эндпоинт | Доступ | Зачем |
|---|---|---|
/actuator/health |
permitAll (без аутентификации) |
liveness/readiness-проверки Docker-оркестратора |
/actuator/info |
permitAll |
версия сборки (org.opencontainers.image.revision) |
/actuator/prometheus |
HTTP Basic METRICS_USER/METRICS_PASSWORD
|
скрейп метрик Alloy |
всё остальное (env, heapdump, threaddump, configprops) |
denyAll |
закрыто даже при попадании в exposure.include
|
Подробнее: docs/observability/overview.md
API
Описание всех эндпоинтов, кодов ответов и соглашений контракта — в
docs/api.md. Машинная спека собирается из контроллеров:
GET /v3/api-docs, интерфейс — Swagger UI (/swagger-ui.html).
Swagger UI работает в dev-контуре, в prod отключён (springdoc.*.enabled=false).
Спецификация контракта (OpenAPI)
Контракт обеспечивает три свойства:
-
Прозрачность моделей данных. Каждая схема (DTO) обогащается
example— готовым объектом с примерами значений, и описаниями всех полей. Служебные поля (напримерisEmpty()для определения пустого PATCH-тела) отмечены@JsonIgnoreи не попадают в JSON и спеку. DTO — чистые Java records, без связей@JsonIdentityInfo/@JsonBackReference, поэтому нет бесконечной вложенности и технических полей Hibernate. -
Самодостаточность контракта. Фронтенд-разработчик или тестировщик может реализовать свою часть задачи, глядя исключительно в Swagger UI — все поля имеют примеры и описания. Глобальный обработчик исключений (
@RestControllerAdvice) размечен@ApiResponseдля каждого кода (400, 401, 403, 404, 500 и др.), поэтому структураproblem+jsonвидна в каждой операции. Контроллеры документируют реальные статусы (@ApiResponse(201),204,206,416), которые возвращаются напрямую, а не дефолтный200. -
Безопасность в prod. В рабочем контуре
springdoc.api-docsиswagger-uiотключены черезapplication-prod.properties— эндпоинтов документации в prod нет (404). Пути/swagger-ui/**и/v3/api-docsнаходятся в white-list обеих security-цепочек (permitAll) и сами по себе защиты не дают: в контурах, где springdoc включён, документация открыта без аутентификации.
Автоматическая проверка контракта — тест
OpenApiDocumentationGuardTest: ≥50 схем, у каждой example + описания полей,
коды в @ApiResponse совпадают с реальными статусами, схемы фреймворка
(StreamingResponseBody, SseEmitter) не попадают в контракт.
Тесты и проверки
Юнит- и интеграционные тесты выполняются на хосте без Docker; e2e-сценарии — только на поднятом dev-стеке; нагрузочное тестирование идёт по полному пользовательскому сценарию через k6.
make e2e-all (и, соответственно, make dev-test) прогоняет Keycloak-контур — рекомендуемую схему развёртывания:
вход через Keycloak, базовые CRUD порталов и уведомлений.
Внутренний JWT-контур (cookie-аутентификация) поддерживается для автономных инсталляций,
но новым пользователям не предлагается — его сценарии вынесены в make e2e-jwt-in-app
и в e2e-all не входят.
Сценарий make e2e-ratelimit в них не входит: логика бакетов, 429 и
Retry-After целиком покрыта RateLimitServiceTest, а e2e-прогон тратит 56
последовательных запросов и всегда упирался бы в лимиты. Цель оставлена для
ручного прогона после изменений в рейт-лимитах.
Подробнее: docs/testing.md
Документация
| Документ | Содержание |
|---|---|
getting-started.md |
Сценарии запуска: dev, хост, рабочий контур |
configuration.md |
Переменные окружения, профили, compose-файлы, почта, миграции |
authentication.md |
JWT-контур, Keycloak, refresh-токены, CSRF |
capcha.md |
Капча: PoW, эмодзи-эскалация, риск-движок, параметры |
access-control.md |
Уровни доступа к порталам, политики заявок, передача владения |
notifications.md |
SSE, long polling, механизм пробуждения, повторы |
rate-limits.md |
Каталог лимитов, добавление нового лимита |
api.md |
Эндпоинты, коды ответов, соглашения контракта |
testing.md |
Тесты, e2e-сценарии, нагрузочное тестирование |
dependency-versions.md |
Полный список версий зависимостей и сервисов |
code-style.md |
Стиль кода, инструменты форматирования, конвенции |
debugging.md |
Частые ошибки и способы их устранения |
observability/ |
Метрики, трейсы, логи, SLO |
runbooks/ |
Реагирование на алерты |