minio
This commit is contained in:
+101
-51
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user