feat: 外观专利改直连 LLM(DeepSeek/Gemini)、无效ASIN菜单更名 BRAND_DB、紫鸟店铺索引场景化
Build Backend JAR / build (push) Has been cancelled
Build Backend JAR / build (push) Has been cancelled
- AppearancePatent:Coze 工作流切换为 llm-host 直连模型(title/appearance 双模型、行并发、重试),配置键迁移并保留旧环境变量兜底 - InvalidAsinDataMapper 补充按值+品牌唯一键查询 - ZiniaoShopIndexService 新增店铺分类/索引场景 - V98:无效ASIN菜单 rename 补充 BRAND_DB;docs 架构优化规划 - 配套单测更新
This commit is contained in:
+84
-2
@@ -17,6 +17,8 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -82,6 +84,8 @@ class ZiniaoShopIndexServiceTest {
|
||||
when(ziniaoAuthService.getOrLoadStaffForIndex("allowed-key", 2L)).thenReturn(List.of(staff(22L)));
|
||||
when(ziniaoAuthService.getOrLoadUserStoresForIndex("allowed-key", 2L, 22L))
|
||||
.thenReturn(List.of(shop("shop-2", "店铺B")));
|
||||
when(ziniaoMemoryStoreService.listAliveEntitiesByType(
|
||||
ZiniaoMemoryStoreService.CACHE_TYPE_SHOP_INDEX_ENTRY, 10000)).thenReturn(List.of());
|
||||
|
||||
service.refreshShopIndex();
|
||||
|
||||
@@ -98,7 +102,6 @@ class ZiniaoShopIndexServiceTest {
|
||||
any(ZiniaoShopIndexEntryDto.class),
|
||||
any(Duration.class)
|
||||
);
|
||||
verify(ziniaoMemoryStoreService, never()).listAliveEntitiesByType(any(), anyInt());
|
||||
verify(ziniaoApiKeyProvider).markIpWhitelistBlocked(
|
||||
eq(blocked),
|
||||
eq("当前服务器 IP 未加入紫鸟白名单")
|
||||
@@ -149,7 +152,6 @@ class ZiniaoShopIndexServiceTest {
|
||||
any(ZiniaoShopIndexEntryDto.class),
|
||||
any(Duration.class)
|
||||
);
|
||||
verify(ziniaoMemoryStoreService, never()).listAliveEntitiesByType(any(), anyInt());
|
||||
verify(ziniaoApiKeyProvider).markIpWhitelistBlocked(
|
||||
eq(partiallyBlocked),
|
||||
eq("当前服务器 IP 未加入紫鸟白名单")
|
||||
@@ -158,6 +160,76 @@ class ZiniaoShopIndexServiceTest {
|
||||
verify(ziniaoApiKeyProvider, never()).markIpWhitelistAllowed(partiallyBlocked);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whitelistBlockedApiKeyMarksItsActiveIndexRowsAsBypassEligible() throws Exception {
|
||||
stubIpWhitelistDetection();
|
||||
ZiniaoApiKeyProvider.ApiKeyAccount blocked = new ZiniaoApiKeyProvider.ApiKeyAccount("blocked-key", "blocked-account");
|
||||
ZiniaoApiKeyProvider.ApiKeyAccount allowed = new ZiniaoApiKeyProvider.ApiKeyAccount("allowed-key", "allowed-account");
|
||||
when(ziniaoApiKeyProvider.listApiKeyAccounts()).thenReturn(List.of(blocked, allowed));
|
||||
when(ziniaoAuthService.resolveCompanyIdForIndex("blocked-key"))
|
||||
.thenThrow(new BusinessException("当前服务器 IP 未加入紫鸟白名单"));
|
||||
when(ziniaoAuthService.resolveCompanyIdForIndex("allowed-key")).thenReturn(2L);
|
||||
when(ziniaoAuthService.getOrLoadStaffForIndex("allowed-key", 2L)).thenReturn(List.of(staff(22L)));
|
||||
when(ziniaoAuthService.getOrLoadUserStoresForIndex("allowed-key", 2L, 22L))
|
||||
.thenReturn(List.of(shop("shop-2", "店铺B")));
|
||||
|
||||
ZiniaoShopIndexEntryDto blockedDto = new ZiniaoShopIndexEntryDto();
|
||||
blockedDto.setNormalizedShopName("blocked-shop");
|
||||
blockedDto.setStatus("ACTIVE");
|
||||
blockedDto.setApiKeyHash(sha256("blocked-key"));
|
||||
blockedDto.setLastRefreshedAt(1000L);
|
||||
ZiniaoMemoryStoreEntity blockedRow = new ZiniaoMemoryStoreEntity();
|
||||
blockedRow.setId(1L);
|
||||
blockedRow.setCacheType("SHOP_INDEX_ENTRY");
|
||||
blockedRow.setCacheKey("s:blocked-shop");
|
||||
blockedRow.setPayloadJson(new ObjectMapper().writeValueAsString(blockedDto));
|
||||
blockedRow.setExpiresAt(LocalDateTime.now().plusHours(12));
|
||||
|
||||
ZiniaoShopIndexEntryDto allowedDto = new ZiniaoShopIndexEntryDto();
|
||||
allowedDto.setNormalizedShopName("allowed-shop");
|
||||
allowedDto.setStatus("ACTIVE");
|
||||
allowedDto.setApiKeyHash(sha256("allowed-key"));
|
||||
allowedDto.setLastRefreshedAt(1000L);
|
||||
ZiniaoMemoryStoreEntity allowedRow = new ZiniaoMemoryStoreEntity();
|
||||
allowedRow.setId(2L);
|
||||
allowedRow.setCacheType("SHOP_INDEX_ENTRY");
|
||||
allowedRow.setCacheKey("s:allowed-shop");
|
||||
allowedRow.setPayloadJson(new ObjectMapper().writeValueAsString(allowedDto));
|
||||
allowedRow.setExpiresAt(LocalDateTime.now().plusHours(12));
|
||||
|
||||
when(ziniaoMemoryStoreService.listAliveEntitiesByType("SHOP_INDEX_ENTRY", 10000))
|
||||
.thenReturn(List.of(blockedRow, allowedRow));
|
||||
|
||||
service.refreshShopIndex();
|
||||
|
||||
ArgumentCaptor<List<ZiniaoMemoryStoreEntity>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(ziniaoMemoryStoreService).updateStaleMarks(captor.capture());
|
||||
assertEquals(1, captor.getValue().size());
|
||||
ZiniaoMemoryStoreEntity updated = captor.getValue().get(0);
|
||||
assertEquals(1L, updated.getId());
|
||||
var payload = new ObjectMapper().readTree(updated.getPayloadJson());
|
||||
assertEquals("IP_WHITELIST", payload.get("refreshBlockedReason").asText());
|
||||
assertEquals("ACTIVE", payload.get("status").asText());
|
||||
assertTrue(payload.get("lastRefreshBlockedAt").asLong() >= 1000L,
|
||||
"lastRefreshBlockedAt 应不小于行内 lastRefreshedAt,否则查询侧豁免条件不成立");
|
||||
}
|
||||
|
||||
@Test
|
||||
void laterWhitelistClearRemovesBlockedMarksFromIndexRows() throws Exception {
|
||||
ZiniaoApiKeyProvider.ApiKeyAccount key = new ZiniaoApiKeyProvider.ApiKeyAccount("key", "acct");
|
||||
when(ziniaoApiKeyProvider.listApiKeyAccounts()).thenReturn(List.of(key));
|
||||
when(ziniaoAuthService.resolveCompanyIdForIndex("key")).thenReturn(1L);
|
||||
when(ziniaoAuthService.getOrLoadStaffForIndex("key", 1L)).thenReturn(List.of(staff(11L)));
|
||||
when(ziniaoAuthService.getOrLoadUserStoresForIndex("key", 1L, 11L))
|
||||
.thenReturn(List.of(shop("s-1", "shop-1")));
|
||||
|
||||
when(ziniaoMemoryStoreService.listAliveEntitiesByType("SHOP_INDEX_ENTRY", 10000)).thenReturn(List.of());
|
||||
|
||||
service.refreshShopIndex();
|
||||
|
||||
verify(ziniaoMemoryStoreService, never()).updateStaleMarks(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonWhitelistCompanyFailureDoesNotOverwriteWhitelistStatus() {
|
||||
ZiniaoApiKeyProvider.ApiKeyAccount failed = new ZiniaoApiKeyProvider.ApiKeyAccount("failed-key", "failed-account");
|
||||
@@ -327,6 +399,16 @@ class ZiniaoShopIndexServiceTest {
|
||||
return (ZiniaoShopIndexRefreshCursorDto) captor.getAllValues().getLast();
|
||||
}
|
||||
|
||||
private String sha256(String value) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void stubIpWhitelistDetection() {
|
||||
when(ziniaoAuthService.isIpWhitelistError(any(BusinessException.class)))
|
||||
.thenAnswer(invocation -> invocation.<BusinessException>getArgument(0).getMessage().contains("白名单"));
|
||||
|
||||
Reference in New Issue
Block a user