refactor: 结果下载直链解析去重(11 处 → 1 处)

新增 common/service/ResultDownloadResolver,7 个模块的 resolveResultDownloadUrl
改为委托调用(appearancepatent/queryasin/withdraw/pricetrack/productrisk/
patroldelete/shopmatch)。

顺带修掉两处隐患:
- 原实现用 userId.equals(entity.getUserId()),userId 为 null 时 NPE
  (新实现用 Objects.equals)
- shopmatch 原实现在 url 为空时抛"任务不存在",与语义不符,统一为"暂无可下载文件"

其余 4 处**刻意保留**,因为它们本就不是重复:
- shopdatacrawl:有 validateUserId + requireResultEntity + ensureResultOwner 前置校验
- deletebrand:返回 null 而非抛异常(调用方依赖该语义)
- similarasin:已分叉为返回 record ResultDownloadInfo
- brand:走的是另一套下载路径
文件名解析 resolveResultDownloadFilename 同样没收口:各模块兜底文件名策略确有差异

配套更新 3 个显式构造 Service 的测试(DelegationTest/HistoryBatchTest/
RollbackSemanticsContractTest)注入新依赖的 mock。mvn test 2795 个全绿。
This commit is contained in:
2026-09-14 05:17:10 +08:00
parent ccce03b4d4
commit 24ada70997
11 changed files with 90 additions and 60 deletions
@@ -98,7 +98,10 @@ class AppearancePatentTaskServiceDelegationTest {
private AppearancePatentTaskService newService() {
return new AppearancePatentTaskService(
localFileStorageService, null, storageProperties, fileTaskMapper, fileResultMapper,
localFileStorageService, null,
// 2026-09:结果下载解析抽到 common/ResultDownloadResolver(去 11 处重复)
mock(com.nanri.aiimage.common.service.ResultDownloadResolver.class),
storageProperties, fileTaskMapper, fileResultMapper,
taskScopeStateMapper, taskChunkMapper, new ObjectMapper(), llmClient, taskCacheService,
properties, taskFileJobService, taskProgressSnapshotService,
transientPayloadStorageService, transactionManager, distributedJobLockService,
@@ -109,7 +112,9 @@ class AppearancePatentTaskServiceDelegationTest {
private AppearancePatentTaskService serviceWithoutTransactionManager() {
return new AppearancePatentTaskService(
localFileStorageService, null, storageProperties, fileTaskMapper, fileResultMapper,
localFileStorageService, null,
mock(com.nanri.aiimage.common.service.ResultDownloadResolver.class),
storageProperties, fileTaskMapper, fileResultMapper,
taskScopeStateMapper, taskChunkMapper, new ObjectMapper(), llmClient, taskCacheService,
properties, taskFileJobService, taskProgressSnapshotService,
transientPayloadStorageService, null, distributedJobLockService,
@@ -207,7 +207,10 @@ class AppearancePatentTaskServiceHistoryBatchTest {
lenient().when(taskProgressSnapshotService.find(any(), any())).thenReturn(null);
service = new AppearancePatentTaskService(
localFileStorageService, null, storageProperties, fileTaskMapper, fileResultMapper,
localFileStorageService, null,
// 2026-09:结果下载解析抽到 common/ResultDownloadResolver(去 11 处重复)
mock(com.nanri.aiimage.common.service.ResultDownloadResolver.class),
storageProperties, fileTaskMapper, fileResultMapper,
taskScopeStateMapper, taskChunkMapper, new ObjectMapper(), llmClient, taskCacheService,
properties, taskFileJobService, taskProgressSnapshotService,
transientPayloadStorageService, transactionManager, distributedJobLockService,
@@ -300,7 +300,10 @@ class RollbackSemanticsContractTest {
private AppearancePatentTaskService appearancePatentService() {
return new AppearancePatentTaskService(
localFileStorageService, ossStorageService, storageProperties, fileTaskMapper, fileResultMapper,
localFileStorageService, ossStorageService,
// 2026-09:结果下载解析抽到 common/ResultDownloadResolver(去 11 处重复)
mock(com.nanri.aiimage.common.service.ResultDownloadResolver.class),
storageProperties, fileTaskMapper, fileResultMapper,
taskScopeStateMapper, taskChunkMapper, objectMapper, mock(AppearancePatentLlmClient.class),
mock(AppearancePatentTaskCacheService.class), mock(com.nanri.aiimage.config.AppearancePatentProperties.class),
taskFileJobService, taskProgressSnapshotService, transientPayloadStorageService, transactionManager,