优化管家婆缓存状态批量查询
This commit is contained in:
parent
53b52995df
commit
c31e394558
@ -79,9 +79,10 @@ public class NgpMonthlyWeeklyService {
|
|||||||
LocalDate monthEnd = month.atEndOfMonth();
|
LocalDate monthEnd = month.atEndOfMonth();
|
||||||
LocalDate dataEnd = month.equals(currentMonth) ? today : monthEnd;
|
LocalDate dataEnd = month.equals(currentMonth) ? today : monthEnd;
|
||||||
LocalDateTime now = LocalDateTime.now(clock);
|
LocalDateTime now = LocalDateTime.now(clock);
|
||||||
|
Map<LocalDate, LocalDateTime> storedRefreshTimes = repository.findRefreshTimes(monthStart, dataEnd);
|
||||||
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
|
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
|
||||||
monthStart.datesUntil(dataEnd.plusDays(1)).forEach(date ->
|
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()
|
List<LocalDate> missingDates = cachedDates.entrySet().stream()
|
||||||
.filter(entry -> entry.getValue() == null)
|
.filter(entry -> entry.getValue() == null)
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
|
|||||||
@ -62,6 +62,26 @@ public class NgpPerformanceCacheRepository {
|
|||||||
return rows.stream().findFirst();
|
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) {
|
public Map<String, NgpStoreTotal> aggregate(LocalDate startDate, LocalDate endDate) {
|
||||||
Map<String, NgpStoreTotal> result = new LinkedHashMap<>();
|
Map<String, NgpStoreTotal> result = new LinkedHashMap<>();
|
||||||
jdbcTemplate.query("""
|
jdbcTemplate.query("""
|
||||||
|
|||||||
@ -46,9 +46,11 @@ public class NgpPerformanceTotalService {
|
|||||||
public NgpPerformanceResult total(NgpPerformanceTotalRequest request) {
|
public NgpPerformanceResult total(NgpPerformanceTotalRequest request) {
|
||||||
validate(request);
|
validate(request);
|
||||||
LocalDateTime now = LocalDateTime.now(BUSINESS_ZONE);
|
LocalDateTime now = LocalDateTime.now(BUSINESS_ZONE);
|
||||||
|
Map<LocalDate, LocalDateTime> storedRefreshTimes = repository.findRefreshTimes(
|
||||||
|
request.startDate(), request.endDate());
|
||||||
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
|
Map<LocalDate, LocalDateTime> cachedDates = new LinkedHashMap<>();
|
||||||
request.startDate().datesUntil(request.endDate().plusDays(1)).forEach(date ->
|
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()
|
List<LocalDate> missingDates = cachedDates.entrySet().stream()
|
||||||
.filter(entry -> entry.getValue() == null)
|
.filter(entry -> entry.getValue() == null)
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
|
|||||||
@ -16,13 +16,13 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ class NgpMonthlyWeeklyServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void currentMonthOnlyRequiresDatesThroughToday() {
|
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));
|
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, 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, 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));
|
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
|
@Test
|
||||||
void buildsMondayToSundayColumnsAndStoreSubtotalsFromDailyCache() {
|
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(
|
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), "a", "网点A", 10000),
|
||||||
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 6), "a", "网点A", 20000),
|
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 6), "a", "网点A", 20000),
|
||||||
@ -86,7 +87,8 @@ class NgpMonthlyWeeklyServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void keepsRequestedStoreOrderAndExcludesOtherStores() {
|
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(
|
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), "网点a", "网点A", 10000),
|
||||||
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "其他网点", "其他网点", 99900)
|
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "其他网点", "其他网点", 99900)
|
||||||
@ -106,7 +108,8 @@ class NgpMonthlyWeeklyServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void acceptsCommaSeparatedStoreNameAndQueriesAllWhenItIsBlank() {
|
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(
|
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), "网点a", "网点A", 10000),
|
||||||
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点b", "网点B", 20000),
|
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点b", "网点B", 20000),
|
||||||
@ -128,8 +131,8 @@ class NgpMonthlyWeeklyServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsStaleMonthlyCacheWhileRefreshingInBackground() {
|
void returnsStaleMonthlyCacheWhileRefreshingInBackground() {
|
||||||
when(repository.findRefreshTime(any()))
|
when(repository.findRefreshTimes(any(), any())).thenReturn(
|
||||||
.thenReturn(Optional.of(LocalDateTime.of(2026, 9, 7, 12, 0)));
|
currentMonthRefreshTimes(LocalDateTime.of(2026, 9, 7, 12, 0)));
|
||||||
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
|
when(repository.findDailyTotals(any(), any())).thenReturn(List.of(
|
||||||
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点a", "网点A", 12345)
|
new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点a", "网点A", 12345)
|
||||||
));
|
));
|
||||||
@ -155,4 +158,11 @@ class NgpMonthlyWeeklyServiceTest {
|
|||||||
assertThat(context.getBean(NgpMonthlyWeeklyService.class)).isNotNull();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.company.agentfoundation.ngp.performance;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -37,4 +38,22 @@ class NgpPerformanceCacheRepositoryTest {
|
|||||||
eq(NgpPerformanceCacheRepository.FILTER_SIGNATURE)
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@ -32,7 +31,7 @@ class NgpPerformanceTotalServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsPendingAndStartsRefreshWhenAnyDayIsMissing() {
|
void returnsPendingAndStartsRefreshWhenAnyDayIsMissing() {
|
||||||
when(repository.findRefreshTime(any())).thenReturn(Optional.empty());
|
when(repository.findRefreshTimes(any(), any())).thenReturn(Map.of());
|
||||||
|
|
||||||
NgpPerformanceResult result = service.total(new NgpPerformanceTotalRequest(
|
NgpPerformanceResult result = service.total(new NgpPerformanceTotalRequest(
|
||||||
LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 6), null));
|
LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 6), null));
|
||||||
@ -46,7 +45,8 @@ class NgpPerformanceTotalServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsCachedTotalAndFiltersUniquePartialStoreName() {
|
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(
|
when(repository.aggregate(any(), any())).thenReturn(Map.of(
|
||||||
NgpPerformanceDayLoader.normalize("兰教练(抖店)"), new NgpStoreTotal("兰教练(抖店)", 490866847L),
|
NgpPerformanceDayLoader.normalize("兰教练(抖店)"), new NgpStoreTotal("兰教练(抖店)", 490866847L),
|
||||||
NgpPerformanceDayLoader.normalize("另一个网点"), new NgpStoreTotal("另一个网点", 200L)
|
NgpPerformanceDayLoader.normalize("另一个网点"), new NgpStoreTotal("另一个网点", 200L)
|
||||||
@ -63,8 +63,9 @@ class NgpPerformanceTotalServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsStaleCachedTotalWhileRefreshingInBackground() {
|
void returnsStaleCachedTotalWhileRefreshingInBackground() {
|
||||||
when(repository.findRefreshTime(any()))
|
when(repository.findRefreshTimes(any(), any())).thenReturn(refreshTimes(
|
||||||
.thenReturn(Optional.of(LocalDateTime.now().minusDays(2)));
|
LocalDate.now().minusDays(3), LocalDate.now().minusDays(2),
|
||||||
|
LocalDateTime.now().minusDays(2)));
|
||||||
when(repository.aggregate(any(), any())).thenReturn(Map.of(
|
when(repository.aggregate(any(), any())).thenReturn(Map.of(
|
||||||
NgpPerformanceDayLoader.normalize("网点A"), new NgpStoreTotal("网点A", 12345L)
|
NgpPerformanceDayLoader.normalize("网点A"), new NgpStoreTotal("网点A", 12345L)
|
||||||
));
|
));
|
||||||
@ -76,4 +77,14 @@ class NgpPerformanceTotalServiceTest {
|
|||||||
assertThat(((NgpPerformanceTotalResponse) result).totalAmount()).isEqualByComparingTo("123.45");
|
assertThat(((NgpPerformanceTotalResponse) result).totalAmount()).isEqualByComparingTo("123.45");
|
||||||
verify(coordinator).ensureRefresh(any());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user