R

realhelpdesk-api

REST API для построения системы управления заявками или внутренней системы обработки запросов.

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)

Контракт обеспечивает три свойства:

  1. Прозрачность моделей данных. Каждая схема (DTO) обогащается example — готовым объектом с примерами значений, и описаниями всех полей. Служебные поля (например isEmpty() для определения пустого PATCH-тела) отмечены @JsonIgnore и не попадают в JSON и спеку. DTO — чистые Java records, без связей @JsonIdentityInfo / @JsonBackReference, поэтому нет бесконечной вложенности и технических полей Hibernate.

  2. Самодостаточность контракта. Фронтенд-разработчик или тестировщик может реализовать свою часть задачи, глядя исключительно в Swagger UI — все поля имеют примеры и описания. Глобальный обработчик исключений (@RestControllerAdvice) размечен @ApiResponse для каждого кода (400, 401, 403, 404, 500 и др.), поэтому структура problem+json видна в каждой операции. Контроллеры документируют реальные статусы (@ApiResponse(201), 204, 206, 416), которые возвращаются напрямую, а не дефолтный 200.

  3. Безопасность в 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/ Реагирование на алерты