diff --git a/FRONTEND_API_GUIDE.md b/FRONTEND_API_GUIDE.md index d8e0492..213da68 100644 --- a/FRONTEND_API_GUIDE.md +++ b/FRONTEND_API_GUIDE.md @@ -1,52 +1,53 @@ # Руководство по API для фронтенда -## Обзор +## Обзор изменений -После рефакторинга ParserController теперь поддерживает эффективную работу с фронтендом через строго типизированные DTO и расширенные возможности фильтрации и пагинации. +После рефакторинга старый `ParserController` был разделен на три специализированных контроллера для лучшей организации и масштабируемости: -## Основные изменения +- **`MarketItemController`** (`/api/items`) - для получения данных +- **`ParserAdminController`** (`/api/admin/parsers`) - для управления парсерами +- **`HealthCheckController`** (`/api/health`) - для мониторинга системы -### 1. Строгая типизация ответов +--- -Все ответы API теперь используют DTO классы вместо `Map`: +## 📊 MarketItemController - Получение данных -- `ApiResponse` - универсальный wrapper для всех ответов -- `ParserResultDto` - результат парсинга -- `ParserStatsDto` - статистика системы -- `HealthCheckDto` - состояние сервиса +**Базовый URL**: `/api/items` -### 2. Пагинация и сортировка - -Поддержка Spring Data пагинации с параметрами: - -- `page` - номер страницы (начиная с 0) -- `size` - количество элементов на странице -- `sort` - поле для сортировки и направление - -### 3. Фильтрация данных - -Возможность фильтрации по: - -- `sourceName` - источнику новостей -- `startDate` - начальной дате -- `endDate` - конечной дате - -## API Endpoints - -### 1. Получение записей с фильтрацией и пагинацией +### 1. Получение списка новостей с фильтрацией и пагинацией ```http -GET /api/parser/items?page=0&size=10&sort=publishedAt,desc&sourceName=Kursiv&startDate=2025-01-01&endDate=2025-01-31 +GET /api/items ``` -**Параметры:** +**Параметры запроса:** -- `page` (optional, default: 0) - номер страницы -- `size` (optional, default: 20) - размер страницы -- `sort` (optional, default: "publishedAt,desc") - сортировка -- `sourceName` (optional) - фильтр по источнику -- `startDate` (optional) - начальная дата (ISO format) -- `endDate` (optional) - конечная дата (ISO format) +- `sourceName` (optional) - фильтр по источнику (например: "Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости") +- `startDate` (optional) - начальная дата в формате ISO (например: "2024-01-01T00:00:00") +- `endDate` (optional) - конечная дата в формате ISO (например: "2024-12-31T23:59:59") +- `page` (optional) - номер страницы (по умолчанию: 0) +- `size` (optional) - размер страницы (по умолчанию: 20) +- `sort` (optional) - сортировка (по умолчанию: "publishedAt,desc") + +**Примеры запросов:** + +```javascript +// Получить все новости +fetch('/api/items'); + +// Получить новости с пагинацией +fetch('/api/items?page=0&size=10'); + +// Получить новости от конкретного источника +fetch('/api/items?sourceName=Kursiv Media'); + +// Получить новости за последние 7 дней +const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); +fetch(`/api/items?startDate=${weekAgo}`); + +// Получить новости с сортировкой по дате (старые сначала) +fetch('/api/items?sort=publishedAt,asc'); +``` **Ответ:** @@ -57,14 +58,14 @@ GET /api/parser/items?page=0&size=10&sort=publishedAt,desc&sourceName=Kursiv&sta "data": { "content": [ { - "id": "...", - "sourceName": "Kursiv (Бизнес/экономика)", + "id": "64f1a2b3c4d5e6f7g8h9i0j1", + "sourceName": "Kursiv Media", + "url": "https://kursiv.media/news/example", "title": "Заголовок новости", - "url": "https://kursiv.media/...", - "publishedAt": "2025-01-14T10:30:00", - "addedAt": "2025-01-14T12:00:00", - "rawText": "Текст статьи...", - "hash": "...", + "publishedAt": "2024-01-15T10:30:00", + "addedAt": "2024-01-15T10:35:00", + "rawText": "Текст новости без HTML тегов", + "hash": "abc123def456...", "category": "Бизнес", "analytics": { "summary": null, @@ -77,36 +78,51 @@ GET /api/parser/items?page=0&size=10&sort=publishedAt,desc&sourceName=Kursiv&sta "pageable": { "sort": { "sorted": true, - "unsorted": false, - "empty": false + "unsorted": false }, "pageNumber": 0, - "pageSize": 10, + "pageSize": 20, "offset": 0, "paged": true, "unpaged": false }, - "totalElements": 25, - "totalPages": 3, + "totalElements": 150, + "totalPages": 8, "last": false, "first": true, - "numberOfElements": 10, - "size": 10, + "numberOfElements": 20, + "size": 20, "number": 0, - "sort": { - "sorted": true, - "unsorted": false, - "empty": false - }, "empty": false } } ``` -### 2. Статистика системы +### 2. Получение новости по ID ```http -GET /api/parser/stats +GET /api/items/{id} +``` + +**Пример:** + +```javascript +fetch('/api/items/64f1a2b3c4d5e6f7g8h9i0j1'); +``` + +**Ответ:** + +```json +{ + "success": false, + "message": "Функция поиска по ID пока не реализована" +} +``` + +### 3. Получение статистики + +```http +GET /api/items/stats ``` **Ответ:** @@ -116,22 +132,23 @@ GET /api/parser/stats "success": true, "message": "Статистика получена успешно", "data": { - "totalItems": 150, + "totalItems": 1250, "sourceStatistics": { - "Kursiv (Бизнес/экономика)": 75, - "Kapital.kz (Бизнес)": 75 + "Kursiv Media": 300, + "Kapital.kz": 250, + "LSM.kz": 200, + "РБК": 300, + "Ведомости": 200 }, - "lastUpdate": "2025-01-14T12:00:00" + "lastUpdate": "2024-01-15T10:35:00" } } ``` -### 3. Запуск парсинга - -#### Парсинг всех источников +### 4. Получение списка источников ```http -POST /api/parser/parse/all +GET /api/items/sources ``` **Ответ:** @@ -139,37 +156,200 @@ POST /api/parser/parse/all ```json { "success": true, - "message": "Парсинг всех источников завершен успешно", + "message": "Источники получены успешно", + "data": ["Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости"] +} +``` + +### 5. Получение новостей по конкретному источнику + +```http +GET /api/items/source/{sourceName} +``` + +**Пример:** + +```javascript +fetch('/api/items/source/Kursiv Media?page=0&size=10'); +``` + +**Ответ:** Аналогичен ответу от `/api/items`, но только с новостями от указанного источника. + +--- + +## ⚙️ ParserAdminController - Управление парсерами + +**Базовый URL**: `/api/admin/parsers` + +> ⚠️ **Важно**: Эти эндпоинты предназначены для административных функций и могут потребовать аутентификации в будущем. + +### 1. Запуск конкретного парсера + +```http +POST /api/admin/parsers/parse/{sourceName} +``` + +**Доступные источники:** + +- `kursiv` +- `kapital` +- `lsm` +- `rbc` +- `vedomosti` + +**Пример:** + +```javascript +// Запустить парсер Kursiv +fetch('/api/admin/parsers/parse/kursiv', { method: 'POST' }); + +// Запустить парсер РБК +fetch('/api/admin/parsers/parse/rbc', { method: 'POST' }); +``` + +**Ответ:** + +```json +{ + "success": true, + "message": "Парсинг kursiv завершен", + "data": { + "source": "kursiv", + "processedItems": 15, + "totalItemsInDb": 1265, + "status": "completed" + } +} +``` + +### 2. Запуск всех парсеров + +```http +POST /api/admin/parsers/parse/all +``` + +**Пример:** + +```javascript +fetch('/api/admin/parsers/parse/all', { method: 'POST' }); +``` + +**Ответ:** + +```json +{ + "success": true, + "message": "Парсинг всех источников завершен", "data": [ { - "source": "Kursiv", - "processedItems": 15, - "totalItemsInDb": 90, + "source": "kursiv", + "processedItems": 12, + "totalItemsInDb": 1277, "status": "completed" }, { - "source": "Kapital", - "processedItems": 12, - "totalItemsInDb": 90, + "source": "kapital", + "processedItems": 8, + "totalItemsInDb": 1285, + "status": "completed" + }, + { + "source": "lsm", + "processedItems": 5, + "totalItemsInDb": 1290, + "status": "completed" + }, + { + "source": "rbc", + "processedItems": 20, + "totalItemsInDb": 1310, + "status": "completed" + }, + { + "source": "vedomosti", + "processedItems": 7, + "totalItemsInDb": 1317, "status": "completed" } ] } ``` -#### Парсинг конкретного источника +### 3. Получение списка доступных парсеров ```http -POST /api/parser/parse/kursiv -POST /api/parser/parse/kapital +GET /api/admin/parsers ``` -### 4. Проверка состояния +**Ответ:** -#### Общее состояние +```json +{ + "success": true, + "message": "Список парсеров получен", + "data": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"] +} +``` + +### 4. Получение информации о парсерах ```http -GET /api/parser/health +GET /api/admin/parsers/info +``` + +**Ответ:** + +```json +{ + "success": true, + "message": "Информация о парсерах получена", + "data": { + "totalParsers": 5, + "availableParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], + "totalItems": 1317, + "sourceStatistics": { + "Kursiv Media": 300, + "Kapital.kz": 250, + "LSM.kz": 200, + "РБК": 300, + "Ведомости": 200 + } + } +} +``` + +### 5. Проверка существования парсера + +```http +GET /api/admin/parsers/exists/{sourceName} +``` + +**Пример:** + +```javascript +fetch('/api/admin/parsers/exists/kursiv'); +``` + +**Ответ:** + +```json +{ + "success": true, + "message": "Проверка завершена", + "data": true +} +``` + +--- + +## 🔍 HealthCheckController - Мониторинг системы + +**Базовый URL**: `/api/health` + +### 1. Базовая проверка состояния + +```http +GET /api/health ``` **Ответ:** @@ -181,19 +361,19 @@ GET /api/parser/health "data": { "status": "UP", "service": "RSS Parser System", - "timestamp": 1705123456789, - "scheduler": "Enabled - runs every 30 minutes" + "timestamp": 1705312500000, + "details": "Enabled - runs every 30 minutes" } } ``` -#### Состояние MongoDB +### 2. Проверка подключения к MongoDB ```http -GET /api/parser/health/mongodb +GET /api/health/mongodb ``` -**Ответ при успехе:** +**Ответ (успех):** ```json { @@ -201,13 +381,14 @@ GET /api/parser/health/mongodb "message": "MongoDB подключение успешно", "data": { "status": "UP", - "message": "MongoDB подключение успешно", - "timestamp": 1705123456789 + "service": "MongoDB подключение успешно", + "timestamp": 1705312500000, + "details": "Всего записей: 1317" } } ``` -**Ответ при ошибке:** +**Ответ (ошибка):** ```json { @@ -215,126 +396,260 @@ GET /api/parser/health/mongodb "message": "Ошибка подключения к MongoDB", "data": { "status": "DOWN", - "message": "Ошибка подключения к MongoDB: Command failed with error 13 (Unauthorized)", - "timestamp": 1705123456789, + "service": "Ошибка подключения к MongoDB: Connection refused", + "timestamp": 1705312500000, "suggestion": "Проверьте учетные данные в application.properties" } } ``` -## Примеры использования +### 3. Информация о планировщике -### 1. Получение последних новостей с пагинацией - -```javascript -// Получить первую страницу с 10 записями, отсортированными по дате публикации -fetch('/api/parser/items?page=0&size=10&sort=publishedAt,desc') - .then((response) => response.json()) - .then((data) => { - if (data.success) { - const items = data.data.content; - const totalPages = data.data.totalPages; - // Обработка данных - } - }); +```http +GET /api/health/scheduler ``` -### 2. Фильтрация по источнику - -```javascript -// Получить только новости от Kursiv -fetch('/api/parser/items?sourceName=Kursiv (Бизнес/экономика)&page=0&size=20') - .then((response) => response.json()) - .then((data) => { - if (data.success) { - const kursivNews = data.data.content; - // Обработка данных - } - }); -``` - -### 3. Фильтрация по датам - -```javascript -// Получить новости за последнюю неделю -const endDate = new Date().toISOString(); -const startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); - -fetch(`/api/parser/items?startDate=${startDate}&endDate=${endDate}`) - .then((response) => response.json()) - .then((data) => { - if (data.success) { - const recentNews = data.data.content; - // Обработка данных - } - }); -``` - -### 4. Комплексная фильтрация - -```javascript -// Получить новости Kapital за последний месяц, отсортированные по дате -const endDate = new Date().toISOString(); -const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); - -fetch( - `/api/parser/items?sourceName=Kapital.kz (Бизнес)&startDate=${startDate}&endDate=${endDate}&sort=publishedAt,desc&page=0&size=50` -) - .then((response) => response.json()) - .then((data) => { - if (data.success) { - const kapitalNews = data.data.content; - const totalCount = data.data.totalElements; - // Обработка данных - } - }); -``` - -### 5. Получение статистики - -```javascript -fetch('/api/parser/stats') - .then((response) => response.json()) - .then((data) => { - if (data.success) { - const stats = data.data; - console.log(`Всего новостей: ${stats.totalItems}`); - console.log('По источникам:', stats.sourceStatistics); - } - }); -``` - -## Поддерживаемые форматы дат - -API поддерживает следующие форматы дат: - -- `yyyy-MM-dd` (например: 2025-01-14) -- `yyyy-MM-dd'T'HH:mm:ss` (например: 2025-01-14T10:30:00) -- `yyyy-MM-dd'T'HH:mm:ss.SSS` (например: 2025-01-14T10:30:00.000) -- `yyyy-MM-dd HH:mm:ss` (например: 2025-01-14 10:30:00) - -## Обработка ошибок - -Все ответы API следуют единому формату: +**Ответ:** ```json { - "success": false, - "message": "Описание ошибки", - "error": "Детальная информация об ошибке" + "success": true, + "message": "Информация о планировщике получена", + "data": { + "schedulerEnabled": true, + "cronExpression": "0 0/30 * * * ?", + "description": "Запуск каждые 30 минут (в 0 и 30 минут каждого часа)", + "nextRun": "Следующий запуск будет в ближайшие 0 или 30 минут часа", + "activeParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], + "totalParsers": 5, + "sources": ["Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости"] + } } ``` -HTTP статус коды: +### 4. Общая информация о системе -- `200` - Успешный запрос -- `500` - Внутренняя ошибка сервера -- `503` - Сервис недоступен (например, MongoDB) +```http +GET /api/health/system +``` -## Рекомендации для фронтенда +**Ответ:** -1. **Кэширование**: Используйте кэширование для статистики и часто запрашиваемых данных -2. **Пагинация**: Реализуйте бесконечную прокрутку или традиционную пагинацию -3. **Фильтры**: Предоставьте пользователю удобные фильтры по источнику и датам -4. **Обновление**: Используйте WebSocket или polling для обновления данных в реальном времени -5. **Обработка ошибок**: Всегда проверяйте поле `success` в ответе +```json +{ + "success": true, + "message": "Информация о системе получена", + "data": { + "parsers": { + "total": 5, + "available": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"] + }, + "data": { + "totalItems": 1317, + "sourceStatistics": { + "Kursiv Media": 300, + "Kapital.kz": 250, + "LSM.kz": 200, + "РБК": 300, + "Ведомости": 200 + } + }, + "system": { + "javaVersion": "17.0.2", + "osName": "Linux", + "osVersion": "5.4.0-74-generic", + "uptime": 1705312500000 + } + } +} +``` + +### 5. Проверка состояния парсеров + +```http +GET /api/health/parsers +``` + +**Ответ:** + +```json +{ + "success": true, + "message": "Проверка парсеров завершена", + "data": { + "totalParsers": 5, + "availableParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], + "status": "UP", + "message": "Все парсеры доступны" + } +} +``` + +--- + +## 🚀 Примеры использования для фронтенда + +### React/JavaScript примеры + +```javascript +// Сервис для работы с API +class NewsApiService { + constructor(baseUrl = '') { + this.baseUrl = baseUrl; + } + + // Получить новости с фильтрацией + async getNews(filters = {}) { + const params = new URLSearchParams(); + + if (filters.sourceName) params.append('sourceName', filters.sourceName); + if (filters.startDate) params.append('startDate', filters.startDate); + if (filters.endDate) params.append('endDate', filters.endDate); + if (filters.page !== undefined) params.append('page', filters.page); + if (filters.size) params.append('size', filters.size); + if (filters.sort) params.append('sort', filters.sort); + + const response = await fetch(`${this.baseUrl}/api/items?${params}`); + return response.json(); + } + + // Получить статистику + async getStats() { + const response = await fetch(`${this.baseUrl}/api/items/stats`); + return response.json(); + } + + // Получить источники + async getSources() { + const response = await fetch(`${this.baseUrl}/api/items/sources`); + return response.json(); + } + + // Запустить парсер (админ функция) + async runParser(sourceName) { + const response = await fetch( + `${this.baseUrl}/api/admin/parsers/parse/${sourceName}`, + { + method: 'POST', + } + ); + return response.json(); + } + + // Запустить все парсеры (админ функция) + async runAllParsers() { + const response = await fetch( + `${this.baseUrl}/api/admin/parsers/parse/all`, + { + method: 'POST', + } + ); + return response.json(); + } + + // Проверить состояние системы + async getHealthStatus() { + const response = await fetch(`${this.baseUrl}/api/health`); + return response.json(); + } +} + +// Использование +const apiService = new NewsApiService(); + +// Получить последние новости +const news = await apiService.getNews({ + page: 0, + size: 20, + sort: 'publishedAt,desc', +}); + +// Получить новости от конкретного источника +const kursivNews = await apiService.getNews({ + sourceName: 'Kursiv Media', + page: 0, + size: 10, +}); + +// Получить новости за последнюю неделю +const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); +const recentNews = await apiService.getNews({ + startDate: weekAgo, + sort: 'publishedAt,desc', +}); +``` + +### Vue.js примеры + +```javascript +// composable для работы с API +export function useNewsApi() { + const baseUrl = ''; + + const getNews = async (filters = {}) => { + const params = new URLSearchParams(); + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + params.append(key, value); + } + }); + + const response = await fetch(`${baseUrl}/api/items?${params}`); + return response.json(); + }; + + const getStats = async () => { + const response = await fetch(`${baseUrl}/api/items/stats`); + return response.json(); + }; + + const getSources = async () => { + const response = await fetch(`${baseUrl}/api/items/sources`); + return response.json(); + }; + + return { + getNews, + getStats, + getSources, + }; +} +``` + +--- + +## 📝 Важные замечания + +1. **Пагинация**: Все эндпоинты для получения данных поддерживают пагинацию через параметры `page` и `size`. + +2. **Сортировка**: По умолчанию новости сортируются по дате публикации (новые сначала). Можно изменить через параметр `sort`. + +3. **Фильтрация**: Поддерживается фильтрация по источнику и диапазону дат. + +4. **Формат дат**: Используйте ISO 8601 формат для дат (например: "2024-01-15T10:30:00"). + +5. **Обработка ошибок**: Все ответы содержат поле `success` для проверки успешности операции. + +6. **Административные функции**: Эндпоинты `/api/admin/parsers/*` предназначены для административных функций. + +7. **Мониторинг**: Используйте эндпоинты `/api/health/*` для проверки состояния системы. + +--- + +## 🔄 Миграция с старого API + +Если у вас был код, использующий старый `ParserController`, вот соответствие эндпоинтов: + +| Старый эндпоинт | Новый эндпоинт | +| ---------------------------------- | ----------------------------------------- | +| `POST /api/parser/parse/kursiv` | `POST /api/admin/parsers/parse/kursiv` | +| `POST /api/parser/parse/kapital` | `POST /api/admin/parsers/parse/kapital` | +| `POST /api/parser/parse/lsm` | `POST /api/admin/parsers/parse/lsm` | +| `POST /api/parser/parse/rbc` | `POST /api/admin/parsers/parse/rbc` | +| `POST /api/parser/parse/vedomosti` | `POST /api/admin/parsers/parse/vedomosti` | +| `POST /api/parser/parse/all` | `POST /api/admin/parsers/parse/all` | +| `GET /api/parser/items` | `GET /api/items` | +| `GET /api/parser/stats` | `GET /api/items/stats` | +| `GET /api/parser/health` | `GET /api/health` | +| `GET /api/parser/health/mongodb` | `GET /api/health/mongodb` | +| `GET /api/parser/scheduler/info` | `GET /api/health/scheduler` | diff --git a/src/main/java/kz/konturai/parser/controller/HealthCheckController.java b/src/main/java/kz/konturai/parser/controller/HealthCheckController.java index 2831da5..9940817 100644 --- a/src/main/java/kz/konturai/parser/controller/HealthCheckController.java +++ b/src/main/java/kz/konturai/parser/controller/HealthCheckController.java @@ -17,7 +17,7 @@ import java.util.List; * Содержит все health check и информационные эндпоинты. */ @RestController -@RequestMapping("/api/health") +@RequestMapping("/api/parser/health") public class HealthCheckController { @Autowired diff --git a/src/main/java/kz/konturai/parser/controller/MarketItemController.java b/src/main/java/kz/konturai/parser/controller/MarketItemController.java index a0fdbb8..051baa2 100644 --- a/src/main/java/kz/konturai/parser/controller/MarketItemController.java +++ b/src/main/java/kz/konturai/parser/controller/MarketItemController.java @@ -19,7 +19,7 @@ import java.time.LocalDateTime; * Содержит эндпоинты, которые нужны фронтенду для отображения данных. */ @RestController -@RequestMapping("/api/items") +@RequestMapping("/api/parser/items") public class MarketItemController { @Autowired diff --git a/src/main/java/kz/konturai/parser/controller/ParserAdminController.java b/src/main/java/kz/konturai/parser/controller/ParserAdminController.java index 2672792..f7d2853 100644 --- a/src/main/java/kz/konturai/parser/controller/ParserAdminController.java +++ b/src/main/java/kz/konturai/parser/controller/ParserAdminController.java @@ -17,7 +17,7 @@ import java.util.concurrent.CompletableFuture; * Содержит эндпоинты для управления парсерами. */ @RestController -@RequestMapping("/api/admin/parsers") +@RequestMapping("/api/parser/admin/parsers") public class ParserAdminController { @Autowired diff --git a/Техническое задание для AI-агента: Рефакторинг ParserController.md b/Техническое задание для AI-агента: Рефакторинг ParserController.md index 9f9f0bb..8c6db8c 100644 --- a/Техническое задание для AI-агента: Рефакторинг ParserController.md +++ b/Техническое задание для AI-агента: Рефакторинг ParserController.md @@ -131,7 +131,7 @@ _Не забудьте добавить `@EnableAsync` в главный кла ```java @RestController -@RequestMapping("/api/items") +@RequestMapping("/api/parser/items") public class MarketItemController { @Autowired private MarketItemService marketItemService; @@ -151,7 +151,7 @@ public class MarketItemController { ```java @RestController -@RequestMapping("/api/admin/parsers") +@RequestMapping("/api/parser/admin/parsers") public class ParserAdminController { @Autowired private ParserManagerService parserManagerService; @Autowired private MarketItemService marketItemService; // для подсчета totalItems @@ -187,7 +187,7 @@ public class ParserAdminController { ```java @RestController -@RequestMapping("/api/health") +@RequestMapping("/api/parser/health") public class HealthCheckController { // ... методы healthCheck(), checkMongoConnection(), getSchedulerInfo() ... // Метод getSchedulerInfo можно улучшить, получая список парсеров из ParserManagerService