新增删除增加公司名
This commit is contained in:
4
app/.env
4
app/.env
@@ -12,7 +12,7 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot
|
|||||||
client_name=ShuFuAI
|
client_name=ShuFuAI
|
||||||
|
|
||||||
|
|
||||||
java_api_base=http://127.0.0.1:18080
|
# java_api_base=http://127.0.0.1:18080
|
||||||
# java_api_base=http://8.136.19.173:18080
|
java_api_base=http://8.136.19.173:18080
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class DeleteBrandParsedFileCacheDto {
|
|||||||
private String fileKey;
|
private String fileKey;
|
||||||
private String sourceFilename;
|
private String sourceFilename;
|
||||||
private String shopName;
|
private String shopName;
|
||||||
|
private String companyName;
|
||||||
private String shopId;
|
private String shopId;
|
||||||
private String platform;
|
private String platform;
|
||||||
private String openStoreUrl;
|
private String openStoreUrl;
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public class DeleteBrandResultItemVo {
|
|||||||
@Schema(description = "按 Excel 文件名解析出的店铺名")
|
@Schema(description = "按 Excel 文件名解析出的店铺名")
|
||||||
private String shopName;
|
private String shopName;
|
||||||
|
|
||||||
|
@Schema(description = "紫鸟公司名称")
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
@Schema(description = "是否匹配到紫鸟店铺")
|
@Schema(description = "是否匹配到紫鸟店铺")
|
||||||
private boolean matched;
|
private boolean matched;
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ public class DeleteBrandRunService {
|
|||||||
item.setMatched(matchResult.isMatched());
|
item.setMatched(matchResult.isMatched());
|
||||||
if (matchResult.isMatched()) {
|
if (matchResult.isMatched()) {
|
||||||
item.setShopId(matchResult.getShopId());
|
item.setShopId(matchResult.getShopId());
|
||||||
|
item.setCompanyName(matchResult.getCompanyName());
|
||||||
item.setPlatform(matchResult.getPlatform());
|
item.setPlatform(matchResult.getPlatform());
|
||||||
item.setOpenStoreUrl(matchResult.getOpenStoreUrl());
|
item.setOpenStoreUrl(matchResult.getOpenStoreUrl());
|
||||||
}
|
}
|
||||||
@@ -153,6 +154,7 @@ public class DeleteBrandRunService {
|
|||||||
cacheDto.setSourceFilename(sourceFile.getOriginalFilename());
|
cacheDto.setSourceFilename(sourceFile.getOriginalFilename());
|
||||||
cacheDto.setShopName(item.getShopName());
|
cacheDto.setShopName(item.getShopName());
|
||||||
cacheDto.setShopId(item.getShopId());
|
cacheDto.setShopId(item.getShopId());
|
||||||
|
cacheDto.setCompanyName(item.getCompanyName());
|
||||||
cacheDto.setPlatform(item.getPlatform());
|
cacheDto.setPlatform(item.getPlatform());
|
||||||
cacheDto.setOpenStoreUrl(item.getOpenStoreUrl());
|
cacheDto.setOpenStoreUrl(item.getOpenStoreUrl());
|
||||||
cacheDto.setTotalRows(parsed.totalRows());
|
cacheDto.setTotalRows(parsed.totalRows());
|
||||||
@@ -204,7 +206,7 @@ public class DeleteBrandRunService {
|
|||||||
|
|
||||||
task.setSuccessFileCount(parsedSuccessCount);
|
task.setSuccessFileCount(parsedSuccessCount);
|
||||||
task.setFailedFileCount(parsedFailedCount);
|
task.setFailedFileCount(parsedFailedCount);
|
||||||
task.setResultJson(JSONUtil.toJsonStr(items));
|
task.setResultJson(toCompactTaskResultJsonForDb(items));
|
||||||
task.setUpdatedAt(LocalDateTime.now());
|
task.setUpdatedAt(LocalDateTime.now());
|
||||||
task.setSourceFileCount(processableFileCount);
|
task.setSourceFileCount(processableFileCount);
|
||||||
if (processableFileCount <= 0) {
|
if (processableFileCount <= 0) {
|
||||||
@@ -295,6 +297,7 @@ public class DeleteBrandRunService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.setShopId(candidate.getShopId());
|
item.setShopId(candidate.getShopId());
|
||||||
|
item.setCompanyName(candidate.getCompanyName());
|
||||||
item.setPlatform(candidate.getPlatform());
|
item.setPlatform(candidate.getPlatform());
|
||||||
item.setOpenStoreUrl(candidate.getOpenStoreUrl());
|
item.setOpenStoreUrl(candidate.getOpenStoreUrl());
|
||||||
|
|
||||||
@@ -716,6 +719,7 @@ public class DeleteBrandRunService {
|
|||||||
String oldStatus = item.getMatchStatus();
|
String oldStatus = item.getMatchStatus();
|
||||||
boolean oldMatched = item.isMatched();
|
boolean oldMatched = item.isMatched();
|
||||||
String oldShopId = item.getShopId();
|
String oldShopId = item.getShopId();
|
||||||
|
String oldCompanyName = item.getCompanyName();
|
||||||
String oldPlatform = item.getPlatform();
|
String oldPlatform = item.getPlatform();
|
||||||
String oldOpenStoreUrl = item.getOpenStoreUrl();
|
String oldOpenStoreUrl = item.getOpenStoreUrl();
|
||||||
String oldMessage = item.getMatchMessage();
|
String oldMessage = item.getMatchMessage();
|
||||||
@@ -724,12 +728,14 @@ public class DeleteBrandRunService {
|
|||||||
item.setMatchStatus(refreshed.getMatchStatus());
|
item.setMatchStatus(refreshed.getMatchStatus());
|
||||||
item.setMatchMessage(refreshed.getMatchMessage());
|
item.setMatchMessage(refreshed.getMatchMessage());
|
||||||
item.setShopId(refreshed.getShopId());
|
item.setShopId(refreshed.getShopId());
|
||||||
|
item.setCompanyName(refreshed.getCompanyName());
|
||||||
item.setPlatform(refreshed.getPlatform());
|
item.setPlatform(refreshed.getPlatform());
|
||||||
item.setOpenStoreUrl(refreshed.getOpenStoreUrl());
|
item.setOpenStoreUrl(refreshed.getOpenStoreUrl());
|
||||||
|
|
||||||
if (!java.util.Objects.equals(oldStatus, item.getMatchStatus())
|
if (!java.util.Objects.equals(oldStatus, item.getMatchStatus())
|
||||||
|| oldMatched != item.isMatched()
|
|| oldMatched != item.isMatched()
|
||||||
|| !java.util.Objects.equals(oldShopId, item.getShopId())
|
|| !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(oldPlatform, item.getPlatform())
|
||||||
|| !java.util.Objects.equals(oldOpenStoreUrl, item.getOpenStoreUrl())
|
|| !java.util.Objects.equals(oldOpenStoreUrl, item.getOpenStoreUrl())
|
||||||
|| !java.util.Objects.equals(oldMessage, item.getMatchMessage())) {
|
|| !java.util.Objects.equals(oldMessage, item.getMatchMessage())) {
|
||||||
@@ -745,7 +751,7 @@ public class DeleteBrandRunService {
|
|||||||
if (!changed) {
|
if (!changed) {
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
task.setResultJson(JSONUtil.toJsonStr(items));
|
task.setResultJson(toCompactTaskResultJsonForDb(items));
|
||||||
task.setSourceFileCount(matchedCount);
|
task.setSourceFileCount(matchedCount);
|
||||||
task.setFailedFileCount(unmatchedCount);
|
task.setFailedFileCount(unmatchedCount);
|
||||||
task.setUpdatedAt(LocalDateTime.now());
|
task.setUpdatedAt(LocalDateTime.now());
|
||||||
@@ -1120,6 +1126,7 @@ public class DeleteBrandRunService {
|
|||||||
item.setFileKey(parsedFile.getFileKey());
|
item.setFileKey(parsedFile.getFileKey());
|
||||||
item.setSourceFilename(parsedFile.getSourceFilename());
|
item.setSourceFilename(parsedFile.getSourceFilename());
|
||||||
item.setShopName(parsedFile.getShopName());
|
item.setShopName(parsedFile.getShopName());
|
||||||
|
item.setCompanyName(parsedFile.getCompanyName());
|
||||||
item.setOutputFilename(outputFile.getName());
|
item.setOutputFilename(outputFile.getName());
|
||||||
item.setDownloadUrl(ossStorageService.getPublicUrl(objectKey));
|
item.setDownloadUrl(ossStorageService.getPublicUrl(objectKey));
|
||||||
item.setTotalRows(parsedFile.getTotalRows());
|
item.setTotalRows(parsedFile.getTotalRows());
|
||||||
@@ -1140,7 +1147,7 @@ public class DeleteBrandRunService {
|
|||||||
task.setStatus("SUCCESS");
|
task.setStatus("SUCCESS");
|
||||||
task.setSuccessFileCount(successCount);
|
task.setSuccessFileCount(successCount);
|
||||||
task.setErrorMessage(null);
|
task.setErrorMessage(null);
|
||||||
task.setResultJson(JSONUtil.toJsonStr(finalItems));
|
task.setResultJson(toCompactTaskResultJsonForDb(finalItems));
|
||||||
task.setUpdatedAt(LocalDateTime.now());
|
task.setUpdatedAt(LocalDateTime.now());
|
||||||
task.setFinishedAt(LocalDateTime.now());
|
task.setFinishedAt(LocalDateTime.now());
|
||||||
fileTaskMapper.updateById(task);
|
fileTaskMapper.updateById(task);
|
||||||
@@ -1351,6 +1358,33 @@ public class DeleteBrandRunService {
|
|||||||
return normalized.isBlank() ? defaultValue : normalized;
|
return normalized.isBlank() ? defaultValue : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 落库用 result_json:去掉国家分组、预览行等大字段,保留匹配摘要与计数。
|
||||||
|
* 索引常命中后单次任务 JSON 体积会陡增,易触发 max_allowed_packet / 更新失败,进而前端 unwrap 报「请求失败」。
|
||||||
|
*/
|
||||||
|
private String toCompactTaskResultJsonForDb(List<DeleteBrandResultItemVo> items) {
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return JSONUtil.toJsonStr(List.of());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<DeleteBrandResultItemVo> clone = objectMapper.readValue(
|
||||||
|
objectMapper.writeValueAsString(items),
|
||||||
|
new TypeReference<List<DeleteBrandResultItemVo>>() {
|
||||||
|
});
|
||||||
|
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,
|
private record ParsedDeleteBrandFile(int totalRows,
|
||||||
List<DeleteBrandCountryGroupVo> countries,
|
List<DeleteBrandCountryGroupVo> countries,
|
||||||
List<DeleteBrandPreviewRowVo> previewRows) {
|
List<DeleteBrandPreviewRowVo> previewRows) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.nanri.aiimage.modules.ziniao.memory.model.entity;
|
package com.nanri.aiimage.modules.ziniao.memory.model.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -15,6 +16,8 @@ public class ZiniaoMemoryStoreEntity {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private String cacheType;
|
private String cacheType;
|
||||||
private String cacheKey;
|
private String cacheKey;
|
||||||
|
@TableField("company_name")
|
||||||
|
private String companyName;
|
||||||
private String payloadJson;
|
private String payloadJson;
|
||||||
private LocalDateTime expiresAt;
|
private LocalDateTime expiresAt;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.nanri.aiimage.common.exception.BusinessException;
|
import com.nanri.aiimage.common.exception.BusinessException;
|
||||||
import com.nanri.aiimage.modules.ziniao.memory.mapper.ZiniaoMemoryStoreMapper;
|
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.memory.model.entity.ZiniaoMemoryStoreEntity;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopIndexEntryDto;
|
||||||
import com.fasterxml.jackson.databind.JavaType;
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -25,6 +26,9 @@ public class ZiniaoMemoryStoreService {
|
|||||||
/** 本表仅用于持久化店铺索引条目(见 ZiniaoShopIndexService)。 */
|
/** 本表仅用于持久化店铺索引条目(见 ZiniaoShopIndexService)。 */
|
||||||
public static final String CACHE_TYPE_SHOP_INDEX_ENTRY = "SHOP_INDEX_ENTRY";
|
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 ZiniaoMemoryStoreMapper ziniaoMemoryStoreMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
@@ -115,29 +119,33 @@ public class ZiniaoMemoryStoreService {
|
|||||||
}
|
}
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
LocalDateTime expiresAt = now.plusSeconds(ttl.getSeconds());
|
LocalDateTime expiresAt = now.plusSeconds(ttl.getSeconds());
|
||||||
|
String companyName = resolveCompanyName(payload);
|
||||||
ZiniaoMemoryStoreEntity entity = findOne(normalizedType, normalizedKey);
|
ZiniaoMemoryStoreEntity entity = findOne(normalizedType, normalizedKey);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
entity = new ZiniaoMemoryStoreEntity();
|
entity = new ZiniaoMemoryStoreEntity();
|
||||||
entity.setCacheType(normalizedType);
|
entity.setCacheType(normalizedType);
|
||||||
entity.setCacheKey(normalizedKey);
|
entity.setCacheKey(normalizedKey);
|
||||||
|
entity.setCompanyName(companyName);
|
||||||
entity.setPayloadJson(payloadJson);
|
entity.setPayloadJson(payloadJson);
|
||||||
entity.setExpiresAt(expiresAt);
|
entity.setExpiresAt(expiresAt);
|
||||||
entity.setCreatedAt(now);
|
entity.setCreatedAt(now);
|
||||||
entity.setUpdatedAt(now);
|
entity.setUpdatedAt(now);
|
||||||
ziniaoMemoryStoreMapper.insert(entity);
|
ziniaoMemoryStoreMapper.insert(entity);
|
||||||
if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) {
|
if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) {
|
||||||
log.info("[ziniao-shop-index-db] insert cacheKey={} expiresAt={} bytes={}",
|
log.info("[ziniao-shop-index-db] insert cacheKey={} companyNameSample={} expiresAt={} bytes={}",
|
||||||
normalizedKey, expiresAt, payloadJson.length());
|
normalizedKey, sampleCompanyNameForLog(companyName), expiresAt, payloadJson.length());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
companyName = mergeCompanyNameForPersist(entity.getCompanyName(), companyName);
|
||||||
|
entity.setCompanyName(companyName);
|
||||||
entity.setPayloadJson(payloadJson);
|
entity.setPayloadJson(payloadJson);
|
||||||
entity.setExpiresAt(expiresAt);
|
entity.setExpiresAt(expiresAt);
|
||||||
entity.setUpdatedAt(now);
|
entity.setUpdatedAt(now);
|
||||||
ziniaoMemoryStoreMapper.updateById(entity);
|
ziniaoMemoryStoreMapper.updateById(entity);
|
||||||
if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) {
|
if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) {
|
||||||
log.info("[ziniao-shop-index-db] update cacheKey={} id={} expiresAt={} bytes={}",
|
log.info("[ziniao-shop-index-db] update cacheKey={} id={} companyNameSample={} expiresAt={} bytes={}",
|
||||||
normalizedKey, entity.getId(), expiresAt, payloadJson.length());
|
normalizedKey, entity.getId(), sampleCompanyNameForLog(companyName), expiresAt, payloadJson.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +157,8 @@ public class ZiniaoMemoryStoreService {
|
|||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
ziniaoMemoryStoreMapper.deleteById(entity.getId());
|
ziniaoMemoryStoreMapper.deleteById(entity.getId());
|
||||||
if (CACHE_TYPE_SHOP_INDEX_ENTRY.equals(normalizedType)) {
|
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());
|
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) {
|
private String normalizeRequired(String value, String message) {
|
||||||
String normalized = Objects.toString(value, "").trim();
|
String normalized = Objects.toString(value, "").trim();
|
||||||
if (normalized.isEmpty()) {
|
if (normalized.isEmpty()) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class ZiniaoShopIndexEntryDto {
|
|||||||
private String normalizedShopName;
|
private String normalizedShopName;
|
||||||
private String shopId;
|
private String shopId;
|
||||||
private String shopName;
|
private String shopName;
|
||||||
|
private String companyName;
|
||||||
private String platform;
|
private String platform;
|
||||||
private Long matchedUserId;
|
private Long matchedUserId;
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ public class ZiniaoShopIndexRefreshCursorDto {
|
|||||||
private Long lastStartedAt;
|
private Long lastStartedAt;
|
||||||
private Long lastFinishedAt;
|
private Long lastFinishedAt;
|
||||||
private Long lastSuccessAt;
|
private Long lastSuccessAt;
|
||||||
|
/** 下一轮刷新从 apiKey 列表的哪个 offset 开始。 */
|
||||||
|
private Integer nextApiKeyOffset;
|
||||||
|
/** 最近一轮可见 apiKey 总量。 */
|
||||||
|
private Integer apiKeyTotal;
|
||||||
|
/** 最近一轮实际处理的 apiKey 数。 */
|
||||||
|
private Integer lastProcessedApiKeyCount;
|
||||||
private Integer invalidUserCount;
|
private Integer invalidUserCount;
|
||||||
private List<Long> sampleInvalidUserIds = new ArrayList<>();
|
private List<Long> sampleInvalidUserIds = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ public class ZiniaoShopMatchResultVo {
|
|||||||
@Schema(description = "店铺名称")
|
@Schema(description = "店铺名称")
|
||||||
private String shopName;
|
private String shopName;
|
||||||
|
|
||||||
|
@Schema(description = "紫鸟公司名称")
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
@Schema(description = "平台")
|
@Schema(description = "平台")
|
||||||
private String platform;
|
private String platform;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import com.nanri.aiimage.modules.shopkey.model.entity.ShopKeyEntity;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -15,16 +17,33 @@ public class ZiniaoApiKeyProvider {
|
|||||||
|
|
||||||
private final ShopKeyMapper shopKeyMapper;
|
private final ShopKeyMapper shopKeyMapper;
|
||||||
|
|
||||||
|
public List<ApiKeyAccount> listApiKeyAccounts() {
|
||||||
|
List<ShopKeyEntity> entities = shopKeyMapper.selectList(new LambdaQueryWrapper<ShopKeyEntity>()
|
||||||
|
.orderByDesc(ShopKeyEntity::getId));
|
||||||
|
Map<String, String> 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<String> listApiKeys() {
|
public List<String> listApiKeys() {
|
||||||
return shopKeyMapper.selectList(new LambdaQueryWrapper<ShopKeyEntity>()
|
return listApiKeyAccounts().stream()
|
||||||
.orderByDesc(ShopKeyEntity::getId))
|
.map(ApiKeyAccount::apiKey)
|
||||||
.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()
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +59,18 @@ public class ZiniaoApiKeyProvider {
|
|||||||
Long total = shopKeyMapper.selectCount(new LambdaQueryWrapper<ShopKeyEntity>());
|
Long total = shopKeyMapper.selectCount(new LambdaQueryWrapper<ShopKeyEntity>());
|
||||||
return total != null && total > 0;
|
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) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ public class ZiniaoShopIndexService {
|
|||||||
cursor.setScopeKey("global");
|
cursor.setScopeKey("global");
|
||||||
cursor.setStatus("IDLE");
|
cursor.setStatus("IDLE");
|
||||||
cursor.setMessage("索引刷新任务尚未执行");
|
cursor.setMessage("索引刷新任务尚未执行");
|
||||||
|
cursor.setNextApiKeyOffset(0);
|
||||||
}
|
}
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@@ -107,25 +108,45 @@ public class ZiniaoShopIndexService {
|
|||||||
return pendingResult("店铺索引缺少 companyId,请等待后台刷新");
|
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();
|
ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
|
||||||
vo.setMatched(true);
|
vo.setMatched(true);
|
||||||
vo.setShopId(entry.getShopId());
|
vo.setShopId(entry.getShopId());
|
||||||
vo.setShopName(entry.getShopName());
|
vo.setShopName(entry.getShopName());
|
||||||
|
vo.setCompanyName(entry.getCompanyName());
|
||||||
vo.setPlatform(entry.getPlatform());
|
vo.setPlatform(entry.getPlatform());
|
||||||
vo.setMatchedUserId(entry.getMatchedUserId());
|
vo.setMatchedUserId(entry.getMatchedUserId());
|
||||||
vo.setOpenStoreUrl(openStoreUrl);
|
vo.setOpenStoreUrl(openStoreUrl);
|
||||||
vo.setMatchStatus(isFresh(entry) ? MATCH_STATUS_MATCHED : MATCH_STATUS_STALE);
|
if (openUrlFailed) {
|
||||||
vo.setMatchMessage(isFresh(entry) ? null : "店铺索引已过保鲜期,请等待后台刷新");
|
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;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshShopIndex() {
|
public void refreshShopIndex() {
|
||||||
long now = Instant.now().toEpochMilli();
|
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();
|
ZiniaoShopIndexRefreshCursorDto cursor = new ZiniaoShopIndexRefreshCursorDto();
|
||||||
cursor.setScopeKey("global");
|
cursor.setScopeKey("global");
|
||||||
cursor.setStatus("RUNNING");
|
cursor.setStatus("RUNNING");
|
||||||
cursor.setLastStartedAt(now);
|
cursor.setLastStartedAt(now);
|
||||||
|
cursor.setNextApiKeyOffset(previousOffset);
|
||||||
ziniaoTransientCacheService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);
|
ziniaoTransientCacheService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);
|
||||||
log.info("[ziniao-index] refresh started");
|
log.info("[ziniao-index] refresh started");
|
||||||
|
|
||||||
@@ -134,11 +155,16 @@ public class ZiniaoShopIndexService {
|
|||||||
Set<Long> allInvalidUserIds = new LinkedHashSet<>();
|
Set<Long> allInvalidUserIds = new LinkedHashSet<>();
|
||||||
int refreshBatchSize = resolveRefreshBatchSize();
|
int refreshBatchSize = resolveRefreshBatchSize();
|
||||||
try {
|
try {
|
||||||
List<String> apiKeys = ziniaoApiKeyProvider.listApiKeys();
|
List<ZiniaoApiKeyProvider.ApiKeyAccount> allApiKeyAccounts = ziniaoApiKeyProvider.listApiKeyAccounts();
|
||||||
if (refreshBatchSize > 0 && apiKeys.size() > refreshBatchSize) {
|
List<ZiniaoApiKeyProvider.ApiKeyAccount> apiKeyAccounts = selectApiKeyBatchByOffset(
|
||||||
apiKeys = apiKeys.subList(0, refreshBatchSize);
|
allApiKeyAccounts,
|
||||||
}
|
previousOffset,
|
||||||
for (String apiKey : apiKeys) {
|
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;
|
Long companyId;
|
||||||
try {
|
try {
|
||||||
companyId = ziniaoAuthService.resolveCompanyIdForIndex(apiKey);
|
companyId = ziniaoAuthService.resolveCompanyIdForIndex(apiKey);
|
||||||
@@ -198,6 +224,7 @@ public class ZiniaoShopIndexService {
|
|||||||
entry.setNormalizedShopName(normalizedShopName);
|
entry.setNormalizedShopName(normalizedShopName);
|
||||||
entry.setShopId(store.getShopId());
|
entry.setShopId(store.getShopId());
|
||||||
entry.setShopName(store.getShopName());
|
entry.setShopName(store.getShopName());
|
||||||
|
entry.setCompanyName(companyName);
|
||||||
entry.setPlatform(store.getPlatform());
|
entry.setPlatform(store.getPlatform());
|
||||||
entry.setMatchedUserId(userId);
|
entry.setMatchedUserId(userId);
|
||||||
entry.setCompanyId(companyId);
|
entry.setCompanyId(companyId);
|
||||||
@@ -260,12 +287,16 @@ public class ZiniaoShopIndexService {
|
|||||||
cursor.setMessage(allInvalidUserIds.isEmpty()
|
cursor.setMessage(allInvalidUserIds.isEmpty()
|
||||||
? null
|
? null
|
||||||
: "本轮刷新已跳过无效 userId 数: " + allInvalidUserIds.size());
|
: "本轮刷新已跳过无效 userId 数: " + allInvalidUserIds.size());
|
||||||
|
cursor.setApiKeyTotal(allApiKeyAccounts.size());
|
||||||
|
cursor.setLastProcessedApiKeyCount(apiKeyAccounts.size());
|
||||||
|
cursor.setNextApiKeyOffset(nextOffset);
|
||||||
cursor.setInvalidUserCount(allInvalidUserIds.size());
|
cursor.setInvalidUserCount(allInvalidUserIds.size());
|
||||||
cursor.setSampleInvalidUserIds(allInvalidUserIds.stream().limit(20).toList());
|
cursor.setSampleInvalidUserIds(allInvalidUserIds.stream().limit(20).toList());
|
||||||
cursor.setLastFinishedAt(now);
|
cursor.setLastFinishedAt(now);
|
||||||
cursor.setLastSuccessAt(now);
|
cursor.setLastSuccessAt(now);
|
||||||
ziniaoTransientCacheService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);
|
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) {
|
} catch (Exception ex) {
|
||||||
cursor.setStatus("FAILED");
|
cursor.setStatus("FAILED");
|
||||||
cursor.setMessage(ex.getMessage());
|
cursor.setMessage(ex.getMessage());
|
||||||
@@ -313,6 +344,7 @@ public class ZiniaoShopIndexService {
|
|||||||
entry.setNormalizedShopName(normalizedShopName);
|
entry.setNormalizedShopName(normalizedShopName);
|
||||||
entry.setStatus(STATUS_CONFLICT);
|
entry.setStatus(STATUS_CONFLICT);
|
||||||
entry.setShopName(representative == null ? normalizedShopName : representative.getShopName());
|
entry.setShopName(representative == null ? normalizedShopName : representative.getShopName());
|
||||||
|
entry.setCompanyName(representative == null ? null : representative.getCompanyName());
|
||||||
entry.setCompanyId(representative == null ? null : representative.getCompanyId());
|
entry.setCompanyId(representative == null ? null : representative.getCompanyId());
|
||||||
entry.setApiKeyHash(representative == null ? null : representative.getApiKeyHash());
|
entry.setApiKeyHash(representative == null ? null : representative.getApiKeyHash());
|
||||||
entry.setCandidateCount(candidates == null ? 0 : candidates.size());
|
entry.setCandidateCount(candidates == null ? 0 : candidates.size());
|
||||||
@@ -427,6 +459,17 @@ public class ZiniaoShopIndexService {
|
|||||||
|| existingEntry.getNormalizedShopName().isBlank()) {
|
|| existingEntry.getNormalizedShopName().isBlank()) {
|
||||||
continue;
|
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())
|
if (STATUS_CONFLICT.equals(existingEntry.getStatus())
|
||||||
&& (existingEntry.getCandidateCount() == null || existingEntry.getCandidateCount() <= 0)
|
&& (existingEntry.getCandidateCount() == null || existingEntry.getCandidateCount() <= 0)
|
||||||
&& (existingEntry.getShopName() == null || existingEntry.getShopName().isBlank())
|
&& (existingEntry.getShopName() == null || existingEntry.getShopName().isBlank())
|
||||||
@@ -475,6 +518,36 @@ public class ZiniaoShopIndexService {
|
|||||||
return batchSize;
|
return batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<ZiniaoApiKeyProvider.ApiKeyAccount> selectApiKeyBatchByOffset(
|
||||||
|
List<ZiniaoApiKeyProvider.ApiKeyAccount> 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<ZiniaoApiKeyProvider.ApiKeyAccount> 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<Long> buildUserIds(List<ZiniaoStaffItemVo> staff) {
|
private List<Long> buildUserIds(List<ZiniaoStaffItemVo> staff) {
|
||||||
List<Long> userIds = new ArrayList<>();
|
List<Long> userIds = new ArrayList<>();
|
||||||
for (ZiniaoStaffItemVo item : staff) {
|
for (ZiniaoStaffItemVo item : staff) {
|
||||||
@@ -535,6 +608,19 @@ public class ZiniaoShopIndexService {
|
|||||||
return null;
|
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) {
|
private String buildApiKeyHash(String apiKey) {
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
|||||||
@@ -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);
|
||||||
Reference in New Issue
Block a user