32 KiB
Руководство по API для фронтенда
Обзор изменений
После рефакторинга старый ParserController был разделен на три специализированных контроллера для лучшей организации и масштабируемости:
MarketItemController(/api/items) - для получения данныхParserAdminController(/api/admin/parsers) - для управления парсерамиHealthCheckController(/api/health) - для мониторинга системы
📊 MarketItemController - Получение данных
Базовый URL: /api/items
1. Получение списка новостей с фильтрацией и пагинацией
GET /api/items
Параметры запроса:
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")
Примеры запросов:
// Получить все новости
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');
Ответ:
{
"success": true,
"message": "Записи получены успешно",
"data": {
"content": [
{
"id": "64f1a2b3c4d5e6f7g8h9i0j1",
"sourceName": "Kursiv Media",
"url": "https://kursiv.media/news/example",
"title": "Заголовок новости",
"publishedAt": "2024-01-15T10:30:00",
"addedAt": "2024-01-15T10:35:00",
"rawText": "Текст новости без HTML тегов",
"hash": "abc123def456...",
"category": "Бизнес",
"analytics": {
"summary": null,
"sentiment": null,
"tags": [],
"entities": {}
}
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false
},
"pageNumber": 0,
"pageSize": 20,
"offset": 0,
"paged": true,
"unpaged": false
},
"totalElements": 150,
"totalPages": 8,
"last": false,
"first": true,
"numberOfElements": 20,
"size": 20,
"number": 0,
"empty": false
}
}
2. Получение новости по ID
GET /api/items/{id}
Пример:
fetch('/api/items/64f1a2b3c4d5e6f7g8h9i0j1');
Ответ:
{
"success": false,
"message": "Функция поиска по ID пока не реализована"
}
3. Получение статистики
GET /api/items/stats
Ответ:
{
"success": true,
"message": "Статистика получена успешно",
"data": {
"totalItems": 1250,
"sourceStatistics": {
"Kursiv Media": 300,
"Kapital.kz": 250,
"LSM.kz": 200,
"РБК": 300,
"Ведомости": 200
},
"lastUpdate": "2024-01-15T10:35:00"
}
}
4. Получение списка источников (sourceName)
GET /api/items/sources
Описание: Возвращает список всех уникальных источников новостей, которые есть в базе данных. Этот эндпоинт полезен для:
- Построения фильтров по источникам
- Отображения списка доступных источников в UI
- Динамического создания выпадающих списков
Ответ:
{
"success": true,
"message": "Источники получены успешно",
"data": [
"Kursiv (Бизнес/экономика)",
"Kapital.kz (Бизнес)",
"LSM.kz",
"РБК",
"Ведомости"
]
}
Примеры использования:
// Получить список всех источников
const response = await fetch('/api/items/sources');
const result = await response.json();
if (result.success) {
const sources = result.data;
console.log('Доступные источники:', sources);
// sources = ["Kursiv (Бизнес/экономика)", "Kapital.kz (Бизнес)", "LSM.kz", "РБК", "Ведомости"]
}
// Использование в React для создания фильтра
function SourceFilter({ onSourceChange }) {
const [sources, setSources] = useState([]);
useEffect(() => {
fetch('/api/items/sources')
.then((res) => res.json())
.then((data) => {
if (data.success) {
setSources(data.data);
}
});
}, []);
return (
<select onChange={(e) => onSourceChange(e.target.value)}>
<option value=''>Все источники</option>
{sources.map((source) => (
<option key={source} value={source}>
{source}
</option>
))}
</select>
);
}
// Использование в Vue.js
export default {
data() {
return {
sources: [],
selectedSource: '',
};
},
async mounted() {
const response = await fetch('/api/items/sources');
const result = await response.json();
if (result.success) {
this.sources = result.data;
}
},
};
Важные замечания:
- Список источников формируется на основе реальных данных в базе
- Если в базе нет данных от какого-то источника, он не будет включен в список
- Названия источников точно соответствуют тем, что используются в поле
sourceNameпри фильтрации - Порядок источников может изменяться в зависимости от того, как MongoDB возвращает уникальные значения
5. Получение новостей по конкретному источнику
GET /api/items/source/{sourceName}
Пример:
fetch('/api/items/source/Kursiv Media?page=0&size=10');
Ответ: Аналогичен ответу от /api/items, но только с новостями от указанного источника.
⚙️ ParserAdminController - Управление парсерами
Базовый URL: /api/admin/parsers
⚠️ Важно: Эти эндпоинты предназначены для административных функций и могут потребовать аутентификации в будущем.
1. Запуск конкретного парсера
POST /api/admin/parsers/parse/{sourceName}
Доступные источники:
kursivkapitallsmrbcvedomosti
Пример:
// Запустить парсер Kursiv
fetch('/api/admin/parsers/parse/kursiv', { method: 'POST' });
// Запустить парсер РБК
fetch('/api/admin/parsers/parse/rbc', { method: 'POST' });
Ответ:
{
"success": true,
"message": "Парсинг kursiv завершен",
"data": {
"source": "kursiv",
"processedItems": 15,
"totalItemsInDb": 1265,
"status": "completed"
}
}
2. Запуск всех парсеров
POST /api/admin/parsers/parse/all
Пример:
fetch('/api/admin/parsers/parse/all', { method: 'POST' });
Ответ:
{
"success": true,
"message": "Парсинг всех источников завершен",
"data": [
{
"source": "kursiv",
"processedItems": 12,
"totalItemsInDb": 1277,
"status": "completed"
},
{
"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. Получение списка доступных парсеров
GET /api/admin/parsers
Ответ:
{
"success": true,
"message": "Список парсеров получен",
"data": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"]
}
4. Получение информации о парсерах
GET /api/admin/parsers/info
Ответ:
{
"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. Проверка существования парсера
GET /api/admin/parsers/exists/{sourceName}
Пример:
fetch('/api/admin/parsers/exists/kursiv');
Ответ:
{
"success": true,
"message": "Проверка завершена",
"data": true
}
🔍 HealthCheckController - Мониторинг системы
Базовый URL: /api/health
1. Базовая проверка состояния
GET /api/health
Ответ:
{
"success": true,
"message": "Сервис работает",
"data": {
"status": "UP",
"service": "RSS Parser System",
"timestamp": 1705312500000,
"details": "Enabled - runs every 30 minutes"
}
}
2. Проверка подключения к MongoDB
GET /api/health/mongodb
Ответ (успех):
{
"success": true,
"message": "MongoDB подключение успешно",
"data": {
"status": "UP",
"service": "MongoDB подключение успешно",
"timestamp": 1705312500000,
"details": "Всего записей: 1317"
}
}
Ответ (ошибка):
{
"success": false,
"message": "Ошибка подключения к MongoDB",
"data": {
"status": "DOWN",
"service": "Ошибка подключения к MongoDB: Connection refused",
"timestamp": 1705312500000,
"suggestion": "Проверьте учетные данные в application.properties"
}
}
3. Информация о планировщике
GET /api/health/scheduler
Ответ:
{
"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", "РБК", "Ведомости"]
}
}
4. Общая информация о системе
GET /api/health/system
Ответ:
{
"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. Проверка состояния парсеров
GET /api/health/parsers
Ответ:
{
"success": true,
"message": "Проверка парсеров завершена",
"data": {
"totalParsers": 5,
"availableParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"],
"status": "UP",
"message": "Все парсеры доступны"
}
}
6. Проверка доступности RSS-лент
GET /api/health/rss
Описание: Проверяет доступность всех RSS-лент и предоставляет рекомендации по альтернативным URL в случае недоступности.
Ответ:
{
"success": true,
"message": "Проверка RSS-лент завершена",
"data": {
"overallStatus": "DEGRADED",
"accessibilityResults": {
"kursiv": true,
"kapital": false,
"lsm": true,
"rbc": false,
"vedomosti": true
},
"totalFeeds": 5,
"accessibleFeeds": 3,
"unaccessibleFeeds": 2,
"recommendations": {
"kapital": "https://kapital.kz/feed/",
"rbc": "https://rbc.ru/rss/"
}
}
}
Примеры использования:
// Проверка доступности RSS-лент
async function checkRssFeeds() {
try {
const response = await fetch('/api/health/rss');
const result = await response.json();
if (result.success) {
const data = result.data;
console.log('Общий статус:', data.overallStatus);
console.log('Доступные ленты:', data.accessibleFeeds);
console.log('Недоступные ленты:', data.unaccessibleFeeds);
// Показать рекомендации для недоступных лент
if (
data.recommendations &&
Object.keys(data.recommendations).length > 0
) {
console.log('Рекомендации по альтернативным URL:');
Object.entries(data.recommendations).forEach(([source, url]) => {
console.log(`${source}: ${url}`);
});
}
}
} catch (error) {
console.error('Ошибка проверки RSS-лент:', error);
}
}
// React компонент для отображения статуса RSS-лент
function RssStatusWidget() {
const [rssStatus, setRssStatus] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/health/rss')
.then((res) => res.json())
.then((data) => {
if (data.success) {
setRssStatus(data.data);
}
setLoading(false);
})
.catch((err) => {
console.error('Ошибка загрузки статуса RSS:', err);
setLoading(false);
});
}, []);
if (loading) return <div>Загрузка...</div>;
return (
<div className='rss-status'>
<h3>Статус RSS-лент</h3>
<div className={`status ${rssStatus.overallStatus.toLowerCase()}`}>
{rssStatus.overallStatus === 'UP'
? '✅ Все ленты доступны'
: rssStatus.overallStatus === 'DEGRADED'
? '⚠️ Некоторые ленты недоступны'
: '❌ Ленты недоступны'}
</div>
<div className='feeds-list'>
{Object.entries(rssStatus.accessibilityResults).map(
([source, isAccessible]) => (
<div
key={source}
className={`feed-item ${
isAccessible ? 'accessible' : 'inaccessible'
}`}
>
<span className='source'>{source}</span>
<span className='status'>{isAccessible ? '✅' : '❌'}</span>
{!isAccessible && rssStatus.recommendations[source] && (
<div className='recommendation'>
Рекомендация: {rssStatus.recommendations[source]}
</div>
)}
</div>
)
)}
</div>
</div>
);
}
🚀 Примеры использования для фронтенда
React/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 getSourcesWithErrorHandling() {
try {
const response = await fetch(`${this.baseUrl}/api/items/sources`);
const result = await response.json();
if (!result.success) {
throw new Error(result.message || 'Ошибка при получении источников');
}
return result.data; // Возвращаем только массив источников
} catch (error) {
console.error('Ошибка при получении источников:', error);
return []; // Возвращаем пустой массив в случае ошибки
}
}
// Запустить парсер (админ функция)
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',
});
// Получить список источников для фильтра
const sources = await apiService.getSourcesWithErrorHandling();
console.log('Доступные источники:', sources);
// Получить новости от конкретного источника
if (sources.length > 0) {
const newsFromFirstSource = await apiService.getNews({
sourceName: sources[0],
page: 0,
size: 10,
});
}
Vue.js примеры
// 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();
};
const getSourcesList = async () => {
try {
const response = await fetch(`${baseUrl}/api/items/sources`);
const result = await response.json();
return result.success ? result.data : [];
} catch (error) {
console.error('Ошибка при получении источников:', error);
return [];
}
};
return {
getNews,
getStats,
getSources,
getSourcesList,
};
}
🎯 Практические примеры использования эндпоинта источников
1. Создание динамического фильтра источников
// React компонент для фильтрации по источникам
import React, { useState, useEffect } from 'react';
function NewsFilter({ onFilterChange }) {
const [sources, setSources] = useState([]);
const [selectedSource, setSelectedSource] = useState('');
useEffect(() => {
// Загружаем список источников при монтировании компонента
fetch('/api/items/sources')
.then((res) => res.json())
.then((data) => {
if (data.success) {
setSources(data.data);
}
})
.catch((err) => console.error('Ошибка загрузки источников:', err));
}, []);
const handleSourceChange = (source) => {
setSelectedSource(source);
onFilterChange({ sourceName: source || undefined });
};
return (
<div className='news-filter'>
<label htmlFor='source-filter'>Фильтр по источнику:</label>
<select
id='source-filter'
value={selectedSource}
onChange={(e) => handleSourceChange(e.target.value)}
>
<option value=''>Все источники</option>
{sources.map((source) => (
<option key={source} value={source}>
{source}
</option>
))}
</select>
</div>
);
}
export default NewsFilter;
2. Отображение статистики по источникам
// Vue.js компонент для отображения статистики
<template>
<div class="sources-stats">
<h3>Статистика по источникам</h3>
<div v-if="loading" class="loading">Загрузка...</div>
<div v-else class="stats-grid">
<div
v-for="source in sources"
:key="source"
class="stat-item"
@click="filterBySource(source)"
>
<span class="source-name">{{ source }}</span>
<span class="source-count">{{ getSourceCount(source) }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
sources: [],
loading: true,
stats: {}
}
},
async mounted() {
await this.loadSources();
await this.loadStats();
this.loading = false;
},
methods: {
async loadSources() {
try {
const response = await fetch('/api/items/sources');
const result = await response.json();
if (result.success) {
this.sources = result.data;
}
} catch (error) {
console.error('Ошибка загрузки источников:', error);
}
},
async loadStats() {
try {
const response = await fetch('/api/items/stats');
const result = await response.json();
if (result.success) {
this.stats = result.data.sourceStatistics;
}
} catch (error) {
console.error('Ошибка загрузки статистики:', error);
}
},
getSourceCount(source) {
return this.stats[source] || 0;
},
filterBySource(source) {
this.$emit('filter-change', { sourceName: source });
}
}
}
</script>
3. Автодополнение для поиска по источникам
// Angular сервис для автодополнения
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class SourceService {
private sourcesSubject = new BehaviorSubject<string[]>([]);
public sources$ = this.sourcesSubject.asObservable();
constructor(private http: HttpClient) {
this.loadSources();
}
private loadSources(): void {
this.http.get<any>('/api/items/sources')
.pipe(
map(response => response.success ? response.data : []),
catchError(error => {
console.error('Ошибка загрузки источников:', error);
return [];
})
)
.subscribe(sources => {
this.sourcesSubject.next(sources);
});
}
searchSources(query: string): Observable<string[]> {
return this.sources$.pipe(
map(sources =>
sources.filter(source =>
source.toLowerCase().includes(query.toLowerCase())
)
)
);
}
getSources(): Observable<string[]> {
return this.sources$;
}
}
// Компонент автодополнения
@Component({
selector: 'app-source-autocomplete',
template: `
<div class="autocomplete">
<input
[(ngModel)]="searchQuery"
(input)="onSearch($event.target.value)"
placeholder="Поиск источника..."
class="search-input"
/>
<div *ngIf="filteredSources.length > 0" class="suggestions">
<div
*ngFor="let source of filteredSources"
(click)="selectSource(source)"
class="suggestion-item"
>
{{ source }}
</div>
</div>
</div>
`
})
export class SourceAutocompleteComponent {
searchQuery = '';
filteredSources: string[] = [];
constructor(private sourceService: SourceService) {}
onSearch(query: string): void {
if (query.length > 0) {
this.sourceService.searchSources(query)
.subscribe(sources => {
this.filteredSources = sources;
});
} else {
this.filteredSources = [];
}
}
selectSource(source: string): void {
this.searchQuery = source;
this.filteredSources = [];
// Эмитим событие выбора источника
}
}
4. Кэширование списка источников
// Сервис с кэшированием для React
class SourceCacheService {
constructor() {
this.cache = null;
this.cacheTime = null;
this.cacheTimeout = 5 * 60 * 1000; // 5 минут
}
async getSources() {
// Проверяем, есть ли актуальный кэш
if (
this.cache &&
this.cacheTime &&
Date.now() - this.cacheTime < this.cacheTimeout
) {
return this.cache;
}
try {
const response = await fetch('/api/items/sources');
const result = await response.json();
if (result.success) {
this.cache = result.data;
this.cacheTime = Date.now();
return result.data;
} else {
throw new Error(result.message);
}
} catch (error) {
console.error('Ошибка загрузки источников:', error);
// Возвращаем кэш, если есть, даже если он устарел
return this.cache || [];
}
}
clearCache() {
this.cache = null;
this.cacheTime = null;
}
}
// Использование в React хуке
function useSources() {
const [sources, setSources] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const sourceService = new SourceCacheService();
sourceService
.getSources()
.then((data) => {
setSources(data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []);
return { sources, loading, error };
}
📝 Важные замечания
-
Пагинация: Все эндпоинты для получения данных поддерживают пагинацию через параметры
pageиsize. -
Сортировка: По умолчанию новости сортируются по дате публикации (новые сначала). Можно изменить через параметр
sort. -
Фильтрация: Поддерживается фильтрация по источнику и диапазону дат.
-
Формат дат: Используйте ISO 8601 формат для дат (например: "2024-01-15T10:30:00").
-
Обработка ошибок: Все ответы содержат поле
successдля проверки успешности операции. -
Административные функции: Эндпоинты
/api/admin/parsers/*предназначены для административных функций. -
Мониторинг: Используйте эндпоинты
/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 |