From 07e030306796efc52f7efe16fe7cfe6de3b90919 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Wed, 9 Sep 2026 10:27:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ngp-monthly-weekly-api.md | 13 ++-- .../config/FoundationProperties.java | 3 - .../ngp/monthly/NgpMonthlyWeeklyRequest.java | 8 ++- .../ngp/monthly/NgpMonthlyWeeklyResponse.java | 1 + .../ngp/monthly/NgpMonthlyWeeklyService.java | 66 ++++++++++++++++--- src/main/resources/application.yml | 21 ------ .../monthly/NgpMonthlyWeeklyServiceTest.java | 34 ++++++++-- 7 files changed, 101 insertions(+), 45 deletions(-) diff --git a/docs/ngp-monthly-weekly-api.md b/docs/ngp-monthly-weekly-api.md index da50f4d..4d9c061 100644 --- a/docs/ngp-monthly-weekly-api.md +++ b/docs/ngp-monthly-weekly-api.md @@ -6,11 +6,17 @@ ```json { - "month": "2026-09" + "month": "2026-09", + "storeNames": [ + "跃动内容电商(抖店)", + "跃动工厂(淘宝1)" + ] } ``` `month` 可省略,省略时使用 Asia/Shanghai 时区的当前月份。不能查询未来月份。 +`storeNames` 可省略或传空数组,此时查询所有网店;传入后仅返回指定网店,并按传入顺序展示。 +只查询一个网店时也可以传 `storeName` 字符串;该字段同样支持逗号或顿号分隔多个名称。 ## 周区间 @@ -34,9 +40,8 @@ - 缓存补齐期间返回 HTTP `202` 和 `retryAfterSeconds`。 - 缓存就绪后返回 HTTP `200`,不再查询 NGP 全量数据。 -网点范围和显示顺序由 `foundation.ngp.monthly-store-names` 配置,目前包含 20 个网店。 -即使某个配置网点当月 -没有业绩,响应中仍保留该网点,金额返回 `null`,以便表格显示 `-`。 +传入 `storeNames` 时,即使指定网店当月没有业绩,响应中仍保留该网店,金额返回 +`null`,以便表格显示 `-`。不传时返回缓存中所有有数据的网店。 ## 成功响应示例 diff --git a/src/main/java/com/company/agentfoundation/config/FoundationProperties.java b/src/main/java/com/company/agentfoundation/config/FoundationProperties.java index 7623c04..722ff9e 100644 --- a/src/main/java/com/company/agentfoundation/config/FoundationProperties.java +++ b/src/main/java/com/company/agentfoundation/config/FoundationProperties.java @@ -310,7 +310,6 @@ public class FoundationProperties { private int maxResponseBytes = 5 * 1024 * 1024; private int currentCacheSeconds = 600; private int historyCacheSeconds = 86400; - private List monthlyStoreNames = new ArrayList<>(); public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } @@ -348,8 +347,6 @@ public class FoundationProperties { public void setCurrentCacheSeconds(int currentCacheSeconds) { this.currentCacheSeconds = currentCacheSeconds; } public int getHistoryCacheSeconds() { return historyCacheSeconds; } public void setHistoryCacheSeconds(int historyCacheSeconds) { this.historyCacheSeconds = historyCacheSeconds; } - public List getMonthlyStoreNames() { return monthlyStoreNames; } - public void setMonthlyStoreNames(List monthlyStoreNames) { this.monthlyStoreNames = monthlyStoreNames; } } public static class Semantic { diff --git a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyRequest.java b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyRequest.java index 8c793e5..c2e9bdb 100644 --- a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyRequest.java +++ b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyRequest.java @@ -1,4 +1,10 @@ package com.company.agentfoundation.ngp.monthly; -public record NgpMonthlyWeeklyRequest(String month) { +import java.util.List; + +public record NgpMonthlyWeeklyRequest( + String month, + List storeNames, + String storeName +) { } diff --git a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyResponse.java b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyResponse.java index 25ec8ee..a68eaea 100644 --- a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyResponse.java +++ b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyResponse.java @@ -9,6 +9,7 @@ public record NgpMonthlyWeeklyResponse( String month, String monthLabel, String title, + List requestedStoreNames, LocalDateTime generatedAt, List weeks, List stores, 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 698cf9f..16d1722 100644 --- a/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java +++ b/src/main/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyService.java @@ -21,6 +21,7 @@ import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -86,23 +87,20 @@ public class NgpMonthlyWeeklyService { List weeks = buildWeeks(month, today); List dailyTotals = repository.findDailyTotals(monthStart, dataEnd); - return buildResponse(month, today, weeks, dailyTotals); + List requestedStoreNames = normalizeRequestedStoreNames(request); + return buildResponse(month, today, weeks, dailyTotals, requestedStoreNames); } private NgpMonthlyWeeklyResponse buildResponse( YearMonth month, LocalDate today, List weeks, - List dailyTotals + List dailyTotals, + List requestedStoreNames ) { Map stores = new LinkedHashMap<>(); - List configuredStoreNames = config.getMonthlyStoreNames() == null - ? List.of() - : config.getMonthlyStoreNames().stream().filter(name -> name != null && !name.isBlank()).toList(); - boolean restrictStores = !configuredStoreNames.isEmpty(); - configuredStoreNames.forEach(storeName -> stores.put( - NgpPerformanceDayLoader.normalize(storeName), - new StoreAccumulator(storeName.trim(), weeks.size()))); + boolean restrictStores = !requestedStoreNames.isEmpty(); + if (restrictStores) initializeRequestedStores(stores, requestedStoreNames, dailyTotals, weeks.size()); long[] weekTotals = new long[weeks.size()]; for (NgpDailyStoreTotal daily : dailyTotals) { int weekIndex = weekIndex(weeks, daily.businessDate()); @@ -136,6 +134,7 @@ public class NgpMonthlyWeeklyService { month.toString(), monthLabel, month.getYear() + "年" + monthLabel + "业绩周统计", + requestedStoreNames, LocalDateTime.now(clock), weeks, rows, @@ -146,6 +145,55 @@ public class NgpMonthlyWeeklyService { ); } + private void initializeRequestedStores( + Map stores, + List requestedStoreNames, + List dailyTotals, + int weekCount + ) { + Map availableStores = new LinkedHashMap<>(); + dailyTotals.forEach(row -> availableStores.putIfAbsent( + NgpPerformanceDayLoader.normalize(row.storeKey()), row.storeName())); + for (String requested : requestedStoreNames) { + String normalized = NgpPerformanceDayLoader.normalize(requested); + String selectedKey = normalized; + String selectedName = availableStores.get(normalized); + if (selectedName == null) { + List> partial = availableStores.entrySet().stream() + .filter(entry -> entry.getKey().contains(normalized) || normalized.contains(entry.getKey())) + .toList(); + if (partial.size() == 1) { + selectedKey = partial.get(0).getKey(); + selectedName = partial.get(0).getValue(); + } else if (partial.size() > 1) { + String candidates = partial.stream().limit(20).map(Map.Entry::getValue) + .reduce((left, right) -> left + "、" + right).orElse(""); + throw new IllegalArgumentException( + "网店名称“" + requested + "”匹配多个结果,请使用完整名称:" + candidates); + } + } + stores.putIfAbsent( + selectedKey, + new StoreAccumulator(selectedName == null ? requested : selectedName, weekCount)); + } + } + + private List normalizeRequestedStoreNames(NgpMonthlyWeeklyRequest request) { + if (request == null) return List.of(); + LinkedHashSet unique = new LinkedHashSet<>(); + if (request.storeNames() != null) { + for (String storeName : request.storeNames()) { + if (storeName != null && !storeName.isBlank()) unique.add(storeName.trim()); + } + } + if (request.storeName() != null) { + for (String storeName : request.storeName().split("[,,、]")) { + if (!storeName.isBlank()) unique.add(storeName.trim()); + } + } + return List.copyOf(unique); + } + private List buildWeeks(YearMonth month, LocalDate today) { List result = new ArrayList<>(); LocalDate cursor = month.atDay(1); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3342fd2..7ddb900 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -149,27 +149,6 @@ foundation: max-response-bytes: 5242880 current-cache-seconds: 600 history-cache-seconds: 86400 - monthly-store-names: - - 跃动内容电商(小红书2店) - - 跃动内容电商(微信小店) - - 跃动内容电商(抖店) - - 跃动内容电商(小红书) - - 跃动内容电商(快手) - - 跃动工厂(拼多多4) - - 跃动工厂(淘宝1) - - 跃动工厂(淘宝2) - - 跃动工厂(天猫店) - - 跃动工厂(天猫2) - - 跃动工厂(京东pop) - - 跃动工厂(拼多多3) - - 跃动工厂(抖店2) - - 跃动工厂(快手) - - 跃动工厂(微信小店1) - - 跃动工厂(小红书1) - - 跃动工厂(拼多多2) - - 跃动工厂(抖店1) - - 跃动工厂(拼多多1) - - 跃动工厂(拼多多5) semantic: agent-id: crm-assistant minimum-confidence: 0.60 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 abd7727..d51f81c 100644 --- a/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java +++ b/src/test/java/com/company/agentfoundation/ngp/monthly/NgpMonthlyWeeklyServiceTest.java @@ -42,7 +42,7 @@ class NgpMonthlyWeeklyServiceTest { void currentMonthOnlyRequiresDatesThroughToday() { when(repository.findRefreshTime(any())).thenReturn(Optional.empty()); - NgpMonthlyWeeklyResult result = service.query(new NgpMonthlyWeeklyRequest(null)); + NgpMonthlyWeeklyResult result = service.query(new NgpMonthlyWeeklyRequest(null, null, null)); assertThat(result).isInstanceOf(NgpMonthlyWeeklyPendingResponse.class); assertThat(((NgpMonthlyWeeklyPendingResponse) result).pendingDays()).isEqualTo(9); @@ -65,7 +65,7 @@ class NgpMonthlyWeeklyServiceTest { new NgpDailyStoreTotal(LocalDate.of(2026, 9, 9), "b", "网点B", 50000) )); - NgpMonthlyWeeklyResult result = service.query(new NgpMonthlyWeeklyRequest("2026-09")); + NgpMonthlyWeeklyResult result = service.query(new NgpMonthlyWeeklyRequest("2026-09", null, null)); assertThat(result).isInstanceOf(NgpMonthlyWeeklyResponse.class); NgpMonthlyWeeklyResponse response = (NgpMonthlyWeeklyResponse) result; @@ -80,10 +80,7 @@ class NgpMonthlyWeeklyServiceTest { } @Test - void keepsConfiguredStoreOrderAndExcludesOtherStores() { - FoundationProperties properties = new FoundationProperties(); - properties.getNgp().setMonthlyStoreNames(List.of("网点B", "网点A", "无数据网点")); - service = new NgpMonthlyWeeklyService(repository, coordinator, properties, CLOCK); + void keepsRequestedStoreOrderAndExcludesOtherStores() { when(repository.findRefreshTime(any())).thenReturn(Optional.of(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), @@ -91,7 +88,7 @@ class NgpMonthlyWeeklyServiceTest { )); NgpMonthlyWeeklyResponse response = (NgpMonthlyWeeklyResponse) service.query( - new NgpMonthlyWeeklyRequest("2026-09")); + new NgpMonthlyWeeklyRequest("2026-09", List.of("网点B", "网点A", "无数据网点"), null)); assertThat(response.stores()).extracting(NgpMonthlyStoreRow::storeName) .containsExactly("网点B", "网点A", "无数据网点"); @@ -99,5 +96,28 @@ class NgpMonthlyWeeklyServiceTest { assertThat(response.stores().get(1).subtotal()).isEqualByComparingTo("100.00"); assertThat(response.stores().get(2).subtotal()).isNull(); assertThat(response.grandTotal()).isEqualByComparingTo("100.00"); + assertThat(response.requestedStoreNames()).containsExactly("网点B", "网点A", "无数据网点"); + } + + @Test + void acceptsCommaSeparatedStoreNameAndQueriesAllWhenItIsBlank() { + when(repository.findRefreshTime(any())).thenReturn(Optional.of(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), + new NgpDailyStoreTotal(LocalDate.of(2026, 9, 1), "网点c", "网点C", 30000) + )); + + NgpMonthlyWeeklyResponse filtered = (NgpMonthlyWeeklyResponse) service.query( + new NgpMonthlyWeeklyRequest("2026-09", null, "网点B、网点A")); + assertThat(filtered.stores()).extracting(NgpMonthlyStoreRow::storeName) + .containsExactly("网点B", "网点A"); + assertThat(filtered.grandTotal()).isEqualByComparingTo("300.00"); + + NgpMonthlyWeeklyResponse all = (NgpMonthlyWeeklyResponse) service.query( + new NgpMonthlyWeeklyRequest("2026-09", null, " ")); + assertThat(all.stores()).extracting(NgpMonthlyStoreRow::storeName) + .containsExactly("网点A", "网点B", "网点C"); + assertThat(all.grandTotal()).isEqualByComparingTo("600.00"); } }