Сервис агрегации и раздачи метаданных книг (ISBN, автор, обложка, описание и т.д.) с гибким выбором полей через API, автоматическим обогащением из открытых источников и очередью для книг, не найденных ни в одном источнике.
Идея: в русскоязычном сегменте нет удобного открытого аналога Google Books API — этот сервис закрывает эту нишу, агрегируя данные из нескольких источников и отдавая их через единый динамический эндпоинт, где потребитель сам выбирает нужные поля.
hypersistence-utilsВместо жёсткой реляционной схемы под каждый тип контента используется гибкая метамодель:
mt_objects — типы контента (Book, Author, Magazine)mt_fields — динамический справочник полей для каждого типа, с привязкой к конкретному flex-слоту (field_num)mt_data — хранилище записей с 50 универсальными колонками value0..value49; какое поле лежит в каком слоте — определяется через mt_fieldsmt_indexes — типизированный индекс (string/num/date) для полей с is_indexed=true, чтобы поиск не сканировал текстовые слотыmt_field_history — история изменений значений при повторном обогащенииsources_meta (JSONB-колонка в mt_data) — provenance: из какого источника взято каждое значение, когда и с какой уверенностью (confidence)unresolved_lookup — очередь ISBN/названий, не найденных ни в одном источникеmt_attachment — задел под будущую загрузку PDF/DjVu для OCR-конвейераapi_keys — минимальная таблица API-ключей для доступа к публичному APIПреимущество такой модели: добавить новое поле для типа контента — это INSERT в mt_fields, а не ALTER TABLE с миграцией и простоем.
./gradlew bootRun
Приложение поднимется на http://localhost:8080, Flyway применит миграции V1__create_core_metadata.sql … V4__support_tables.sql автоматически.
http://localhost:8080/swagger-ui.htmlhttp://localhost:8080/api-docsТестовый API-ключ уже засеян миграцией: dev-local-key-change-me (см. таблицу api_keys).
Google Books API требует собственный ключ (анонимная квота исчерпана Google глобально) — получить бесплатно на console.cloud.google.com: создать проект → включить Books API → Public data → создать API key.
# application.yml
google:
books:
api-key: ВАШ_КЛЮЧ
Единый динамический эндпоинт — потребитель сам выбирает поля через ?fields=, вместо получения "простыни" всех данных сразу.
# Список книг с выбором конкретных полей
curl "http://localhost:8080/api/v1/books?apiKey=dev-local-key-change-me&fields=title,isbn,author_name"
# Поиск по индексированному полю
curl "http://localhost:8080/api/v1/books/search?apiKey=dev-local-key-change-me&field=ISBN&value=9785041558389"
# Получить книгу по GUID
curl "http://localhost:8080/api/v1/books/{guid}?apiKey=dev-local-key-change-me&fields=title,description"
# Создать книгу вручную
curl -X POST "http://localhost:8080/api/v1/books?apiKey=dev-local-key-change-me" \
-H "Content-Type: application/json" \
-d '{"values": {"Title": "Мастер и Маргарита", "ISBN": "9785041558389"}, "source": "manual"}'
Список доступных полей объекта — через Metadata API (источник для будущего UI-конструктора):
curl "http://localhost:8080/api/metadata/objects/Book/fields?orgId=1"
Цепочка источников, каждый бесплатный и не требующий партнёрской регистрации (кроме Google Books, которому нужен свободный API-ключ):
| Порядок | Источник | Ключ нужен | Покрытие |
|---|---|---|---|
| 1 | Open Library | Нет | Международное, слабое для чисто русских изданий |
| 2 | Google Books | Да (бесплатный) | Хорошее, включая книги через ЛитРес |
| 3 | FantLab | Нет | Отличное для фантастики/фэнтези на русском |
| 4 | LibGen | Нет | Широкое, но ненадёжное; выключено по умолчанию |
curl -X POST "http://localhost:8080/api/v1/enrichment/public/by-isbn?apiKey=dev-local-key-change-me&isbn=9785041558389"
Логика: идём по источникам по порядку, останавливаемся на первом найденном. Если ничего не найдено — ISBN сохраняется в unresolved_lookup со списком attempted_sources, чтобы можно было разобрать вручную позже.
Технически открытый и мощный источник (структурированный поиск по ISBN), но представляет собой теневую библиотеку — юридически серая зона даже для одних метаданных. Выключен по умолчанию (libgen.enabled: false), включается осознанно:
libgen: enabled: true
Также может быть недоступен без VPN в зависимости от региона.
Изначально рассматривался как основной источник (catalitv2 API), но:
app_id/secret_key) — не мгновенно.sha-подпись обязательна всегда.Решено не тратить силы на партнёрскую регистрацию, пока хватает бесплатных источников. Код может быть переиспользован при пересмотре решения.
Каждое значение в mt_data сопровождается записью в sources_meta (JSONB):
{"2": {"source": "googlebooks", "updated_at": "2026-08-18T00:32:00Z", "confidence": 0.7}}
Ключ — номер flex-слота (field_num). При повторном обогащении старое значение поля сохраняется в mt_field_history перед перезаписью.
mt_attachment уже заложена под эту задачу.AuthorName пишется как текст, AuthorId (lookup на объект Author) не заполняется автоматически.unresolved_lookup и повторных попыток обогащения — не реализован.