This commit is contained in:
arys
2026-03-01 13:11:21 +05:00
parent 6803106aae
commit fa4bfff326
3 changed files with 139 additions and 15 deletions
@@ -13,6 +13,8 @@ import kz.konturai.parser.service.MinIOService;
import kz.konturai.parser.service.PostingTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -200,6 +202,30 @@ public class MarketingAnalysisV3Controller {
}
}
@GetMapping("/videos/{videoFilename}")
public ResponseEntity<Resource> getPostVideo(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String videoFilename
) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
try {
if (!minIOService.fileExists(videoFilename)) {
return ResponseEntity.notFound().build();
}
InputStream inputStream = minIOService.downloadFile(videoFilename);
InputStreamResource resource = new InputStreamResource(inputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "video/mp4")
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(resource);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/{analysisId}/strategy-preview")
public ResponseEntity<?> previewStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@@ -283,7 +309,10 @@ public class MarketingAnalysisV3Controller {
MarketingStrategy strategy = strategyOpt.get();
if (!strategy.getUserId().equals(userId)) return forbiddenResponse();
return ResponseEntity.ok(ApiResponse.success(strategy));
Map<String, Object> result = strategyService.getStrategyResult(strategyId);
if (result == null) return notFoundResponse("Ошибка сборки стратегии");
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
return internalErrorResponse(e);
}
@@ -305,7 +334,10 @@ public class MarketingAnalysisV3Controller {
Optional<MarketingStrategy> strategyOpt = strategyService.getStrategyByAnalysisId(analysisId);
if (strategyOpt.isEmpty()) return notFoundResponse("Стратегия не найдена");
return ResponseEntity.ok(ApiResponse.success(strategyOpt.get()));
Map<String, Object> result = strategyService.getStrategyResult(strategyOpt.get().getId());
if (result == null) return notFoundResponse("Ошибка сборки стратегии");
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
return internalErrorResponse(e);
}
@@ -451,6 +483,8 @@ public class MarketingAnalysisV3Controller {
"postIndex", postIndex,
"imageUrl", updatedItem.getImageUrl() != null ? updatedItem.getImageUrl() : "",
"imageFilename", updatedItem.getImageFilename() != null ? updatedItem.getImageFilename() : "",
"videoUrl", updatedItem.getVideoUrl() != null ? updatedItem.getVideoUrl() : "",
"videoFilename", updatedItem.getVideoFilename() != null ? updatedItem.getVideoFilename() : "",
"theme", updatedItem.getTheme() != null ? updatedItem.getTheme() : "",
"platform", updatedItem.getPlatform() != null ? updatedItem.getPlatform() : "",
"publishDate", updatedItem.getPublishDate()
@@ -1,5 +1,7 @@
package kz.konturai.parser.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -7,6 +9,7 @@ import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@@ -15,7 +18,8 @@ import java.util.Map;
@Slf4j
public class GeminiVideoGenerationService {
private final RestTemplate restTemplate = new RestTemplate(); // Можно заинжектить через @Bean, если есть
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Value("${gemini.veo.api.url:https://generativelanguage.googleapis.com/v1beta/models/veo:generateVideo}")
private String videoApiUrl;
@@ -24,7 +28,6 @@ public class GeminiVideoGenerationService {
private String apiKey;
public byte[] generateVideo(String prompt) {
log.info("Запуск генерации видео через Gemini Veo. Промпт: {}", prompt);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
@@ -37,22 +40,46 @@ public class GeminiVideoGenerationService {
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<byte[]> response = restTemplate.exchange(
ResponseEntity<String> response = restTemplate.exchange(
videoApiUrl,
HttpMethod.POST,
entity,
byte[].class
String.class
);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
log.info("Видео успешно сгенерировано. Размер: {} байт", response.getBody().length);
return response.getBody();
String jsonBody = response.getBody();
JsonNode rootNode = objectMapper.readTree(jsonBody);
String base64Video = null;
if (rootNode.has("predictions") && rootNode.get("predictions").isArray()) {
JsonNode prediction = rootNode.get("predictions").get(0);
if (prediction.has("bytesBase64Encoded")) {
base64Video = prediction.get("bytesBase64Encoded").asText();
}
} else if (rootNode.has("candidates") && rootNode.get("candidates").isArray()) {
JsonNode parts = rootNode.at("/candidates/0/content/parts");
if (parts.isArray() && parts.size() > 0 && parts.get(0).has("inlineData")) {
base64Video = parts.get(0).at("/inlineData/data").asText();
}
} else if (rootNode.has("videoBase64")) {
base64Video = rootNode.get("videoBase64").asText();
} else if (rootNode.has("base64")) {
base64Video = rootNode.get("base64").asText();
}
if (base64Video != null && !base64Video.isEmpty()) {
return Base64.getDecoder().decode(base64Video);
} else {
if (!jsonBody.trim().startsWith("{")) {
return jsonBody.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
}
return null;
}
} else {
log.error("Ошибка API генерации видео. Код: {}", response.getStatusCode());
return null;
}
} catch (Exception e) {
log.error("Критическая ошибка при генерации видео: {}", e.getMessage(), e);
return null;
}
}
@@ -8,6 +8,7 @@ import kz.konturai.parser.dto.StatusHistoryEntry;
import kz.konturai.parser.enums.StrategyModel;
import kz.konturai.parser.model.MarketingAnalysisV3Document;
import kz.konturai.parser.model.MarketingStrategy;
import kz.konturai.parser.model.PostingTask;
import kz.konturai.parser.repository.MarketingAnalysisV3Repository;
import kz.konturai.parser.repository.MarketingStrategyRepository;
import lombok.RequiredArgsConstructor;
@@ -20,11 +21,7 @@ import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
@Service
@RequiredArgsConstructor
@@ -36,6 +33,7 @@ public class MarketingStrategyV3Service {
private final OpenAIAnalyticsService aiService;
private final ImageGenerationService imageGenerationService;
private final GeminiVideoGenerationService geminiVideoService;
private final PostingTaskService postingTaskService;
private final MinIOService minIOService;
private final ObjectMapper objectMapper = new ObjectMapper()
@@ -127,6 +125,71 @@ public class MarketingStrategyV3Service {
}
}
// ИДЕАЛЬНЫЙ СБОРЩИК ОТВЕТА (Интеграция с Автопостингом)
public Map<String, Object> getStrategyResult(String strategyId) {
Optional<MarketingStrategy> optStrategy = repository.findById(strategyId);
if (optStrategy.isEmpty()) {
return null;
}
MarketingStrategy strategy = optStrategy.get();
Map<String, Object> response = new HashMap<>();
response.put("strategyId", strategy.getId());
response.put("analysisId", strategy.getAnalysisId());
response.put("status", strategy.getStatus());
response.put("createdAt", strategy.getCreatedAt());
response.put("completedAt", strategy.getCompletedAt());
response.put("durationWeeks", strategy.getDurationWeeks());
response.put("priorityPlatforms", strategy.getPriorityPlatforms());
response.put("strategyData", strategy.getStrategyData());
if (strategy.getWeeklyPlans() != null && strategy.getPostCalendar() != null) {
Map<String, Object> strategyContent = new HashMap<>();
strategyContent.put("weeklyPlans", strategy.getWeeklyPlans());
List<Map<String, Object>> postCalendarList = new ArrayList<>();
List<PostingTask> tasks = postingTaskService.getStrategyTasks(strategyId);
for (MarketingStrategy.PostCalendarItem item : strategy.getPostCalendar()) {
Map<String, Object> dtoItem = new HashMap<>();
dtoItem.put("publishDate", item.getPublishDate());
dtoItem.put("platform", item.getPlatform());
dtoItem.put("contentType", item.getContentType());
dtoItem.put("theme", item.getTheme());
dtoItem.put("postText", item.getPostText());
dtoItem.put("hashtags", item.getHashtags());
dtoItem.put("publishTime", item.getPublishTime());
dtoItem.put("imageUrl", item.getImageUrl());
dtoItem.put("imageFilename", item.getImageFilename());
dtoItem.put("videoUrl", item.getVideoUrl());
dtoItem.put("videoFilename", item.getVideoFilename());
Optional<PostingTask> matchingTask = tasks.stream()
.filter(task -> task.getPublishDate() != null && item.getPublishDate() != null
&& task.getPublishDate().equals(item.getPublishDate())
&& task.getPlatform() != null && item.getPlatform() != null
&& task.getPlatform().equalsIgnoreCase(item.getPlatform()))
.findFirst();
if (matchingTask.isPresent()) {
dtoItem.put("taskId", matchingTask.get().getId());
dtoItem.put("taskStatus", matchingTask.get().getStatus());
} else {
dtoItem.put("taskStatus", null);
}
postCalendarList.add(dtoItem);
}
strategyContent.put("postCalendar", postCalendarList);
response.put("strategy", strategyContent);
response.put("postCalendar", postCalendarList);
response.put("weeklyPlans", strategy.getWeeklyPlans());
}
return response;
}
public MarketingStrategy.PostCalendarItem regeneratePostImage(String strategyId, int postIndex) {
Optional<MarketingStrategy> optStrategy = repository.findById(strategyId);
if (optStrategy.isEmpty()) {