diff --git a/app/.env b/app/.env index 856e41a..085b36a 100644 --- a/app/.env +++ b/app/.env @@ -12,7 +12,7 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot client_name=ShuFuAI -java_api_base=http://127.0.0.1:18080 -# java_api_base=http://8.136.19.173:18080 +# java_api_base=http://127.0.0.1:18080 +java_api_base=http://8.136.19.173:18080 diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/cache/DeleteBrandParsedFileCacheDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/cache/DeleteBrandParsedFileCacheDto.java index 59cd40e..11ef9c7 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/cache/DeleteBrandParsedFileCacheDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/cache/DeleteBrandParsedFileCacheDto.java @@ -12,6 +12,7 @@ public class DeleteBrandParsedFileCacheDto { private String fileKey; private String sourceFilename; private String shopName; + private String companyName; private String shopId; private String platform; private String openStoreUrl; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandResultItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandResultItemVo.java index c95bbaf..5f50a2c 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandResultItemVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandResultItemVo.java @@ -21,6 +21,9 @@ public class DeleteBrandResultItemVo { @Schema(description = "按 Excel 文件名解析出的店铺名") private String shopName; + @Schema(description = "紫鸟公司名称") + private String companyName; + @Schema(description = "是否匹配到紫鸟店铺") private boolean matched; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java index 9152476..54965c6 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java @@ -133,6 +133,7 @@ public class DeleteBrandRunService { item.setMatched(matchResult.isMatched()); if (matchResult.isMatched()) { item.setShopId(matchResult.getShopId()); + item.setCompanyName(matchResult.getCompanyName()); item.setPlatform(matchResult.getPlatform()); item.setOpenStoreUrl(matchResult.getOpenStoreUrl()); } @@ -153,6 +154,7 @@ public class DeleteBrandRunService { cacheDto.setSourceFilename(sourceFile.getOriginalFilename()); cacheDto.setShopName(item.getShopName()); cacheDto.setShopId(item.getShopId()); + cacheDto.setCompanyName(item.getCompanyName()); cacheDto.setPlatform(item.getPlatform()); cacheDto.setOpenStoreUrl(item.getOpenStoreUrl()); cacheDto.setTotalRows(parsed.totalRows()); @@ -204,7 +206,7 @@ public class DeleteBrandRunService { task.setSuccessFileCount(parsedSuccessCount); task.setFailedFileCount(parsedFailedCount); - task.setResultJson(JSONUtil.toJsonStr(items)); + task.setResultJson(toCompactTaskResultJsonForDb(items)); task.setUpdatedAt(LocalDateTime.now()); task.setSourceFileCount(processableFileCount); if (processableFileCount <= 0) { @@ -295,6 +297,7 @@ public class DeleteBrandRunService { } item.setShopId(candidate.getShopId()); + item.setCompanyName(candidate.getCompanyName()); item.setPlatform(candidate.getPlatform()); item.setOpenStoreUrl(candidate.getOpenStoreUrl()); @@ -716,6 +719,7 @@ public class DeleteBrandRunService { String oldStatus = item.getMatchStatus(); boolean oldMatched = item.isMatched(); String oldShopId = item.getShopId(); + String oldCompanyName = item.getCompanyName(); String oldPlatform = item.getPlatform(); String oldOpenStoreUrl = item.getOpenStoreUrl(); String oldMessage = item.getMatchMessage(); @@ -724,12 +728,14 @@ public class DeleteBrandRunService { item.setMatchStatus(refreshed.getMatchStatus()); item.setMatchMessage(refreshed.getMatchMessage()); item.setShopId(refreshed.getShopId()); + item.setCompanyName(refreshed.getCompanyName()); item.setPlatform(refreshed.getPlatform()); item.setOpenStoreUrl(refreshed.getOpenStoreUrl()); if (!java.util.Objects.equals(oldStatus, item.getMatchStatus()) || oldMatched != item.isMatched() || !java.util.Objects.equals(oldShopId, item.getShopId()) + || !java.util.Objects.equals(oldCompanyName, item.getCompanyName()) || !java.util.Objects.equals(oldPlatform, item.getPlatform()) || !java.util.Objects.equals(oldOpenStoreUrl, item.getOpenStoreUrl()) || !java.util.Objects.equals(oldMessage, item.getMatchMessage())) { @@ -745,7 +751,7 @@ public class DeleteBrandRunService { if (!changed) { return task; } - task.setResultJson(JSONUtil.toJsonStr(items)); + task.setResultJson(toCompactTaskResultJsonForDb(items)); task.setSourceFileCount(matchedCount); task.setFailedFileCount(unmatchedCount); task.setUpdatedAt(LocalDateTime.now()); @@ -1120,6 +1126,7 @@ public class DeleteBrandRunService { item.setFileKey(parsedFile.getFileKey()); item.setSourceFilename(parsedFile.getSourceFilename()); item.setShopName(parsedFile.getShopName()); + item.setCompanyName(parsedFile.getCompanyName()); item.setOutputFilename(outputFile.getName()); item.setDownloadUrl(ossStorageService.getPublicUrl(objectKey)); item.setTotalRows(parsedFile.getTotalRows()); @@ -1140,7 +1147,7 @@ public class DeleteBrandRunService { task.setStatus("SUCCESS"); task.setSuccessFileCount(successCount); task.setErrorMessage(null); - task.setResultJson(JSONUtil.toJsonStr(finalItems)); + task.setResultJson(toCompactTaskResultJsonForDb(finalItems)); task.setUpdatedAt(LocalDateTime.now()); task.setFinishedAt(LocalDateTime.now()); fileTaskMapper.updateById(task); @@ -1351,6 +1358,33 @@ public class DeleteBrandRunService { return normalized.isBlank() ? defaultValue : normalized; } + /** + * 落库用 result_json:去掉国家分组、预览行等大字段,保留匹配摘要与计数。 + * 索引常命中后单次任务 JSON 体积会陡增,易触发 max_allowed_packet / 更新失败,进而前端 unwrap 报「请求失败」。 + */ + private String toCompactTaskResultJsonForDb(List items) { + if (items == null || items.isEmpty()) { + return JSONUtil.toJsonStr(List.of()); + } + try { + List clone = objectMapper.readValue( + objectMapper.writeValueAsString(items), + new TypeReference>() { + }); + for (DeleteBrandResultItemVo vo : clone) { + if (vo == null) { + continue; + } + vo.setCountries(new ArrayList<>()); + vo.setPreviewRows(new ArrayList<>()); + } + return JSONUtil.toJsonStr(clone); + } catch (Exception ex) { + log.warn("[delete-brand] compact task result json failed, msg={}", ex.getMessage()); + return JSONUtil.toJsonStr(items); + } + } + private record ParsedDeleteBrandFile(int totalRows, List countries, List previewRows) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/model/entity/ZiniaoMemoryStoreEntity.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/model/entity/ZiniaoMemoryStoreEntity.java index cc4ef88..b8bc636 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/model/entity/ZiniaoMemoryStoreEntity.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/model/entity/ZiniaoMemoryStoreEntity.java @@ -1,6 +1,7 @@ package com.nanri.aiimage.modules.ziniao.memory.model.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -15,6 +16,8 @@ public class ZiniaoMemoryStoreEntity { private Long id; private String cacheType; private String cacheKey; + @TableField("company_name") + private String companyName; private String payloadJson; private LocalDateTime expiresAt; private LocalDateTime createdAt; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/service/ZiniaoMemoryStoreService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/service/ZiniaoMemoryStoreService.java index 8227e6d..3c4f992 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/service/ZiniaoMemoryStoreService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/memory/service/ZiniaoMemoryStoreService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.nanri.aiimage.common.exception.BusinessException; import com.nanri.aiimage.modules.ziniao.memory.mapper.ZiniaoMemoryStoreMapper; import com.nanri.aiimage.modules.ziniao.memory.model.entity.ZiniaoMemoryStoreEntity; +import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopIndexEntryDto; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; @@ -25,6 +26,9 @@ public class ZiniaoMemoryStoreService { /** 本表仅用于持久化店铺索引条目(见 ZiniaoShopIndexService)。 */ public static final String CACHE_TYPE_SHOP_INDEX_ENTRY = "SHOP_INDEX_ENTRY"; + /** INFO 日志里 company_name 采样最大字符数(避免过长刷屏)。 */ + private static final int COMPANY_NAME_LOG_SAMPLE_MAX_CHARS = 64; + private final ZiniaoMemoryStoreMapper ziniaoMemoryStoreMapper; private final ObjectMapper objectMapper; @@ -115,29 +119,33 @@ public class ZiniaoMemoryStoreService { } LocalDateTime now = LocalDateTime.now(); LocalDateTime expiresAt = now.plusSeconds(ttl.getSeconds()); + String companyName = resolveCompanyName(payload); ZiniaoMemoryStoreEntity entity = findOne(normalizedType, normalizedKey); if (entity == null) { entity = new ZiniaoMemoryStoreEntity(); entity.setCacheType(normalizedType); entity.setCacheKey(normalizedKey); + entity.setCompanyName(companyName); entity.setPayloadJson(payloadJson); entity.setExpiresAt(expiresAt); entity.setCreatedAt(now); entity.setUpdatedAt(now); ziniaoMemoryStoreMapper.insert(entity); if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) { - log.info("[ziniao-shop-index-db] insert cacheKey={} expiresAt={} bytes={}", - normalizedKey, expiresAt, payloadJson.length()); + log.info("[ziniao-shop-index-db] insert cacheKey={} companyNameSample={} expiresAt={} bytes={}", + normalizedKey, sampleCompanyNameForLog(companyName), expiresAt, payloadJson.length()); } return; } + companyName = mergeCompanyNameForPersist(entity.getCompanyName(), companyName); + entity.setCompanyName(companyName); entity.setPayloadJson(payloadJson); entity.setExpiresAt(expiresAt); entity.setUpdatedAt(now); ziniaoMemoryStoreMapper.updateById(entity); if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) { - log.info("[ziniao-shop-index-db] update cacheKey={} id={} expiresAt={} bytes={}", - normalizedKey, entity.getId(), expiresAt, payloadJson.length()); + log.info("[ziniao-shop-index-db] update cacheKey={} id={} companyNameSample={} expiresAt={} bytes={}", + normalizedKey, entity.getId(), sampleCompanyNameForLog(companyName), expiresAt, payloadJson.length()); } } @@ -149,7 +157,8 @@ public class ZiniaoMemoryStoreService { if (entity != null) { ziniaoMemoryStoreMapper.deleteById(entity.getId()); if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) { - log.info("[ziniao-shop-index-db] delete cacheKey={} id={}", entity.getCacheKey(), entity.getId()); + log.info("[ziniao-shop-index-db] delete cacheKey={} id={} companyNameSample={}", + entity.getCacheKey(), entity.getId(), sampleCompanyNameForLog(entity.getCompanyName())); } } } @@ -210,6 +219,39 @@ public class ZiniaoMemoryStoreService { return entity.getExpiresAt() == null || !entity.getExpiresAt().isAfter(LocalDateTime.now()); } + private String resolveCompanyName(Object payload) { + if (payload instanceof ZiniaoShopIndexEntryDto dto) { + return Objects.toString(dto.getCompanyName(), "").trim(); + } + return ""; + } + + /** + * 更新行时:若 payload 未带公司名,保留库里已有值,避免历史/仅改 status 的回写把 company_name 清空。 + */ + private static String mergeCompanyNameForPersist(String existingDbValue, String fromPayload) { + String fromNew = fromPayload == null ? "" : fromPayload.trim(); + if (!fromNew.isEmpty()) { + return fromNew; + } + String existing = existingDbValue == null ? "" : existingDbValue.trim(); + return existing; + } + + /** + * 日志用 company_name 采样:空显示 (empty),过长截断并附带原长度,便于运行时对照 DB 写入意图且不刷屏。 + */ + private static String sampleCompanyNameForLog(String companyName) { + String s = companyName == null ? "" : companyName.trim(); + if (s.isEmpty()) { + return "(empty)"; + } + if (s.length() <= COMPANY_NAME_LOG_SAMPLE_MAX_CHARS) { + return s; + } + return s.substring(0, COMPANY_NAME_LOG_SAMPLE_MAX_CHARS) + "…(" + s.length() + " chars)"; + } + private String normalizeRequired(String value, String message) { String normalized = Objects.toString(value, "").trim(); if (normalized.isEmpty()) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexEntryDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexEntryDto.java index 6c5f0f0..0ea34cd 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexEntryDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexEntryDto.java @@ -10,6 +10,7 @@ public class ZiniaoShopIndexEntryDto { private String normalizedShopName; private String shopId; private String shopName; + private String companyName; private String platform; private Long matchedUserId; private Long companyId; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexRefreshCursorDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexRefreshCursorDto.java index b3d07d0..78d69ad 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexRefreshCursorDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopIndexRefreshCursorDto.java @@ -13,6 +13,12 @@ public class ZiniaoShopIndexRefreshCursorDto { private Long lastStartedAt; private Long lastFinishedAt; private Long lastSuccessAt; + /** 下一轮刷新从 apiKey 列表的哪个 offset 开始。 */ + private Integer nextApiKeyOffset; + /** 最近一轮可见 apiKey 总量。 */ + private Integer apiKeyTotal; + /** 最近一轮实际处理的 apiKey 数。 */ + private Integer lastProcessedApiKeyCount; private Integer invalidUserCount; private List sampleInvalidUserIds = new ArrayList<>(); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java index fb2568c..cae5757 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java @@ -19,6 +19,9 @@ public class ZiniaoShopMatchResultVo { @Schema(description = "店铺名称") private String shopName; + @Schema(description = "紫鸟公司名称") + private String companyName; + @Schema(description = "平台") private String platform; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoApiKeyProvider.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoApiKeyProvider.java index b72b2e4..94ec38d 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoApiKeyProvider.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoApiKeyProvider.java @@ -7,7 +7,9 @@ import com.nanri.aiimage.modules.shopkey.model.entity.ShopKeyEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -15,16 +17,33 @@ public class ZiniaoApiKeyProvider { private final ShopKeyMapper shopKeyMapper; + public List listApiKeyAccounts() { + List entities = shopKeyMapper.selectList(new LambdaQueryWrapper() + .orderByDesc(ShopKeyEntity::getId)); + Map accountByApiKey = new LinkedHashMap<>(); + for (ShopKeyEntity entity : entities) { + String apiKey = normalizeApiKey(entity == null ? null : entity.getZiniaoToken()); + if (apiKey == null) { + continue; + } + String accountName = entity.getZiniaoAccountName() == null ? null : entity.getZiniaoAccountName().trim(); + if (!accountByApiKey.containsKey(apiKey)) { + accountByApiKey.put(apiKey, accountName); + continue; + } + String existingName = accountByApiKey.get(apiKey); + if ((existingName == null || existingName.isBlank()) && accountName != null && !accountName.isBlank()) { + accountByApiKey.put(apiKey, accountName); + } + } + return accountByApiKey.entrySet().stream() + .map(entry -> new ApiKeyAccount(entry.getKey(), entry.getValue())) + .toList(); + } + public List listApiKeys() { - return shopKeyMapper.selectList(new LambdaQueryWrapper() - .orderByDesc(ShopKeyEntity::getId)) - .stream() - .map(ShopKeyEntity::getZiniaoToken) - .filter(token -> token != null && !token.isBlank()) - .map(String::trim) - .map(token -> token.regionMatches(true, 0, "Bearer ", 0, 7) ? token.substring(7).trim() : token) - .filter(token -> !token.isBlank()) - .distinct() + return listApiKeyAccounts().stream() + .map(ApiKeyAccount::apiKey) .toList(); } @@ -40,4 +59,18 @@ public class ZiniaoApiKeyProvider { Long total = shopKeyMapper.selectCount(new LambdaQueryWrapper()); return total != null && total > 0; } + + private String normalizeApiKey(String token) { + if (token == null || token.isBlank()) { + return null; + } + String normalized = token.trim(); + if (normalized.regionMatches(true, 0, "Bearer ", 0, 7)) { + normalized = normalized.substring(7).trim(); + } + return normalized.isBlank() ? null : normalized; + } + + public record ApiKeyAccount(String apiKey, String accountName) { + } } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java index bf66eb7..3a42ad9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java @@ -69,6 +69,7 @@ public class ZiniaoShopIndexService { cursor.setScopeKey("global"); cursor.setStatus("IDLE"); cursor.setMessage("索引刷新任务尚未执行"); + cursor.setNextApiKeyOffset(0); } return cursor; } @@ -107,25 +108,45 @@ public class ZiniaoShopIndexService { return pendingResult("店铺索引缺少 companyId,请等待后台刷新"); } - String openStoreUrl = ziniaoAuthService.buildOpenStoreUrlByScope(apiKey, companyId, entry.getMatchedUserId(), entry.getShopId()); + String openStoreUrl = null; + boolean openUrlFailed = false; + try { + openStoreUrl = ziniaoAuthService.buildOpenStoreUrlByScope(apiKey, companyId, entry.getMatchedUserId(), entry.getShopId()); + } catch (BusinessException ex) { + openUrlFailed = true; + log.warn("[ziniao-shop-index] buildOpenStoreUrl failed normalizedName={} msg={}", normalizedShopName, ex.getMessage()); + } ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo(); vo.setMatched(true); vo.setShopId(entry.getShopId()); vo.setShopName(entry.getShopName()); + vo.setCompanyName(entry.getCompanyName()); vo.setPlatform(entry.getPlatform()); vo.setMatchedUserId(entry.getMatchedUserId()); vo.setOpenStoreUrl(openStoreUrl); - vo.setMatchStatus(isFresh(entry) ? MATCH_STATUS_MATCHED : MATCH_STATUS_STALE); - vo.setMatchMessage(isFresh(entry) ? null : "店铺索引已过保鲜期,请等待后台刷新"); + if (openUrlFailed) { + vo.setMatchStatus(MATCH_STATUS_STALE); + vo.setMatchMessage("打开店铺链接暂时无法生成,请稍后重试"); + } else { + vo.setMatchStatus(isFresh(entry) ? MATCH_STATUS_MATCHED : MATCH_STATUS_STALE); + vo.setMatchMessage(isFresh(entry) ? null : "店铺索引已过保鲜期,请等待后台刷新"); + } return vo; } public void refreshShopIndex() { long now = Instant.now().toEpochMilli(); + ZiniaoShopIndexRefreshCursorDto previousCursor = ziniaoTransientCacheService + .get(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", ZiniaoShopIndexRefreshCursorDto.class) + .orElse(null); + int previousOffset = previousCursor == null || previousCursor.getNextApiKeyOffset() == null + ? 0 + : Math.max(previousCursor.getNextApiKeyOffset(), 0); ZiniaoShopIndexRefreshCursorDto cursor = new ZiniaoShopIndexRefreshCursorDto(); cursor.setScopeKey("global"); cursor.setStatus("RUNNING"); cursor.setLastStartedAt(now); + cursor.setNextApiKeyOffset(previousOffset); ziniaoTransientCacheService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL); log.info("[ziniao-index] refresh started"); @@ -134,11 +155,16 @@ public class ZiniaoShopIndexService { Set allInvalidUserIds = new LinkedHashSet<>(); int refreshBatchSize = resolveRefreshBatchSize(); try { - List apiKeys = ziniaoApiKeyProvider.listApiKeys(); - if (refreshBatchSize > 0 && apiKeys.size() > refreshBatchSize) { - apiKeys = apiKeys.subList(0, refreshBatchSize); - } - for (String apiKey : apiKeys) { + List allApiKeyAccounts = ziniaoApiKeyProvider.listApiKeyAccounts(); + List apiKeyAccounts = selectApiKeyBatchByOffset( + allApiKeyAccounts, + previousOffset, + refreshBatchSize + ); + int nextOffset = computeNextOffset(allApiKeyAccounts.size(), previousOffset, apiKeyAccounts.size(), refreshBatchSize); + for (ZiniaoApiKeyProvider.ApiKeyAccount apiKeyAccount : apiKeyAccounts) { + String apiKey = apiKeyAccount.apiKey(); + String companyName = apiKeyAccount.accountName(); Long companyId; try { companyId = ziniaoAuthService.resolveCompanyIdForIndex(apiKey); @@ -198,6 +224,7 @@ public class ZiniaoShopIndexService { entry.setNormalizedShopName(normalizedShopName); entry.setShopId(store.getShopId()); entry.setShopName(store.getShopName()); + entry.setCompanyName(companyName); entry.setPlatform(store.getPlatform()); entry.setMatchedUserId(userId); entry.setCompanyId(companyId); @@ -260,12 +287,16 @@ public class ZiniaoShopIndexService { cursor.setMessage(allInvalidUserIds.isEmpty() ? null : "本轮刷新已跳过无效 userId 数: " + allInvalidUserIds.size()); + cursor.setApiKeyTotal(allApiKeyAccounts.size()); + cursor.setLastProcessedApiKeyCount(apiKeyAccounts.size()); + cursor.setNextApiKeyOffset(nextOffset); cursor.setInvalidUserCount(allInvalidUserIds.size()); cursor.setSampleInvalidUserIds(allInvalidUserIds.stream().limit(20).toList()); cursor.setLastFinishedAt(now); cursor.setLastSuccessAt(now); ziniaoTransientCacheService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL); - log.info("[ziniao-index] refresh success invalidUsersSkipped={}", allInvalidUserIds.size()); + log.info("[ziniao-index] refresh success invalidUsersSkipped={} apiKeyProcessed={}/{} nextOffset={}", + allInvalidUserIds.size(), apiKeyAccounts.size(), allApiKeyAccounts.size(), nextOffset); } catch (Exception ex) { cursor.setStatus("FAILED"); cursor.setMessage(ex.getMessage()); @@ -313,6 +344,7 @@ public class ZiniaoShopIndexService { entry.setNormalizedShopName(normalizedShopName); entry.setStatus(STATUS_CONFLICT); entry.setShopName(representative == null ? normalizedShopName : representative.getShopName()); + entry.setCompanyName(representative == null ? null : representative.getCompanyName()); entry.setCompanyId(representative == null ? null : representative.getCompanyId()); entry.setApiKeyHash(representative == null ? null : representative.getApiKeyHash()); entry.setCandidateCount(candidates == null ? 0 : candidates.size()); @@ -427,6 +459,17 @@ public class ZiniaoShopIndexService { || existingEntry.getNormalizedShopName().isBlank()) { continue; } + String companyNameInRow = entity.getCompanyName(); + if ((existingEntry.getCompanyName() == null || existingEntry.getCompanyName().isBlank()) + && companyNameInRow != null && !companyNameInRow.isBlank()) { + existingEntry.setCompanyName(companyNameInRow.trim()); + } + if (existingEntry.getCompanyName() == null || existingEntry.getCompanyName().isBlank()) { + String accountFromKeys = resolveAccountNameByApiKeyHash(existingEntry.getApiKeyHash()); + if (accountFromKeys != null && !accountFromKeys.isBlank()) { + existingEntry.setCompanyName(accountFromKeys.trim()); + } + } if (STATUS_CONFLICT.equals(existingEntry.getStatus()) && (existingEntry.getCandidateCount() == null || existingEntry.getCandidateCount() <= 0) && (existingEntry.getShopName() == null || existingEntry.getShopName().isBlank()) @@ -475,6 +518,36 @@ public class ZiniaoShopIndexService { return batchSize; } + private List selectApiKeyBatchByOffset( + List allApiKeyAccounts, + int offset, + int refreshBatchSize) { + if (allApiKeyAccounts == null || allApiKeyAccounts.isEmpty()) { + return List.of(); + } + int total = allApiKeyAccounts.size(); + if (refreshBatchSize <= 0 || refreshBatchSize >= total) { + return new ArrayList<>(allApiKeyAccounts); + } + int normalizedOffset = Math.floorMod(offset, total); + List selected = new ArrayList<>(refreshBatchSize); + for (int i = 0; i < refreshBatchSize; i++) { + selected.add(allApiKeyAccounts.get((normalizedOffset + i) % total)); + } + return selected; + } + + private int computeNextOffset(int totalApiKeys, int previousOffset, int processedCount, int refreshBatchSize) { + if (totalApiKeys <= 0) { + return 0; + } + int step = processedCount > 0 ? processedCount : Math.max(refreshBatchSize, 0); + if (step <= 0) { + return Math.floorMod(previousOffset, totalApiKeys); + } + return Math.floorMod(previousOffset + step, totalApiKeys); + } + private List buildUserIds(List staff) { List userIds = new ArrayList<>(); for (ZiniaoStaffItemVo item : staff) { @@ -535,6 +608,19 @@ public class ZiniaoShopIndexService { return null; } + private String resolveAccountNameByApiKeyHash(String apiKeyHash) { + String apiKey = findApiKeyByHash(apiKeyHash); + if (apiKey == null) { + return null; + } + for (ZiniaoApiKeyProvider.ApiKeyAccount acc : ziniaoApiKeyProvider.listApiKeyAccounts()) { + if (apiKey.equals(acc.apiKey())) { + return acc.accountName(); + } + } + return null; + } + private String buildApiKeyHash(String apiKey) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); diff --git a/backend-java/src/main/resources/db/V8__add_company_name_to_ziniao_memory_store.sql b/backend-java/src/main/resources/db/V8__add_company_name_to_ziniao_memory_store.sql new file mode 100644 index 0000000..4c1e4fd --- /dev/null +++ b/backend-java/src/main/resources/db/V8__add_company_name_to_ziniao_memory_store.sql @@ -0,0 +1,4 @@ +ALTER TABLE biz_ziniao_memory_store + ADD COLUMN company_name VARCHAR(255) NULL COMMENT '紫鸟公司名称' AFTER cache_key; + +CREATE INDEX idx_company_name ON biz_ziniao_memory_store (company_name);