diff --git a/IMAGE_GENERATION_FRONTEND.md b/IMAGE_GENERATION_FRONTEND.md
index 1c3dd51..57875a7 100644
--- a/IMAGE_GENERATION_FRONTEND.md
+++ b/IMAGE_GENERATION_FRONTEND.md
@@ -12,25 +12,26 @@
В объекте `PostCalendarItem` добавлены два новых поля для работы с изображениями:
-| Поле | Тип | Описание | Обязательное |
-| --------------- | ------ | ------------------------------------------------------------------- | ------------ |
-| `imageUrl` | string | Путь к изображению в MinIO (используется для получения изображения) | Нет |
-| `imageFilename` | string | Имя файла изображения в MinIO | Нет |
+| Поле | Тип | Описание | Обязательное |
+| --------------- | ------ | ------------------------------------------------------------ | ------------ |
+| `imageUrl` | string | Имя файла изображения (используется для получения через API) | Нет |
+| `imageFilename` | string | Имя файла изображения | Нет |
**Важно:**
- Поля могут быть `null`, если генерация изображения не удалась
- В этом случае пост публикуется без изображения
-- Оба поля содержат одинаковое значение (имя файла в MinIO)
+- Оба поля содержат одинаковое значение (имя файла)
+- Для получения изображения используйте API эндпоинт `/api/marketing/analysis/images/{imageFilename}`
### 2. PostingTask (Задачи публикации)
В объекте `PostingTask` также добавлены поля для изображений:
-| Поле | Тип | Описание | Обязательное |
-| --------------- | ------ | ----------------------------- | ------------ |
-| `imageUrl` | string | Путь к изображению в MinIO | Нет |
-| `imageFilename` | string | Имя файла изображения в MinIO | Нет |
+| Поле | Тип | Описание | Обязательное |
+| --------------- | ------ | ------------------------------------------------------------ | ------------ |
+| `imageUrl` | string | Имя файла изображения (используется для получения через API) | Нет |
+| `imageFilename` | string | Имя файла изображения | Нет |
---
@@ -76,29 +77,71 @@
## Получение изображений
-### Способ 1: Через MinIO API (рекомендуется)
+### Через бэкенд API
-Если у вас есть доступ к MinIO API, используйте `imageUrl` или `imageFilename` для получения изображения:
+Все изображения должны получаться через бэкенд API. Прямой доступ к MinIO с фронтенда не предусмотрен.
-```
-GET {minio-endpoint}/{bucket-name}/{imageFilename}
+#### Эндпоинт для получения изображения
+
+**GET** `/api/marketing/analysis/images/{imageFilename}`
+
+Возвращает изображение поста в формате PNG.
+
+#### Параметры пути
+
+| Параметр | Тип | Описание |
+| --------------- | ------ | --------------------- |
+| `imageFilename` | string | Имя файла изображения |
+
+#### Заголовки запроса
+
+| Заголовок | Тип | Обязательный | Описание |
+| --------------- | ------ | ------------ | ------------------------------------ |
+| `Authorization` | string | Да | JWT токен в формате `Bearer {token}` |
+
+#### Пример запроса
+
+```javascript
+const imageUrl = `/api/marketing/analysis/images/${post.imageFilename}`;
+
+fetch(imageUrl, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+})
+ .then((response) => {
+ if (response.ok) {
+ return response.blob();
+ }
+ throw new Error('Failed to load image');
+ })
+ .then((blob) => {
+ const imageObjectUrl = URL.createObjectURL(blob);
+ // Используйте imageObjectUrl для отображения
+ });
```
-Где:
+#### Пример ответа
-- `minio-endpoint` - адрес вашего MinIO сервера (например, `http://92.38.48.166:9000`)
-- `bucket-name` - имя bucket (обычно `konturai`)
-- `imageFilename` - значение из поля `imageFilename` или `imageUrl`
+- **Успешный ответ (200 OK):**
-### Способ 2: Через бэкенд API (если реализован)
+ - Content-Type: `image/png`
+ - Body: бинарные данные изображения PNG
-Если бэкенд предоставляет эндпоинт для получения изображений, используйте его:
+- **Ошибка 401 Unauthorized:**
-```
-GET /api/marketing/images/{imageFilename}
-```
+ - Токен отсутствует или невалиден
-**Примечание:** На данный момент такой эндпоинт не реализован. При необходимости его можно добавить.
+- **Ошибка 404 Not Found:**
+
+ - Изображение не найдено на сервере
+
+- **Ошибка 500 Internal Server Error:**
+ - Внутренняя ошибка сервера при загрузке изображения
+
+#### Кэширование
+
+Сервер возвращает заголовок `Cache-Control: public, max-age=3600`, что позволяет браузеру кэшировать изображения на 1 час.
---
@@ -110,22 +153,26 @@ GET /api/marketing/images/{imageFilename}
```javascript
// Пример на JavaScript/TypeScript
-const PostCard = ({ post }) => {
+const PostCard = ({ post, apiBaseUrl, authToken }) => {
const hasImage = post.imageUrl && post.imageFilename;
+ const imageUrl = hasImage
+ ? `${apiBaseUrl}/api/marketing/analysis/images/${post.imageFilename}`
+ : null;
return (
{post.theme}
{post.postText}
- {hasImage ? (
+ {imageUrl ? (

{
// Fallback если изображение не загрузилось
e.target.style.display = 'none';
}}
+ // Если требуется авторизация, используйте fetch с заголовками
/>
) : (
Изображение не сгенерировано
@@ -146,12 +193,17 @@ const PostCard = ({ post }) => {
Всегда предусматривайте fallback для случаев, когда:
- Изображение не сгенерировано (`imageUrl` = `null`)
-- Изображение не найдено в MinIO
+- Изображение не найдено на сервере
- Ошибка при загрузке изображения
```javascript
const [imageError, setImageError] = useState(false);
+const getImageUrl = (imageFilename) => {
+ if (!imageFilename) return null;
+ return `${API_BASE_URL}/api/marketing/analysis/images/${imageFilename}`;
+};
+
const handleImageError = () => {
setImageError(true);
};
@@ -182,7 +234,7 @@ return (

= ({
- post,
- minioEndpoint,
- bucketName,
-}) => {
+const PostCard: React.FC
= ({ post, apiBaseUrl }) => {
const [imageError, setImageError] = useState(false);
const getImageUrl = (): string | null => {
if (!post.imageFilename) return null;
- return `${minioEndpoint}/${bucketName}/${post.imageFilename}`;
+ return `${apiBaseUrl}/api/marketing/analysis/images/${post.imageFilename}`;
};
const imageUrl = getImageUrl();
@@ -360,15 +407,14 @@ interface PostCalendarItem {
const props = defineProps<{
post: PostCalendarItem;
- minioEndpoint: string;
- bucketName: string;
+ apiBaseUrl: string;
}>();
const imageError = ref(false);
const imageUrl = computed(() => {
if (!props.post.imageFilename) return null;
- return `${props.minioEndpoint}/${props.bucketName}/${props.post.imageFilename}`;
+ return `${props.apiBaseUrl}/api/marketing/analysis/images/${props.post.imageFilename}`;
});
const formatDate = (dateString: string) => {
@@ -389,10 +435,11 @@ const formatDate = (dateString: string) => {
### 2. Хранение изображений
-- Все изображения хранятся в MinIO
+- Все изображения хранятся на бэкенде
- Формат изображений: **PNG**
- Размер изображений: **1024x1024 пикселей**
- Имя файла уникально для каждого поста
+- Доступ к изображениям только через API эндпоинт
### 3. Публикация постов
@@ -410,16 +457,13 @@ const formatDate = (dateString: string) => {
## Конфигурация
-Для работы с изображениями вам понадобятся следующие настройки:
+Для работы с изображениями вам понадобится базовый URL вашего API:
```typescript
// config.ts
-export const MINIO_CONFIG = {
- endpoint: 'http://92.38.48.166:9000', // или ваш MinIO endpoint
- bucketName: 'konturai',
- // Если требуется авторизация:
- // accessKey: 'your-access-key',
- // secretKey: 'your-secret-key',
+export const API_CONFIG = {
+ baseUrl: 'http://your-backend-url', // URL вашего бэкенда
+ // Например: 'http://localhost:8080' или 'https://api.example.com'
};
```
@@ -439,7 +483,12 @@ export const MINIO_CONFIG = {
```jsx
{
- hasImage &&
;
+ hasImage && (
+
+ );
}
```
@@ -459,9 +508,10 @@ export const MINIO_CONFIG = {
При возникновении проблем:
1. Проверьте, что `imageUrl` и `imageFilename` не `null`
-2. Убедитесь, что MinIO доступен и изображение существует
-3. Проверьте консоль браузера на наличие ошибок CORS (если обращаетесь к MinIO напрямую)
-4. Убедитесь, что используете правильный endpoint и bucket name
+2. Убедитесь, что используете правильный API эндпоинт: `/api/marketing/analysis/images/{imageFilename}`
+3. Проверьте, что JWT токен валиден и передается в заголовке `Authorization`
+4. Проверьте консоль браузера на наличие ошибок сети или авторизации
+5. Убедитесь, что используете правильный базовый URL API
---
diff --git a/src/main/java/kz/konturai/parser/controller/MarketingController.java b/src/main/java/kz/konturai/parser/controller/MarketingController.java
index bcc39bb..8bcc3db 100644
--- a/src/main/java/kz/konturai/parser/controller/MarketingController.java
+++ b/src/main/java/kz/konturai/parser/controller/MarketingController.java
@@ -201,6 +201,45 @@ public class MarketingController {
}
}
+ /**
+ * Получает изображение поста по имени файла
+ *
+ * @param authHeader Authorization header с JWT токеном
+ * @param imageFilename Имя файла изображения
+ * @return Изображение в формате PNG
+ */
+ @GetMapping("/images/{imageFilename}")
+ public ResponseEntity getPostImage(
+ @RequestHeader(value = "Authorization", required = false) String authHeader,
+ @PathVariable String imageFilename) {
+
+ String userId = extractUserIdFromHeader(authHeader);
+ if (userId == null) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ }
+
+ // Проверяем существование файла в MinIO
+ try {
+ if (!minIOService.fileExists(imageFilename)) {
+ logger.warn("Image file not found: {}", imageFilename);
+ return ResponseEntity.notFound().build();
+ }
+
+ // Загружаем изображение из MinIO
+ InputStream inputStream = minIOService.downloadFile(imageFilename);
+ byte[] bytes = inputStream.readAllBytes();
+ inputStream.close();
+
+ return ResponseEntity.ok()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
+ .header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
+ .body(bytes);
+ } catch (Exception e) {
+ logger.error("Error loading image {}: {}", imageFilename, e.getMessage(), e);
+ return ResponseEntity.internalServerError().build();
+ }
+ }
+
@PostMapping("/strategy/generate")
public ResponseEntity> generateStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,