Notes API (Java, Spring Boot)
Функциональность
- CRUD заметок + теги (Many-to-Many)
- Поиск по тегам и строке; Full‑Text Search (PostgreSQL tsvector + GIN)
- Архивирование заметок
- Рендер Markdown → HTML (flexmark) с кэшированием (Caffeine)
- Экспорт заметок: MD/HTML/PDF (openhtmltopdf)
- Пакетный экспорт в ZIP (синхронно/фоновая задача)
- Безопасность: Spring Security + JWT; владелец (owner) у заметки
- Rate limiting: Bucket4j (100 req/min по токену/IP)
- Метрики и здоровье: Spring Boot Actuator + Prometheus
- Flyway миграции, Swagger UI
Стек
- Java 21, Spring Boot 3.3
- PostgreSQL 16, Flyway
- JPA/Hibernate, Validation
- OpenAPI/Swagger (springdoc)
- Caffeine Cache, Bucket4j, Micrometer + Prometheus
- JWT (jjwt)
Быстрый старт
- Запустить PostgreSQL локально (Docker): docker compose up -d
- Собрать и запустить приложение: mvn spring-boot:run
- Swagger UI: http://localhost:8080/swagger-ui.html
- Метрики Prometheus: http://localhost:8080/actuator/prometheus
Конфигурация (по умолчанию)
- Файл: src/main/resources/application.yml
- БД: jdbc:postgresql://localhost:5432/notes (user/pass: notes/notes)
- JWT: секрет base64 в
app.jwt.secret(замените на свой) - Кэш: Caffeine, expireAfterWrite 30m
- Rate limit: 100 req/min
Аутентификация (JWT)
- Регистрация: POST /api/auth/register { email, password }
- Логин: POST /api/auth/login { email, password }
- В ответе: { token }
- Использование: передавать заголовок Authorization: Bearer
Модель данных
- Note{id, title, content(md), archived, createdAt, updatedAt, tags[], owner}
- Tag{id, name}
- User{id, email, passwordHash, role}
REST API
- POST /api/notes — создать заметку
- GET /api/notes — список с фильтрами и пагинацией
- Параметры:
q— строка поиска (по title/content)tags— список тегов через запятую (any-of)archived— включать архивные (false по умолчанию)page,size,sort(например,createdAt,desc)fts— true, чтобы использовать полнотекстовый поиск (tsvector)
- Параметры:
- GET /api/notes/{id} — получить заметку (только свою)
- PUT /api/notes/{id} — обновить (только свою)
- PATCH /api/notes/{id}/archive — архивировать/разархивировать
- DELETE /api/notes/{id} — удалить
- GET /api/notes/{id}/render — получить HTML из Markdown (кэшируется)
- GET /api/notes/{id}/export?fmt=md|html|pdf — экспорт заметки
- POST /api/notes/export-batch — экспорт пачки заметок в ZIP
- Тело: { "ids": [1,2,3], "fmt": "pdf|html|md", "background": true|false }
- Если background=true → 202 Accepted { jobId }
- GET /api/notes/export-batch/{jobId} — статус/скачивание ZIP (DONE → файл)
Полнотекстовый поиск (FTS)
- Миграция V2 добавляет tsvector-колонку
note.search+ GIN-индекс - Включение: передайте
fts=trueиq=...в GET /api/notes - Теги и архивность учитываются при FTS-поиске
- По умолчанию используется простой LIKE-поиск по title/content
Кэширование Markdown → HTML
- Кэш
noteHtml(Caffeine) поnoteId - Инвалидация при изменении/архивировании/удалении заметки
Rate limiting
- 100 запросов/мин по токену (Bearer) или по IP (аноним)
- При превышении → 429 Too Many Requests
Метрики и наблюдаемость
- Actuator: /actuator/health, /actuator/info, /actuator/prometheus
- Пользовательские метрики:
notes.created— создание заметокnotes.render.html— рендеры HTMLnotes.export.html,notes.export.pdf,notes.export.batch{fmt}— экспорт
Примеры cURL
-
Регистрация и логин: curl -X POST http://localhost:8080/api/auth/register
-H "Content-Type: application/json"
-d '{"email":"me@example.com","password":"secret"}'TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login
-H "Content-Type: application/json"
-d '{"email":"me@example.com","password":"secret"}' | jq -r .token) -
Создание заметки: curl -X POST http://localhost:8080/api/notes
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"title":"Daily log","content":"# Day 1\n- task1","tags":["work","log"]}' -
Поиск с тегами и FTS: curl "http://localhost:8080/api/notes?tags=work,log&q=task&fts=true&size=10&sort=createdAt,desc"
-H "Authorization: Bearer $TOKEN" -
Архивирование: curl -X PATCH http://localhost:8080/api/notes/1/archive
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json" -d '{"archived":true}' -
Экспорт PDF: curl -L "http://localhost:8080/api/notes/1/export?fmt=pdf"
-H "Authorization: Bearer $TOKEN" -o note1.pdf -
Пакетный экспорт (фон): JOB=$(curl -s -X POST http://localhost:8080/api/notes/export-batch
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"ids":[1,2,3],"fmt":"pdf","background":true}' | jq -r .jobId)curl -L http://localhost:8080/api/notes/export-batch/$JOB
-H "Authorization: Bearer $TOKEN" -o notes.zip
Разработка и тестирование
- Интеграционные тесты: Testcontainers (PostgreSQL)
- Запуск тестов:
mvn test - Локально без токена можно использовать сервисный слой напрямую; для REST — требуется JWT
Примечания по безопасности
- Заметки привязываются к пользователю (owner); доступ только к своим
- Откройте/закройте нужные эндпоинты в SecurityConfig при необходимости
- Обязательно замените
app.jwt.secretна собственный 256‑битный base64 ключ
План улучшений
- Хранение результатов фоновых заданий вне памяти (S3/FS/БД)
- Роли/права, refresh‑токены, logout
- Полн. поиск с ранжированием и подсветкой
- Кэширование PDF и экспортов