- /Техническое задание №1: Основа проекта, модели данных и репозитории.md - /Техническое задание №2: Базовое управление кампаниями и каналами (CRUD).md - /Техническое задание №3: Модуль AI-генерации контента,md - /Техническое задание: Модуль SMM-автоматизации.md - /Техническое задание: Фронтенд-приложение для AI-платформы.md - /Техническое задание №1 Основа проекта, модели данных и репозитории.md - /Техническое задание №2 Базовое управление кампаниями и каналами (CRUD).md - /Техническое задание №3 Модуль AI-генерации контента,md - /Техническое задание Модуль SMM-автоматизации.md - /Техническое задание Фронтенд-приложение для AI-платформы.md
465 lines
16 KiB
Markdown
465 lines
16 KiB
Markdown
# Руководство по API для фронтенда: Генерация и история отчётов
|
|
|
|
## Базовый URL
|
|
|
|
```
|
|
/api/parser/report
|
|
```
|
|
|
|
## 1. Генерация отчёта
|
|
|
|
### POST /api/parser/report/generate
|
|
|
|
Создаёт новый отчёт и возвращает файл для скачивания.
|
|
|
|
**Заголовки:**
|
|
|
|
```
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Тело запроса:**
|
|
|
|
```json
|
|
{
|
|
"reportTitle": "Еженедельный анализ новостного фона",
|
|
"authorName": "Имя Аналитика",
|
|
"companyName": "Название Компании Клиента",
|
|
"startDate": "2025-09-10T00:00:00",
|
|
"endDate": "2025-09-17T23:59:59",
|
|
"format": "PDF"
|
|
}
|
|
```
|
|
|
|
**Параметры:**
|
|
|
|
- `reportTitle` (string, обязательный) - заголовок отчёта
|
|
- `authorName` (string, обязательный) - имя автора
|
|
- `companyName` (string, обязательный) - название компании
|
|
- `startDate` (string, обязательный) - дата начала периода в формате ISO 8601
|
|
- `endDate` (string, обязательный) - дата окончания периода в формате ISO 8601
|
|
- `format` (string, обязательный) - формат файла: "PDF" или "DOCX"
|
|
|
|
**Успешный ответ (200 OK):**
|
|
|
|
```
|
|
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
|
|
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
|
|
[бинарные данные файла]
|
|
```
|
|
|
|
**Пример на JavaScript:**
|
|
|
|
```javascript
|
|
async function generateReport(reportData) {
|
|
try {
|
|
const response = await fetch('/api/parser/report/generate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(reportData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// Получаем имя файла из заголовка
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';
|
|
|
|
// Создаём blob и скачиваем файл
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
} catch (error) {
|
|
console.error('Ошибка при генерации отчёта:', error);
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
const reportData = {
|
|
reportTitle: 'Еженедельный анализ новостного фона',
|
|
authorName: 'Иван Петров',
|
|
companyName: 'ООО Рога и Копыта',
|
|
startDate: '2025-01-01T00:00:00',
|
|
endDate: '2025-01-07T23:59:59',
|
|
format: 'PDF'
|
|
};
|
|
|
|
generateReport(reportData);
|
|
```
|
|
|
|
## 2. Получение истории отчётов
|
|
|
|
### GET /api/parser/report/history
|
|
|
|
Возвращает пагинированный список всех сгенерированных отчётов.
|
|
|
|
**Параметры запроса:**
|
|
|
|
- `page` (int, опциональный, по умолчанию: 0) - номер страницы (начиная с 0)
|
|
- `size` (int, опциональный, по умолчанию: 20) - количество элементов на странице
|
|
- `sort` (string, опциональный, по умолчанию: "createdAt,desc") - поле и направление сортировки
|
|
|
|
**Пример запроса:**
|
|
|
|
```
|
|
GET /api/parser/report/history?page=0&size=10&sort=createdAt,desc
|
|
```
|
|
|
|
**Успешный ответ (200 OK):**
|
|
|
|
```json
|
|
{
|
|
"content": [
|
|
{
|
|
"id": "507f1f77bcf86cd799439011",
|
|
"reportTitle": "Еженедельный анализ новостного фона",
|
|
"authorName": "Имя Аналитика",
|
|
"companyName": "Название Компании Клиента",
|
|
"startDate": "2025-09-10T00:00:00",
|
|
"endDate": "2025-09-17T23:59:59",
|
|
"format": "PDF",
|
|
"filename": "report_2025-09-17-1234567890.pdf",
|
|
"fileSize": 1234567,
|
|
"createdAt": "2025-09-17T21:11:58"
|
|
}
|
|
],
|
|
"pageable": {
|
|
"sort": {
|
|
"sorted": true,
|
|
"unsorted": false,
|
|
"empty": false
|
|
},
|
|
"pageNumber": 0,
|
|
"pageSize": 20,
|
|
"offset": 0,
|
|
"paged": true,
|
|
"unpaged": false
|
|
},
|
|
"totalElements": 1,
|
|
"totalPages": 1,
|
|
"last": true,
|
|
"first": true,
|
|
"numberOfElements": 1,
|
|
"size": 20,
|
|
"number": 0,
|
|
"sort": {
|
|
"sorted": true,
|
|
"unsorted": false,
|
|
"empty": false
|
|
},
|
|
"empty": false
|
|
}
|
|
```
|
|
|
|
**Пример на JavaScript:**
|
|
|
|
```javascript
|
|
async function getReportHistory(page = 0, size = 20, sort = 'createdAt,desc') {
|
|
try {
|
|
const params = new URLSearchParams({
|
|
page: page.toString(),
|
|
size: size.toString(),
|
|
sort: sort
|
|
});
|
|
|
|
const response = await fetch(`/api/parser/report/history?${params}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Ошибка при получении истории отчётов:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
getReportHistory(0, 10, 'createdAt,desc')
|
|
.then((data) => {
|
|
console.log('Отчёты:', data.content);
|
|
console.log('Всего элементов:', data.totalElements);
|
|
console.log('Всего страниц:', data.totalPages);
|
|
})
|
|
.catch((error) => {
|
|
console.error('Ошибка:', error);
|
|
});
|
|
```
|
|
|
|
## 3. Скачивание отчёта из истории
|
|
|
|
### GET /api/parser/report/history/{id}
|
|
|
|
Скачивает конкретный отчёт по его ID.
|
|
|
|
**Параметры:**
|
|
|
|
- `id` (string, обязательный) - ID отчёта из истории
|
|
|
|
**Успешный ответ (200 OK):**
|
|
|
|
```
|
|
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
|
|
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
|
|
[бинарные данные файла]
|
|
```
|
|
|
|
**Ошибки:**
|
|
|
|
- `404 Not Found` - отчёт с указанным ID не найден
|
|
- `500 Internal Server Error` - ошибка при чтении файла
|
|
|
|
**Пример на JavaScript:**
|
|
|
|
```javascript
|
|
async function downloadReport(reportId) {
|
|
try {
|
|
const response = await fetch(`/api/parser/report/history/${reportId}`);
|
|
|
|
if (response.status === 404) {
|
|
throw new Error('Отчёт не найден');
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// Получаем имя файла из заголовка
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';
|
|
|
|
// Создаём blob и скачиваем файл
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
} catch (error) {
|
|
console.error('Ошибка при скачивании отчёта:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Использование
|
|
downloadReport('507f1f77bcf86cd799439011')
|
|
.then(() => console.log('Отчёт скачан'))
|
|
.catch((error) => console.error('Ошибка:', error));
|
|
```
|
|
|
|
## 4. Полный пример React компонента
|
|
|
|
```jsx
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
const ReportManager = () => {
|
|
const [reports, setReports] = useState([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [currentPage, setCurrentPage] = useState(0);
|
|
const [totalPages, setTotalPages] = useState(0);
|
|
const [formData, setFormData] = useState({
|
|
reportTitle: '',
|
|
authorName: '',
|
|
companyName: '',
|
|
startDate: '',
|
|
endDate: '',
|
|
format: 'PDF'
|
|
});
|
|
|
|
// Загрузка истории отчётов
|
|
const loadReports = async (page = 0) => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await getReportHistory(page, 10);
|
|
setReports(data.content);
|
|
setTotalPages(data.totalPages);
|
|
setCurrentPage(page);
|
|
} catch (error) {
|
|
console.error('Ошибка загрузки истории:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Генерация нового отчёта
|
|
const handleGenerate = async (e) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
try {
|
|
await generateReport(formData);
|
|
// Перезагружаем историю после генерации
|
|
await loadReports(currentPage);
|
|
} catch (error) {
|
|
console.error('Ошибка генерации:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Скачивание отчёта
|
|
const handleDownload = async (reportId) => {
|
|
try {
|
|
await downloadReport(reportId);
|
|
} catch (error) {
|
|
console.error('Ошибка скачивания:', error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadReports();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="report-manager">
|
|
<h2>Генерация отчётов</h2>
|
|
|
|
{/* Форма генерации */}
|
|
<form onSubmit={handleGenerate} className="report-form">
|
|
<div>
|
|
<label>Заголовок отчёта:</label>
|
|
<input type="text" value={formData.reportTitle} onChange={(e) => setFormData({ ...formData, reportTitle: e.target.value })} required />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Автор:</label>
|
|
<input type="text" value={formData.authorName} onChange={(e) => setFormData({ ...formData, authorName: e.target.value })} required />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Компания:</label>
|
|
<input type="text" value={formData.companyName} onChange={(e) => setFormData({ ...formData, companyName: e.target.value })} required />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Дата начала:</label>
|
|
<input type="datetime-local" value={formData.startDate} onChange={(e) => setFormData({ ...formData, startDate: e.target.value })} required />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Дата окончания:</label>
|
|
<input type="datetime-local" value={formData.endDate} onChange={(e) => setFormData({ ...formData, endDate: e.target.value })} required />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Формат:</label>
|
|
<select value={formData.format} onChange={(e) => setFormData({ ...formData, format: e.target.value })}>
|
|
<option value="PDF">PDF</option>
|
|
<option value="DOCX">DOCX</option>
|
|
</select>
|
|
</div>
|
|
|
|
<button type="submit" disabled={loading}>
|
|
{loading ? 'Генерация...' : 'Сгенерировать отчёт'}
|
|
</button>
|
|
</form>
|
|
|
|
{/* История отчётов */}
|
|
<h2>История отчётов</h2>
|
|
|
|
{loading ? (
|
|
<div>Загрузка...</div>
|
|
) : (
|
|
<>
|
|
<div className="reports-list">
|
|
{reports.map((report) => (
|
|
<div key={report.id} className="report-item">
|
|
<h3>{report.reportTitle}</h3>
|
|
<p>Автор: {report.authorName}</p>
|
|
<p>Компания: {report.companyName}</p>
|
|
<p>
|
|
Период: {new Date(report.startDate).toLocaleDateString()} - {new Date(report.endDate).toLocaleDateString()}
|
|
</p>
|
|
<p>Формат: {report.format}</p>
|
|
<p>Размер: {(report.fileSize / 1024).toFixed(1)} KB</p>
|
|
<p>Создан: {new Date(report.createdAt).toLocaleString()}</p>
|
|
<button onClick={() => handleDownload(report.id)}>Скачать</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Пагинация */}
|
|
<div className="pagination">
|
|
<button onClick={() => loadReports(currentPage - 1)} disabled={currentPage === 0}>
|
|
Предыдущая
|
|
</button>
|
|
<span>
|
|
Страница {currentPage + 1} из {totalPages}
|
|
</span>
|
|
<button onClick={() => loadReports(currentPage + 1)} disabled={currentPage >= totalPages - 1}>
|
|
Следующая
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ReportManager;
|
|
```
|
|
|
|
## 5. Обработка ошибок
|
|
|
|
### Типичные ошибки и их обработка:
|
|
|
|
1. **400 Bad Request** - неверные данные в запросе
|
|
2. **404 Not Found** - отчёт не найден (при скачивании)
|
|
3. **500 Internal Server Error** - внутренняя ошибка сервера
|
|
|
|
```javascript
|
|
async function handleApiCall(apiFunction, ...args) {
|
|
try {
|
|
return await apiFunction(...args);
|
|
} catch (error) {
|
|
if (error.message.includes('404')) {
|
|
alert('Отчёт не найден');
|
|
} else if (error.message.includes('400')) {
|
|
alert('Неверные данные запроса');
|
|
} else {
|
|
alert('Произошла ошибка: ' + error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. Форматы дат
|
|
|
|
Все даты должны передаваться в формате ISO 8601:
|
|
|
|
- `2025-01-01T00:00:00` - 1 января 2025, 00:00:00
|
|
- `2025-01-01T23:59:59` - 1 января 2025, 23:59:59
|
|
|
|
Для HTML input[type="datetime-local"] используйте формат:
|
|
|
|
- `2025-01-01T00:00` (без секунд и часового пояса)
|
|
|
|
## 7. Размеры файлов
|
|
|
|
Размеры файлов возвращаются в байтах. Для отображения пользователю конвертируйте:
|
|
|
|
```javascript
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
```
|