Skip to content

Levasey/com.springDataJPA.library

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Библиотека — Spring Boot 3, Spring Data JPA

Веб-приложение для учёта читателей и книг: выдача и возврат, поиск в шапке (в разделе книг — по каталогу, в разделе читателей — по читателям), отдельные страницы /books/search и /people/search, публичная страница /about («О библиотеке») и футер с контактами на всех страницах с общим layout (templates/fragments/footer.html), пагинация и множественная сортировка списка книг (на странице /books — форма с чекбоксами; порядок ключей сортировки на бэкенде: название → автор → жанр → год). Читатель (роль USER) в карточке книги может отметить книгу прочитанной или снять отметку; список таких книг виден в /me. В каталоге и полнотекстовом поиске по книгам для читателя доступен фильтр «без прочитанных» (hide_read=true): в списке /books исключение выполняется в запросе к БД (корректная пагинация), в /books/search — после выборки по запросу. У каждой книги задаётся жанр из фиксированного списка (Genre, JPA ENUM строкой); при создании и редактировании жанр выбирается в форме, в списке, поиске и карточке жанр показывается текстом. При добавлении читателя (/people/new) создаётся учётка каталога со случайным временным паролем; читатель задаёт свой пароль по одноразовой ссылке из приветственного письма (Spring Mail, см. «Почта») или по ссылке, показанной библиотекарю на /people, если письмо не отправилось. Вход в каталог: логин в БД — нормализованный email (нижний регистр, без лишних пробелов); на форме входа можно указать email или номер читательского билета (как в карточке). Читатель с ролью USER может воспользоваться /forgot-password: при включённой почте уходит письмо с одноразовой ссылкой на /catalog/setup-password, при этом не раскрывается, существует ли такая учётка (см. «Сброс пароля»). К публичным формам (забыли пароль, установка пароля по ссылке) применяется лимит запросов с одного IP (in-memory, настраивается в library.rate-limit; в тестовом профиле отключён). Spring Boot 3, Spring Data JPA, Hibernate 6, Thymeleaf, PostgreSQL; миграции Flyway; Spring Security включён с CSRF для форм; тесты на JUnit 5, Mockito, H2.


Требования

  • JDK 17+ (для Spring Boot 3; на JDK 24 тесты используют -Dnet.bytebuddy.experimental=true в Surefire, см. pom.xml).
  • PostgreSQL с базой library (или своё имя — в конфиге).
  • Maven Wrapper: ./mvnw (отдельно Maven не обязателен).

Настройка базы и секретов

  1. Создайте БД, например:

    CREATE DATABASE library;
  2. Скопируйте пример локальных свойств:

    cp src/main/resources/application-local.yml.example src/main/resources/application-local.yml

    Задайте пароль в файле или через LIBRARY_DB_PASSWORD. Файл application-local.yml рекомендуется добавить в .gitignore (секреты).

  3. Запуск с профилем local подхватит application-local.yml:

    ./mvnw spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=local"
  4. Схема БД создаётся и обновляется Flyway (src/main/resources/db/migration). JPA в основном профиле: spring.jpa.hibernate.ddl-auto=validate (сущности и миграции должны совпадать).
    Миграция V5__book_genre.sql добавляет колонку genre в таблицу book (значение по умолчанию OTHER для уже существующих записей). Таблица V8__catalog_password_setup_token.sql хранит одноразовые токены для страницы /catalog/setup-password. Миграция V10__person_read_book.sql создаёт связующую таблицу «читатель — отмеченные как прочитанные книги» (отдельно от выдачи book.person_id).
    Если база уже существовала с кастомной схемой без истории Flyway, выполните baseline или используйте чистую БД — см. документацию Flyway.


Почта: письмо при создании читателя

По умолчанию отправка выключена (library.mail.welcome-reader.enabled: false). После /people/new читателю нужна ссылка /catalog/setup-password?token=…: она попадает в письмо (если всё настроено) или отображается библиотекарю на списке /people во всплывающем сообщении, если письмо отправить не удалось (нет SMTP, нет публичного URL и т.д.).

Нужны публичный URL сайта (для корректной ссылки в письме) и SMTP (если письмо должно уходить само). Пример в application-local.yml:

library:
  app:
    public-base-url: "http://localhost:8080"
  catalog-password-setup:
    token-validity-hours: 168
  mail:
    welcome-reader:
      enabled: true
      from: "Библиотека <noreply@example.com>"

spring:
  mail:
    host: smtp.example.com
    port: 587
    username: your-user
    password: your-secret
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true

Письмо ставится в очередь после успешного коммита. В письме: логин (email), номер читательского билета и ссылка на установку пароля. Токен одноразовый, срок жизни задаётся library.catalog-password-setup.token-validity-hours.

Без spring.mail.host или без library.app.public-base-url автоотправка не выполняется — используйте ссылку из баннера на /people.

Страница /register по-прежнему выдаёт одноразовый пароль на экране (отдельный сценарий, без карточки читателя и без письма со ссылкой).


Сброс пароля читателя (/forgot-password)

Публичная форма отправляет POST на /forgot-password. Если для указанного email есть включённая учётка каталога с ролью USER, после коммита транзакции создаётся одноразовый токен (как при приветствии) и при library.mail.password-reset.enabled: true и настроенном spring.mail уходит письмо со ссылкой на /catalog/setup-password?token=…. Если учётки нет или это не читатель — пользователю показывается тот же успешный сценарий (без намёка на перечисление логинов).

Пример фрагмента конфигурации:

library:
  app:
    public-base-url: "http://localhost:8080"
  mail:
    password-reset:
      enabled: true
      from: "Библиотека <noreply@example.com>"

spring:
  mail:
    host: smtp.example.com
    port: 587
    username: your-user
    password: your-secret
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true

Ограничение частоты запросов (library.rate-limit)

Снижает злоупотребления по адресам POST /forgot-password, GET/POST /catalog/setup-password: фиксированное окно одна минута на ключ «IP + тип запроса» (in-memory, один экземпляр приложения). Параметры в application.yml:

  • enabled — глобальное включение (в application-test.yml для тестов задано false).
  • forgot-password-post-per-minute, catalog-setup-get-per-minute, catalog-setup-post-per-minute — пороги.
  • trust-x-forwarded-for — если приложение за reverse proxy и нужно брать первый адрес из X-Forwarded-For как идентификатор клиента.

При превышении лимита выполняется редирект на /rate-limit-exceeded (шаблон error/rate-limit.html). В открытом интернете дополнительно имеет смысл настроить лимиты на стороне proxy или общий store (Redis), если инстансов несколько.


Сборка и тесты

./mvnw clean package    # JAR: target/com.springdatajpa.library.jar
./mvnw test

Через Makefile: make package, make test, make clean (по умолчанию ./mvnw).


Запуск

  • Исполняемый JAR: после ./mvnw clean package запуск:
    java -jar target/com.springdatajpa.library.jar
    Переменные БД (SPRING_DATASOURCE_* или LIBRARY_DB_PASSWORD) задайте в окружении или через профиль (см. ниже).
  • Профиль prod: сужает детализацию health-ответа и уровни логов (см. application-prod.yml):
    --spring.profiles.active=prod
  • Actuator: по HTTP открыты /actuator/health (GET без авторизации — для проб/load balancer) и /actuator/info (только для аутентифицированных пользователей вместе с остальными actuator-путями). При необходимости ограничьте доступ на уровне reverse proxy.
  • Откройте http://localhost:8080/people, http://localhost:8080/books (порт по умолчанию — 8080).

Проект изначально учебный; для публичного сервиса дополнительно настраивают HTTPS, резервное копирование БД, централизованные логи и мониторинг на стороне инфраструктуры.


Возможности и изменения (по сравнению с классическим Spring MVC)

Область Описание
Стек Spring Boot 3.3, jakarta.* , Hibernate 6, Spring Security (CSRF в формах), опционально Spring Mail для приветственного письма читателю.
Шаблон layout Данные для шапки (параметр q, путь запроса, цель поиска) приходят из LayoutModelAdvice (@ControllerAdvice), чтобы не использовать в Thymeleaf 3.1+ отключённые по умолчанию объекты #request. Подключён футер с контактами; разметка контактов — fragments/footer.
О библиотеке GET /about открыт без входа (permitAll в SecurityConfig). Текст и реквизиты в шаблонах about.html и fragments/footer.html (футер на всех страницах с layout.html).
Поиск в шапке У вошедшего пользователя форма на sticky-панели: /books/search или /people/search в зависимости от текущего раздела (/people… vs остальное). Плейсхолдер и подпись для скринридеров переключаются (книги / читатели).
Книги Список с Page<Book> и ссылками «Назад / Вперёд»; сортировка — форма на странице: можно отметить сразу несколько критериев (параметры sort_by_year, sort_by_genre, sort_by_title, sort_by_author; отсутствие в query = false). Фильтр по выдаче: availability_preset = all | free | issued; если он есть в запросе, он важнее сочетания sort_by_availability и availability_issued_first. Для читателя: hide_read=true — не показывать книги, отмеченные им как прочитанные (совмещается с сортировкой и availability_preset). Поиск Containing по названию и автору; на странице результатов — ссылки в карточку и редактирование; карточка книги без лишнего второго запроса (JOIN FETCH владельца). Поле genre (EnumType.STRING); в /books/new и /books/{id}/edit — выпадающий список жанров (модель genres). PATCH /books/{id}/read (форма с read=true/false) — только ROLE_USER: личная отметка «прочитано».
Карточка читателя в каталоге GET /me — профиль, взятые книги, прочитанные (отмеченные самим читателем), смена пароля (/me/password).
Читатели Поиск по подстроке имени, фамилии, отчества, email или номера читательского билета (/people/search, q). Создание на /people/new: Person + учётка каталога, одноразовый токен, приветственное письмо со ссылкой на пароль или ссылка библиотекарю (см. «Почта»). Страница /catalog/setup-password (без входа) — установка пароля по токену. getBooksByPersonId при отсутствии читателя даёт 404, как и остальные операции.
Обновление книги Поля обновляются на управляемой сущности (без «слепого» save копии).
Выдача personId с @Min(1) на параметре + проверка в сервисе. После успешной выдачи редирект на карточку книги /books/{id} (как после возврата).
Ошибки GlobalExceptionHandler: в том числе NoResourceFoundException → 404, ServletRequestBindingException → обобщённое сообщение пользователю, детали в debug-лог.
Лимиты PublicEndpointRateLimitFilter + MinuteBucketRateLimiter для публичных форм пароля (см. «Ограничение частоты запросов»).

Шаблоны: src/main/resources/templates/ (не webapp/WEB-INF/views).


Полезные URL

Путь Описание
/about Описание библиотеки и стека; контакты продублированы в футере на каждой странице (без входа)
/people Список читателей
/people/search Поиск по имени, фамилии, отчеству, email или номеру билета (q=); карточка / правка (роль библиотекаря)
/people/new Новый читатель (без поля пароля у библиотекаря)
/catalog/setup-password Установка пароля каталога по токену из письма (GET с token= или форма POST)
/forgot-password Запрос ссылки на сброс пароля для читателя (роль USER); см. «Сброс пароля»
/register Регистрация библиотекаря (роль LIBRARIAN); только для уже вошедшего библиотекаря; начальный пароль показывается однократно после отправки формы
/login, /logout Вход и выход
/me, /me/password Профиль читателя (ROLE_USER): данные, взятые и прочитанные книги, смена пароля каталога
/rate-limit-exceeded Страница при превышении лимита запросов к публичным формам сброса/установки пароля
/books Список; пример query: ?page=1&books_per_page=10&sort_by_year=true&sort_by_author=true&availability_preset=free&hide_read=true (читатель: скрыть уже прочитанные; несколько сортировок и фильтры совместимы) PATCH /books/{id}/read?read=true — отметить прочитанной; read=false — убрать отметку
/books/search Поиск по названию или автору (q=); опционально hide_read=true (читатель); у каждой позиции — переход в карточку / правка
/books/new Новая книга (название, автор, год, жанр из списка)
GET /actuator/health Проверка живости приложения (без входа)

Структура (выжимка)

src/main/java/com/springdatajpa/library/
├── LibraryApplication.java
├── config/
│   ├── SecurityConfig.java
│   ├── PublicEndpointRateLimitFilter.java
│   ├── RateLimitConfiguration.java
│   ├── RateLimitProperties.java
│   ├── LibraryAccessDeniedHandler.java
│   └── LayoutModelAdvice.java   # модель для шапки (поиск, путь URI)
├── controllers/
├── services/                 # ReaderWelcomeMailService, CatalogPasswordSetupService, …
├── support/                  # TransactionCallbacks, MinuteBucketRateLimiter
├── repositories/
├── models/
└── exception/

src/main/resources/
├── application.yml
├── application-prod.yml      # профиль prod: логи, health без деталей
├── application-test.yml      # H2, без Flyway — для @DataJpaTest
├── db/migration/             # Flyway
└── templates/                # Thymeleaf: `fragments/layout.html`, `fragments/footer.html`, `about.html`, …

src/test/java/                # Mockito + @DataJpaTest

Лицензия

Учебный проект; при публикации добавьте лицензию при необходимости.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors