130 lines
5.5 KiB
Java
130 lines
5.5 KiB
Java
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);
|
|
}
|
|
}
|