This commit is contained in:
root
2025-09-14 18:10:45 +05:00
parent 8d8303991e
commit a0dc055868
5 changed files with 513 additions and 198 deletions
+507 -192
View File
@@ -1,52 +1,53 @@
# Руководство по API для фронтенда
## Обзор
## Обзор изменений
После рефакторинга ParserController теперь поддерживает эффективную работу с фронтендом через строго типизированные DTO и расширенные возможности фильтрации и пагинации.
После рефакторинга старый `ParserController` был разделен на три специализированных контроллера для лучшей организации и масштабируемости:
## Основные изменения
- **`MarketItemController`** (`/api/items`) - для получения данных
- **`ParserAdminController`** (`/api/admin/parsers`) - для управления парсерами
- **`HealthCheckController`** (`/api/health`) - для мониторинга системы
### 1. Строгая типизация ответов
---
Все ответы API теперь используют DTO классы вместо `Map<String, Object>`:
## 📊 MarketItemController - Получение данных
- `ApiResponse<T>` - универсальный 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` |
@@ -17,7 +17,7 @@ import java.util.List;
* Содержит все health check и информационные эндпоинты.
*/
@RestController
@RequestMapping("/api/health")
@RequestMapping("/api/parser/health")
public class HealthCheckController {
@Autowired
@@ -19,7 +19,7 @@ import java.time.LocalDateTime;
* Содержит эндпоинты, которые нужны фронтенду для отображения данных.
*/
@RestController
@RequestMapping("/api/items")
@RequestMapping("/api/parser/items")
public class MarketItemController {
@Autowired
@@ -17,7 +17,7 @@ import java.util.concurrent.CompletableFuture;
* Содержит эндпоинты для управления парсерами.
*/
@RestController
@RequestMapping("/api/admin/parsers")
@RequestMapping("/api/parser/admin/parsers")
public class ParserAdminController {
@Autowired
@@ -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