my analysys

This commit is contained in:
root
2025-12-04 23:49:02 +05:00
parent 16609d3eb2
commit 4fa0a01c61
4 changed files with 207 additions and 30 deletions
@@ -0,0 +1,115 @@
package kz.konturai.parser.config;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class MongoConfig {
private static final Logger logger = LoggerFactory.getLogger(MongoConfig.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
@Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new StringToMapConverter());
converters.add(new DocumentToMapConverter());
converters.add(new MapToStringConverter());
return new MongoCustomConversions(converters);
}
/**
* Конвертер для чтения: String -> Map<String, Object>
* Обрабатывает случай, когда target_audience хранится как строка в MongoDB
*/
@ReadingConverter
public static class StringToMapConverter implements Converter<String, Map<String, Object>> {
@Override
public Map<String, Object> convert(String source) {
logger.debug("StringToMapConverter: Converting string to Map: {}", source);
if (source == null || source.trim().isEmpty()) {
logger.debug("StringToMapConverter: Source is null or empty, returning empty Map");
return new HashMap<>();
}
try {
// Если это уже JSON строка, парсим её
if (source.trim().startsWith("{") || source.trim().startsWith("[")) {
Map<String, Object> result = objectMapper.readValue(source,
new TypeReference<Map<String, Object>>() {
});
logger.debug("StringToMapConverter: Successfully parsed JSON string to Map");
return result;
}
// Если это простая строка, создаём пустой Map
logger.warn("StringToMapConverter: target_audience is a plain string, not JSON: {}", source);
return new HashMap<>();
} catch (Exception e) {
logger.error("StringToMapConverter: Error converting string to Map for target_audience: {}", source, e);
return new HashMap<>();
}
}
}
/**
* Конвертер для чтения: Document -> Map<String, Object>
* Обрабатывает случай, когда MongoDB возвращает Document вместо Map
*/
@ReadingConverter
public static class DocumentToMapConverter implements Converter<Document, Map<String, Object>> {
@Override
public Map<String, Object> convert(Document source) {
logger.debug("DocumentToMapConverter: Converting Document to Map");
if (source == null) {
logger.debug("DocumentToMapConverter: Source is null, returning empty Map");
return new HashMap<>();
}
Map<String, Object> result = new HashMap<>();
for (String key : source.keySet()) {
Object value = source.get(key);
// Рекурсивно конвертируем вложенные Document
if (value instanceof Document) {
result.put(key, convert((Document) value));
} else {
result.put(key, value);
}
}
logger.debug("DocumentToMapConverter: Successfully converted Document to Map with {} keys", result.size());
return result;
}
}
/**
* Конвертер для записи: Map<String, Object> -> String
* Не используется, но может быть полезен для обратной совместимости
*/
@WritingConverter
public static class MapToStringConverter implements Converter<Map<String, Object>, String> {
@Override
public String convert(Map<String, Object> source) {
if (source == null || source.isEmpty()) {
return null;
}
try {
return objectMapper.writeValueAsString(source);
} catch (Exception e) {
logger.error("Error converting Map to String: {}", e.getMessage(), e);
return null;
}
}
}
}
@@ -15,6 +15,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.validation.Valid;
import java.io.InputStream;
@@ -29,6 +31,8 @@ import java.util.stream.Collectors;
@RequestMapping("/api/marketing/analysis")
public class MarketingController {
private static final Logger logger = LoggerFactory.getLogger(MarketingController.class);
private final MarketingAnalysisService marketingAnalysisService;
private final MarketingStrategyService marketingStrategyService;
private final MinIOService minIOService;
@@ -50,9 +54,17 @@ public class MarketingController {
private String extractUserIdFromHeader(String authHeader) {
if (authHeader == null || authHeader.isEmpty()) {
logger.debug("Authorization header is null or empty");
return null;
}
try {
String userId = jwtService.extractUserIdFromHeader(authHeader);
logger.debug("Extracted userId from header: {}", userId);
return userId;
} catch (Exception e) {
logger.error("Error extracting userId from header: {}", e.getMessage(), e);
return null;
}
return jwtService.extractUserIdFromHeader(authHeader);
}
private ResponseEntity<?> unauthorizedResponse() {
@@ -91,10 +103,14 @@ public class MarketingController {
analysis.getId(),
"processing",
estimatedCompletion,
"Анализ типа '" +
(analysis.getAnalysisType() != null && !analysis.getAnalysisType().isEmpty()
? analysis.getAnalysisType().get(0) : "неизвестно") +
"' запущен успешно. Результаты будут готовы в течение 5-10 минут."))
"Анализ типа '" +
(analysis.getAnalysisType() != null && !analysis
.getAnalysisType().isEmpty()
? analysis.getAnalysisType()
.get(0)
: "неизвестно")
+
"' запущен успешно. Результаты будут готовы в течение 5-10 минут."))
.collect(Collectors.toList());
return ResponseEntity.ok(ApiResponse.success(
"Создано анализов: " + analyses.size() + ". Все анализы запущены успешно.",
@@ -327,17 +343,41 @@ public class MarketingController {
public ResponseEntity<?> getMyAnalyses(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
return unauthorizedResponse();
try {
logger.info("Getting analyses for user");
String userId = extractUserIdFromHeader(authHeader);
if (userId == null) {
logger.warn("Unauthorized request to /my endpoint");
return unauthorizedResponse();
}
logger.debug("Fetching analyses for userId: {}", userId);
List<MarketingAnalysis> analyses = marketingAnalysisService.getUserAnalyses(userId);
logger.debug("Found {} analyses for userId: {}", analyses.size(), userId);
List<AnalysisHistoryResponse> responseList = analyses.stream()
.map(analysis -> {
try {
return convertToHistoryResponse(analysis);
} catch (Exception e) {
logger.error("Error converting analysis {} to history response: {}",
analysis != null ? analysis.getId() : "null",
e.getMessage(), e);
throw e;
}
})
.collect(Collectors.toList());
logger.info("Successfully retrieved {} analyses for userId: {}", responseList.size(), userId);
return ResponseEntity.ok(ApiResponse.success(responseList));
} catch (Exception e) {
logger.error("Error in getMyAnalyses endpoint: {}", e.getMessage(), e);
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла внутренняя ошибка сервера. Попробуйте позже.");
return ResponseEntity.status(500)
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
}
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")
@@ -422,11 +462,16 @@ public class MarketingController {
}
private AnalysisHistoryResponse convertToHistoryResponse(MarketingAnalysis analysis) {
if (analysis == null) {
logger.error("Attempted to convert null analysis to history response");
throw new IllegalArgumentException("Analysis cannot be null");
}
AnalysisHistoryResponse response = new AnalysisHistoryResponse();
response.setAnalysisId(analysis.getId());
response.setBusinessNiche(analysis.getBusinessNiche());
response.setProduct(analysis.getProduct());
// Convert targetAudience Map to String representation
Map<String, Object> targetAudienceMap = analysis.getTargetAudience();
if (targetAudienceMap != null) {
@@ -435,14 +480,16 @@ public class MarketingController {
@SuppressWarnings("unchecked")
List<String> genders = (List<String>) targetAudienceMap.get("genders");
if (genders != null && !genders.isEmpty()) {
audienceBuilder.append("Гендер: ").append(String.join(", ", genders)).append(". ");
audienceBuilder.append("Гендер: ").append(String.join(", ", genders))
.append(". ");
}
}
if (targetAudienceMap.containsKey("ageRanges")) {
@SuppressWarnings("unchecked")
List<String> ageRanges = (List<String>) targetAudienceMap.get("ageRanges");
if (ageRanges != null && !ageRanges.isEmpty()) {
audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges)).append(". ");
audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges))
.append(". ");
}
}
if (targetAudienceMap.containsKey("types")) {
@@ -454,24 +501,24 @@ public class MarketingController {
}
response.setTargetAudience(audienceBuilder.toString().trim());
}
// Convert region List to String
List<String> regions = analysis.getRegion();
if (regions != null && !regions.isEmpty()) {
response.setRegion(String.join(", ", regions));
}
response.setGoal(analysis.getGoal());
response.setDetailLevel(analysis.getDetailLevel());
response.setStrongSide(analysis.getStrongSide());
response.setWeakSide(analysis.getWeakSide());
// Convert analysisType List to String
List<String> analysisTypes = analysis.getAnalysisType();
if (analysisTypes != null && !analysisTypes.isEmpty()) {
response.setAnalysisType(String.join(", ", analysisTypes));
}
response.setStatus(analysis.getStatus());
response.setUserId(analysis.getUserId());
response.setCreatedAt(analysis.getCreatedAt());
@@ -593,6 +640,7 @@ public class MarketingController {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<ErrorResponse>> handleGenericException(Exception e) {
logger.error("Unhandled exception in MarketingController: {}", e.getMessage(), e);
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"Произошла внутренняя ошибка сервера. Попробуйте позже.");
@@ -91,7 +91,8 @@ public class MarketingAnalysisService {
logger.info("Starting marketing analysis processing for ID: {}", analysisId);
// Create a modified request with single analysis type for this analysis
MarketingAnalysisRequest singleTypeRequest = createSingleTypeRequest(request, analysis.getAnalysisType().get(0));
MarketingAnalysisRequest singleTypeRequest = createSingleTypeRequest(request,
analysis.getAnalysisType().get(0));
// Generate marketing report
Map<String, Object> reportData = generateMarketingReport(singleTypeRequest);
@@ -136,7 +137,8 @@ public class MarketingAnalysisService {
}
}
private MarketingAnalysisRequest createSingleTypeRequest(MarketingAnalysisRequest originalRequest, String analysisType) {
private MarketingAnalysisRequest createSingleTypeRequest(MarketingAnalysisRequest originalRequest,
String analysisType) {
MarketingAnalysisRequest singleTypeRequest = new MarketingAnalysisRequest();
singleTypeRequest.setBusinessNiche(originalRequest.getBusinessNiche());
singleTypeRequest.setProduct(originalRequest.getProduct());
@@ -164,7 +166,7 @@ public class MarketingAnalysisService {
StringBuilder contextBuilder = new StringBuilder();
contextBuilder.append("Ниша бизнеса: ").append(request.getBusinessNiche()).append("\n");
contextBuilder.append("Продукт/услуга: ").append(request.getProduct()).append("\n");
// Format target audience
Map<String, Object> targetAudienceData = request.getTargetAudience();
if (targetAudienceData != null) {
@@ -192,7 +194,7 @@ public class MarketingAnalysisService {
}
contextBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n");
}
// Format regions
List<String> regions = request.getRegion();
if (regions != null && !regions.isEmpty()) {
@@ -454,7 +456,7 @@ public class MarketingAnalysisService {
markdown.append("## Информация о бизнесе\n\n");
markdown.append("- **Ниша бизнеса:** ").append(request.getBusinessNiche()).append("\n");
markdown.append("- **Продукт/услуга:** ").append(request.getProduct()).append("\n");
// Format target audience
Map<String, Object> targetAudienceData = request.getTargetAudience();
if (targetAudienceData != null) {
@@ -482,13 +484,13 @@ public class MarketingAnalysisService {
}
markdown.append("- **Целевая аудитория:** ").append(audienceBuilder.toString().trim()).append("\n");
}
// Format regions
List<String> regions = request.getRegion();
if (regions != null && !regions.isEmpty()) {
markdown.append("- **Регион:** ").append(String.join(", ", regions)).append("\n");
}
markdown.append("- **Цель на 6-12 месяцев:** ").append(request.getGoal()).append("\n");
String detailLevelName = (String) reportData.get("detailLevel");
@@ -595,7 +597,15 @@ public class MarketingAnalysisService {
}
public List<MarketingAnalysis> getUserAnalyses(String userId) {
return repository.findByUserIdOrderByCreatedAtDesc(userId);
logger.debug("Fetching analyses for userId: {}", userId);
try {
List<MarketingAnalysis> analyses = repository.findByUserIdOrderByCreatedAtDesc(userId);
logger.debug("Found {} analyses for userId: {}", analyses.size(), userId);
return analyses;
} catch (Exception e) {
logger.error("Error fetching analyses for userId {}: {}", userId, e.getMessage(), e);
throw e;
}
}
private MarketingAnalysisResult.MarketingReport buildReportFromData(Map<String, Object> reportData,
@@ -53,6 +53,10 @@ logging.level.org.springframework.data.mongodb=INFO
logging.level.com.mongodb=WARN
logging.level.kz.konturai.parser.service.OllamaAnalyticsService=INFO
logging.level.kz.konturai.parser.controller.MarketingController=DEBUG
logging.level.kz.konturai.parser.service.MarketingAnalysisService=DEBUG
logging.level.kz.konturai.parser.config.MongoConfig=DEBUG
logging.level.org.springframework.data.mongodb.core.convert=DEBUG
# OpenAI Configuration
openai.api.key=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