From 0ba6a5b9e318a8dfa3042d0a7ecf2ca391d67a64 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Wed, 23 Sep 2026 15:29:14 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ngp/monthly/NgpMonthlyWeeklyService.java | 26 ++++++++++++++++-- .../NgpPerformanceTotalService.java | 27 +++++++++++++++++-- .../monthly/NgpMonthlyWeeklyServiceTest.java | 16 +++++++++++ .../NgpPerformanceTotalServiceTest.java | 16 +++++++++++ 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java index 71db3e0..2310167 100644 --- a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java +++ b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java @@ -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 missingDates = monthStart.datesUntil(dataEnd.plusDays(1)) - .filter(date -> !fresh(date, repository.findRefreshTime(date).orElse(null), now)) + Map cachedDates = new LinkedHashMap<>(); + monthStart.datesUntil(dataEnd.plusDays(1)).forEach(date -> + cachedDates.put(date, repository.findRefreshTime(date).orElse(null))); + List 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 staleDates = cachedDates.entrySet().stream() + .filter(entry -> !fresh(entry.getKey(), entry.getValue(), now)) + .map(Map.Entry::getKey) + .toList(); + refreshStaleInBackground(staleDates); List weeks = buildWeeks(month, today); List 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 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 { diff --git a/src/main/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalService.java b/src/main/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalService.java index 2201f2d..5eec6a7 100644 --- a/src/main/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalService.java +++ b/src/main/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalService.java @@ -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 missingDates = request.startDate().datesUntil(request.endDate().plusDays(1)) - .filter(date -> !fresh(date, repository.findRefreshTime(date).orElse(null), now)) + Map cachedDates = new LinkedHashMap<>(); + request.startDate().datesUntil(request.endDate().plusDays(1)).forEach(date -> + cachedDates.put(date, repository.findRefreshTime(date).orElse(null))); + List 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 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 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 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 selectStores(Map stores, String storeInput) { if (storeInput.isBlank()) return List.copyOf(stores.values()); Set selectedKeys = new LinkedHashSet<>(); diff --git a/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java b/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java index c08d011..6d5b915 100644 --- a/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java +++ b/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java @@ -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()) { diff --git a/src/test/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalServiceTest.java b/src/test/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalServiceTest.java index 6eb3b68..c199b3b 100644 --- a/src/test/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalServiceTest.java +++ b/src/test/java/com/company/agentfoundation/ngp/performance/NgpPerformanceTotalServiceTest.java @@ -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()); + } }