This commit is contained in:
root
2025-12-05 01:16:16 +05:00
parent 0a37e9a681
commit 95ba34e7fa
2 changed files with 140 additions and 51 deletions
+101 -51
View File
@@ -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 (
<div className='post-card'>
<h3>{post.theme}</h3>
<p>{post.postText}</p>
{hasImage ? (
{imageUrl ? (
<img
src={`${MINIO_ENDPOINT}/${BUCKET_NAME}/${post.imageFilename}`}
src={imageUrl}
alt={post.theme}
onError={(e) => {
// Fallback если изображение не загрузилось
e.target.style.display = 'none';
}}
// Если требуется авторизация, используйте fetch с заголовками
/>
) : (
<div className='no-image-placeholder'>Изображение не сгенерировано</div>
@@ -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 (
<img
src={
post.imageUrl
? `${MINIO_ENDPOINT}/${BUCKET_NAME}/${post.imageFilename}`
? `${API_BASE_URL}/api/marketing/analysis/images/${post.imageFilename}`
: null
}
alt={post.theme}
@@ -228,20 +280,15 @@ interface PostCalendarItem {
interface PostCardProps {
post: PostCalendarItem;
minioEndpoint: string;
bucketName: string;
apiBaseUrl: string;
}
const PostCard: React.FC<PostCardProps> = ({
post,
minioEndpoint,
bucketName,
}) => {
const PostCard: React.FC<PostCardProps> = ({ 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 && <img src={getImageUrl(post)} alt={post.theme} />;
hasImage && (
<img
src={`${API_BASE_URL}/api/marketing/analysis/images/${post.imageFilename}`}
alt={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
---
@@ -201,6 +201,45 @@ public class MarketingController {
}
}
/**
* Получает изображение поста по имени файла
*
* @param authHeader Authorization header с JWT токеном
* @param imageFilename Имя файла изображения
* @return Изображение в формате PNG
*/
@GetMapping("/images/{imageFilename}")
public ResponseEntity<byte[]> 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,