This commit is contained in:
root
2025-12-03 09:02:21 +05:00
parent 6678a67d73
commit 8b1a96efe5
16 changed files with 2629 additions and 587 deletions
@@ -29,512 +29,517 @@ import java.util.stream.Collectors;
@RequestMapping("/api/marketing/analysis")
public class MarketingController {
private final MarketingAnalysisService marketingAnalysisService;
private final MarketingStrategyService marketingStrategyService;
private final MinIOService minIOService;
private final JwtService jwtService;
private final PostingTaskService postingTaskService;
private final MarketingAnalysisService marketingAnalysisService;
private final MarketingStrategyService marketingStrategyService;
private final MinIOService minIOService;
private final JwtService jwtService;
private final PostingTaskService postingTaskService;
public MarketingController(
MarketingAnalysisService marketingAnalysisService,
MarketingStrategyService marketingStrategyService,
MinIOService minIOService,
JwtService jwtService,
PostingTaskService postingTaskService) {
this.marketingAnalysisService = marketingAnalysisService;
this.marketingStrategyService = marketingStrategyService;
this.minIOService = minIOService;
this.jwtService = jwtService;
this.postingTaskService = postingTaskService;
}
private String extractUserIdFromHeader(String authHeader) {
if (authHeader == null || authHeader.isEmpty()) {
return null;
}
return jwtService.extractUserIdFromHeader(authHeader);
}
private ResponseEntity<?> unauthorizedResponse() {
ErrorResponse error = new ErrorResponse(
"UNAUTHORIZED",
"Требуется аутентификация. Пожалуйста, предоставьте валидный JWT токен.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("Не авторизован", error));
}
@PostMapping("/start")
public ResponseEntity<?> startAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@Valid @RequestBody MarketingAnalysisRequest request) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
public MarketingController(
MarketingAnalysisService marketingAnalysisService,
MarketingStrategyService marketingStrategyService,
MinIOService minIOService,
JwtService jwtService,
PostingTaskService postingTaskService) {
this.marketingAnalysisService = marketingAnalysisService;
this.marketingStrategyService = marketingStrategyService;
this.minIOService = minIOService;
this.jwtService = jwtService;
this.postingTaskService = postingTaskService;
}
// Create analysis record
MarketingAnalysis analysis = marketingAnalysisService.startAnalysis(request, userId);
// Start async processing
marketingAnalysisService.processAnalysis(analysis.getId(), request);
// Calculate estimated completion time (5-10 minutes)
LocalDateTime estimatedCompletion = LocalDateTime.now().plusMinutes(8);
MarketingAnalysisResponse response = new MarketingAnalysisResponse(
analysis.getId(),
"processing",
estimatedCompletion,
"Анализ запущен успешно. Результаты будут готовы в течение 5-10 минут.");
return ResponseEntity.ok(ApiResponse.success("Анализ запущен успешно", response));
}
@GetMapping("/{analysisId}")
public ResponseEntity<?> getAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
private String extractUserIdFromHeader(String authHeader) {
if (authHeader == null || authHeader.isEmpty()) {
return null;
}
return jwtService.extractUserIdFromHeader(authHeader);
}
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
private ResponseEntity<?> unauthorizedResponse() {
ErrorResponse error = new ErrorResponse(
"UNAUTHORIZED",
"Требуется аутентификация. Пожалуйста, предоставьте валидный JWT токен.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("Не авторизован", error));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
@PostMapping("/start")
public ResponseEntity<?> startAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@Valid @RequestBody MarketingAnalysisRequest request) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
// Create analysis record
MarketingAnalysis analysis = marketingAnalysisService.startAnalysis(request, userId);
// Start async processing
marketingAnalysisService.processAnalysis(analysis.getId(), request);
// Calculate estimated completion time (5-10 minutes)
LocalDateTime estimatedCompletion = LocalDateTime.now().plusMinutes(8);
MarketingAnalysisResponse response = new MarketingAnalysisResponse(
analysis.getId(),
"processing",
estimatedCompletion,
"Анализ запущен успешно. Результаты будут готовы в течение 5-10 минут.");
return ResponseEntity.ok(ApiResponse.success("Анализ запущен успешно", response));
}
MarketingAnalysisResult result = marketingAnalysisService.getAnalysisResult(analysisId);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{analysisId}")
public ResponseEntity<?> getAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
@GetMapping("/{analysisId}/download")
public ResponseEntity<?> downloadPdf(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
MarketingAnalysisResult result = marketingAnalysisService.getAnalysisResult(analysisId);
return ResponseEntity.ok(ApiResponse.success(result));
}
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
return ResponseEntity.notFound().build();
@GetMapping("/{analysisId}/download")
public ResponseEntity<?> downloadPdf(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
return ResponseEntity.notFound().build();
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
if (analysis.getPdfFilePath() == null) {
return ResponseEntity.notFound().build();
}
try {
InputStream inputStream = minIOService.downloadFile(analysis.getPdfFilePath());
byte[] bytes = inputStream.readAllBytes();
inputStream.close();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + analysis.getPdfFilename() + "\"")
.contentType(MediaType.APPLICATION_PDF)
.body(bytes);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
@PostMapping("/strategy/generate")
public ResponseEntity<?> generateStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@RequestParam String analysisId,
@Valid @RequestBody(required = false) MarketingStrategyRequest request) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
// Check if user owns the analysis
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", "Анализ не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
if (request == null) {
request = new MarketingStrategyRequest();
}
try {
MarketingStrategy strategy = marketingStrategyService.generateStrategy(analysisId, request,
userId);
MarketingStrategyResponse response = new MarketingStrategyResponse();
response.setStrategyId(strategy.getId());
response.setAnalysisId(strategy.getAnalysisId());
response.setStatus(strategy.getStatus());
response.setCreatedAt(strategy.getCreatedAt());
response.setDurationWeeks(strategy.getDurationWeeks());
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
return ResponseEntity.ok(ApiResponse.success(
"Генерация стратегии запущена успешно. Результаты будут готовы в течение 3-5 минут.",
response));
} catch (IllegalArgumentException e) {
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", e.getMessage());
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
} catch (IllegalStateException e) {
ErrorResponse error = new ErrorResponse("ANALYSIS_NOT_COMPLETED", e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error("Анализ еще не завершен", error));
} catch (Exception e) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла ошибка при запуске генерации стратегии");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
}
}
if (analysis.getPdfFilePath() == null) {
return ResponseEntity.notFound().build();
@GetMapping("/strategy/{strategyId}")
public ResponseEntity<?> getStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
MarketingStrategyResponse result = marketingStrategyService.getStrategyResult(strategyId);
return ResponseEntity.ok(ApiResponse.success(result));
}
try {
InputStream inputStream = minIOService.downloadFile(analysis.getPdfFilePath());
byte[] bytes = inputStream.readAllBytes();
inputStream.close();
@GetMapping("/{analysisId}/strategy")
public ResponseEntity<?> getStrategyByAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + analysis.getPdfFilename() + "\"")
.contentType(MediaType.APPLICATION_PDF)
.body(bytes);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
@PostMapping("/strategy/generate")
public ResponseEntity<?> generateStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@RequestParam String analysisId,
@Valid @RequestBody(required = false) MarketingStrategyRequest request) {
// Check if user owns the analysis
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
}
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
MarketingStrategyResponse result = marketingStrategyService.getStrategyByAnalysisId(analysisId);
if (result == null) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия для указанного анализа не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
// Check if user owns the analysis
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", "Анализ не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
@GetMapping("/my")
public ResponseEntity<?> getMyAnalyses(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
List<MarketingAnalysis> analyses = marketingAnalysisService.getUserAnalyses(userId);
List<AnalysisHistoryResponse> responseList = analyses.stream()
.map(this::convertToHistoryResponse)
.collect(Collectors.toList());
return ResponseEntity.ok(ApiResponse.success(responseList));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
@GetMapping("/strategy/my")
public ResponseEntity<?> getMyStrategies(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
List<MarketingStrategy> strategies = marketingStrategyService.getUserStrategies(userId);
List<StrategyHistoryResponse> responseList = strategies.stream()
.map(this::convertToStrategyHistoryResponse)
.collect(Collectors.toList());
return ResponseEntity.ok(ApiResponse.success(responseList));
}
if (request == null) {
request = new MarketingStrategyRequest();
@GetMapping("/{analysisId}/history")
public ResponseEntity<?> getAnalysisHistory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
AnalysisHistoryResponse response = convertToHistoryResponse(analysis);
return ResponseEntity.ok(ApiResponse.success(response));
}
try {
MarketingStrategy strategy = marketingStrategyService.generateStrategy(analysisId, request, userId);
@GetMapping("/strategy/{strategyId}/history")
public ResponseEntity<?> getStrategyHistory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId) {
MarketingStrategyResponse response = new MarketingStrategyResponse();
response.setStrategyId(strategy.getId());
response.setAnalysisId(strategy.getAnalysisId());
response.setStatus(strategy.getStatus());
response.setCreatedAt(strategy.getCreatedAt());
response.setDurationWeeks(strategy.getDurationWeeks());
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
return ResponseEntity.ok(ApiResponse.success(
"Генерация стратегии запущена успешно. Результаты будут готовы в течение 3-5 минут.",
response));
} catch (IllegalArgumentException e) {
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", e.getMessage());
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
} catch (IllegalStateException e) {
ErrorResponse error = new ErrorResponse("ANALYSIS_NOT_COMPLETED", e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error("Анализ еще не завершен", error));
} catch (Exception e) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла ошибка при запуске генерации стратегии");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
}
}
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
@GetMapping("/strategy/{strategyId}")
public ResponseEntity<?> getStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId) {
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
StrategyHistoryResponse response = convertToStrategyHistoryResponse(strategy);
return ResponseEntity.ok(ApiResponse.success(response));
}
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
private AnalysisHistoryResponse convertToHistoryResponse(MarketingAnalysis analysis) {
AnalysisHistoryResponse response = new AnalysisHistoryResponse();
response.setAnalysisId(analysis.getId());
response.setBusinessNiche(analysis.getBusinessNiche());
response.setProduct(analysis.getProduct());
response.setTargetAudience(analysis.getTargetAudience());
response.setRegion(analysis.getRegion());
response.setGoal(analysis.getGoal());
response.setDetailLevel(analysis.getDetailLevel());
response.setStrongSide(analysis.getStrongSide());
response.setWeakSide(analysis.getWeakSide());
response.setAnalysisType(analysis.getAnalysisType());
response.setStatus(analysis.getStatus());
response.setUserId(analysis.getUserId());
response.setCreatedAt(analysis.getCreatedAt());
response.setCompletedAt(analysis.getCompletedAt());
response.setStatusHistory(analysis.getStatusHistory());
return response;
}
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
private StrategyHistoryResponse convertToStrategyHistoryResponse(MarketingStrategy strategy) {
StrategyHistoryResponse response = new StrategyHistoryResponse();
response.setStrategyId(strategy.getId());
response.setAnalysisId(strategy.getAnalysisId());
response.setStatus(strategy.getStatus());
response.setUserId(strategy.getUserId());
response.setDurationWeeks(strategy.getDurationWeeks());
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
response.setCreatedAt(strategy.getCreatedAt());
response.setCompletedAt(strategy.getCompletedAt());
response.setStatusHistory(strategy.getStatusHistory());
return response;
}
MarketingStrategyResponse result = marketingStrategyService.getStrategyResult(strategyId);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/strategy/{strategyId}/start")
public ResponseEntity<?> startStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId,
@RequestBody(required = false) StartStrategyRequest request) {
@GetMapping("/{analysisId}/strategy")
public ResponseEntity<?> getStrategyByAnalysis(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
// Проверяем существование стратегии и права доступа
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
if (!"completed".equals(strategy.getStatus())) {
ErrorResponse error = new ErrorResponse(
"INVALID_STATUS",
"Стратегия еще не завершена. Статус: " + strategy.getStatus());
return ResponseEntity.status(400)
.body(ApiResponse.error("Стратегия не готова к запуску", error));
}
try {
// Создаем задачи из стратегии
List<PostingTask> tasks = postingTaskService.createTasksFromStrategy(strategyId);
// Получаем список платформ
List<String> platforms = tasks.stream()
.map(PostingTask::getPlatform)
.distinct()
.collect(Collectors.toList());
StartStrategyResponse response = new StartStrategyResponse(
strategyId,
tasks.size(),
platforms,
"Стратегия успешно запущена. Создано задач: " + tasks.size());
return ResponseEntity.ok(ApiResponse.success(
"Стратегия успешно запущена",
response));
} catch (IllegalStateException e) {
ErrorResponse error = new ErrorResponse(
"MISSING_CREDENTIALS",
e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error("Не удалось запустить стратегию", error));
} catch (IllegalArgumentException e) {
ErrorResponse error = new ErrorResponse(
"INVALID_STRATEGY",
e.getMessage());
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
} catch (Exception e) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла ошибка при запуске стратегии");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
}
}
// Check if user owns the analysis
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<ErrorResponse>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> details = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
details.put(error.getField(), error.getDefaultMessage());
});
ErrorResponse errorResponse = new ErrorResponse(
"VALIDATION_ERROR",
"Ошибка валидации входных данных",
details);
return ResponseEntity.badRequest()
.body(ApiResponse.error("Ошибка валидации", errorResponse));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<ErrorResponse>> handleGenericException(Exception e) {
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла внутренняя ошибка сервера. Попробуйте позже.");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", errorResponse));
}
MarketingStrategyResponse result = marketingStrategyService.getStrategyByAnalysisId(analysisId);
if (result == null) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия для указанного анализа не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/my")
public ResponseEntity<?> getMyAnalyses(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
List<MarketingAnalysis> analyses = marketingAnalysisService.getUserAnalyses(userId);
List<AnalysisHistoryResponse> responseList = analyses.stream()
.map(this::convertToHistoryResponse)
.collect(Collectors.toList());
return ResponseEntity.ok(ApiResponse.success(responseList));
}
@GetMapping("/strategy/my")
public ResponseEntity<?> getMyStrategies(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
List<MarketingStrategy> strategies = marketingStrategyService.getUserStrategies(userId);
List<StrategyHistoryResponse> responseList = strategies.stream()
.map(this::convertToStrategyHistoryResponse)
.collect(Collectors.toList());
return ResponseEntity.ok(ApiResponse.success(responseList));
}
@GetMapping("/{analysisId}/history")
public ResponseEntity<?> getAnalysisHistory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String analysisId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
if (optAnalysis.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Анализ с указанным ID не найден");
return ResponseEntity.status(404)
.body(ApiResponse.error("Анализ не найден", error));
}
MarketingAnalysis analysis = optAnalysis.get();
if (!userId.equals(analysis.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этому анализу");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
AnalysisHistoryResponse response = convertToHistoryResponse(analysis);
return ResponseEntity.ok(ApiResponse.success(response));
}
@GetMapping("/strategy/{strategyId}/history")
public ResponseEntity<?> getStrategyHistory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
StrategyHistoryResponse response = convertToStrategyHistoryResponse(strategy);
return ResponseEntity.ok(ApiResponse.success(response));
}
private AnalysisHistoryResponse convertToHistoryResponse(MarketingAnalysis analysis) {
AnalysisHistoryResponse response = new AnalysisHistoryResponse();
response.setAnalysisId(analysis.getId());
response.setProduct(analysis.getProduct());
response.setLocation(analysis.getLocation());
response.setClientType(analysis.getClientType());
response.setDifferentiator(analysis.getDifferentiator());
response.setAnalysisType(analysis.getAnalysisType());
response.setStatus(analysis.getStatus());
response.setUserId(analysis.getUserId());
response.setCreatedAt(analysis.getCreatedAt());
response.setCompletedAt(analysis.getCompletedAt());
response.setStatusHistory(analysis.getStatusHistory());
return response;
}
private StrategyHistoryResponse convertToStrategyHistoryResponse(MarketingStrategy strategy) {
StrategyHistoryResponse response = new StrategyHistoryResponse();
response.setStrategyId(strategy.getId());
response.setAnalysisId(strategy.getAnalysisId());
response.setStatus(strategy.getStatus());
response.setUserId(strategy.getUserId());
response.setDurationWeeks(strategy.getDurationWeeks());
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
response.setCreatedAt(strategy.getCreatedAt());
response.setCompletedAt(strategy.getCompletedAt());
response.setStatusHistory(strategy.getStatusHistory());
return response;
}
@PostMapping("/strategy/{strategyId}/start")
public ResponseEntity<?> startStrategy(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String strategyId,
@RequestBody(required = false) StartStrategyRequest request) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
}
// Проверяем существование стратегии и права доступа
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
if (optStrategy.isEmpty()) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Стратегия с указанным ID не найдена");
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
}
MarketingStrategy strategy = optStrategy.get();
if (!userId.equals(strategy.getUserId())) {
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"У вас нет доступа к этой стратегии");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Доступ запрещен", error));
}
if (!"completed".equals(strategy.getStatus())) {
ErrorResponse error = new ErrorResponse(
"INVALID_STATUS",
"Стратегия еще не завершена. Статус: " + strategy.getStatus());
return ResponseEntity.status(400)
.body(ApiResponse.error("Стратегия не готова к запуску", error));
}
try {
// Создаем задачи из стратегии
List<PostingTask> tasks = postingTaskService.createTasksFromStrategy(strategyId);
// Получаем список платформ
List<String> platforms = tasks.stream()
.map(PostingTask::getPlatform)
.distinct()
.collect(Collectors.toList());
StartStrategyResponse response = new StartStrategyResponse(
strategyId,
tasks.size(),
platforms,
"Стратегия успешно запущена. Создано задач: " + tasks.size());
return ResponseEntity.ok(ApiResponse.success(
"Стратегия успешно запущена",
response));
} catch (IllegalStateException e) {
ErrorResponse error = new ErrorResponse(
"MISSING_CREDENTIALS",
e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error("Не удалось запустить стратегию", error));
} catch (IllegalArgumentException e) {
ErrorResponse error = new ErrorResponse(
"INVALID_STRATEGY",
e.getMessage());
return ResponseEntity.status(404)
.body(ApiResponse.error("Стратегия не найдена", error));
} catch (Exception e) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла ошибка при запуске стратегии");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
}
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<ErrorResponse>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> details = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
details.put(error.getField(), error.getDefaultMessage());
});
ErrorResponse errorResponse = new ErrorResponse(
"VALIDATION_ERROR",
"Ошибка валидации входных данных",
details);
return ResponseEntity.badRequest()
.body(ApiResponse.error("Ошибка валидации", errorResponse));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<ErrorResponse>> handleGenericException(Exception e) {
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла внутренняя ошибка сервера. Попробуйте позже.");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", errorResponse));
}
}
@@ -5,10 +5,14 @@ import java.util.List;
public class AnalysisHistoryResponse {
private String analysisId;
private String businessNiche;
private String product;
private String location;
private String clientType;
private String differentiator;
private String targetAudience;
private String region;
private String goal;
private String detailLevel;
private String strongSide;
private String weakSide;
private String analysisType;
private String status;
private String userId;
@@ -27,6 +31,14 @@ public class AnalysisHistoryResponse {
this.analysisId = analysisId;
}
public String getBusinessNiche() {
return businessNiche;
}
public void setBusinessNiche(String businessNiche) {
this.businessNiche = businessNiche;
}
public String getProduct() {
return product;
}
@@ -35,28 +47,52 @@ public class AnalysisHistoryResponse {
this.product = product;
}
public String getLocation() {
return location;
public String getTargetAudience() {
return targetAudience;
}
public void setLocation(String location) {
this.location = location;
public void setTargetAudience(String targetAudience) {
this.targetAudience = targetAudience;
}
public String getClientType() {
return clientType;
public String getRegion() {
return region;
}
public void setClientType(String clientType) {
this.clientType = clientType;
public void setRegion(String region) {
this.region = region;
}
public String getDifferentiator() {
return differentiator;
public String getGoal() {
return goal;
}
public void setDifferentiator(String differentiator) {
this.differentiator = differentiator;
public void setGoal(String goal) {
this.goal = goal;
}
public String getDetailLevel() {
return detailLevel;
}
public void setDetailLevel(String detailLevel) {
this.detailLevel = detailLevel;
}
public String getStrongSide() {
return strongSide;
}
public void setStrongSide(String strongSide) {
this.strongSide = strongSide;
}
public String getWeakSide() {
return weakSide;
}
public void setWeakSide(String weakSide) {
this.weakSide = weakSide;
}
public String getStatus() {
@@ -4,28 +4,44 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import kz.konturai.parser.model.AnalysisType;
import kz.konturai.parser.model.DetailLevel;
import kz.konturai.parser.validator.ValidAnalysisType;
import kz.konturai.parser.validator.ValidClientType;
import kz.konturai.parser.validator.ValidDetailLevel;
import kz.konturai.parser.validator.ValidRegion;
public class MarketingAnalysisRequest {
@NotBlank(message = "Поле 'businessNiche' обязательно для заполнения")
@Size(min = 3, max = 200, message = "Поле 'businessNiche' должно содержать от 3 до 200 символов")
@Pattern(regexp = "^[\\p{L}\\p{N}\\s\\-,]+$", message = "Поле 'businessNiche' содержит недопустимые символы")
private String businessNiche;
@NotBlank(message = "Поле 'product' обязательно для заполнения")
@Size(min = 3, max = 200, message = "Поле 'product' должно содержать от 3 до 200 символов")
@Pattern(regexp = "^[\\p{L}\\p{N}\\s\\-,]+$", message = "Поле 'product' содержит недопустимые символы")
private String product;
@NotBlank(message = "Поле 'location' обязательно для заполнения")
@Size(min = 2, max = 150, message = "Поле 'location' должно содержать от 2 до 150 символов")
@Pattern(regexp = "^[\\p{L}\\p{N}\\s\\-,]+$", message = "Поле 'location' содержит недопустимые символы")
private String location;
@NotBlank(message = "Поле 'targetAudience' обязательно для заполнения")
@Size(min = 3, max = 300, message = "Поле 'targetAudience' должно содержать от 3 до 300 символов")
private String targetAudience;
@NotBlank(message = "Поле 'client' обязательно для заполнения")
@ValidClientType(message = "Поле 'client' должно быть одним из: B2B клиенты, B2C клиенты, Частные лица, Корпорации, Малый бизнес")
private String client;
@NotBlank(message = "Поле 'region' обязательно для заполнения")
@ValidRegion(message = "Поле 'region' должно быть одним из допустимых городов Казахстана")
private String region;
@NotBlank(message = "Поле 'differentiator' обязательно для заполнения")
@Size(min = 10, max = 500, message = "Поле 'differentiator' должно содержать от 10 до 500 символов")
private String differentiator;
@NotBlank(message = "Поле 'goal' обязательно для заполнения")
@Size(min = 10, max = 500, message = "Поле 'goal' должно содержать от 10 до 500 символов")
private String goal;
@NotBlank(message = "Поле 'detailLevel' обязательно для заполнения")
@ValidDetailLevel(message = "Поле 'detailLevel' должно быть одним из: КРАТКО, СТАНДАРТНО, ПОДРОБНО")
private String detailLevel;
@Size(max = 500, message = "Поле 'strongSide' должно содержать не более 500 символов")
private String strongSide;
@Size(max = 500, message = "Поле 'weakSide' должно содержать не более 500 символов")
private String weakSide;
@NotBlank(message = "Поле 'analysisType' обязательно для заполнения")
@ValidAnalysisType(message = "Поле 'analysisType' должно быть одним из: РЫНОК, КОНКУРЕНТЫ, ЦА, КАНАЛЫ, SWOT")
@@ -34,15 +50,27 @@ public class MarketingAnalysisRequest {
public MarketingAnalysisRequest() {
}
public MarketingAnalysisRequest(String product, String location, String client, String differentiator,
String analysisType) {
public MarketingAnalysisRequest(String businessNiche, String product, String targetAudience, String region,
String goal, String detailLevel, String strongSide, String weakSide, String analysisType) {
this.businessNiche = businessNiche;
this.product = product;
this.location = location;
this.client = client;
this.differentiator = differentiator;
this.targetAudience = targetAudience;
this.region = region;
this.goal = goal;
this.detailLevel = detailLevel;
this.strongSide = strongSide;
this.weakSide = weakSide;
this.analysisType = analysisType;
}
public String getBusinessNiche() {
return businessNiche;
}
public void setBusinessNiche(String businessNiche) {
this.businessNiche = businessNiche;
}
public String getProduct() {
return product;
}
@@ -51,28 +79,56 @@ public class MarketingAnalysisRequest {
this.product = product;
}
public String getLocation() {
return location;
public String getTargetAudience() {
return targetAudience;
}
public void setLocation(String location) {
this.location = location;
public void setTargetAudience(String targetAudience) {
this.targetAudience = targetAudience;
}
public String getClient() {
return client;
public String getRegion() {
return region;
}
public void setClient(String client) {
this.client = client;
public void setRegion(String region) {
this.region = region;
}
public String getDifferentiator() {
return differentiator;
public String getGoal() {
return goal;
}
public void setDifferentiator(String differentiator) {
this.differentiator = differentiator;
public void setGoal(String goal) {
this.goal = goal;
}
public String getDetailLevel() {
return detailLevel;
}
public void setDetailLevel(String detailLevel) {
this.detailLevel = detailLevel;
}
public DetailLevel getDetailLevelEnum() {
return DetailLevel.fromString(detailLevel);
}
public String getStrongSide() {
return strongSide;
}
public void setStrongSide(String strongSide) {
this.strongSide = strongSide;
}
public String getWeakSide() {
return weakSide;
}
public void setWeakSide(String weakSide) {
this.weakSide = weakSide;
}
public String getAnalysisType() {
@@ -0,0 +1,36 @@
package kz.konturai.parser.model;
public enum DetailLevel {
КРАТКО("Кратко"),
СТАНДАРТНО("Стандартно"),
ПОДРОБНО("Подробно");
private final String displayName;
DetailLevel(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public static DetailLevel fromString(String value) {
if (value == null) {
return null;
}
try {
return DetailLevel.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
// Try to match by display name
for (DetailLevel level : DetailLevel.values()) {
if (level.getDisplayName().equalsIgnoreCase(value) ||
level.name().equalsIgnoreCase(value)) {
return level;
}
}
return null;
}
}
}
@@ -16,17 +16,29 @@ public class MarketingAnalysis {
@Id
private String id;
@Field("business_niche")
private String businessNiche;
@Field("product")
private String product;
@Field("location")
private String location;
@Field("target_audience")
private String targetAudience;
@Field("client_type")
private String clientType;
@Field("region")
private String region;
@Field("differentiator")
private String differentiator;
@Field("goal")
private String goal;
@Field("detail_level")
private String detailLevel; // КРАТКО, СТАНДАРТНО, ПОДРОБНО
@Field("strong_side")
private String strongSide;
@Field("weak_side")
private String weakSide;
@Field("analysis_type")
private String analysisType; // РЫНОК, КОНКУРЕНТЫ, ЦА, КАНАЛЫ, SWOT
@@ -61,21 +73,17 @@ public class MarketingAnalysis {
this.statusHistory = new ArrayList<>();
}
public MarketingAnalysis(String product, String location, String clientType, String differentiator) {
public MarketingAnalysis(String businessNiche, String product, String targetAudience, String region,
String goal, String detailLevel, String strongSide, String weakSide, String analysisType) {
this();
this.businessNiche = businessNiche;
this.product = product;
this.location = location;
this.clientType = clientType;
this.differentiator = differentiator;
}
public MarketingAnalysis(String product, String location, String clientType, String differentiator,
String analysisType) {
this();
this.product = product;
this.location = location;
this.clientType = clientType;
this.differentiator = differentiator;
this.targetAudience = targetAudience;
this.region = region;
this.goal = goal;
this.detailLevel = detailLevel;
this.strongSide = strongSide;
this.weakSide = weakSide;
this.analysisType = analysisType;
}
@@ -95,28 +103,60 @@ public class MarketingAnalysis {
this.product = product;
}
public String getLocation() {
return location;
public String getBusinessNiche() {
return businessNiche;
}
public void setLocation(String location) {
this.location = location;
public void setBusinessNiche(String businessNiche) {
this.businessNiche = businessNiche;
}
public String getClientType() {
return clientType;
public String getTargetAudience() {
return targetAudience;
}
public void setClientType(String clientType) {
this.clientType = clientType;
public void setTargetAudience(String targetAudience) {
this.targetAudience = targetAudience;
}
public String getDifferentiator() {
return differentiator;
public String getRegion() {
return region;
}
public void setDifferentiator(String differentiator) {
this.differentiator = differentiator;
public void setRegion(String region) {
this.region = region;
}
public String getGoal() {
return goal;
}
public void setGoal(String goal) {
this.goal = goal;
}
public String getDetailLevel() {
return detailLevel;
}
public void setDetailLevel(String detailLevel) {
this.detailLevel = detailLevel;
}
public String getStrongSide() {
return strongSide;
}
public void setStrongSide(String strongSide) {
this.strongSide = strongSide;
}
public String getWeakSide() {
return weakSide;
}
public void setWeakSide(String weakSide) {
this.weakSide = weakSide;
}
public String getStatus() {
@@ -4,6 +4,7 @@ import kz.konturai.parser.dto.MarketingAnalysisRequest;
import kz.konturai.parser.dto.MarketingAnalysisResult;
import kz.konturai.parser.dto.StatusHistoryEntry;
import kz.konturai.parser.model.AnalysisType;
import kz.konturai.parser.model.DetailLevel;
import kz.konturai.parser.model.MarketingAnalysis;
import kz.konturai.parser.repository.MarketingAnalysisRepository;
import org.slf4j.Logger;
@@ -38,10 +39,14 @@ public class MarketingAnalysisService {
public MarketingAnalysis startAnalysis(MarketingAnalysisRequest request, String userId) {
MarketingAnalysis analysis = new MarketingAnalysis(
request.getBusinessNiche(),
request.getProduct(),
request.getLocation(),
request.getClient(),
request.getDifferentiator(),
request.getTargetAudience(),
request.getRegion(),
request.getGoal(),
request.getDetailLevel(),
request.getStrongSide(),
request.getWeakSide(),
request.getAnalysisType());
analysis.setUserId(userId);
analysis.setStatus("queued");
@@ -122,32 +127,52 @@ public class MarketingAnalysisService {
private Map<String, Object> generateMarketingReport(MarketingAnalysisRequest request) {
Map<String, Object> report = new HashMap<>();
// Get analysis type
// Get analysis type and detail level
AnalysisType analysisType = request.getAnalysisTypeEnum();
String analysisTypeName = analysisType != null ? analysisType.getDisplayName() : "Общий";
DetailLevel detailLevel = request.getDetailLevelEnum();
String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно";
// Build context for AI
String context = String.format(
"Продукт/услуга: %s\n" +
"Локация: %s\n" +
"Тип клиентов: %s\n" +
"Уникальные особенности: %s\n" +
"Тип анализа: %s\n",
request.getProduct(),
request.getLocation(),
request.getClient(),
request.getDifferentiator(),
analysisTypeName);
// Build context for AI with all new fields
StringBuilder contextBuilder = new StringBuilder();
contextBuilder.append("Ниша бизнеса: ").append(request.getBusinessNiche()).append("\n");
contextBuilder.append("Продукт/услуга: ").append(request.getProduct()).append("\n");
contextBuilder.append("Целевая аудитория: ").append(request.getTargetAudience()).append("\n");
contextBuilder.append("Регион: ").append(request.getRegion()).append("\n");
contextBuilder.append("Цель на 6-12 месяцев: ").append(request.getGoal()).append("\n");
contextBuilder.append("Уровень детализации: ").append(detailLevelName).append("\n");
if (request.getStrongSide() != null && !request.getStrongSide().trim().isEmpty()) {
contextBuilder.append("Сильная сторона: ").append(request.getStrongSide()).append("\n");
}
if (request.getWeakSide() != null && !request.getWeakSide().trim().isEmpty()) {
contextBuilder.append("Слабая сторона: ").append(request.getWeakSide()).append("\n");
}
contextBuilder.append("Тип анализа: ").append(analysisTypeName).append("\n");
String context = contextBuilder.toString();
// Generate analysis based on type
String analysisContent = generateAnalysisByType(context, analysisType);
// Generate analysis based on type and detail level
String analysisContent = generateAnalysisByType(context, analysisType, detailLevel);
report.put("analysisType", analysisTypeName);
report.put("detailLevel", detailLevelName);
report.put("summary", analysisContent != null ? analysisContent : "Анализ не удалось сгенерировать.");
// Generate target audience description
String audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию (1-2 абзаца). " +
"Укажи, какие каналы коммуникации наиболее подходят для этой аудитории. " +
"Ответ должен быть на русском языке.\n\n" + context;
// Generate target audience description (adjusted by detail level)
String audiencePrompt;
if (detailLevel == DetailLevel.КРАТКО) {
audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию кратко (1 абзац). " +
"Укажи основные каналы коммуникации. Ответ должен быть на русском языке.\n\n" + context;
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
audiencePrompt = "На основе информации о бизнесе проведи детальный анализ целевой аудитории (3-4 абзаца). "
+
"Включи демографические характеристики, психографический профиль, потребности, поведенческие паттерны. "
+
"Укажи, какие каналы коммуникации наиболее подходят для этой аудитории и почему. " +
"Ответ должен быть на русском языке.\n\n" + context;
} else {
audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию (1-2 абзаца). " +
"Укажи, какие каналы коммуникации наиболее подходят для этой аудитории. " +
"Ответ должен быть на русском языке.\n\n" + context;
}
String audienceDescription = openAIAnalyticsService.generateWithInstruction(context, audiencePrompt, "ru");
// Extract channels from description or generate separately
@@ -164,22 +189,50 @@ public class MarketingAnalysisService {
channels.isEmpty() ? Arrays.asList("Instagram", "LinkedIn", "Telegram") : channels);
report.put("targetAudience", targetAudience);
// Generate recommendations
String recommendationsPrompt = "На основе информации о бизнесе сформулируй 4-6 практических рекомендаций для маркетинговой стратегии. "
+
"Каждая рекомендация должна быть конкретной и применимой. " +
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
// Generate recommendations (adjusted by detail level)
String recommendationsPrompt;
if (detailLevel == DetailLevel.КРАТКО) {
recommendationsPrompt = "На основе информации о бизнесе сформулируй 3-4 краткие практические рекомендации для маркетинговой стратегии. "
+
"Каждая рекомендация должна быть конкретной. " +
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
recommendationsPrompt = "На основе информации о бизнесе сформулируй 6-8 детальных практических рекомендаций для маркетинговой стратегии. "
+
"Каждая рекомендация должна быть конкретной, применимой и содержать обоснование. " +
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
} else {
recommendationsPrompt = "На основе информации о бизнесе сформулируй 4-6 практических рекомендаций для маркетинговой стратегии. "
+
"Каждая рекомендация должна быть конкретной и применимой. " +
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
}
String recommendationsStr = openAIAnalyticsService.generateWithInstruction(context, recommendationsPrompt,
"ru");
List<String> recommendations = parseRecommendations(recommendationsStr);
report.put("recommendations",
recommendations.isEmpty() ? Arrays.asList("Рекомендации не удалось сгенерировать.") : recommendations);
// Generate strategy
String strategyPrompt = "На основе информации о бизнесе создай краткую маркетинговую стратегию. " +
"Укажи рекомендуемую длительность кампании (например, '2 недели', '1 месяц'), " +
"3-5 каналов коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " +
"Ответ должен быть структурированным текстом на русском языке.\n\n" + context;
// Generate strategy (adjusted by detail level)
String strategyPrompt;
if (detailLevel == DetailLevel.КРАТКО) {
strategyPrompt = "На основе информации о бизнесе создай краткую маркетинговую стратегию. " +
"Укажи рекомендуемую длительность кампании (например, '2 недели', '1 месяц'), " +
"3-4 канала коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " +
"Ответ должен быть кратким структурированным текстом на русском языке.\n\n" + context;
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
strategyPrompt = "На основе информации о бизнесе создай детальную маркетинговую стратегию. " +
"Укажи рекомендуемую длительность кампании с обоснованием (например, '2 недели', '1 месяц'), " +
"5-7 каналов коммуникации с описанием их преимуществ, типы контента (например, 'посты', 'сторис', 'баннеры') "
+
"и примерный план действий. Ответ должен быть подробным структурированным текстом на русском языке.\n\n"
+ context;
} else {
strategyPrompt = "На основе информации о бизнесе создай маркетинговую стратегию. " +
"Укажи рекомендуемую длительность кампании (например, '2 недели', '1 месяц'), " +
"3-5 каналов коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " +
"Ответ должен быть структурированным текстом на русском языке.\n\n" + context;
}
String strategyText = openAIAnalyticsService.generateWithInstruction(context, strategyPrompt, "ru");
Map<String, Object> strategy = parseStrategy(strategyText, channels);
@@ -188,49 +241,60 @@ public class MarketingAnalysisService {
return report;
}
private String generateAnalysisByType(String context, AnalysisType analysisType) {
private String generateAnalysisByType(String context, AnalysisType analysisType, DetailLevel detailLevel) {
if (analysisType == null) {
analysisType = AnalysisType.РЫНОК; // Default
}
if (detailLevel == null) {
detailLevel = DetailLevel.СТАНДАРТНО; // Default
}
String prompt;
String lengthInstruction;
// Adjust length based on detail level
if (detailLevel == DetailLevel.КРАТКО) {
lengthInstruction = "Ответ должен быть кратким структурированным текстом на русском языке (1-2 абзаца).";
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
lengthInstruction = "Ответ должен быть подробным структурированным текстом на русском языке (5-8 абзацев) с глубоким анализом.";
} else {
lengthInstruction = "Ответ должен быть структурированным текстом на русском языке (3-5 абзацев).";
}
switch (analysisType) {
case РЫНОК:
prompt = "Проведи детальный анализ рынка на основе предоставленной информации о бизнесе. " +
prompt = "Проведи анализ рынка на основе предоставленной информации о бизнесе. " +
"Включи: размер рынка, динамику роста, основные сегменты, тренды и перспективы развития. " +
"Ответ должен быть структурированным текстом на русском языке (3-5 абзацев).\n\n" + context;
lengthInstruction + "\n\n" + context;
break;
case КОНКУРЕНТЫ:
prompt = "Проведи анализ конкурентов на основе предоставленной информации о бизнесе. " +
"Включи: основных конкурентов, их сильные и слабые стороны, позиционирование, " +
"ценовую политику и маркетинговые стратегии. Ответ должен быть структурированным текстом на русском языке (3-5 абзацев).\n\n"
+ context;
"ценовую политику и маркетинговые стратегии. " + lengthInstruction + "\n\n" + context;
break;
case ЦА:
prompt = "Проведи детальный анализ целевой аудитории на основе предоставленной информации о бизнесе. " +
prompt = "Проведи анализ целевой аудитории на основе предоставленной информации о бизнесе. " +
"Включи: демографические характеристики, психографический профиль, потребности и боли, " +
"поведенческие паттерны и предпочтения. Ответ должен быть структурированным текстом на русском языке (3-5 абзацев).\n\n"
+ context;
"поведенческие паттерны и предпочтения. " + lengthInstruction + "\n\n" + context;
break;
case КАНАЛЫ:
prompt = "Проведи анализ маркетинговых каналов на основе предоставленной информации о бизнесе. " +
"Включи: оценку эффективности различных каналов коммуникации, рекомендации по выбору каналов, "
+
"особенности использования каждого канала и бюджетные рекомендации. Ответ должен быть структурированным текстом на русском языке (3-5 абзацев).\n\n"
+ context;
"особенности использования каждого канала и бюджетные рекомендации. " + lengthInstruction
+ "\n\n" + context;
break;
case SWOT:
prompt = "Проведи SWOT-анализ на основе предоставленной информации о бизнесе. " +
"Включи детальный анализ: Сильных сторон (Strengths), Слабых сторон (Weaknesses), " +
"Возможностей (Opportunities) и Угроз (Threats). Ответ должен быть структурированным текстом на русском языке, "
+
"с четким разделением по каждому разделу SWOT (3-5 абзацев).\n\n" + context;
"Включи анализ: Сильных сторон (Strengths), Слабых сторон (Weaknesses), " +
"Возможностей (Opportunities) и Угроз (Threats). " +
"Ответ должен быть структурированным текстом на русском языке, " +
"с четким разделением по каждому разделу SWOT. " + lengthInstruction + "\n\n" + context;
break;
default:
prompt = "На основе следующей информации о бизнесе создай краткое резюме маркетингового анализа (2-3 абзаца). "
+
prompt = "На основе следующей информации о бизнесе создай резюме маркетингового анализа. " +
"Выдели ключевые возможности и особенности бизнеса. " +
"Ответ должен быть на русском языке, деловым стилем.\n\n" + context;
lengthInstruction + "\n\n" + context;
}
return openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
@@ -329,10 +393,23 @@ public class MarketingAnalysisService {
markdown.append("# Маркетинговый анализ\n\n");
markdown.append("## Информация о бизнесе\n\n");
markdown.append("- **Ниша бизнеса:** ").append(request.getBusinessNiche()).append("\n");
markdown.append("- **Продукт/услуга:** ").append(request.getProduct()).append("\n");
markdown.append("- **Локация:** ").append(request.getLocation()).append("\n");
markdown.append("- **Тип клиентов:** ").append(request.getClient()).append("\n");
markdown.append("- **Уникальные особенности:** ").append(request.getDifferentiator()).append("\n");
markdown.append("- **Целевая аудитория:** ").append(request.getTargetAudience()).append("\n");
markdown.append("- **Регион:** ").append(request.getRegion()).append("\n");
markdown.append("- **Цель на 6-12 месяцев:** ").append(request.getGoal()).append("\n");
String detailLevelName = (String) reportData.get("detailLevel");
if (detailLevelName != null) {
markdown.append("- **Уровень детализации:** ").append(detailLevelName).append("\n");
}
if (request.getStrongSide() != null && !request.getStrongSide().trim().isEmpty()) {
markdown.append("- **Сильная сторона:** ").append(request.getStrongSide()).append("\n");
}
if (request.getWeakSide() != null && !request.getWeakSide().trim().isEmpty()) {
markdown.append("- **Слабая сторона:** ").append(request.getWeakSide()).append("\n");
}
String analysisTypeName = (String) reportData.get("analysisType");
if (analysisTypeName != null) {
@@ -0,0 +1,21 @@
package kz.konturai.parser.validator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import kz.konturai.parser.model.DetailLevel;
public class DetailLevelValidator implements ConstraintValidator<ValidDetailLevel, String> {
@Override
public void initialize(ValidDetailLevel constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.trim().isEmpty()) {
return true; // @NotBlank will handle null/empty
}
return DetailLevel.fromString(value) != null;
}
}
@@ -0,0 +1,44 @@
package kz.konturai.parser.validator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class RegionValidator implements ConstraintValidator<ValidRegion, String> {
private static final List<String> VALID_REGIONS = Arrays.asList(
"Алматы",
"Астана",
"Шымкент",
"Караганда",
"Актобе",
"Тараз",
"Павлодар",
"Усть-Каменогорск",
"Семей",
"Костанай",
"Кызылорда",
"Уральск",
"Петропавловск",
"Атырау",
"Актау",
"Туркестан",
"Кокшетау",
"Талдыкорган",
"Экибастуз",
"Рудный");
@Override
public void initialize(ValidRegion constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.trim().isEmpty()) {
return true; // @NotBlank will handle null/empty
}
return VALID_REGIONS.contains(value);
}
}
@@ -0,0 +1,22 @@
package kz.konturai.parser.validator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Constraint(validatedBy = DetailLevelValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidDetailLevel {
String message() default "Поле 'detailLevel' должно быть одним из: КРАТКО, СТАНДАРТНО, ПОДРОБНО";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,22 @@
package kz.konturai.parser.validator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Constraint(validatedBy = RegionValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidRegion {
String message() default "Поле 'region' должно быть одним из допустимых городов Казахстана";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}