缓存更新

This commit is contained in:
tanglong 2026-09-23 15:29:14 +08:00
parent f0f74489ec
commit 0ba6a5b9e3
4 changed files with 81 additions and 4 deletions

View File

@ -5,6 +5,8 @@ import com.company.agentfoundation.ngp.performance.NgpDailyStoreTotal;
import com.company.agentfoundation.ngp.performance.NgpPerformanceCacheRepository;
import com.company.agentfoundation.ngp.performance.NgpPerformanceDayLoader;
import com.company.agentfoundation.ngp.performance.NgpPerformanceRefreshCoordinator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -29,6 +31,7 @@ import java.util.Map;
@Service
public class NgpMonthlyWeeklyService {
private static final Logger log = LoggerFactory.getLogger(NgpMonthlyWeeklyService.class);
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
private static final String[] CHINESE_MONTHS = {
"一月", "二月", "三月", "四月", "五月", "六月",
@ -71,8 +74,12 @@ public class NgpMonthlyWeeklyService {
LocalDate monthEnd = month.atEndOfMonth();
LocalDate dataEnd = month.equals(currentMonth) ? today : monthEnd;
LocalDateTime now = LocalDateTime.now(clock);
List<LocalDate> missingDates = monthStart.datesUntil(dataEnd.plusDays(1))
.filter(date -> !fresh(date, repository.findRefreshTime(date).orElse(null), now))
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
monthStart.datesUntil(dataEnd.plusDays(1)).forEach(date ->
cachedDates.put(date, repository.findRefreshTime(date).orElse(null)));
List<LocalDate> missingDates = cachedDates.entrySet().stream()
.filter(entry -> entry.getValue() == null)
.map(Map.Entry::getKey)
.toList();
if (!missingDates.isEmpty()) {
refreshCoordinator.ensureRefresh(missingDates);
@ -86,6 +93,11 @@ public class NgpMonthlyWeeklyService {
retrySeconds
);
}
List<LocalDate> staleDates = cachedDates.entrySet().stream()
.filter(entry -> !fresh(entry.getKey(), entry.getValue(), now))
.map(Map.Entry::getKey)
.toList();
refreshStaleInBackground(staleDates);
List<NgpMonthlyWeek> weeks = buildWeeks(month, today);
List<NgpDailyStoreTotal> dailyTotals = repository.findDailyTotals(monthStart, dataEnd);
@ -240,6 +252,16 @@ public class NgpMonthlyWeeklyService {
return Duration.between(refreshedAt, now).getSeconds() <= Math.max(ttlSeconds, 0);
}
private void refreshStaleInBackground(List<LocalDate> staleDates) {
if (staleDates.isEmpty()) return;
try {
refreshCoordinator.ensureRefresh(staleDates);
} catch (RuntimeException exception) {
log.warn("Serving stale NGP monthly cache after refresh scheduling failed dates={} err={}",
staleDates.size(), exception.getMessage());
}
}
private YearMonth parseMonth(String raw, YearMonth defaultMonth) {
if (raw == null || raw.isBlank()) return defaultMonth;
try {

View File

@ -1,6 +1,8 @@
package com.company.agentfoundation.ngp.performance;
import com.company.agentfoundation.config.FoundationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@ -11,6 +13,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@ -19,6 +22,7 @@ import java.util.Set;
@Service
public class NgpPerformanceTotalService {
private static final Logger log = LoggerFactory.getLogger(NgpPerformanceTotalService.class);
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
private static final int MAX_RANGE_DAYS = 31;
@ -39,8 +43,12 @@ public class NgpPerformanceTotalService {
public NgpPerformanceResult total(NgpPerformanceTotalRequest request) {
validate(request);
LocalDateTime now = LocalDateTime.now(BUSINESS_ZONE);
List<LocalDate> missingDates = request.startDate().datesUntil(request.endDate().plusDays(1))
.filter(date -> !fresh(date, repository.findRefreshTime(date).orElse(null), now))
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
request.startDate().datesUntil(request.endDate().plusDays(1)).forEach(date ->
cachedDates.put(date, repository.findRefreshTime(date).orElse(null)));
List<LocalDate> missingDates = cachedDates.entrySet().stream()
.filter(entry -> entry.getValue() == null)
.map(Map.Entry::getKey)
.toList();
if (!missingDates.isEmpty()) {
refreshCoordinator.ensureRefresh(missingDates);
@ -53,6 +61,11 @@ public class NgpPerformanceTotalService {
retrySeconds
);
}
List<LocalDate> staleDates = cachedDates.entrySet().stream()
.filter(entry -> !fresh(entry.getKey(), entry.getValue(), now))
.map(Map.Entry::getKey)
.toList();
refreshStaleInBackground(staleDates);
String storeInput = request.storeName() == null ? "" : request.storeName().trim();
Map<String, NgpStoreTotal> totals = repository.aggregate(request.startDate(), request.endDate());
@ -76,6 +89,16 @@ public class NgpPerformanceTotalService {
return Duration.between(refreshedAt, now).getSeconds() <= Math.max(ttlSeconds, 0);
}
private void refreshStaleInBackground(List<LocalDate> staleDates) {
if (staleDates.isEmpty()) return;
try {
refreshCoordinator.ensureRefresh(staleDates);
} catch (RuntimeException exception) {
log.warn("Serving stale NGP performance cache after refresh scheduling failed dates={} err={}",
staleDates.size(), exception.getMessage());
}
}
private List<NgpStoreTotal> selectStores(Map<String, NgpStoreTotal> stores, String storeInput) {
if (storeInput.isBlank()) return List.copyOf(stores.values());
Set<String> selectedKeys = new LinkedHashSet<>();

View File

@ -122,6 +122,22 @@ class NgpMonthlyWeeklyServiceTest {
assertThat(all.grandTotal()).isEqualByComparingTo("600.00");
}
@Test
void returnsStaleMonthlyCacheWhileRefreshingInBackground() {
when(repository.findRefreshTime(any()))
.thenReturn(Optional.of(LocalDateTime.of(2026, 9, 7, 12, 0)));
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点a", "网点A", 12345)
));
NgpMonthlyWeeklyResult result = service.query(
new NgpMonthlyWeeklyRequest("2026-09", null, null));
assertThat(result).isInstanceOf(NgpMonthlyWeeklyResponse.class);
assertThat(((NgpMonthlyWeeklyResponse) result).grandTotal()).isEqualByComparingTo("123.45");
verify(coordinator).ensureRefresh(any());
}
@Test
void springCanCreateServiceWhenTestClockConstructorAlsoExists() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {

View File

@ -58,4 +58,20 @@ class NgpPerformanceTotalServiceTest {
assertThat(response.totalAmount()).isEqualByComparingTo("4908668.47");
assertThat(response.formattedTotal()).isEqualTo("4,908,668.47");
}
@Test
void returnsStaleCachedTotalWhileRefreshingInBackground() {
when(repository.findRefreshTime(any()))
.thenReturn(Optional.of(LocalDateTime.now().minusDays(2)));
when(repository.aggregate(any(), any())).thenReturn(Map.of(
NgpPerformanceDayLoader.normalize("网点A"), new NgpStoreTotal("网点A", 12345L)
));
NgpPerformanceResult result = service.total(new NgpPerformanceTotalRequest(
LocalDate.now().minusDays(3), LocalDate.now().minusDays(2), null));
assertThat(result).isInstanceOf(NgpPerformanceTotalResponse.class);
assertThat(((NgpPerformanceTotalResponse) result).totalAmount()).isEqualByComparingTo("123.45");
verify(coordinator).ensureRefresh(any());
}
}