Update 10 files
- /Техническое задание №1: Основа проекта, модели данных и репозитории.md - /Техническое задание №2: Базовое управление кампаниями и каналами (CRUD).md - /Техническое задание №3: Модуль AI-генерации контента,md - /Техническое задание: Модуль SMM-автоматизации.md - /Техническое задание: Фронтенд-приложение для AI-платформы.md - /Техническое задание №1 Основа проекта, модели данных и репозитории.md - /Техническое задание №2 Базовое управление кампаниями и каналами (CRUD).md - /Техническое задание №3 Модуль AI-генерации контента,md - /Техническое задание Модуль SMM-автоматизации.md - /Техническое задание Фронтенд-приложение для AI-платформы.md
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
# Руководство по 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];
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user