Skip to content

Repository files navigation

User Service

Работает с PostgreSQL и Redis, хранит аватары в S3-совместимом хранилище, интегрируется с Keycloak и Kafka. Ниже собраны инструкции по запуску, настройке профилей, тестированию и офлайн-прогреву.

Содержание

Быстрый старт

Локальная разработка (local)

  1. Установите Docker и Docker Compose v2.
  2. Выполните docker compose up -d в корне репозитория — запустятся PostgreSQL 18.0 для приложения, отдельная PostgreSQL 18.0 для Keycloak, сам Keycloak, Redis 8.2.3-alpine, Kafka 4.0.0, MinIO и клиент mc. Все сервисы проброшены на 127.0.0.1, теги образов зафиксированы, для MinIO автоматически создаётся бакет corpbucket с приватной политикой доступа.
  3. Запустите приложение командой ./gradlew bootRun. Профиль local активируется автоматически.
  4. Проверяйте здоровье сервиса по адресу http://localhost:8080/api/v1/actuator/health. Консоль MinIO доступна на http://127.0.0.1:9001 (логин/пароль user/password), консоль Keycloak — на http://127.0.0.1:9090 (администратор admin/password).

Параметры локального окружения заданы в src/main/resources/application-local.yaml, инфраструктура описана в docker-compose.yml.

Контейнер для prod

  1. Соберите образ: docker build -t user-service .. Мультистейдж-сборка упакует bootJar, настроит JRE-слой и добавит healthcheck.
  2. Запустите контейнер, передав переменные окружения из раздела «Переменные окружения prod» и пробросив порт 8080, например: docker run -p 8080:8080 --env-file .env user-service.
  3. Healthcheck внутри образа обращается к /api/v1/actuator/health/readiness, поэтому его же следует мониторить в оркестраторе.

Внутри контейнера профиль prod активируется переменной окружения SPRING_PROFILES_ACTIVE=prod (см. Dockerfile), а параметры JVM задаются через JAVA_TOOL_OPTIONS.

Порты и сервисы

Сервис Порт (host) Описание
Приложение (Swagger UI) 8080 REST API, Swagger UI: http://localhost:8080/api/v1/swagger-ui.html
PostgreSQL (app) 5432 База приложения (контейнер user_service_db)
Keycloak 9090 Консоль и issuer realm users: http://localhost:9090
Redis 6379 Кеш; можно подключать метрики/TTL для сессий и кэшировать профили
Kafka 9094 Публичный listener брокера; пригоден для событий профиля/аватаров
MinIO API / Console 9000 / 9001 S3-совместимое хранилище и веб-консоль

Значения взяты из docker-compose.yml и подходят для локальной разработки. При изменении пробросов обновляйте эту таблицу и параметры подключения в application-local.yaml/переменных окружения.

Архитектура и код

Модель данных

  • Liquibase-миграции. Базовый changeset 001-users-schema-and-demo-data.sql создаёт две таблицы: справочник стран countries и таблицу пользователей users, в которой хранится ссылка на страну проживания. В users находятся пути к исходным и преобразованным версиям аватаров, поэтому отдельной таблицы аватаров нет. Контекстно-зависимые TRUNCATE/INSERT выполняются только вне prod, и боевое окружение стартует с пустыми таблицами.
  • Учётные данные. Поле пароля (password) отсутствует в таблице пользователей: Keycloak остаётся единственным источником аутентификации, а сервис хранит только профильные атрибуты.

Профили конфигурации

Профиль Назначение Источник данных Liquibase Redis Особенности
local Локальная разработка через docker-compose jdbc:postgresql://localhost:5432/user_service, пользователь user/password Включён, ожидает classpath:db/changelog/db.changelog-master.yaml localhost:6379 MinIO и внешние клиенты работают на локальные заглушки
prod Боевое окружение Переменные DB_URL, DB_USER, DB_PASSWORD Включён, ожидает classpath:db/changelog/db.changelog-master.yaml Настраивается через REDIS_HOST, REDIS_PORT Обязательные параметры клиентов и S3 берутся из переменных окружения
test Интеграционные тесты (Testcontainers) JDBC-URL подменяется контейнером PostgreSQL Отключён Автоконфигурация Redis исключена Баннер выключен, логирование снижено

Переменные окружения prod

Категория Переменные Назначение
База данных DB_URL, DB_USER, DB_PASSWORD JDBC-строка подключения и учётные данные
Redis REDIS_HOST, REDIS_PORT (опционально) Хост и порт кеша, по умолчанию redis:6379
S3 S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET, S3_REGION, S3_URL_EXPIRATION Настройка S3-совместимого хранилища, региона (us-east-1 по умолчанию) и TTL presigned-ссылок
Внешние сервисы PROJECT_SVC_URL, PAYMENT_SVC_URL Базовые URL интеграций
Аватары AVATAR_STORAGE_PATH, AVATAR_THUMBNAIL_MAX_SIDE, AVATAR_PROFILE_MAX_SIDE,
AVATAR_ALLOWED_MIME_TYPE_1, AVATAR_ALLOWED_MIME_TYPE_2, AVATAR_ALLOWED_MIME_TYPE_3
Переопределение параметров хранения и валидации загрузок
Keycloak KEYCLOAK_ISSUER_URI, KEYCLOAK_AUDIENCE, KEYCLOAK_USER_ID_CLAIM Настройка ресурс-сервера и claim с идентификатором пользователя

Все переменные заданы в src/main/resources/application-prod.yaml: обязательные отмечены оператором :?, остальные имеют дефолты. Redis по умолчанию обращается к сервису redis из docker-compose, а URL сторонних сервисов и ключи доступа к S3 обязательны для прод-окружения.

Liquibase использует master-changelog db/changelog/db.changelog-master.yaml, поэтому убедитесь, что файл доступен в classpath при запуске контейнера.

Keycloak и JWT

Про локальное окружение

  • Keycloak поднимается из docker-compose.yml и доступен на http://localhost:9090.
  • После запуска доступна только административная консоль, поэтому конфигурацию нужно применить самостоятельно. Создайте realm users, публичный клиент postman (Direct Access Grants) и клиент-аудиторию user-service, добавляющую значение в claim aud. Пользователи test_user (роль USER) и admin_user (роль ADMIN) также заводятся вручную или импортируются из подготовленного экспорта — docker-compose их не загружает.
  • Профиль local в application-local.yaml содержит базовые значения issuer-uri, audience и имя claim с идентификатором пользователя (user_id).

Про переменные окружения в prod

  • KEYCLOAK_ISSUER_URI — обязательный issuer realm-а; без него приложение не запустится.
  • KEYCLOAK_AUDIENCE — ожидаемая аудитория токена. Если клиент в Keycloak переименован, обновите переменную.
  • KEYCLOAK_USER_ID_CLAIM — claim с идентификатором пользователя. По умолчанию user_id, при его отсутствии сервис читает стандартный sub.

Конфигурация и параметры

Параметры аватаров

Настройки user.avatar задаются в application.yaml и переопределяются переменными окружения в application-prod.yaml. Они определяют базовый префикс ключей в S3, габариты превью/профильной версии и список допустимых MIME-типов. Класс AvatarProperties нормализует значения, гарантирует ненулевой список MIME и предоставляет дефолты (storage path, JPEG/PNG/WebP, размеры 170 и 1080 пикселей).

Настройки S3

Секция services.s3 описывает подключение к MinIO/AWS S3: endpoint, ключи доступа, bucket и время жизни presigned URL. Профиль local направляет на MinIO из docker-compose.yml, prod требует обязательные переменные. В коде значения биндятся в S3Properties, где нормализуется регион и задаётся дефолтное время истечения PT120H.

Параметры безопасности

  • spring.security.oauth2.resourceserver.jwt.issuer-uri — URL realm-а Keycloak. В local зашит http://localhost:9090/realms/users, а в prod значение прокидывается через KEYCLOAK_ISSUER_URI.
  • app.security.jwt.audience — ожидаемая аудитория токена. Значение берётся из app.security.jwt.audience, по умолчанию равно user-service и может быть переопределено переменной KEYCLOAK_AUDIENCE.
  • app.security.jwt.user-id-claim — имя claim для идентификатора пользователя. По умолчанию user_id, но можно сменить через KEYCLOAK_USER_ID_CLAIM. При отсутствии claim используется fallback на sub.
  • Все параметры задаются в application-local.yaml и application-prod.yaml, поэтому при обновлении настроек Keycloak достаточно поправить профиль или переменные окружения.

Логирование

logback-spring.xml настраивает асинхронный вывод в консоль с профилями local, prod и test. В local включён детальный DEBUG для пакета приложения и WARN для Spring/Hibernate, тогда как prod ограничивается INFO. Очередь асинхронного аппендера увеличена до 1024 и настроена на неблокирующий режим, чтобы не тормозить обработку запросов.

Безопасность API

Публичные эндпойнты

  • OPTIONS /** — предзапросы браузеров.
  • GET /actuator/health/**, GET /actuator/info — мониторинг состояния.
  • /v3/api-docs/**, /swagger-ui/**, /swagger-ui.html — документация.
  • Все остальные эндпойнты требуют валидного Bearer-токена.

Проверка JWT

  • Приложение настроено как OAuth2 Resource Server и валидирует токены локально через JwtDecoder.
  • Проверяются подпись, срок действия, issuer и аудитория (aud) с помощью AudienceValidator.
  • Claim roles преобразуется в ROLE_* через JwtGrantedAuthoritiesConverter, поэтому роли USER/ADMIN из Keycloak доступны в hasRole.
  • Ошибки 401 и 403 возвращаются обработчиками JsonErrorAuthenticationEntryPoint и JsonErrorAccessDeniedHandler в формате ErrorResponse.

Методовая авторизация

  • Компонент UserSecurity извлекает идентификатор пользователя из claim user_id (либо sub) и определяет, является ли запрос администраторским.
  • UserAvatarController использует @PreAuthorize("@userSecurity.canAccessUserResource(#userId, authentication)"), чтобы доступ к операциям был только у владельца ресурса или администратора.
  • Аналогичным образом можно защитить будущие контроллеры, переиспользуя методы userSecurity.

Офлайн-прогрев

Скрипт ./install.sh готовит окружение для работы без доступа к интернету:

  • проверяет наличие Docker и Java, выводит их версии;
  • подготавливает Gradle Wrapper и прогревает зависимости (включая Testcontainers) через сборку с тестами;
  • скачивает заранее зафиксированные Docker-образы PostgreSQL, Redis, Keycloak, Kafka, Temurin JDK/JRE, а также служебные образы Testcontainers (testcontainers/ryuk, alpine);
  • поддерживает гибкие настройки через переменные окружения:
    • версии Docker-образов переопределяются (например, POSTGRES_IMAGE=18.1 ./install.sh);
    • опциональные сервисы (Keycloak, Kafka, MinIO) управляются флагами PULL_*=0|1.

После выполнения скрипта можно запускать Gradle с флагом --offline и использовать локальные образы.

Тестирование

  • Интеграционный smoke-тест DatabaseSmokeIt поднимает PostgreSQL 18.0 в Testcontainers и выполняет select 1, проверяя корректность DataSource.
  • Gradle настроен на запуск тестов в профиле test с подробными логами стандартных потоков.

Команда запуска: ./gradlew test. При необходимости предварительно выполните офлайн-прогрев.

OpenAPI и Swagger UI

Благодаря зависимости springdoc-openapi-starter-webmvc-ui после запуска сервиса Swagger UI доступен по адресу http://localhost:8080/api/v1/swagger-ui.html. В правом верхнем углу появится кнопка Authorize: нажмите её и вставьте Bearer <JWT> из Keycloak для работы с защищёнными эндпоинтами (realm users, аудитория user-service). После авторизации можно использовать кнопку Try it out для ручного тестирования REST-эндпоинтов.

Обработка ошибок

В приложении действует единый глобальный обработчик исключений (GlobalExceptionHandler), который формирует ответы в формате ErrorResponse. Он используется как для стандартных ошибок Spring (BindException, MethodArgumentNotValidException, ConstraintViolationException), так и для кастомных потомков BaseServiceException.

Ошибки безопасности

  • 401 (ErrorCode.UNAUTHORIZED, USR-4000) — возвращается JsonErrorAuthenticationEntryPoint, если токен отсутствует или недействителен.
  • 403 (ErrorCode.ACCESS_DENIED, USR-4001) — возвращается JsonErrorAccessDeniedHandler, когда токен не даёт необходимых прав.
  • Ответы формируются в формате ErrorResponse: в details.path добавляется URI запроса, что упрощает трассировку.

Формат ответа ErrorResponse

Поле Тип Описание
code String Стабильный идентификатор ошибки (ErrorCode#getCode)
message String Сообщение для клиента
timestamp Instant Момент формирования ответа
details Map<String, String> Дополнительные сведения (например, ошибки полей)

Пример ответа для ошибки валидации:

{
  "code": "USR-1001",
  "message": "Данные не прошли валидацию",
  "timestamp": "2024-05-01T12:34:56.789Z",
  "details": {
    "email": "must be a well-formed email address"
  }
}

Основные коды ошибок

Код HTTP-статус Назначение
USR-1000 400 Ошибки биндинга запроса (BindException)
USR-1001 422 Нарушение бизнес-валидации входных данных
USR-1002 422 Ошибки загрузки аватара
USR-2000 404 Сущность не найдена (EntityNotFoundException)
USR-2001 404 Пользователь не найден
USR-2002 404 Страна не найдена
USR-2003 404 Аватар не найден
USR-3000 409 Нарушение ограничений целостности (например, уникальность)
USR-4000 401 Требуется аутентификация
USR-4001 403 Доступ запрещён
USR-7000 500 Ошибки файлового хранилища
USR-9000 500 Неперехваченные исключения (RuntimeException, Exception)

Для внедрения новых бизнес-ошибок создавайте собственные классы, наследуясь от BaseServiceException, и указывайте подходящий ErrorCode. В этом случае обработчик автоматически сформирует ответ в нужном формате.

Полезные файлы

  • docker-compose.yml — локальная инфраструктура для профиля local: две PostgreSQL 18.0 (приложение и Keycloak), Keycloak, Redis, Kafka, MinIO и mc, которые создают бакет corpbucket с приватной политикой доступа.
  • Dockerfile — мультистейдж-сборка образа с разделением на build/runtime, настройкой профиля prod, healthcheck и отдельным пользователем app.
  • install.sh — сценарий офлайн-прогрева: проверяет инструменты, прогревает Gradle и скачивает pinned Docker-образы для приложения и тестов.
  • src/main/resources/application-*.yaml — базовые настройки приложения и профилей local/prod, включая параметры Redis, метрик и аватаров; src/test/resources/application-test.yaml — конфигурация профиля test для интеграционных тестов.
  • docs/tech-debt-log.md — журнал технического долга; docs/unit-test-guidelines.md — правила написания юнит-тестов в проекте.

Проверка зависимостей Gradle

В проекте включена строгая проверка зависимостей Gradle (verification-metadata.xml). Каждая сборка сверяет контрольные суммы артефактов из конфигураций compileClasspath и runtimeClasspath, поэтому любые изменения дерева зависимостей требуют обновления metadata.

Как обновлять metadata

  1. Внесите необходимые изменения в build.gradle.kts или settings.gradle.kts (добавление/удаление зависимостей, изменение версий, глобальные exclude).

  2. Выполните команду для вашей среды.

    Linux/macOS (bash/zsh)

    ./gradlew --write-verification-metadata sha256

    При необходимости принудительно обновить артефакты перед генерацией metadata добавьте --refresh-dependencies:

    ./gradlew --write-verification-metadata sha256 --refresh-dependencies

    Windows (PowerShell)

    .\gradlew `
      --write-verification-metadata sha256

    С обновлением зависимостей:

    .\gradlew `
      --write-verification-metadata sha256 `
      --refresh-dependencies

    Windows (cmd.exe)

    gradlew --write-verification-metadata sha256

    С обновлением зависимостей:

    gradlew --write-verification-metadata sha256 --refresh-dependencies

    Gradle пересоберёт gradle/verification-metadata.xml, добавив контрольные суммы новых артефактов.

  3. Проверьте, что файл обновился автоматически, и закоммитьте его вместе с правками зависимостей.

Ограничения

  • Не используйте динамические версии (+, latest.release и т.п.) — они ломают воспроизводимость и влекут ошибки верификации.
  • Старайтесь ограничивать exclude только нужными конфигурациями, чтобы не вызывать массовых обновлений metadata.
  • Не редактируйте verification-metadata.xml вручную: любые изменения вносите только через команду --write-verification-metadata.
  • При необходимости полной очистки окружения остановите демоны Gradle (./gradlew --stop) и удалите каталоги .gradle/ и ~/.gradle/caches.

P.S.

  • Форматирование таблиц в README фиксируется средствами IDE; не пытайтесь вручную «выравнивать» их или перестраивать.
  • Не используйте прямые ссылки на файлы проекта в формате 【F:filename†L1-L10】.

Contributors

Languages