Merge branch 'main' of https://github.com/dg04-hi/hi-backend into main
# Conflicts: # store/src/main/java/com/ktds/hi/store/biz/service/TagService.java
This commit is contained in:
commit
18efa9f2eb
@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import jakarta.validation.constraints.*;
|
import jakarta.validation.constraints.*;
|
||||||
@ -151,8 +152,10 @@ public class AnalyticsController {
|
|||||||
@Parameter(description = "AI 피드백 ID", required = true)
|
@Parameter(description = "AI 피드백 ID", required = true)
|
||||||
@PathVariable @NotNull Long feedbackId,
|
@PathVariable @NotNull Long feedbackId,
|
||||||
@RequestBody ActionPlanCreateRequest request,
|
@RequestBody ActionPlanCreateRequest request,
|
||||||
|
@AuthenticationPrincipal long id,
|
||||||
HttpServletRequest httpRequest) {
|
HttpServletRequest httpRequest) {
|
||||||
|
|
||||||
|
System.out.println("test => " + id);
|
||||||
|
|
||||||
// validation 체크
|
// validation 체크
|
||||||
if (request.getActionPlanSelect() == null || request.getActionPlanSelect().isEmpty()) {
|
if (request.getActionPlanSelect() == null || request.getActionPlanSelect().isEmpty()) {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ spring:
|
|||||||
# Redis 설정
|
# Redis 설정
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: ${REDIS_HOST:localhost} //로컬
|
host: ${REDIS_HOST:localhost}
|
||||||
port: ${REDIS_PORT:6379}
|
port: ${REDIS_PORT:6379}
|
||||||
password: ${REDIS_PASSWORD:}
|
password: ${REDIS_PASSWORD:}
|
||||||
timeout: 2000ms
|
timeout: 2000ms
|
||||||
|
|||||||
@ -29,10 +29,28 @@ public class TagService implements TagUseCase {
|
|||||||
private final TagRepositoryPort tagRepositoryPort;
|
private final TagRepositoryPort tagRepositoryPort;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TopClickedTagResponse> getTopClickedTags(Integer storeId) {
|
public List<AllTagResponse> getAllTags() {
|
||||||
|
log.info("모든 활성화된 태그 목록 조회 시작");
|
||||||
|
|
||||||
|
List<Tag> tags = tagRepositoryPort.findAllActiveTags();
|
||||||
|
|
||||||
|
List<AllTagResponse> responses = tags.stream()
|
||||||
|
.map(tag -> AllTagResponse.builder()
|
||||||
|
.id(tag.getId())
|
||||||
|
.tagCategory(tag.getTagCategory().name())
|
||||||
|
.tagName(tag.getTagName())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("모든 활성화된 태그 목록 조회 완료: count={}", responses.size());
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TopClickedTagResponse> getTopClickedTags() {
|
||||||
log.info("가장 많이 클릭된 상위 5개 태그 조회 시작");
|
log.info("가장 많이 클릭된 상위 5개 태그 조회 시작");
|
||||||
|
|
||||||
List<Tag> topTags = tagRepositoryPort.findTopClickedTags(storeId);
|
List<Tag> topTags = tagRepositoryPort.findTopClickedTags();
|
||||||
|
|
||||||
AtomicInteger rank = new AtomicInteger(1);
|
AtomicInteger rank = new AtomicInteger(1);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.ktds.hi.store.biz.usecase.in;
|
package com.ktds.hi.store.biz.usecase.in;
|
||||||
|
|
||||||
|
import com.ktds.hi.store.infra.dto.response.AllTagResponse;
|
||||||
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -13,6 +14,13 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface TagUseCase {
|
public interface TagUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 활성화된 태그 목록 조회
|
||||||
|
*
|
||||||
|
* @return 모든 태그 목록
|
||||||
|
*/
|
||||||
|
List<AllTagResponse> getAllTags();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 가장 많이 클릭된 상위 5개 태그 조회
|
* 가장 많이 클릭된 상위 5개 태그 조회
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ktds.hi.store.infra.controller;
|
package com.ktds.hi.store.infra.controller;
|
||||||
|
|
||||||
import com.ktds.hi.store.biz.usecase.in.TagUseCase;
|
import com.ktds.hi.store.biz.usecase.in.TagUseCase;
|
||||||
|
import com.ktds.hi.store.infra.dto.response.AllTagResponse;
|
||||||
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
||||||
import com.ktds.hi.common.dto.ApiResponse;
|
import com.ktds.hi.common.dto.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -26,6 +27,18 @@ public class TagController {
|
|||||||
|
|
||||||
private final TagUseCase tagUseCase;
|
private final TagUseCase tagUseCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 활성화된 태그 목록 조회 API
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "모든 태그 조회", description = "활성화된 모든 태그 목록을 조회합니다.")
|
||||||
|
public ResponseEntity<ApiResponse<List<AllTagResponse>>> getAllTags() {
|
||||||
|
|
||||||
|
List<AllTagResponse> tags = tagUseCase.getAllTags();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(tags));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 가장 많이 클릭된 상위 5개 태그 조회 API
|
* 가장 많이 클릭된 상위 5개 태그 조회 API
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.ktds.hi.store.infra.dto.response;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 태그 응답 DTO
|
||||||
|
* 태그 기본 정보를 담는 응답 클래스
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class AllTagResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 태그 ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 태그 카테고리
|
||||||
|
*/
|
||||||
|
private String tagCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 태그명
|
||||||
|
*/
|
||||||
|
private String tagName;
|
||||||
|
}
|
||||||
@ -42,6 +42,7 @@ public class CacheAdapter implements CachePort {
|
|||||||
log.debug("캐시 저장: key={}, ttl={}초", key, ttlSeconds);
|
log.debug("캐시 저장: key={}, ttl={}초", key, ttlSeconds);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("캐시 저장 실패: key={}, error={}", key, e.getMessage());
|
log.warn("캐시 저장 실패: key={}, error={}", key, e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.ktds.hi.store.infra.gateway;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ktds.hi.store.biz.usecase.out.CachePort;
|
||||||
import com.ktds.hi.store.biz.usecase.out.ExternalPlatformPort;
|
import com.ktds.hi.store.biz.usecase.out.ExternalPlatformPort;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -15,6 +16,8 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +32,7 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final CachePort cachePort;
|
||||||
|
|
||||||
@Value("${external.kakao.crawler.url:http://localhost:9001}")
|
@Value("${external.kakao.crawler.url:http://localhost:9001}")
|
||||||
private String kakaoCrawlerUrl;
|
private String kakaoCrawlerUrl;
|
||||||
@ -147,11 +151,11 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 카카오 응답 파싱 및 Redis 저장 (단순화된 안정적인 방식)
|
* 카카오 응답 파싱 및 Redis 저장 (CacheAdapter 활용)
|
||||||
*/
|
*/
|
||||||
private int parseAndStoreToRedis(Long storeId, String platform, String responseBody) {
|
private int parseAndStoreToRedis(Long storeId, String platform, String responseBody) {
|
||||||
try {
|
try {
|
||||||
log.info("카카오 API 응답: {}", responseBody);
|
log.info("카카오 API 응답 파싱 시작: storeId={}", storeId);
|
||||||
|
|
||||||
if (responseBody == null || responseBody.trim().isEmpty()) {
|
if (responseBody == null || responseBody.trim().isEmpty()) {
|
||||||
log.warn("카카오 응답이 비어있음: storeId={}", storeId);
|
log.warn("카카오 응답이 비어있음: storeId={}", storeId);
|
||||||
@ -160,20 +164,35 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
|
|
||||||
JsonNode root = objectMapper.readTree(responseBody);
|
JsonNode root = objectMapper.readTree(responseBody);
|
||||||
|
|
||||||
// 🔥 실제 카카오 응답 구조에 맞는 파싱
|
// 실제 카카오 응답 구조 확인
|
||||||
if (!root.has("success") || !root.get("success").asBoolean()) {
|
if (!root.has("success") || !root.get("success").asBoolean()) {
|
||||||
log.warn("카카오 API 응답 실패: {}", responseBody);
|
log.warn("카카오 API 호출 실패: {}", root.path("message").asText());
|
||||||
updateSyncStatus(storeId, platform, "FAILED", 0);
|
updateSyncStatusWithCache(storeId, platform, "FAILED", 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reviews 배열 직접 접근
|
||||||
JsonNode reviewsNode = root.get("reviews");
|
JsonNode reviewsNode = root.get("reviews");
|
||||||
if (reviewsNode == null || !reviewsNode.isArray()) {
|
if (reviewsNode == null || !reviewsNode.isArray()) {
|
||||||
log.warn("카카오 응답에 reviews 배열이 없음");
|
log.warn("카카오 응답에 reviews 배열이 없음");
|
||||||
updateSyncStatus(storeId, platform, "SUCCESS", 0);
|
updateSyncStatusWithCache(storeId, platform, "SUCCESS", 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 매장 정보 파싱 (옵션)
|
||||||
|
Map<String, Object> storeInfo = null;
|
||||||
|
if (root.has("store_info")) {
|
||||||
|
JsonNode storeInfoNode = root.get("store_info");
|
||||||
|
storeInfo = new HashMap<>();
|
||||||
|
storeInfo.put("id", storeInfoNode.path("id").asText());
|
||||||
|
storeInfo.put("name", storeInfoNode.path("name").asText());
|
||||||
|
storeInfo.put("category", storeInfoNode.path("category").asText());
|
||||||
|
storeInfo.put("rating", storeInfoNode.path("rating").asText());
|
||||||
|
storeInfo.put("reviewCount", storeInfoNode.path("review_count").asText());
|
||||||
|
storeInfo.put("status", storeInfoNode.path("status").asText());
|
||||||
|
storeInfo.put("address", storeInfoNode.path("address").asText());
|
||||||
|
}
|
||||||
|
|
||||||
// 리뷰 데이터 파싱
|
// 리뷰 데이터 파싱
|
||||||
List<Map<String, Object>> reviews = new ArrayList<>();
|
List<Map<String, Object>> reviews = new ArrayList<>();
|
||||||
for (JsonNode reviewNode : reviewsNode) {
|
for (JsonNode reviewNode : reviewsNode) {
|
||||||
@ -181,23 +200,35 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
|
|
||||||
// 기본 정보
|
// 기본 정보
|
||||||
review.put("reviewId", generateReviewId(reviewNode));
|
review.put("reviewId", generateReviewId(reviewNode));
|
||||||
review.put("content", reviewNode.path("content").asText(""));
|
|
||||||
review.put("rating", reviewNode.path("rating").asDouble(0.0));
|
|
||||||
review.put("reviewerName", reviewNode.path("reviewer_name").asText(""));
|
review.put("reviewerName", reviewNode.path("reviewer_name").asText(""));
|
||||||
review.put("createdAt", reviewNode.path("date").asText(""));
|
|
||||||
review.put("platform", platform);
|
|
||||||
|
|
||||||
// 카카오 특화 정보
|
|
||||||
review.put("reviewerLevel", reviewNode.path("reviewer_level").asText(""));
|
review.put("reviewerLevel", reviewNode.path("reviewer_level").asText(""));
|
||||||
|
review.put("rating", reviewNode.path("rating").asInt(0));
|
||||||
|
review.put("date", reviewNode.path("date").asText(""));
|
||||||
|
review.put("content", reviewNode.path("content").asText(""));
|
||||||
review.put("likes", reviewNode.path("likes").asInt(0));
|
review.put("likes", reviewNode.path("likes").asInt(0));
|
||||||
review.put("photoCount", reviewNode.path("photo_count").asInt(0));
|
review.put("photoCount", reviewNode.path("photo_count").asInt(0));
|
||||||
review.put("hasPhotos", reviewNode.path("has_photos").asBoolean(false));
|
review.put("hasPhotos", reviewNode.path("has_photos").asBoolean(false));
|
||||||
|
review.put("platform", platform);
|
||||||
|
|
||||||
// 배지 정보
|
// 리뷰어 통계
|
||||||
|
if (reviewNode.has("reviewer_stats")) {
|
||||||
|
JsonNode statsNode = reviewNode.get("reviewer_stats");
|
||||||
|
Map<String, Object> reviewerStats = new HashMap<>();
|
||||||
|
reviewerStats.put("reviews", statsNode.path("reviews").asInt(0));
|
||||||
|
reviewerStats.put("averageRating", statsNode.path("average_rating").asDouble(0.0));
|
||||||
|
reviewerStats.put("followers", statsNode.path("followers").asInt(0));
|
||||||
|
review.put("reviewerStats", reviewerStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 배지
|
||||||
if (reviewNode.has("badges") && reviewNode.get("badges").isArray()) {
|
if (reviewNode.has("badges") && reviewNode.get("badges").isArray()) {
|
||||||
List<String> badges = new ArrayList<>();
|
List<String> badges = new ArrayList<>();
|
||||||
reviewNode.get("badges").forEach(badge -> badges.add(badge.asText()));
|
for (JsonNode badgeNode : reviewNode.get("badges")) {
|
||||||
|
badges.add(badgeNode.asText());
|
||||||
|
}
|
||||||
review.put("badges", badges);
|
review.put("badges", badges);
|
||||||
|
} else {
|
||||||
|
review.put("badges", new ArrayList<String>());
|
||||||
}
|
}
|
||||||
|
|
||||||
reviews.add(review);
|
reviews.add(review);
|
||||||
@ -205,41 +236,20 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
|
|
||||||
log.info("파싱된 리뷰 수: {}", reviews.size());
|
log.info("파싱된 리뷰 수: {}", reviews.size());
|
||||||
|
|
||||||
// Redis 저장 (단순한 방식)
|
// CacheAdapter를 통한 Redis 저장
|
||||||
saveToRedis(storeId, platform, reviews);
|
saveToRedisWithCache(storeId, platform, reviews, storeInfo);
|
||||||
|
|
||||||
// 동기화 상태 업데이트
|
// 동기화 상태 업데이트
|
||||||
updateSyncStatus(storeId, platform, "SUCCESS", reviews.size());
|
updateSyncStatusWithCache(storeId, platform, "SUCCESS", reviews.size());
|
||||||
|
|
||||||
return reviews.size();
|
return reviews.size();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("카카오 응답 파싱 및 Redis 저장 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
log.error("카카오 응답 파싱 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||||
updateSyncStatus(storeId, platform, "FAILED", 0);
|
updateSyncStatusWithCache(storeId, platform, "FAILED", 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 🔥 누락된 generateReviewId 메서드 추가
|
|
||||||
*/
|
|
||||||
private String generateReviewId(JsonNode reviewNode) {
|
|
||||||
try {
|
|
||||||
String reviewerName = reviewNode.path("reviewer_name").asText("");
|
|
||||||
String date = reviewNode.path("date").asText("");
|
|
||||||
String content = reviewNode.path("content").asText("");
|
|
||||||
|
|
||||||
// 고유한 ID 생성 (리뷰어명 + 날짜 + 컨텐츠 해시)
|
|
||||||
String combined = reviewerName + "_" + date + "_" + content.hashCode();
|
|
||||||
return "kakao_" + Math.abs(combined.hashCode());
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 예외 발생 시 타임스탬프 기반 ID 생성
|
|
||||||
return "kakao_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 계정 연동 메서드들 =====
|
// ===== 계정 연동 메서드들 =====
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -582,6 +592,90 @@ public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Redis 저장 실패: storeId={}, platform={}, error={}",
|
log.error("Redis 저장 실패: storeId={}, platform={}, error={}",
|
||||||
storeId, platform, e.getMessage());
|
storeId, platform, e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CacheAdapter를 통한 Redis 저장 (안전한 방식)
|
||||||
|
*/
|
||||||
|
private void saveToRedisWithCache(Long storeId, String platform, List<Map<String, Object>> reviews, Map<String, Object> storeInfo) {
|
||||||
|
try {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
String redisKey = String.format("external:reviews:pending:%d:%s:%d", storeId, platform, timestamp);
|
||||||
|
|
||||||
|
Map<String, Object> cacheData = new HashMap<>();
|
||||||
|
cacheData.put("storeId", storeId);
|
||||||
|
cacheData.put("platform", platform);
|
||||||
|
cacheData.put("reviews", reviews);
|
||||||
|
cacheData.put("reviewCount", reviews.size());
|
||||||
|
cacheData.put("timestamp", timestamp);
|
||||||
|
cacheData.put("status", "PENDING");
|
||||||
|
cacheData.put("syncTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||||
|
|
||||||
|
if (storeInfo != null) {
|
||||||
|
cacheData.put("storeInfo", storeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 CacheAdapter 활용 (TTL 1일 = 86400초)
|
||||||
|
cachePort.putStoreCache(redisKey, cacheData, 86400);
|
||||||
|
|
||||||
|
log.info("CacheAdapter로 리뷰 데이터 저장 완료: key={}, reviewCount={}, hasStoreInfo={}",
|
||||||
|
redisKey, reviews.size(), storeInfo != null);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("CacheAdapter 저장 실패: storeId={}, platform={}, error={}", storeId, platform, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CacheAdapter를 통한 동기화 상태 업데이트
|
||||||
|
*/
|
||||||
|
private void updateSyncStatusWithCache(Long storeId, String platform, String status, int count) {
|
||||||
|
try {
|
||||||
|
String statusKey = String.format("external:sync:status:%d:%s", storeId, platform);
|
||||||
|
|
||||||
|
Map<String, Object> statusData = new HashMap<>();
|
||||||
|
statusData.put("storeId", storeId);
|
||||||
|
statusData.put("platform", platform);
|
||||||
|
statusData.put("status", status);
|
||||||
|
statusData.put("syncedCount", count);
|
||||||
|
statusData.put("timestamp", System.currentTimeMillis());
|
||||||
|
|
||||||
|
// CacheAdapter 활용 (TTL 1일)
|
||||||
|
cachePort.putStoreCache(statusKey, statusData, 86400);
|
||||||
|
|
||||||
|
log.info("CacheAdapter로 동기화 상태 저장 완료: storeId={}, platform={}, status={}, count={}",
|
||||||
|
storeId, platform, status, count);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("CacheAdapter 상태 저장 실패: storeId={}, platform={}, error={}",
|
||||||
|
storeId, platform, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리뷰 ID 생성
|
||||||
|
*/
|
||||||
|
private String generateReviewId(JsonNode reviewNode) {
|
||||||
|
try {
|
||||||
|
String reviewerName = reviewNode.path("reviewer_name").asText("");
|
||||||
|
String date = reviewNode.path("date").asText("");
|
||||||
|
String content = reviewNode.path("content").asText("");
|
||||||
|
|
||||||
|
String combined = reviewerName + "|" + date + "|" + content.substring(0, Math.min(50, content.length()));
|
||||||
|
int hash = Math.abs(combined.hashCode());
|
||||||
|
|
||||||
|
return String.format("kakao_%s_%d_%d",
|
||||||
|
date.replaceAll("[^0-9]", ""),
|
||||||
|
hash,
|
||||||
|
System.currentTimeMillis() % 1000);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return String.format("kakao_fallback_%d_%d",
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
(int)(Math.random() * 10000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user