추세검색 설정 수정

This commit is contained in:
Macbook
2026-05-27 02:10:00 +09:00
parent 2e08c6b16f
commit fc06a16184
27 changed files with 1262 additions and 147 deletions
@@ -0,0 +1,129 @@
package com.goldenchart.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.dto.TrendSearchRequest;
import com.goldenchart.dto.TrendSearchResultDto;
import com.goldenchart.dto.TrendSearchResultsResponse;
import com.goldenchart.entity.GcAppSettings;
import com.goldenchart.entity.GcTrendSearchSnapshot;
import com.goldenchart.repository.GcAppSettingsRepository;
import com.goldenchart.repository.GcTrendSearchSnapshotRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class TrendSearchSnapshotService {
private static final TypeReference<List<TrendSearchResultDto>> RESULT_LIST_TYPE = new TypeReference<>() {};
private final GcTrendSearchSnapshotRepository snapshotRepo;
private final GcAppSettingsRepository appSettingsRepo;
private final TrendSearchService trendSearchService;
private final TrendSearchSettingsHelper settingsHelper;
private final ObjectMapper mapper;
@Transactional(readOnly = true)
public TrendSearchResultsResponse getResults(Long userId, String deviceId) {
String scopeKey = TrendSearchScope.key(userId, deviceId);
return snapshotRepo.findById(scopeKey)
.map(this::toResponse)
.orElse(TrendSearchResultsResponse.empty());
}
@Transactional
public List<TrendSearchResultDto> scanAndPersist(Long userId, String deviceId, TrendSearchRequest request) {
GcAppSettings app = findAppSettings(userId, deviceId);
TrendSearchRequest req = app != null
? settingsHelper.mergeRequestWithSettings(request, app)
: (request != null ? request : new TrendSearchRequest());
List<TrendSearchResultDto> results = trendSearchService.scan(req);
persist(scopeKey(userId, deviceId), userId, deviceId, req, results);
return results;
}
@Transactional
public void runScheduledScan(Long userId, String deviceId) {
String scopeKey = TrendSearchScope.key(userId, deviceId);
GcAppSettings app = findAppSettings(userId, deviceId);
if (app == null) return;
TrendSearchRequest last = snapshotRepo.findById(scopeKey)
.map(s -> settingsHelper.parseRequestJson(s.getRequestJson()))
.orElse(null);
TrendSearchRequest req = settingsHelper.buildRequest(app, last);
List<TrendSearchResultDto> results = trendSearchService.scan(req);
persist(scopeKey, userId, deviceId, req, results);
log.debug("[TrendSearch] scheduled scan scope={} count={}", scopeKey, results.size());
}
private void persist(String scopeKey, Long userId, String deviceId,
TrendSearchRequest req, List<TrendSearchResultDto> results) {
try {
String resultsJson = mapper.writeValueAsString(results != null ? results : Collections.emptyList());
String requestJson = mapper.writeValueAsString(req);
GcTrendSearchSnapshot snap = snapshotRepo.findById(scopeKey).orElseGet(() ->
GcTrendSearchSnapshot.builder().scopeKey(scopeKey).build());
snap.setUserId(userId);
snap.setDeviceId(deviceId);
snap.setTimeframe(req.getTimeframe() != null ? req.getTimeframe() : "1d");
snap.setScannedAt(LocalDateTime.now());
snap.setRequestJson(requestJson);
snap.setResultsJson(resultsJson);
snapshotRepo.save(snap);
} catch (Exception e) {
log.warn("[TrendSearch] persist failed scope={}: {}", scopeKey, e.getMessage());
}
}
private TrendSearchResultsResponse toResponse(GcTrendSearchSnapshot snap) {
List<TrendSearchResultDto> list = Collections.emptyList();
try {
if (snap.getResultsJson() != null && !snap.getResultsJson().isBlank()) {
list = mapper.readValue(snap.getResultsJson(), RESULT_LIST_TYPE);
}
} catch (Exception e) {
log.warn("[TrendSearch] deserialize results scope={}: {}", snap.getScopeKey(), e.getMessage());
}
Instant scannedAt = snap.getScannedAt() != null
? snap.getScannedAt().atZone(ZoneId.systemDefault()).toInstant()
: null;
return TrendSearchResultsResponse.builder()
.results(list)
.timeframe(snap.getTimeframe())
.scannedAt(scannedAt)
.build();
}
private GcAppSettings findAppSettings(Long userId, String deviceId) {
if (userId != null) {
return appSettingsRepo.findByUserId(userId).orElse(null);
}
if (deviceId != null && !deviceId.isBlank()) {
return appSettingsRepo.findByDeviceId(deviceId).orElse(null);
}
return null;
}
private static String scopeKey(Long userId, String deviceId) {
return TrendSearchScope.key(userId, deviceId);
}
/** 스케줄러: 마지막 스캔 시각 (없으면 epoch) */
@Transactional(readOnly = true)
public Optional<LocalDateTime> lastScannedAt(Long userId, String deviceId) {
return snapshotRepo.findById(TrendSearchScope.key(userId, deviceId))
.map(GcTrendSearchSnapshot::getScannedAt);
}
}