Обзор
REST JSON API для мобильного приложения IPR SMART. Совместим по маршрутам и формату ответов с legacy old-mobible-api, работает с базой данных ipr_smart.
| Параметр | Значение |
|---|---|
| Базовый URL (dev) | http://localhost:8000 |
| Базовый URL (prod) | https://mobile2.iprbookshop.ru |
| Префикс API | Нет — маршруты в корне (/users/auth, не /api/...) |
| Канонические пути v3 | С префиксом /v3/ |
| Документация | GET /docs |
| Health check | GET /up |
VERSION-API: 3 запросы к legacy-путям (например /books/catalog) автоматически перенаправляются (302) на /v3/books/catalog.
Установка и запуск
composer install
cp .env.example .env # Windows: copy .env.example .env
php artisan key:generate
Настройте .env (см. раздел ниже), затем:
php artisan serve
# → http://localhost:8000
Для production: document root указывает на каталог public/. CI деплоит на mobile2.iprbookshop.ru (master) и mobile.dev.iprbookshop.ru (develop).
Переменные окружения
Для MariaDB/MySQL 5.7 в OSPanel укажите DB_COLLATION=utf8mb4_unicode_ci (MySQL 8 collation utf8mb4_0900_ai_ci не поддерживается).
| Переменная | Обязательна | Описание |
|---|---|---|
DB_* | Да | Подключение к MySQL/MariaDB, база ipr_smart |
DB_COLLATION | Рекомендуется | utf8mb4_unicode_ci для MariaDB 10.x |
SEARCH_API_URL | Да | URL внешнего поискового сервиса (каталоги книг/журналов) |
MOBILE_AUTH_SALT | Да | Соль для токенов и legacy-паролей |
PAGE_SIZE | Нет | Размер страницы пагинации (по умолчанию 50) |
CONTENT_PATH | Да* | Путь к файлам публикаций (PDF/EPUB) |
OAUTH_TOKEN | Для OAuth | Секрет для GET /oauth |
APP_SOURCE_URL | Нет | Базовый URL ссылок на публикации |
APP_DEBUG | Нет | При true отсутствие VERSION-API трактуется как версия 3 |
Заголовки и версии API
Почти все запросы обязаны содержать заголовок:
VERSION-API: 3
Исключение: GET /oauth — заголовок не требуется (как в old-mobible-api).
| Версия | Формат ответа | Авторизация токеном |
|---|---|---|
1 | Legacy (success: "true") | Только uid |
2 | Legacy | uid + deviceId + accessToken + date |
3 | v3 (meta + data) | Как версия 2 + редирект на /v3/ |
VERSION-API (при APP_DEBUG=false) возвращает HTTP 404.
Авторизация
Публичные маршруты (с VERSION-API)
Параметры uid / deviceId / accessToken не нужны:
POST /users/authGET /users/statusGET /users/checkGET /users/restorePOST /users/create
Без VERSION-API
GET /oauth
Защищённые маршруты
Middleware ApiAccess проверяет следующие параметры (query string или body):
| Параметр | Обязателен | Описание |
|---|---|---|
uid | Да | Числовой ID пользователя |
deviceId | Да (v2+) | Идентификатор устройства (тот же, что при авторизации) |
accessToken | Да (v2+) | Ежедневный токен (см. формулу ниже) |
date | Нет | Дата в формате Ymd (по умолчанию — сегодня) |
Формула токена доступа
Поток авторизации
Пароли при входе
- Принимается только формат приложения:
md5(md5(plain) + MOBILE_AUTH_SALT) - Legacy MD5 в БД → сравнивается с
md5(хранимый_md5 + соль) - Bcrypt в БД → хеш приложения сверяется с сохранённым верификатором (регистрация, восстановление пароля)
Формат ответов
v3 (VERSION-API: 3)
{
"meta": { "success": true, "message": "" },
"data": { ... }
}
Ошибка:
{
"meta": { "success": false, "message": "Текст ошибки" },
"data": null
}
Legacy (VERSION-API: 1–2)
{ "success": "true", "message": "", ... }
{ "success": "false", "message": "Текст ошибки" }
Авторизация (POST /users/auth) при legacy оборачивает данные в ключ user:
{
"success": "true",
"message": "",
"user": {
"username": "...", "uid": 123, "accessToken": "...",
"cid": 45, "fullname": "...", "org_name": "..."
}
}
Исключения из общих правил
| Эндпоинт | Формат |
|---|---|
GET /users/status | Всегда legacy, даже при VERSION-API: 3 |
GET /oauth | { success: bool, message, data? } — boolean, не строка |
POST /favorite/uploads с listId | Всегда legacy { success: "true", message } |
| Цитаты, закладки, рейтинг, контент | Собственный { meta, data } без повторной обёртки ApiController |
GET /v3/autocomplete | Всегда v3-обёртка |
Пагинация
{
"meta": { "success": true, "message": "" },
"data": {
"total": 100,
"per_page": 50,
"current_page": 1,
"last_page": 2,
"next_page_url": "...",
"prev_page_url": null,
"from": 1,
"to": 50,
"data": [ ... ]
}
}
Исключение: GET /users/status всегда возвращает legacy-формат независимо от версии API (см. таблицу выше).
Совместимость с old-mobible-api
API реализован как замена legacy old-mobible-api с сохранением маршрутов, имён полей (pagetitle, tv_*) и форматов ответов.
Сводка по группам маршрутов
| Группа | Статус | Примечание |
|---|---|---|
| Users + OAuth | Совпадает | OAuth без VERSION-API; legacy auth → ключ user |
| v3 Books / Journals / Numbers | Совпадает | Legacy-пути редиректятся на /v3/ |
| v3 Search | Частично | Тот же маршрут и параметры; backend — Search API вместо Sphinx |
| Favorites | Совпадает | listId + uploads[]; type 1/2 для номеров |
| Quotes / Bookmarks / Rating | Совпадает | Цитаты отдают ext_id; закладки без хранения text |
GET /download | Совпадает | XOR-шифрованный PDF, ключ — первые 16 символов device token |
GET /search/prepare | Не реализован | Клиент не вызывает |
GET /ugnp/s | Не реализован | Клиент читает локальный ugnp.json |
POST /send/request | Совпадает | Обращение в поддержку |
Legacy GET /search (v1–2) | Частично | Старый формат { total, search[] } с фильтрами не воспроизведён |
Значения selection_options (каталог книг)
| Значение | Фильтр |
|---|---|
1 | Приоритетные (sort_priority = 1) |
2 | Рекомендованные |
3 | Новинки (за 30 дней) |
4 | Популярные (read_count > 200) |
5 | Часто добавляемые в избранное |
Пользователи
Авторизация пользователя и регистрация устройства.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
username | string | Да | Логин или email |
passwd | string | Да | Пароль (см. раздел авторизации) |
device_id | string | Да | Уникальный ID устройства |
Ответ v3 (data):
{
"meta": { "success": true, "message": "" },
"data": {
"username": "user@example.com",
"uid": 123,
"cid": 45,
"fullname": "Иванов И.И.",
"org_name": "Университет",
"email": "user@example.com",
"blockedAfter": 1735689600,
"blockedHash": "abc...",
"accessToken": "device_token_from_db",
"clientLogotype": "https://...",
"clientColor": "ff9900"
}
}
При VERSION-API: 1–2 те же поля находятся внутри ключа user (см. раздел «Формат ответов»).
Проверка статуса подписки и версии приложения.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
uid | int | Да | ID пользователя |
{
"success": "true",
"message": "",
"version": { "version": 13, "strong": true },
"username": "Иванов И.И.",
"org_name": "Университет",
"clientLogotype": "https://...",
"clientColor": "ff9900"
}
Проверка учётных данных приглашённого пользователя (до регистрации). Для уже зарегистрированных учёток используйте POST /users/auth.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
username | string | Да | Логин/email приглашения |
password / passwd | string | Да | Пароль приглашения |
Восстановление пароля — генерирует новый и отправляет на email.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
email | string | Да | Email зарегистрированного пользователя |
Регистрация нового пользователя по приглашению. Поддерживаются POST и GET (query-параметры, как в legacy-клиентах).
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
username | string | Да | Логин приглашения |
password / passwd | string | Да | Пароль приглашения |
email | string | Да | Email нового пользователя |
fullname | string | Да | ФИО |
usertype | int | Да | Тип пользователя: 1 — студент, 2 — аспирант, 3 — преподаватель |
user_password | string | Да | Новый пароль (мин. 6 символов) |
OAuth-авторизация для внешних систем. Заголовок VERSION-API не требуется.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
username | string | Да | Логин/email |
password | string | Да | Пароль (сравнивается как md5(password) с БД) |
time | string | Да | Временная метка |
hash | string | Да | md5(OAUTH_TOKEN + time) |
Книги
Все маршруты защищённые. Канонический путь: /v3/books/...
Каталог книг организации (через Search API).
| Параметр | Тип | Описание |
|---|---|---|
page | int | Номер страницы (по умолчанию 1) |
ugnp | string | Код УГНП (фильтр, 00 = все) |
title | string | Название |
authors | string | Авторы |
pubhouse | string | Издательство |
year_left | int | Год от |
year_right | int | Год до |
pub_types | string | Типы изданий через ||, напр. учебник||монография |
selection_options | int | Подборка: 1–5 (см. раздел «Совместимость»). Алиас: selected_options |
Элемент data.data[]: id, pagetitle, tv_year, tv_desc, tv_liability, tv_author
Карточка книги с оглавлением, рейтингом и статусом избранного.
| Параметр | Тип | Описание |
|---|---|---|
id | int (path) | ID книги |
Дополнительные поля: isFavorite ("0"/"1"), size, content (оглавление), rating, user_rating
Избранные книги пользователя.
| Параметр | Тип | Описание |
|---|---|---|
page | int | Страница |
last_id | int | ID последней записи (для инкрементальной подгрузки) |
Автодополнение для фильтров каталога.
| Параметр | Тип | Описание |
|---|---|---|
type | int | 13 — книги, 28 — журналы |
field | string | pagetitle, tv_author, tv_pubhouse |
query | string | Строка поиска |
Журналы
| Параметр | Тип | Описание |
|---|---|---|
page | int | Страница |
title | string | Название |
pubhouse | string | Издательство |
ugnp | string | УГНП (по умолчанию 00) |
Элемент data.data[]: id, pagetitle, tv_year, tv_desc, tv_pubhouse, pubhouse, image
Карточка журнала со списком доступных годов.
Поля: id, pagetitle, tv_desc, tv_pubhouse, pubhouse, image, years[] (id, pagetitle)
yearId = periodical_id × 10000 + year. Пример: журнал 100, год 2024 → 100012024
Избранные номера журналов пользователя (как в старом API). Параметры: page, last_id
Номера журналов
Список номеров журнала за год.
Ответ: id, pagetitle, pagetitle_journal, numbers[]
Карточка номера журнала (аналогично книге: isFavorite, content, rating).
Поиск
Полнотекстовый поиск через Search API.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
s | string | Да | Поисковый запрос |
type | int | Нет* | 1 — книги, 2 — номера журналов. Обязателен, если не передан id |
page | int | Нет | Страница результатов каталога (по умолчанию 1) |
id | int | Нет | ID издания: искать слово в оглавлении/страницах этой публикации |
Избранное
Тип публикации: type=1 — книга, type=2 — журнал или номер. Если type не указан, тип определяется по ID.
Добавить в избранное.
| Параметр | Тип | Описание |
|---|---|---|
id | int (path) | ID публикации |
type | int | 1 (книга) или 2 (журнал/номер). Если не передан — определяется по ID |
Удалить из избранного. Параметр type — как выше.
Если издания не было в избранном: meta.message = «Издание не находилсь в избранном».
Пакетная синхронизация избранного. Поддерживаются два формата тела запроса.
Legacy (old-mobible-api): параметр listId — массив или одно значение ID книг. Ответ всегда legacy:
listId[]=123&listId[]=456&uid=123
→ { "success": "true", "message": "Издания успешно добавлены!" }
Расширенный формат:
{
"uploads": [
{ "id": 123, "type": 1, "action": "add" },
{ "id": 456, "type": 2, "action": "delete" }
]
}
type: 1 — книга, 2 — номер журнала. action: add или delete.
Цитаты, закладки, контент, рейтинг
Маршруты на уровне корня (не внутри /books/). {id} — ID публикации (книги).
Список цитат. Элемент: ext_id, page, text, create_date
| Параметр | Описание |
|---|---|
page_id | Номер страницы |
text | Текст цитаты |
Параметр: quote_id
Обновить цитату. Параметр: text
Список закладок (формат аналогичен цитатам).
Добавить закладку.
| Параметр | Описание |
|---|---|
page_id | Номер страницы (обязателен) |
text | Комментарий (в legacy API; в новой схеме не сохраняется) |
Параметр: bookmark_id
Оглавление публикации. Ответ: [{ "page": 1, "text": "Глава 1" }, ...]
Поиск слова в оглавлении/тексте страниц издания.
| Параметр | Описание |
|---|---|
s | Искомое слово или фраза |
page | Опционально: искать только на этой странице |
Ответ: [{ "page": 15, "text": "..." }, ...]
Установить оценку. Параметр: value (1–5).
{
"meta": { "success": true, "message": "" },
"data": { "value": 4.5, "user_value": 5 }
}
Примеры запросов
1. Авторизация
passwd — не открытый пароль, а md5(md5(plain) + MOBILE_AUTH_SALT):
curl -X POST "http://localhost:8000/users/auth" \
-H "VERSION-API: 3" \
-d "username=user@example.com" \
-d "passwd=COMPUTED_ANDROID_HASH" \
-d "device_id=my-device-001"
2. Вычисление токена (PHP)
$salt = 'Ga90IfDBzo2Mqcva'; // MOBILE_AUTH_SALT
$deviceToken = '...'; // accessToken из ответа auth
$deviceId = 'my-device-001';
$date = date('Ymd');
$token = md5($salt . $deviceToken . $deviceId . $date);
3. Каталог книг
curl "http://localhost:8000/v3/books/catalog?page=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN&date=20250714" \
-H "VERSION-API: 3"
4. Карточка книги
curl "http://localhost:8000/v3/books/42?uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
-H "VERSION-API: 3"
5. Поиск
curl "http://localhost:8000/v3/search?s=математика&type=1&page=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
-H "VERSION-API: 3"
6. Добавить в избранное
curl -X PUT "http://localhost:8000/favorite/42?type=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
-H "VERSION-API: 3"
7. Синхронизация избранного (legacy listId)
curl -X POST "http://localhost:8000/favorite/uploads" \
-H "VERSION-API: 3" \
-d "listId[]=42&listId[]=43&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN"
8. OAuth (без VERSION-API)
curl "http://localhost:8000/oauth?username=user&password=secret&time=1234567890&hash=COMPUTED_HASH"
Типичные ошибки
| Ситуация | Ответ |
|---|---|
Нет заголовка VERSION-API (кроме /oauth) | HTTP 404 |
Неверный uid | Нет данных о пользователе |
| Неверный токен | Ошибка авторизации |
| Неверный пароль при auth | указан неверный пароль |
| Пустой поисковый запрос | По данному запросу не найдено изданий. |
| Search API недоступен | Ошибка поискового сервиса: ... |
| Несовместимая collation MySQL | Установите DB_COLLATION=utf8mb4_unicode_ci в .env |
IPR SMART Mobile API · Laravel 11.56.1 · Совместимость с old-mobible-api