后台店铺数据记录页新增 Tab 切换与重复 ASIN 分析;task-169/170 HTTP 客户端配置接入;dedupe/brand 相关改造
Build Backend JAR / build (push) Has been cancelled

- 管理后台:店铺数据记录改为列表分页展示(去任务号/文件列/累计标题,'最新'改'更新时间'),新增'重复 ASIN' Tab 按跨店聚合展示 ASIN/店铺/分组/国家/日期/价格;修复失败/进行中任务被过滤不显示的问题
- task-169: BrandCheckHttpConfigResolver 从 aiimage.http-client.* 读取品牌检查超时/重试(默认同现状、钳制),附 8 条测试
- task-170: surefire 内存调整为 1536m
- dedupe: DedupeRunService 重构(事务边界/进度)、新增 DedupeRunProgressVo、Controller 与结果 VO 调整,PermissionMenuService 相应适配
- brand/dedupe 前端:BrandDedupeTab/BrandPatrolDeleteTab 改造、新增 CountrySelector 组件与 country-options、类型与接口端对齐、测试更新
- 移除无引用文件:backend/static/logo.jpg、prompts/
This commit is contained in:
2026-09-03 01:57:41 +08:00
parent 152ea6eec0
commit 417a2bf831
20 changed files with 1883 additions and 475 deletions
@@ -0,0 +1,83 @@
package com.nanri.aiimage.config;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* task-169:品牌检查客户端配置接入契约(plan 10,同 168 模式)。
* 从 aiimage.http-client.* 读取;默认与 BrandCheckProperties 现状一致
* connect 10s / read 60s);覆盖生效;非法值钳制。
*/
class BrandCheckHttpConfigResolverTest {
private HttpClientProperties properties;
private BrandCheckHttpConfigResolver resolver;
@BeforeEach
void setUp() {
properties = new HttpClientProperties();
resolver = new BrandCheckHttpConfigResolver(properties);
}
@Test
void timeoutFromConfigApplied() {
assertEquals(10_000L, resolver.connectTimeoutMillis());
assertEquals(60_000L, resolver.readTimeoutMillis());
assertEquals(90_000L, resolver.callTimeoutMillis());
}
@Test
void defaultKeptSameAsBrandCheckCurrent() {
// 现状基线:BrandCheckProperties connect 10s / read 60s
assertEquals(10_000L, resolver.connectTimeoutMillis());
assertEquals(60_000L, resolver.readTimeoutMillis());
}
@Test
void overrideTakesEffect() {
properties.setConnectTimeoutMillis(15_000);
properties.setReadTimeoutMillis(45_000);
properties.setMaxRetries(2);
assertEquals(15_000L, resolver.connectTimeoutMillis());
assertEquals(45_000L, resolver.readTimeoutMillis());
assertEquals(2, resolver.maxRetries());
}
@Test
void callTimeoutReflected() {
properties.setCallTimeoutMillis(120_000);
assertEquals(120_000L, resolver.callTimeoutMillis());
}
@Test
void retryConfigApplied() {
assertEquals(3, resolver.maxRetries());
properties.setMaxRetries(0);
assertEquals(0, resolver.maxRetries());
}
@Test
void invalidTimeoutsClamped() {
properties.setConnectTimeoutMillis(-100);
assertEquals(1_000L, resolver.connectTimeoutMillis());
properties.setReadTimeoutMillis(99_999_999L);
assertEquals(3_600_000L, resolver.readTimeoutMillis());
}
@Test
void resolverWired() {
assertTrue(resolver.connectTimeoutMillis() > 0);
assertTrue(resolver.readTimeoutMillis() > 0);
assertTrue(resolver.maxRetries() >= 0);
}
@Test
void behaviorSameAsCurrent() {
assertEquals(10_000L, resolver.connectTimeoutMillis());
assertEquals(60_000L, resolver.readTimeoutMillis());
}
}
@@ -381,7 +381,9 @@ class PermissionMenuServiceTest {
}
@Test
void ordinaryAdminReplacementPreservesDirectGrantsOutsideEffectiveScope() {
void ordinaryAdminReplacementRemovesDirectGrantsOutsideOwnEffectiveScope() {
// 管理员权限被回收后,其名下员工超出管理员范围的直接授权必须一并移除,
// 否则回收了管理员的菜单权限,员工权限却还在(也永远删不掉)。
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
AdminUserMapper userMapper = mock(AdminUserMapper.class);
@@ -405,10 +407,10 @@ class PermissionMenuServiceTest {
ArgumentCaptor<UserColumnPermissionEntity> inserted =
ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
verify(permissionMapper, times(2)).insert(inserted.capture());
verify(permissionMapper, times(1)).insert(inserted.capture());
assertThat(inserted.getAllValues())
.extracting(UserColumnPermissionEntity::getColumnId)
.containsExactly(1L, 2L);
.containsExactly(1L);
}
@Test