Работает с PostgreSQL и Redis, хранит аватары в S3-совместимом хранилище, интегрируется с Keycloak и Kafka. Ниже собраны инструкции по запуску, настройке профилей, тестированию и офлайн-прогреву.
- Быстрый старт
- Архитектура и код
- Профили конфигурации
- Переменные окружения
prod - Keycloak и JWT
- Конфигурация и параметры
- Безопасность API
- Офлайн-прогрев
- Тестирование
- OpenAPI и Swagger UI
- Обработка ошибок
- Полезные файлы
- Проверка зависимостей Gradle
- Как обновлять metadata
- P.S.
- Установите Docker и Docker Compose v2.
- Выполните
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с приватной политикой доступа. - Запустите приложение командой
./gradlew bootRun. Профильlocalактивируется автоматически. - Проверяйте здоровье сервиса по адресу
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.
- Соберите образ:
docker build -t user-service .. Мультистейдж-сборка упакуетbootJar, настроит JRE-слой и добавит healthcheck. - Запустите контейнер, передав переменные окружения из раздела
«Переменные окружения
prod» и пробросив порт8080, например:docker run -p 8080:8080 --env-file .env user-service. - 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 исключена | Баннер выключен, логирование снижено |
| Категория | Переменные | Назначение |
|---|---|---|
| База данных | 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 поднимается из
docker-compose.ymlи доступен наhttp://localhost:9090. - После запуска доступна только административная консоль, поэтому конфигурацию нужно применить самостоятельно.
Создайте realm
users, публичный клиентpostman(Direct Access Grants) и клиент-аудиториюuser-service, добавляющую значение в claimaud. Пользователиtest_user(рольUSER) иadmin_user(рольADMIN) также заводятся вручную или импортируются из подготовленного экспорта — docker-compose их не загружает. - Профиль
localвapplication-local.yamlсодержит базовые значенияissuer-uri,audienceи имя claim с идентификатором пользователя (user_id).
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 пикселей).
Секция 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 и настроена на неблокирующий режим, чтобы не тормозить обработку запросов.
OPTIONS /**— предзапросы браузеров.GET /actuator/health/**,GET /actuator/info— мониторинг состояния./v3/api-docs/**,/swagger-ui/**,/swagger-ui.html— документация.- Все остальные эндпойнты требуют валидного Bearer-токена.
- Приложение настроено как OAuth2 Resource Server и валидирует токены локально через
JwtDecoder. - Проверяются подпись, срок действия, issuer и аудитория (
aud) с помощьюAudienceValidator. - Claim
rolesпреобразуется вROLE_*черезJwtGrantedAuthoritiesConverter, поэтому ролиUSER/ADMINиз Keycloak доступны вhasRole. - Ошибки 401 и 403 возвращаются обработчиками
JsonErrorAuthenticationEntryPointиJsonErrorAccessDeniedHandlerв форматеErrorResponse.
- Компонент
UserSecurityизвлекает идентификатор пользователя из claimuser_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. При необходимости предварительно выполните офлайн-прогрев.
Благодаря зависимости 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 запроса, что упрощает трассировку.
| Поле | Тип | Описание |
|---|---|---|
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 (verification-metadata.xml).
Каждая сборка сверяет контрольные суммы артефактов из
конфигураций compileClasspath и runtimeClasspath, поэтому
любые изменения дерева зависимостей требуют обновления metadata.
-
Внесите необходимые изменения в
build.gradle.ktsилиsettings.gradle.kts(добавление/удаление зависимостей, изменение версий, глобальныеexclude). -
Выполните команду для вашей среды.
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, добавив контрольные суммы новых артефактов. -
Проверьте, что файл обновился автоматически, и закоммитьте его вместе с правками зависимостей.
- Не используйте динамические версии (
+,latest.releaseи т.п.) — они ломают воспроизводимость и влекут ошибки верификации. - Старайтесь ограничивать
excludeтолько нужными конфигурациями, чтобы не вызывать массовых обновлений metadata. - Не редактируйте
verification-metadata.xmlвручную: любые изменения вносите только через команду--write-verification-metadata. - При необходимости полной очистки окружения остановите демоны Gradle
(
./gradlew --stop) и удалите каталоги.gradle/и~/.gradle/caches.
- Форматирование таблиц в README фиксируется средствами IDE; не пытайтесь вручную «выравнивать» их или перестраивать.
- Не используйте прямые ссылки на файлы проекта в формате 【F:filename†L1-L10】.