优化管家婆缓存状态批量查询

This commit is contained in:
tanglong 2026-09-24 14:33:15 +08:00
parent 53b52995df
commit c31e394558
6 changed files with 79 additions and 16 deletions

View File

@ -79,9 +79,10 @@ public class NgpMonthlyWeeklyService {
LocalDate monthEnd = month.atEndOfMonth();
LocalDate dataEnd = month.equals(currentMonth) ? today : monthEnd;
LocalDateTime now = LocalDateTime.now(clock);
Map<LocalDate, LocalDateTime> storedRefreshTimes = repository.findRefreshTimes(monthStart, dataEnd);
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
monthStart.datesUntil(dataEnd.plusDays(1)).forEach(date ->
cachedDates.put(date, repository.findRefreshTime(date).orElse(null)));
cachedDates.put(date, storedRefreshTimes.get(date)));
List<LocalDate> missingDates = cachedDates.entrySet().stream()
.filter(entry -> entry.getValue() == null)
.map(Map.Entry::getKey)

View File

@ -62,6 +62,26 @@ public class NgpPerformanceCacheRepository {
return rows.stream().findFirst();
}
public Map<LocalDate, LocalDateTime> findRefreshTimes(LocalDate startDate, LocalDate endDate) {
Map<LocalDate, LocalDateTime> result = new LinkedHashMap<>();
jdbcTemplate.query("""
SELECT business_date, refreshed_at
FROM ngp_performance_daily_status
WHERE business_date BETWEEN ? AND ?
AND filter_signature = ?
ORDER BY business_date
""",
(RowCallbackHandler) resultSet -> result.put(
resultSet.getObject("business_date", LocalDate.class),
resultSet.getTimestamp("refreshed_at").toLocalDateTime()
),
startDate,
endDate,
FILTER_SIGNATURE
);
return Map.copyOf(result);
}
public Map<String, NgpStoreTotal> aggregate(LocalDate startDate, LocalDate endDate) {
Map<String, NgpStoreTotal> result = new LinkedHashMap<>();
jdbcTemplate.query("""

View File

@ -46,9 +46,11 @@ public class NgpPerformanceTotalService {
public NgpPerformanceResult total(NgpPerformanceTotalRequest request) {
validate(request);
LocalDateTime now = LocalDateTime.now(BUSINESS_ZONE);
Map<LocalDate, LocalDateTime> storedRefreshTimes = repository.findRefreshTimes(
request.startDate(), request.endDate());
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
request.startDate().datesUntil(request.endDate().plusDays(1)).forEach(date ->
cachedDates.put(date, repository.findRefreshTime(date).orElse(null)));
cachedDates.put(date, storedRefreshTimes.get(date)));
List<LocalDate> missingDates = cachedDates.entrySet().stream()
.filter(entry -> entry.getValue() == null)
.map(Map.Entry::getKey)

View File

@ -16,13 +16,13 @@ import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -45,7 +45,7 @@ class NgpMonthlyWeeklyServiceTest {
@Test
void currentMonthOnlyRequiresDatesThroughToday() {
when(repository.findRefreshTime(any())).thenReturn(Optional.empty());
when(repository.findRefreshTimes(any(), any())).thenReturn(Map.of());
NgpMonthlyWeeklyResult result = service.query(new NgpMonthlyWeeklyRequest(null, null, null));
@ -57,12 +57,13 @@ class NgpMonthlyWeeklyServiceTest {
LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 2), LocalDate.of(2026, 9, 3),
LocalDate.of(2026, 9, 4), LocalDate.of(2026, 9, 5), LocalDate.of(2026, 9, 6),
LocalDate.of(2026, 9, 7), LocalDate.of(2026, 9, 8), LocalDate.of(2026, 9, 9));
verify(repository, times(9)).findRefreshTime(any());
verify(repository).findRefreshTimes(LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 9));
}
@Test
void buildsMondayToSundayColumnsAndStoreSubtotalsFromDailyCache() {
when(repository.findRefreshTime(any())).thenReturn(Optional.of(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findRefreshTimes(any(), any())).thenReturn(
currentMonthRefreshTimes(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "a", "网点A", 10000),
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 6), "a", "网点A", 20000),
@ -86,7 +87,8 @@ class NgpMonthlyWeeklyServiceTest {
@Test
void keepsRequestedStoreOrderAndExcludesOtherStores() {
when(repository.findRefreshTime(any())).thenReturn(Optional.of(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findRefreshTimes(any(), any())).thenReturn(
currentMonthRefreshTimes(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点a", "网点A", 10000),
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "其他网点", "其他网点", 99900)
@ -106,7 +108,8 @@ class NgpMonthlyWeeklyServiceTest {
@Test
void acceptsCommaSeparatedStoreNameAndQueriesAllWhenItIsBlank() {
when(repository.findRefreshTime(any())).thenReturn(Optional.of(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findRefreshTimes(any(), any())).thenReturn(
currentMonthRefreshTimes(LocalDateTime.of(2026, 9, 9, 11, 59)));
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点a", "网点A", 10000),
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点b", "网点B", 20000),
@ -128,8 +131,8 @@ class NgpMonthlyWeeklyServiceTest {
@Test
void returnsStaleMonthlyCacheWhileRefreshingInBackground() {
when(repository.findRefreshTime(any()))
.thenReturn(Optional.of(LocalDateTime.of(2026, 9, 7, 12, 0)));
when(repository.findRefreshTimes(any(), any())).thenReturn(
currentMonthRefreshTimes(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)
));
@ -155,4 +158,11 @@ class NgpMonthlyWeeklyServiceTest {
assertThat(context.getBean(NgpMonthlyWeeklyService.class)).isNotNull();
}
}
private Map<LocalDate, LocalDateTime> currentMonthRefreshTimes(LocalDateTime refreshedAt) {
Map<LocalDate, LocalDateTime> result = new LinkedHashMap<>();
LocalDate.of(2026, 9, 1).datesUntil(LocalDate.of(2026, 9, 10))
.forEach(date -> result.put(date, refreshedAt));
return Map.copyOf(result);
}
}

View File

@ -2,6 +2,7 @@ package com.company.agentfoundation.ngp.performance;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import java.time.LocalDate;
@ -37,4 +38,22 @@ class NgpPerformanceCacheRepositoryTest {
eq(NgpPerformanceCacheRepository.FILTER_SIGNATURE)
);
}
@Test
void loadsRefreshTimesForTheWholeDateRangeInOneQuery() {
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
NgpPerformanceCacheRepository repository = new NgpPerformanceCacheRepository(jdbcTemplate);
LocalDate startDate = LocalDate.of(2026, 9, 1);
LocalDate endDate = LocalDate.of(2026, 9, 24);
repository.findRefreshTimes(startDate, endDate);
verify(jdbcTemplate).query(
contains("business_date BETWEEN ? AND ?"),
any(RowCallbackHandler.class),
eq(startDate),
eq(endDate),
eq(NgpPerformanceCacheRepository.FILTER_SIGNATURE)
);
}
}

View File

@ -8,7 +8,6 @@ import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ -32,7 +31,7 @@ class NgpPerformanceTotalServiceTest {
@Test
void returnsPendingAndStartsRefreshWhenAnyDayIsMissing() {
when(repository.findRefreshTime(any())).thenReturn(Optional.empty());
when(repository.findRefreshTimes(any(), any())).thenReturn(Map.of());
NgpPerformanceResult result = service.total(new NgpPerformanceTotalRequest(
LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 6), null));
@ -46,7 +45,8 @@ class NgpPerformanceTotalServiceTest {
@Test
void returnsCachedTotalAndFiltersUniquePartialStoreName() {
when(repository.findRefreshTime(any())).thenReturn(Optional.of(LocalDateTime.now()));
when(repository.findRefreshTimes(any(), any())).thenReturn(refreshTimes(
LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 6), LocalDateTime.now()));
when(repository.aggregate(any(), any())).thenReturn(Map.of(
NgpPerformanceDayLoader.normalize("兰教练(抖店)"), new NgpStoreTotal("兰教练(抖店)", 490866847L),
NgpPerformanceDayLoader.normalize("另一个网点"), new NgpStoreTotal("另一个网点", 200L)
@ -63,8 +63,9 @@ class NgpPerformanceTotalServiceTest {
@Test
void returnsStaleCachedTotalWhileRefreshingInBackground() {
when(repository.findRefreshTime(any()))
.thenReturn(Optional.of(LocalDateTime.now().minusDays(2)));
when(repository.findRefreshTimes(any(), any())).thenReturn(refreshTimes(
LocalDate.now().minusDays(3), LocalDate.now().minusDays(2),
LocalDateTime.now().minusDays(2)));
when(repository.aggregate(any(), any())).thenReturn(Map.of(
NgpPerformanceDayLoader.normalize("网点A"), new NgpStoreTotal("网点A", 12345L)
));
@ -76,4 +77,14 @@ class NgpPerformanceTotalServiceTest {
assertThat(((NgpPerformanceTotalResponse) result).totalAmount()).isEqualByComparingTo("123.45");
verify(coordinator).ensureRefresh(any());
}
private Map<LocalDate, LocalDateTime> refreshTimes(
LocalDate startDate,
LocalDate endDate,
LocalDateTime refreshedAt
) {
Map<LocalDate, LocalDateTime> result = new java.util.LinkedHashMap<>();
startDate.datesUntil(endDate.plusDays(1)).forEach(date -> result.put(date, refreshedAt));
return Map.copyOf(result);
}
}