From 95ba34e7fa4c8dea3b4b275afde0e1826d7f3666 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 5 Dec 2025 01:16:16 +0500 Subject: [PATCH] minio --- IMAGE_GENERATION_FRONTEND.md | 152 ++++++++++++------ .../controller/MarketingController.java | 39 +++++ 2 files changed, 140 insertions(+), 51 deletions(-) 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 ? ( {post.theme} { // 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.theme} = ({ - 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 && {post.theme}; + hasImage && ( + {post.theme} + ); } ``` @@ -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,