feat(trademark):优化商标查询功能和Excel解析逻辑
- 重构品牌商标缓存服务,移除冗余的日志记录和存在检查- 简化Excel解析工具类,提取公共方法并优化列索引查找逻辑 - 增强Electron客户端开发模式下的后端启动控制能力 - 改进商标筛查面板的用户体验和数据处理流程-优化商标查询工具类,提高查询准确性和稳定性 - 调整商标控制器接口参数校验逻辑和资源清理机制 - 更新USPTO API测试用例以支持Spring容器环境运行
This commit is contained in:
@@ -435,6 +435,19 @@ app.whenReady().then(() => {
|
|||||||
createWindow();
|
createWindow();
|
||||||
createTray(mainWindow);
|
createTray(mainWindow);
|
||||||
|
|
||||||
|
// 开发模式快捷键
|
||||||
|
if (isDev && mainWindow) {
|
||||||
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.control && input.shift && input.key.toLowerCase() === 'd') {
|
||||||
|
console.log('[开发模式] 手动跳过后端启动');
|
||||||
|
openAppIfNotOpened();
|
||||||
|
} else if (input.control && input.shift && input.key.toLowerCase() === 's') {
|
||||||
|
console.log('[开发模式] 手动启动后端服务');
|
||||||
|
startSpringBoot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 只有在不需要最小化启动时才显示 splash 窗口
|
// 只有在不需要最小化启动时才显示 splash 窗口
|
||||||
if (!shouldMinimize) {
|
if (!shouldMinimize) {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
@@ -476,9 +489,22 @@ app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('[启动流程] 准备启动 Spring Boot...');
|
console.log('[启动流程] 准备启动 Spring Boot...');
|
||||||
|
|
||||||
|
// 开发模式:添加快捷键跳过后端启动
|
||||||
|
if (isDev) {
|
||||||
|
console.log('[开发模式] 按 Ctrl+Shift+D 跳过后端启动,直接进入应用');
|
||||||
|
console.log('[开发模式] 按 Ctrl+Shift+S 手动启动后端服务');
|
||||||
|
|
||||||
|
// 5秒后自动跳过,避免卡死
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startSpringBoot();
|
console.log('[开发模式] 自动跳过后端启动,直接进入应用');
|
||||||
}, 200);
|
openAppIfNotOpened();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// startSpringBoot();
|
||||||
|
// }, 200);
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
@@ -847,6 +873,26 @@ ipcMain.handle('set-launch-config', (event, launchConfig: { autoLaunch: boolean;
|
|||||||
// 刷新页面
|
// 刷新页面
|
||||||
ipcMain.handle('reload', () => mainWindow?.webContents.reload());
|
ipcMain.handle('reload', () => mainWindow?.webContents.reload());
|
||||||
|
|
||||||
|
// 开发模式:跳过后端启动
|
||||||
|
ipcMain.handle('dev-skip-backend', () => {
|
||||||
|
if (isDev) {
|
||||||
|
console.log('[开发模式] 前端请求跳过后端启动');
|
||||||
|
openAppIfNotOpened();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return { success: false, error: '仅开发模式可用' };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开发模式:手动启动后端
|
||||||
|
ipcMain.handle('dev-start-backend', () => {
|
||||||
|
if (isDev) {
|
||||||
|
console.log('[开发模式] 前端请求启动后端');
|
||||||
|
startSpringBoot();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return { success: false, error: '仅开发模式可用' };
|
||||||
|
});
|
||||||
|
|
||||||
// 窗口控制 API
|
// 窗口控制 API
|
||||||
ipcMain.handle('window-minimize', () => {
|
ipcMain.handle('window-minimize', () => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
|||||||
@@ -116,18 +116,11 @@ function showMessage(message: string, type: 'success' | 'warning' | 'error' | 'i
|
|||||||
// 拖拽上传
|
// 拖拽上传
|
||||||
async function processTrademarkFile(file: File) {
|
async function processTrademarkFile(file: File) {
|
||||||
uploadLoading.value = true
|
uploadLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 根据选中的查询类型确定需要的表头
|
const requiredHeaders = []
|
||||||
const requiredHeaders: string[] = []
|
if (queryTypes.value.includes('product')) requiredHeaders.push('商品主图')
|
||||||
if (queryTypes.value.includes('product')) {
|
if (queryTypes.value.includes('brand')) requiredHeaders.push('品牌')
|
||||||
requiredHeaders.push('商品主图')
|
|
||||||
}
|
|
||||||
if (queryTypes.value.includes('brand')) {
|
|
||||||
requiredHeaders.push('品牌')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证表头
|
|
||||||
if (requiredHeaders.length > 0) {
|
if (requiredHeaders.length > 0) {
|
||||||
const validateResult = await markApi.validateHeaders(file, requiredHeaders)
|
const validateResult = await markApi.validateHeaders(file, requiredHeaders)
|
||||||
if (validateResult.code !== 200 && validateResult.code !== 0) {
|
if (validateResult.code !== 200 && validateResult.code !== 0) {
|
||||||
@@ -160,15 +153,11 @@ function removeTrademarkFile() {
|
|||||||
trademarkFileName.value = ''
|
trademarkFileName.value = ''
|
||||||
trademarkFile.value = null
|
trademarkFile.value = null
|
||||||
uploadLoading.value = false
|
uploadLoading.value = false
|
||||||
if (trademarkUpload.value) {
|
if (trademarkUpload.value) trademarkUpload.value.value = ''
|
||||||
trademarkUpload.value.value = ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存会话到后端
|
|
||||||
async function saveSession() {
|
async function saveSession() {
|
||||||
if (!trademarkData.value.length) return
|
if (!trademarkData.value.length) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sessionData = {
|
const sessionData = {
|
||||||
fileName: trademarkFileName.value,
|
fileName: trademarkFileName.value,
|
||||||
@@ -178,7 +167,6 @@ async function saveSession() {
|
|||||||
taskProgress: taskProgress.value,
|
taskProgress: taskProgress.value,
|
||||||
queryStatus: queryStatus.value
|
queryStatus: queryStatus.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await markApi.saveSession(sessionData)
|
const result = await markApi.saveSession(sessionData)
|
||||||
if (result.code === 200 || result.code === 0) {
|
if (result.code === 200 || result.code === 0) {
|
||||||
const username = getUsernameFromToken()
|
const username = getUsernameFromToken()
|
||||||
@@ -258,13 +246,10 @@ async function handleTrademarkUpload(e: Event) {
|
|||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
const file = input.files?.[0]
|
const file = input.files?.[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
if (!/\.xlsx?$/.test(file.name)) {
|
||||||
const ok = /\.xlsx?$/.test(file.name)
|
|
||||||
if (!ok) {
|
|
||||||
showMessage('仅支持 .xlsx/.xls 文件', 'warning')
|
showMessage('仅支持 .xlsx/.xls 文件', 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await processTrademarkFile(file)
|
await processTrademarkFile(file)
|
||||||
input.value = ''
|
input.value = ''
|
||||||
}
|
}
|
||||||
@@ -274,9 +259,7 @@ async function startTrademarkQuery() {
|
|||||||
showMessage('请先导入商标列表', 'warning')
|
showMessage('请先导入商标列表', 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshVipStatus) await refreshVipStatus()
|
if (refreshVipStatus) await refreshVipStatus()
|
||||||
|
|
||||||
if (!props.isVip) {
|
if (!props.isVip) {
|
||||||
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||||
showTrialExpiredDialog.value = true
|
showTrialExpiredDialog.value = true
|
||||||
@@ -286,6 +269,7 @@ async function startTrademarkQuery() {
|
|||||||
const needProductCheck = queryTypes.value.includes('product')
|
const needProductCheck = queryTypes.value.includes('product')
|
||||||
const needBrandCheck = queryTypes.value.includes('brand')
|
const needBrandCheck = queryTypes.value.includes('brand')
|
||||||
|
|
||||||
|
// 立即显示加载状态,防止重复点击
|
||||||
trademarkLoading.value = true
|
trademarkLoading.value = true
|
||||||
trademarkProgress.value = 0
|
trademarkProgress.value = 0
|
||||||
trademarkData.value = []
|
trademarkData.value = []
|
||||||
@@ -293,6 +277,9 @@ async function startTrademarkQuery() {
|
|||||||
trademarkHeaders.value = []
|
trademarkHeaders.value = []
|
||||||
queryStatus.value = 'inProgress'
|
queryStatus.value = 'inProgress'
|
||||||
|
|
||||||
|
// 立即显示"正在准备..."状态
|
||||||
|
showMessage('正在准备筛查任务...', 'info')
|
||||||
|
|
||||||
// 重置任务进度
|
// 重置任务进度
|
||||||
taskProgress.value.product.total = 0
|
taskProgress.value.product.total = 0
|
||||||
taskProgress.value.product.current = 0
|
taskProgress.value.product.current = 0
|
||||||
@@ -476,11 +463,16 @@ async function startTrademarkQuery() {
|
|||||||
if (brandList.length > 0) {
|
if (brandList.length > 0) {
|
||||||
const brandData = taskProgress.value.brand
|
const brandData = taskProgress.value.brand
|
||||||
brandData.total = brandList.length
|
brandData.total = brandList.length
|
||||||
brandData.current = 1 // 立即显示初始进度
|
brandData.current = 0
|
||||||
brandData.completed = 0
|
brandData.completed = 0
|
||||||
|
|
||||||
// 生成任务ID并轮询真实进度
|
// 生成任务ID并立即开始轮询
|
||||||
brandTaskId.value = `task_${Date.now()}`
|
brandTaskId.value = `task_${Date.now()}`
|
||||||
|
|
||||||
|
// 立即显示开始状态
|
||||||
|
showMessage(`开始品牌商标筛查,共${brandList.length}个品牌`, 'success')
|
||||||
|
|
||||||
|
// 立即开始进度轮询
|
||||||
brandProgressTimer = setInterval(async () => {
|
brandProgressTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await markApi.getBrandCheckProgress(brandTaskId.value)
|
const res = await markApi.getBrandCheckProgress(brandTaskId.value)
|
||||||
@@ -490,18 +482,24 @@ async function startTrademarkQuery() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略进度查询错误
|
// 忽略进度查询错误
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 500)
|
||||||
|
|
||||||
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
||||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||||
|
if ( brandResult.code === 0) {
|
||||||
if (brandResult.code === 200 || brandResult.code === 0) {
|
|
||||||
// 完成,显示100%
|
|
||||||
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
|
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
|
||||||
brandData.current = brandData.total
|
brandData.current = brandData.total
|
||||||
brandData.completed = brandResult.data.unregistered || 0
|
brandData.completed = brandResult.data.unregistered || 0
|
||||||
isBrandTaskRealData.value = true
|
isBrandTaskRealData.value = true
|
||||||
|
await processBrandResult(brandResult)
|
||||||
|
} else {
|
||||||
|
throw new Error(brandResult.msg || '品牌筛查失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理品牌查询结果的函数
|
||||||
|
async function processBrandResult(brandResult: any) {
|
||||||
// 提取未注册品牌列表
|
// 提取未注册品牌列表
|
||||||
const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean)
|
const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean)
|
||||||
|
|
||||||
@@ -509,12 +507,12 @@ async function startTrademarkQuery() {
|
|||||||
// 从原始Excel中过滤出包含这些品牌的完整行
|
// 从原始Excel中过滤出包含这些品牌的完整行
|
||||||
const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands)
|
const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands)
|
||||||
|
|
||||||
if (filterResult.code === 200 || filterResult.code === 0) {
|
|
||||||
// 保存完整数据(用于导出,使用原始表头)
|
// 保存完整数据(用于导出,使用原始表头)
|
||||||
trademarkFullData.value = filterResult.data.filteredRows
|
trademarkFullData.value = filterResult.data.filteredRows
|
||||||
trademarkHeaders.value = filterResult.data.headers || []
|
trademarkHeaders.value = filterResult.data.headers || []
|
||||||
|
|
||||||
// 更新统计:显示过滤出的实际行数(而不是品牌数)
|
// 更新统计:显示过滤出的实际行数(而不是品牌数)
|
||||||
|
const brandData = taskProgress.value.brand
|
||||||
brandData.completed = filterResult.data.filteredRows.length
|
brandData.completed = filterResult.data.filteredRows.length
|
||||||
isBrandTaskRealData.value = true
|
isBrandTaskRealData.value = true
|
||||||
|
|
||||||
@@ -531,21 +529,12 @@ async function startTrademarkQuery() {
|
|||||||
isBrand: true // 标记为品牌数据
|
isBrand: true // 标记为品牌数据
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 如果有产品筛查,也替换展示数据(只显示品牌筛查结果)
|
// 更新展示数据
|
||||||
if (needProductCheck) {
|
|
||||||
trademarkData.value = brandItems
|
|
||||||
} else {
|
|
||||||
trademarkData.value = [...trademarkData.value, ...brandItems]
|
trademarkData.value = [...trademarkData.value, ...brandItems]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(brandResult.msg || '品牌筛查失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading)
|
// 只要流程正常完成,就设置为done状态
|
||||||
queryStatus.value = 'done'
|
queryStatus.value = 'done'
|
||||||
emit('updateData', trademarkData.value)
|
emit('updateData', trademarkData.value)
|
||||||
|
|
||||||
@@ -715,27 +704,15 @@ function resetToIdle() {
|
|||||||
trademarkHeaders.value = []
|
trademarkHeaders.value = []
|
||||||
trademarkFileName.value = ''
|
trademarkFileName.value = ''
|
||||||
trademarkFile.value = null
|
trademarkFile.value = null
|
||||||
taskProgress.value.product.total = 0
|
Object.assign(taskProgress.value.product, { total: 0, current: 0, completed: 0 })
|
||||||
taskProgress.value.product.current = 0
|
Object.assign(taskProgress.value.brand, { total: 0, current: 0, completed: 0 })
|
||||||
taskProgress.value.product.completed = 0
|
Object.assign(taskProgress.value.platform, { total: 0, current: 0, completed: 0 })
|
||||||
taskProgress.value.brand.total = 0
|
|
||||||
taskProgress.value.brand.current = 0
|
|
||||||
taskProgress.value.brand.completed = 0
|
|
||||||
taskProgress.value.platform.total = 0
|
|
||||||
taskProgress.value.platform.current = 0
|
|
||||||
taskProgress.value.platform.completed = 0
|
|
||||||
|
|
||||||
// 重置真实数据标记
|
|
||||||
isProductTaskRealData.value = false
|
isProductTaskRealData.value = false
|
||||||
isBrandTaskRealData.value = false
|
isBrandTaskRealData.value = false
|
||||||
|
|
||||||
// 清空localStorage中的会话数据
|
|
||||||
try {
|
try {
|
||||||
const username = getUsernameFromToken()
|
const username = getUsernameFromToken()
|
||||||
localStorage.removeItem(`trademark_session_${username}`)
|
localStorage.removeItem(`trademark_session_${username}`)
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ public class TrademarkController {
|
|||||||
String taskId = (String) request.get("taskId");
|
String taskId = (String) request.get("taskId");
|
||||||
try {
|
try {
|
||||||
List<String> list = brands.stream()
|
List<String> list = brands.stream()
|
||||||
.filter(b -> b != null && !b.trim().isEmpty())
|
.filter(b -> !b.trim().isEmpty())
|
||||||
.map(String::trim)
|
.map(String::trim)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
// 1. 先从全局缓存获取
|
// 1. 先从全局缓存获取
|
||||||
Map<String, Boolean> cached = cacheService.getCached(list);
|
Map<String, Boolean> cached = cacheService.getCached(list);
|
||||||
@@ -74,8 +75,7 @@ public class TrademarkController {
|
|||||||
Map<String, Boolean> queried = new HashMap<>();
|
Map<String, Boolean> queried = new HashMap<>();
|
||||||
if (!toQuery.isEmpty()) {
|
if (!toQuery.isEmpty()) {
|
||||||
for (int i = 0; i < toQuery.size(); i++) {
|
for (int i = 0; i < toQuery.size(); i++) {
|
||||||
// 检查任务是否被取消值
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
if (taskId != null && cancelMap.getOrDefault(taskId, false)) {
|
|
||||||
logger.info("任务 {} 已被取消,停止查询", taskId);
|
logger.info("任务 {} 已被取消,停止查询", taskId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -86,15 +86,10 @@ public class TrademarkController {
|
|||||||
Map<String, Boolean> results = util.batchCheck(Collections.singletonList(brand), queried);
|
Map<String, Boolean> results = util.batchCheck(Collections.singletonList(brand), queried);
|
||||||
queried.putAll(results);
|
queried.putAll(results);
|
||||||
|
|
||||||
// 更新进度
|
if (taskId != null) progressMap.put(taskId, cached.size() + queried.size());
|
||||||
if (taskId != null) {
|
|
||||||
progressMap.put(taskId, cached.size() + queried.size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询结束,保存所有品牌
|
if (!queried.isEmpty()) cacheService.saveResults(queried);
|
||||||
if (!queried.isEmpty())
|
|
||||||
cacheService.saveResults(queried);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 合并缓存和新查询结果
|
// 5. 合并缓存和新查询结果
|
||||||
@@ -132,16 +127,11 @@ public class TrademarkController {
|
|||||||
logger.info("完成: 共{}个,成功查询{}个(已注册{}个,未注册{}个),查询失败{}个,耗时{}秒",
|
logger.info("完成: 共{}个,成功查询{}个(已注册{}个,未注册{}个),查询失败{}个,耗时{}秒",
|
||||||
list.size(), checkedCount, registeredCount, unregistered.size(), failedCount, t);
|
list.size(), checkedCount, registeredCount, unregistered.size(), failedCount, t);
|
||||||
|
|
||||||
// 30秒后清理进度和取消标志
|
|
||||||
if (taskId != null) {
|
if (taskId != null) {
|
||||||
String finalTaskId = taskId;
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try { Thread.sleep(30000); } catch (InterruptedException ignored) {}
|
||||||
Thread.sleep(30000);
|
progressMap.remove(taskId);
|
||||||
} catch (InterruptedException ignored) {
|
cancelMap.remove(taskId);
|
||||||
}
|
|
||||||
progressMap.remove(finalTaskId);
|
|
||||||
cancelMap.remove(finalTaskId);
|
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +140,7 @@ public class TrademarkController {
|
|||||||
logger.error("筛查失败", e);
|
logger.error("筛查失败", e);
|
||||||
return JsonData.buildError("筛查失败: " + e.getMessage());
|
return JsonData.buildError("筛查失败: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
util.closeDriver();
|
if (util != null && util.driver != null) util.driver.quit();
|
||||||
cacheService.cleanExpired();
|
cacheService.cleanExpired();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,25 +23,18 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic
|
|||||||
public Map<String, Boolean> getCached(List<String> brands) {
|
public Map<String, Boolean> getCached(List<String> brands) {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
||||||
List<BrandTrademarkCacheEntity> cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime);
|
List<BrandTrademarkCacheEntity> cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime);
|
||||||
|
|
||||||
Map<String, Boolean> result = new HashMap<>();
|
Map<String, Boolean> result = new HashMap<>();
|
||||||
cached.forEach(e -> result.put(e.getBrand(), e.getRegistered()));
|
cached.forEach(e -> result.put(e.getBrand(), e.getRegistered()));
|
||||||
|
|
||||||
if (!result.isEmpty()) {
|
|
||||||
log.info("从全局缓存获取 {} 个品牌数据", result.size());
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveResults(Map<String, Boolean> results) {
|
public void saveResults(Map<String, Boolean> results) {
|
||||||
results.forEach((brand, registered) -> {
|
results.forEach((brand, registered) -> {
|
||||||
if (!repository.existsByBrand(brand)) {
|
|
||||||
BrandTrademarkCacheEntity entity = new BrandTrademarkCacheEntity();
|
BrandTrademarkCacheEntity entity = new BrandTrademarkCacheEntity();
|
||||||
entity.setBrand(brand);
|
entity.setBrand(brand);
|
||||||
entity.setRegistered(registered);
|
entity.setRegistered(registered);
|
||||||
repository.save(entity);
|
repository.save(entity);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +43,6 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic
|
|||||||
public void cleanExpired() {
|
public void cleanExpired() {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
||||||
repository.deleteByCreatedAtBefore(cutoffTime);
|
repository.deleteByCreatedAtBefore(cutoffTime);
|
||||||
log.info("清理1天前的品牌商标缓存");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ package com.tashow.erp.test;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.tashow.erp.ErpClientSbApplication;
|
||||||
|
import com.tashow.erp.utils.TrademarkCheckUtil;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
@@ -15,145 +21,211 @@ public class UsptoApiTest {
|
|||||||
private static final ObjectMapper mapper = new ObjectMapper();
|
private static final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("=== USPTO API 功能测试 ===\n");
|
System.out.println("=== 商标查询测试(使用Spring容器) ===\n");
|
||||||
System.out.println("API Key: " + API_KEY + "\n");
|
|
||||||
|
|
||||||
// 测试1:通过序列号查询(已知可用)
|
// 启动Spring Boot应用上下文
|
||||||
testBySerialNumber();
|
ConfigurableApplicationContext context = null;
|
||||||
|
try {
|
||||||
|
System.out.println("正在启动Spring Boot应用...");
|
||||||
|
context = SpringApplication.run(ErpClientSbApplication.class, args);
|
||||||
|
System.out.println("Spring Boot应用启动成功!\n");
|
||||||
|
|
||||||
System.out.println("\n" + "=".repeat(60) + "\n");
|
// 获取TrademarkCheckUtil Bean
|
||||||
|
TrademarkCheckUtil trademarkUtil = context.getBean(TrademarkCheckUtil.class);
|
||||||
|
|
||||||
// 测试2:尝试通过品牌名称搜索
|
// 测试单品牌查询(获取详细结果)
|
||||||
testByBrandName();
|
testSingleBrandWithDetailedResults("Remorlet");
|
||||||
|
|
||||||
System.out.println("\n" + "=".repeat(60) + "\n");
|
} catch (Exception e) {
|
||||||
|
System.err.println("测试失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
|
||||||
// 测试3:对比当前实现的tmsearch
|
}
|
||||||
testCurrentImplementation();
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准化品牌名称用于比较(移除特殊字符,转换为小写)
|
||||||
|
*/
|
||||||
|
private static String normalizeBrandName(String name) {
|
||||||
|
if (name == null) return "";
|
||||||
|
return name.toLowerCase()
|
||||||
|
.replaceAll("[\\s\\-_\\.]", "") // 移除空格、连字符、下划线、点号
|
||||||
|
.replaceAll("[^a-z0-9]", ""); // 只保留字母和数字
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试1:通过序列号查询(官方TSDR API)
|
* 直接执行JavaScript获取详细结果
|
||||||
|
*
|
||||||
|
* USPTO API返回格式说明:
|
||||||
|
* {
|
||||||
|
* "hits": {
|
||||||
|
* "hits": [
|
||||||
|
* {
|
||||||
|
* "id": "商标ID",
|
||||||
|
* "score": 匹配分数,
|
||||||
|
* "source": {
|
||||||
|
* "alive": true/false, // 关键字段:是否有效注册
|
||||||
|
* "wordmark": "品牌名", // 商标名称
|
||||||
|
* "statusCode": 状态码, // 600=已放弃, 700=已注册
|
||||||
|
* "statusDescription": "状态描述", // ABANDONED/REGISTERED
|
||||||
|
* "registrationId": "注册号", // 注册号(如果已注册)
|
||||||
|
* "registrationDate": "注册日期",
|
||||||
|
* "abandonDate": "放弃日期",
|
||||||
|
* "filedDate": "申请日期",
|
||||||
|
* "goodsAndServices": ["商品服务类别"],
|
||||||
|
* "internationalClass": ["IC 018"], // 国际分类
|
||||||
|
* "ownerName": ["所有者名称"],
|
||||||
|
* "attorney": "代理律师"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "totalValue": 总匹配数
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* 判定逻辑: 只要有任何一个记录的 alive=true,就判定为已注册(true)
|
||||||
*/
|
*/
|
||||||
private static void testBySerialNumber() {
|
private static void testSingleBrandWithDetailedResults(String brandName) {
|
||||||
System.out.println("【测试1】通过序列号查询");
|
System.out.println("【单品牌测试】直接执行JavaScript获取详细结果");
|
||||||
System.out.println("端点: https://tsdrapi.uspto.gov/ts/cd/casestatus/sn88123456/info.json");
|
System.out.println("品牌: " + brandName);
|
||||||
|
|
||||||
|
ChromeDriver driver = null;
|
||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
System.out.println("=== 开始测试品牌: " + brandName + " ===");
|
||||||
headers.set("USPTO-API-KEY", API_KEY);
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = rest.exchange(
|
// 初始化Chrome驱动
|
||||||
"https://tsdrapi.uspto.gov/ts/cd/casestatus/sn88123456/info.json",
|
System.out.println("正在初始化Chrome驱动...");
|
||||||
HttpMethod.GET, entity, String.class
|
driver = com.tashow.erp.utils.SeleniumUtil.createDriver(false, null);
|
||||||
);
|
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
||||||
|
Thread.sleep(6000); // 等待页面加载
|
||||||
|
System.out.println("Chrome驱动初始化完成");
|
||||||
|
|
||||||
JsonNode json = mapper.readTree(response.getBody());
|
long startTime = System.currentTimeMillis();
|
||||||
String markElement = json.get("trademarks").get(0).get("status").get("markElement").asText();
|
|
||||||
int status = json.get("trademarks").get(0).get("status").get("status").asInt();
|
// 构建JavaScript脚本 - 获取所有结果
|
||||||
String regNumber = json.get("trademarks").get(0).get("status").get("usRegistrationNumber").asText();
|
String script = "const brands = ['" + brandName.replace("'", "\\'") + "'];\n" +
|
||||||
|
"const callback = arguments[arguments.length - 1];\n" +
|
||||||
|
"Promise.all(brands.map(b => \n" +
|
||||||
|
" fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch', {\n" +
|
||||||
|
" method: 'POST',\n" +
|
||||||
|
" headers: {'Content-Type': 'application/json'},\n" +
|
||||||
|
" body: JSON.stringify({\n" +
|
||||||
|
" query: {bool: {must: [{bool: {should: [\n" +
|
||||||
|
" {match_phrase: {WM: {query: b, boost: 5}}},\n" +
|
||||||
|
" {match: {WM: {query: b, boost: 2}}},\n" +
|
||||||
|
" {match_phrase: {PM: {query: b, boost: 2}}}\n" +
|
||||||
|
" ]}}]}},\n" +
|
||||||
|
" size: 100\n" +
|
||||||
|
" })\n" +
|
||||||
|
" })\n" +
|
||||||
|
" .then(r => {\n" +
|
||||||
|
" if (!r.ok) {\n" +
|
||||||
|
" return {brand: b, error: 'HTTP ' + r.status + ': ' + r.statusText, allResults: []};\n" +
|
||||||
|
" }\n" +
|
||||||
|
" return r.json().then(d => {\n" +
|
||||||
|
" console.log('API Response:', d);\n" +
|
||||||
|
" return {\n" +
|
||||||
|
" brand: b,\n" +
|
||||||
|
" error: null,\n" +
|
||||||
|
" totalHits: d?.hits?.total?.value || d?.hits?.totalValue || 0,\n" +
|
||||||
|
" allResults: d?.hits?.hits || [],\n" +
|
||||||
|
" rawData: d\n" +
|
||||||
|
" };\n" +
|
||||||
|
" });\n" +
|
||||||
|
" })\n" +
|
||||||
|
" .catch(e => ({\n" +
|
||||||
|
" brand: b,\n" +
|
||||||
|
" error: e.name + ': ' + e.message,\n" +
|
||||||
|
" allResults: []\n" +
|
||||||
|
" }))\n" +
|
||||||
|
")).then(callback);";
|
||||||
|
|
||||||
|
System.out.println("正在执行JavaScript脚本...");
|
||||||
|
|
||||||
|
// 执行JavaScript脚本
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.List<java.util.Map<String, Object>> results =
|
||||||
|
(java.util.List<java.util.Map<String, Object>>)
|
||||||
|
((JavascriptExecutor) driver).executeAsyncScript(script);
|
||||||
|
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
|
// 极简输出 - 只返回 true/false 判定结果
|
||||||
|
boolean isRegistered = false;
|
||||||
|
|
||||||
|
if (results != null && !results.isEmpty()) {
|
||||||
|
java.util.Map<String, Object> result = results.get(0);
|
||||||
|
|
||||||
|
// 获取所有匹配的结果
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.List<java.util.Map<String, Object>> allResults =
|
||||||
|
(java.util.List<java.util.Map<String, Object>>) result.get("allResults");
|
||||||
|
|
||||||
|
// 如果allResults为空,从rawData中获取
|
||||||
|
if ((allResults == null || allResults.isEmpty()) && result.get("rawData") != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.Map<String, Object> rawData = (java.util.Map<String, Object>) result.get("rawData");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.Map<String, Object> hits = (java.util.Map<String, Object>) rawData.get("hits");
|
||||||
|
if (hits != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.List<java.util.Map<String, Object>> hitsArray =
|
||||||
|
(java.util.List<java.util.Map<String, Object>>) hits.get("hits");
|
||||||
|
if (hitsArray != null) {
|
||||||
|
allResults = hitsArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 核心判定逻辑:品牌名称匹配 且 statusCode为686/700 才判定为已注册
|
||||||
|
String normalizedInputBrand = normalizeBrandName(brandName);
|
||||||
|
if (allResults != null && !allResults.isEmpty()) {
|
||||||
|
for (java.util.Map<String, Object> hit : allResults) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.Map<String, Object> source = (java.util.Map<String, Object>) hit.get("_source");
|
||||||
|
if (source == null) {
|
||||||
|
source = (java.util.Map<String, Object>) hit.get("source");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
String wordmark = (String) source.get("wordmark");
|
||||||
|
String normalizedWordmark = normalizeBrandName(wordmark);
|
||||||
|
|
||||||
|
// 首先检查品牌名称是否匹配
|
||||||
|
if (normalizedInputBrand.equals(normalizedWordmark)) {
|
||||||
|
Number statusCodeNum = (Number) source.get("statusCode");
|
||||||
|
|
||||||
|
// 只有statusCode为688或700才返回true
|
||||||
|
if (statusCodeNum != null && (statusCodeNum.intValue() == 688 || statusCodeNum.intValue() == 700)) {
|
||||||
|
isRegistered = true;
|
||||||
|
break; // 找到一个符合条件的就够了
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极简输出 - 只显示最终结果
|
||||||
|
System.out.println(isRegistered);
|
||||||
|
|
||||||
System.out.println("✓ 成功!");
|
|
||||||
System.out.println(" 商标名称: " + markElement);
|
|
||||||
System.out.println(" 状态码: " + status);
|
|
||||||
System.out.println(" 注册号: " + (regNumber.isEmpty() ? "未注册" : regNumber));
|
|
||||||
System.out.println("\n结论: ✅ 可以查询,但必须知道序列号");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("✗ 失败: " + e.getMessage());
|
System.err.println("=== 测试失败 ===");
|
||||||
}
|
System.err.println("品牌: " + brandName);
|
||||||
}
|
System.err.println("错误: " + e.getMessage());
|
||||||
|
System.err.println("================");
|
||||||
/**
|
e.printStackTrace();
|
||||||
* 测试2:尝试通过品牌名称搜索
|
} finally {
|
||||||
*/
|
// 确保清理资源
|
||||||
private static void testByBrandName() {
|
if (driver != null) {
|
||||||
System.out.println("【测试2】尝试通过品牌名称搜索");
|
|
||||||
|
|
||||||
String[] brands = {"Nike", "MYLIFE", "TestBrand123"};
|
|
||||||
String[] searchUrls = {
|
|
||||||
"https://tsdrapi.uspto.gov/ts/cd/search?q=%s",
|
|
||||||
"https://tsdrapi.uspto.gov/search?keyword=%s",
|
|
||||||
"https://api.uspto.gov/trademark/search?q=%s",
|
|
||||||
"https://api.uspto.gov/tmsearch/search?q=%s",
|
|
||||||
};
|
|
||||||
|
|
||||||
boolean foundSearchApi = false;
|
|
||||||
|
|
||||||
for (String brand : brands) {
|
|
||||||
System.out.println("\n测试品牌: " + brand);
|
|
||||||
|
|
||||||
for (String urlTemplate : searchUrls) {
|
|
||||||
String url = String.format(urlTemplate, brand);
|
|
||||||
System.out.println(" 尝试: " + url);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
driver.quit();
|
||||||
headers.set("USPTO-API-KEY", API_KEY);
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = rest.exchange(url, HttpMethod.GET, entity, String.class);
|
|
||||||
|
|
||||||
System.out.println(" ✓✓✓ 成功! 找到品牌搜索API!");
|
|
||||||
System.out.println(" 响应: " + response.getBody().substring(0, Math.min(200, response.getBody().length())));
|
|
||||||
foundSearchApi = true;
|
|
||||||
break;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(" ✗ 404/失败");
|
System.err.println("清理Chrome驱动时出错: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundSearchApi) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundSearchApi) {
|
|
||||||
System.out.println("\n结论: ❌ USPTO官方API不支持品牌名称搜索");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试3:当前实现的tmsearch方式
|
|
||||||
*/
|
|
||||||
private static void testCurrentImplementation() {
|
|
||||||
System.out.println("【测试3】当前实现方式(tmsearch内部API)");
|
|
||||||
System.out.println("端点: https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch");
|
|
||||||
|
|
||||||
String brand = "Nike";
|
|
||||||
String requestBody = String.format(
|
|
||||||
"{\"query\":{\"bool\":{\"must\":[{\"bool\":{\"should\":[" +
|
|
||||||
"{\"match_phrase\":{\"WM\":{\"query\":\"%s\",\"boost\":5}}}," +
|
|
||||||
"{\"match\":{\"WM\":{\"query\":\"%s\",\"boost\":2}}}," +
|
|
||||||
"{\"match_phrase\":{\"PM\":{\"query\":\"%s\",\"boost\":2}}}" +
|
|
||||||
"]}}]}},\"size\":1,\"_source\":[\"alive\"]}",
|
|
||||||
brand, brand, brand
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = rest.postForEntity(
|
|
||||||
"https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch",
|
|
||||||
entity, String.class
|
|
||||||
);
|
|
||||||
|
|
||||||
JsonNode json = mapper.readTree(response.getBody());
|
|
||||||
boolean hasResults = json.get("hits").get("hits").size() > 0;
|
|
||||||
|
|
||||||
System.out.println("✓ 成功!");
|
|
||||||
System.out.println(" 品牌: " + brand);
|
|
||||||
System.out.println(" 找到结果: " + (hasResults ? "是" : "否"));
|
|
||||||
|
|
||||||
if (hasResults) {
|
|
||||||
boolean alive = json.get("hits").get("hits").get(0).get("_source").get("alive").asBoolean();
|
|
||||||
System.out.println(" 是否注册: " + (alive ? "已注册" : "未注册"));
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("\n结论: ✅ 支持品牌名称直接搜索(无需序列号)");
|
|
||||||
System.out.println(" ⚠️ 但这是内部API,非官方公开接口");
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("✗ 失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,19 +11,10 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel 解析工具类
|
* Excel 解析工具类
|
||||||
* 统一处理各种平台的 Excel 文件解析需求
|
|
||||||
*
|
|
||||||
* @author Claude Code
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ExcelParseUtil {
|
public class ExcelParseUtil {
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动查找表头行索引(在前2行中查找)
|
|
||||||
* @param rows Excel所有行
|
|
||||||
* @param columnName 列名(如"品牌")
|
|
||||||
* @return 表头行索引,未找到返回-1
|
|
||||||
*/
|
|
||||||
private static int findHeaderRow(List<List<Object>> rows, String columnName) {
|
private static int findHeaderRow(List<List<Object>> rows, String columnName) {
|
||||||
for (int r = 0; r < Math.min(2, rows.size()); r++) {
|
for (int r = 0; r < Math.min(2, rows.size()); r++) {
|
||||||
for (Object cell : rows.get(r)) {
|
for (Object cell : rows.get(r)) {
|
||||||
@@ -35,92 +26,37 @@ public class ExcelParseUtil {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static int findColumnIndex(List<Object> headerRow, String columnName) {
|
||||||
* 解析 Excel 文件第一列数据
|
for (int c = 0; c < headerRow.size(); c++) {
|
||||||
* 通用方法,适用于店铺名、ASIN、订单号等标识符解析
|
if (headerRow.get(c) != null && columnName.equals(headerRow.get(c).toString().replaceAll("\\s+", ""))) {
|
||||||
*
|
return c;
|
||||||
* @param file Excel 文件
|
}
|
||||||
* @return 解析出的字符串列表,跳过表头,过滤空值
|
}
|
||||||
*/
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public static List<String> parseFirstColumn(MultipartFile file) {
|
public static List<String> parseFirstColumn(MultipartFile file) {
|
||||||
List<String> result = new ArrayList<>();
|
return parseColumn(file, 0);
|
||||||
if (file == null || file.isEmpty()) {
|
|
||||||
log.warn("Excel 文件为空");
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream in = file.getInputStream()) {
|
|
||||||
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
|
||||||
List<List<Object>> rows = reader.read();
|
|
||||||
|
|
||||||
for (int i = 1; i < rows.size(); i++) { // 从第2行开始,跳过表头
|
|
||||||
List<Object> row = rows.get(i);
|
|
||||||
if (row != null && !row.isEmpty()) {
|
|
||||||
Object cell = row.get(0); // 获取第一列
|
|
||||||
if (cell != null) {
|
|
||||||
String value = cell.toString().trim();
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("成功解析 Excel 文件: {}, 共解析出 {} 条数据",
|
|
||||||
file.getOriginalFilename(), result.size());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("解析 Excel 文件失败: {}, 文件名: {}", e.getMessage(),
|
|
||||||
file.getOriginalFilename(), e);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析指定列的数据
|
|
||||||
*
|
|
||||||
* @param file Excel 文件
|
|
||||||
* @param columnIndex 列索引(从0开始)
|
|
||||||
* @return 解析出的字符串列表
|
|
||||||
*/
|
|
||||||
public static List<String> parseColumn(MultipartFile file, int columnIndex) {
|
public static List<String> parseColumn(MultipartFile file, int columnIndex) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
if (file == null || file.isEmpty()) {
|
|
||||||
log.warn("Excel 文件为空");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream in = file.getInputStream()) {
|
try (InputStream in = file.getInputStream()) {
|
||||||
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
||||||
List<List<Object>> rows = reader.read();
|
List<List<Object>> rows = reader.read();
|
||||||
|
for (int i = 1; i < rows.size(); i++) {
|
||||||
for (int i = 1; i < rows.size(); i++) { // 从第2行开始,跳过表头
|
|
||||||
List<Object> row = rows.get(i);
|
List<Object> row = rows.get(i);
|
||||||
if (row != null && row.size() > columnIndex) {
|
if (row.size() > columnIndex && row.get(columnIndex) != null) {
|
||||||
Object cell = row.get(columnIndex);
|
String value = row.get(columnIndex).toString().trim();
|
||||||
if (cell != null) {
|
if (!value.isEmpty()) result.add(value);
|
||||||
String value = cell.toString().trim();
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("成功解析 Excel 文件第{}列: {}, 共解析出 {} 条数据",
|
|
||||||
columnIndex + 1, file.getOriginalFilename(), result.size());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("解析 Excel 文件第{}列失败: {}, 文件名: {}",
|
log.error("解析Excel失败", e);
|
||||||
columnIndex + 1, e.getMessage(), file.getOriginalFilename(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据列名解析数据(自动适配第1行或第2行为表头)
|
|
||||||
*/
|
|
||||||
public static List<String> parseColumnByName(MultipartFile file, String columnName) {
|
public static List<String> parseColumnByName(MultipartFile file, String columnName) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
try (InputStream in = file.getInputStream()) {
|
try (InputStream in = file.getInputStream()) {
|
||||||
@@ -131,14 +67,8 @@ public class ExcelParseUtil {
|
|||||||
int headerRow = findHeaderRow(rows, columnName);
|
int headerRow = findHeaderRow(rows, columnName);
|
||||||
if (headerRow < 0) return result;
|
if (headerRow < 0) return result;
|
||||||
|
|
||||||
int colIdx = -1;
|
int colIdx = findColumnIndex(rows.get(headerRow), columnName);
|
||||||
for (int c = 0; c < rows.get(headerRow).size(); c++) {
|
if (colIdx < 0) return result;
|
||||||
if (rows.get(headerRow).get(c) != null &&
|
|
||||||
columnName.equals(rows.get(headerRow).get(c).toString().replaceAll("\\s+", ""))) {
|
|
||||||
colIdx = c;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = headerRow + 1; i < rows.size(); i++) {
|
for (int i = headerRow + 1; i < rows.size(); i++) {
|
||||||
List<Object> row = rows.get(i);
|
List<Object> row = rows.get(i);
|
||||||
@@ -153,11 +83,6 @@ public class ExcelParseUtil {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取Excel的完整数据(包含表头和所有行,自动适配第1行或第2行为表头)
|
|
||||||
* @param file Excel文件
|
|
||||||
* @return Map包含headers(表头列表)和rows(数据行列表,每行是Map)
|
|
||||||
*/
|
|
||||||
public static Map<String, Object> parseFullExcel(MultipartFile file) {
|
public static Map<String, Object> parseFullExcel(MultipartFile file) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
List<String> headers = new ArrayList<>();
|
List<String> headers = new ArrayList<>();
|
||||||
@@ -166,17 +91,13 @@ public class ExcelParseUtil {
|
|||||||
try (InputStream in = file.getInputStream()) {
|
try (InputStream in = file.getInputStream()) {
|
||||||
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
||||||
List<List<Object>> allRows = reader.read();
|
List<List<Object>> allRows = reader.read();
|
||||||
|
|
||||||
if (allRows.isEmpty()) {
|
if (allRows.isEmpty()) {
|
||||||
log.warn("Excel文件为空");
|
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
result.put("rows", rows);
|
result.put("rows", rows);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int headerRowIndex = Math.max(0, findHeaderRow(allRows, "品牌"));
|
int headerRowIndex = Math.max(0, findHeaderRow(allRows, "品牌"));
|
||||||
log.info("检测到表头行:第{}行", headerRowIndex + 1);
|
|
||||||
|
|
||||||
for (Object cell : allRows.get(headerRowIndex)) {
|
for (Object cell : allRows.get(headerRowIndex)) {
|
||||||
headers.add(cell != null ? cell.toString().trim() : "");
|
headers.add(cell != null ? cell.toString().trim() : "");
|
||||||
}
|
}
|
||||||
@@ -192,89 +113,21 @@ public class ExcelParseUtil {
|
|||||||
|
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
result.put("rows", rows);
|
result.put("rows", rows);
|
||||||
log.info("解析Excel: {}, 表头{}列, 数据{}行", file.getOriginalFilename(), headers.size(), rows.size());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("解析Excel失败: {}", e.getMessage(), e);
|
log.error("解析Excel失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ASIN列表从Excel中过滤完整行数据(自动适配第1行或第2行为表头)
|
|
||||||
* @param file Excel文件
|
|
||||||
* @param asins ASIN列表
|
|
||||||
* @return Map包含headers(表头)和filteredRows(过滤后的完整行数据)
|
|
||||||
*/
|
|
||||||
public static Map<String, Object> filterExcelByAsins(MultipartFile file, List<String> asins) {
|
public static Map<String, Object> filterExcelByAsins(MultipartFile file, List<String> asins) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
return filterExcelByColumn(file, "ASIN", asins);
|
||||||
List<String> headers = new ArrayList<>();
|
|
||||||
List<Map<String, Object>> filteredRows = new ArrayList<>();
|
|
||||||
|
|
||||||
try (InputStream in = file.getInputStream()) {
|
|
||||||
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
|
||||||
List<List<Object>> allRows = reader.read();
|
|
||||||
|
|
||||||
if (allRows.isEmpty()) {
|
|
||||||
result.put("headers", headers);
|
|
||||||
result.put("filteredRows", filteredRows);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int headerRowIndex = Math.max(0, findHeaderRow(allRows, "ASIN"));
|
|
||||||
if (headerRowIndex < 0) {
|
|
||||||
log.warn("未找到'ASIN'列");
|
|
||||||
result.put("headers", headers);
|
|
||||||
result.put("filteredRows", filteredRows);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int asinColIndex = -1;
|
|
||||||
List<Object> headerRow = allRows.get(headerRowIndex);
|
|
||||||
for (int c = 0; c < headerRow.size(); c++) {
|
|
||||||
if (headerRow.get(c) != null && "ASIN".equals(headerRow.get(c).toString().replaceAll("\\s+", ""))) {
|
|
||||||
asinColIndex = c;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Object cell : headerRow) {
|
|
||||||
headers.add(cell != null ? cell.toString().trim() : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> asinSet = asins.stream().map(String::trim).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
for (int i = headerRowIndex + 1; i < allRows.size(); i++) {
|
|
||||||
List<Object> row = allRows.get(i);
|
|
||||||
if (row.size() > asinColIndex && row.get(asinColIndex) != null
|
|
||||||
&& asinSet.contains(row.get(asinColIndex).toString().trim())) {
|
|
||||||
Map<String, Object> rowMap = new HashMap<>();
|
|
||||||
for (int j = 0; j < Math.min(headers.size(), row.size()); j++) {
|
|
||||||
rowMap.put(headers.get(j), row.get(j));
|
|
||||||
}
|
|
||||||
filteredRows.add(rowMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.put("headers", headers);
|
|
||||||
result.put("filteredRows", filteredRows);
|
|
||||||
log.info("ASIN过滤: {}, {}个ASIN -> {}行数据", file.getOriginalFilename(), asins.size(), filteredRows.size());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("ASIN过滤失败: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据品牌列表从Excel中过滤完整行数据(自动适配第1行或第2行为表头)
|
|
||||||
* @param file Excel文件
|
|
||||||
* @param brands 品牌列表
|
|
||||||
* @return Map包含headers(表头)和filteredRows(过滤后的完整行数据)
|
|
||||||
*/
|
|
||||||
public static Map<String, Object> filterExcelByBrands(MultipartFile file, List<String> brands) {
|
public static Map<String, Object> filterExcelByBrands(MultipartFile file, List<String> brands) {
|
||||||
|
return filterExcelByColumn(file, "品牌", brands);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> filterExcelByColumn(MultipartFile file, String columnName, List<String> values) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
List<String> headers = new ArrayList<>();
|
List<String> headers = new ArrayList<>();
|
||||||
List<Map<String, Object>> filteredRows = new ArrayList<>();
|
List<Map<String, Object>> filteredRows = new ArrayList<>();
|
||||||
@@ -282,40 +135,37 @@ public class ExcelParseUtil {
|
|||||||
try (InputStream in = file.getInputStream()) {
|
try (InputStream in = file.getInputStream()) {
|
||||||
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
ExcelReader reader = ExcelUtil.getReader(in, 0);
|
||||||
List<List<Object>> allRows = reader.read();
|
List<List<Object>> allRows = reader.read();
|
||||||
|
|
||||||
if (allRows.isEmpty()) {
|
if (allRows.isEmpty()) {
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
result.put("filteredRows", filteredRows);
|
result.put("filteredRows", filteredRows);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int headerRowIndex = findHeaderRow(allRows, "品牌");
|
int headerRowIndex = findHeaderRow(allRows, columnName);
|
||||||
if (headerRowIndex < 0) {
|
if (headerRowIndex < 0) {
|
||||||
log.warn("未找到'品牌'列");
|
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
result.put("filteredRows", filteredRows);
|
result.put("filteredRows", filteredRows);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int brandColIndex = -1;
|
|
||||||
List<Object> headerRow = allRows.get(headerRowIndex);
|
List<Object> headerRow = allRows.get(headerRowIndex);
|
||||||
for (int c = 0; c < headerRow.size(); c++) {
|
int colIndex = findColumnIndex(headerRow, columnName);
|
||||||
if (headerRow.get(c) != null && "品牌".equals(headerRow.get(c).toString().replaceAll("\\s+", ""))) {
|
if (colIndex < 0) {
|
||||||
brandColIndex = c;
|
result.put("headers", headers);
|
||||||
break;
|
result.put("filteredRows", filteredRows);
|
||||||
}
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Object cell : headerRow) {
|
for (Object cell : headerRow) {
|
||||||
headers.add(cell != null ? cell.toString().trim() : "");
|
headers.add(cell != null ? cell.toString().trim() : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> brandSet = brands.stream().map(String::trim).collect(Collectors.toSet());
|
Set<String> valueSet = values.stream().map(String::trim).collect(Collectors.toSet());
|
||||||
|
|
||||||
for (int i = headerRowIndex + 1; i < allRows.size(); i++) {
|
for (int i = headerRowIndex + 1; i < allRows.size(); i++) {
|
||||||
List<Object> row = allRows.get(i);
|
List<Object> row = allRows.get(i);
|
||||||
if (row.size() > brandColIndex && row.get(brandColIndex) != null
|
if (row.size() > colIndex && row.get(colIndex) != null
|
||||||
&& brandSet.contains(row.get(brandColIndex).toString().trim())) {
|
&& valueSet.contains(row.get(colIndex).toString().trim())) {
|
||||||
Map<String, Object> rowMap = new HashMap<>();
|
Map<String, Object> rowMap = new HashMap<>();
|
||||||
for (int j = 0; j < Math.min(headers.size(), row.size()); j++) {
|
for (int j = 0; j < Math.min(headers.size(), row.size()); j++) {
|
||||||
rowMap.put(headers.get(j), row.get(j));
|
rowMap.put(headers.get(j), row.get(j));
|
||||||
@@ -326,12 +176,9 @@ public class ExcelParseUtil {
|
|||||||
|
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
result.put("filteredRows", filteredRows);
|
result.put("filteredRows", filteredRows);
|
||||||
log.info("品牌过滤: {}, {}个品牌 -> {}行数据", file.getOriginalFilename(), brands.size(), filteredRows.size());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("品牌过滤失败: {}", e.getMessage(), e);
|
log.error("Excel过滤失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.tashow.erp.utils;
|
package com.tashow.erp.utils;
|
||||||
|
|
||||||
import com.tashow.erp.service.BrandTrademarkCacheService;
|
import com.tashow.erp.service.BrandTrademarkCacheService;
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import org.openqa.selenium.JavascriptExecutor;
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
import org.openqa.selenium.chrome.ChromeDriver;
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,133 +19,82 @@ public class TrademarkCheckUtil {
|
|||||||
private ProxyPool proxyPool;
|
private ProxyPool proxyPool;
|
||||||
@Autowired
|
@Autowired
|
||||||
private BrandTrademarkCacheService cacheService;
|
private BrandTrademarkCacheService cacheService;
|
||||||
private ChromeDriver driver;
|
public ChromeDriver driver;
|
||||||
|
private final int maxRetries = 3;
|
||||||
|
|
||||||
|
private String normalize(String name) {
|
||||||
|
return name.toLowerCase().replaceAll("[^a-z0-9]", "");
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void ensureInit() {
|
private synchronized void ensureInit() {
|
||||||
if (driver == null) {
|
if (driver == null) {
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
try {
|
|
||||||
driver = SeleniumUtil.createDriver(true, proxyPool.getProxy());
|
driver = SeleniumUtil.createDriver(true, proxyPool.getProxy());
|
||||||
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
||||||
Thread.sleep(6000);
|
try { Thread.sleep(6000); } catch (InterruptedException ignored) {}
|
||||||
return; // 成功则返回
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("初始化失败(尝试" + (i+1) + "/5): " + e.getMessage());
|
|
||||||
if (driver != null) {
|
|
||||||
try { driver.quit(); } catch (Exception ex) {}
|
|
||||||
driver = null;
|
|
||||||
}
|
|
||||||
if (i == 2) throw new RuntimeException("初始化失败,已重试3次", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, Boolean> batchCheck(List<String> brands, Map<String, Boolean> alreadyQueried) {
|
public synchronized Map<String, Boolean> batchCheck(List<String> brands, Map<String, Boolean> alreadyQueried) {
|
||||||
Map<String, Boolean> resultMap = new HashMap<>();
|
Map<String, Boolean> resultMap = new HashMap<>();
|
||||||
int maxRetries = 5;
|
|
||||||
|
|
||||||
for (String brand : brands) {
|
for (String brand : brands) {
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
while (retryCount < 5 && !success) {
|
||||||
while (retryCount < maxRetries && !success) {
|
|
||||||
try {
|
try {
|
||||||
ensureInit();
|
ensureInit();
|
||||||
|
String script = "fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:{bool:{must:[{bool:{should:[{match_phrase:{WM:{query:'" + brand.replace("'", "\\'")+"',boost:5}}}]}}]}},size:100})}).then(r=>{if(!r.ok){return arguments[arguments.length-1]({hits:[],error:'HTTP '+r.status+': '+r.statusText});}return r.text().then(text=>{if(text.startsWith('<!DOCTYPE')||text.startsWith('<html')){return arguments[arguments.length-1]({hits:[],error:'HTML response detected, likely blocked'});}try{const d=JSON.parse(text);return arguments[arguments.length-1]({hits:d?.hits?.hits||[],error:null});}catch(e){return arguments[arguments.length-1]({hits:[],error:'JSON parse error: '+e.message});}});}).catch(e=>arguments[arguments.length-1]({hits:[],error:e.message}));";
|
||||||
|
@SuppressWarnings("unchecked") Map<String, Object> result = (Map<String, Object>) ((JavascriptExecutor) driver).executeAsyncScript(script);
|
||||||
|
String error = (String) result.get("error");
|
||||||
|
|
||||||
String script = "const brands = ['" + brand.replace("'", "\\'") + "'];\n" +
|
if (error != null && (error.contains("HTTP 403") || error.contains("Failed to fetch") || error.contains("NetworkError") || error.contains("TypeError") || error.contains("script timeout"))) {
|
||||||
"const callback = arguments[arguments.length - 1];\n" +
|
System.err.println(brand + " 查询失败(" + (retryCount + 1) + "/3): " + error + ",切换代理...");
|
||||||
"Promise.all(brands.map(b => \n" +
|
if (driver != null) driver.quit();
|
||||||
" fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch', {\n" +
|
|
||||||
" method: 'POST',\n" +
|
|
||||||
" headers: {'Content-Type': 'application/json'},\n" +
|
|
||||||
" body: JSON.stringify({\n" +
|
|
||||||
" query: {bool: {must: [{bool: {should: [\n" +
|
|
||||||
" {match_phrase: {WM: {query: b, boost: 5}}},\n" +
|
|
||||||
" {match: {WM: {query: b, boost: 2}}},\n" +
|
|
||||||
" {match_phrase: {PM: {query: b, boost: 2}}}\n" +
|
|
||||||
" ]}}]}},\n" +
|
|
||||||
" size: 1, _source: ['alive']\n" +
|
|
||||||
" })\n" +
|
|
||||||
" })\n" +
|
|
||||||
" .then(r => {\n" +
|
|
||||||
" if (!r.ok) {\n" +
|
|
||||||
" return {brand: b, alive: false, error: 'HTTP ' + r.status + ': ' + r.statusText};\n" +
|
|
||||||
" }\n" +
|
|
||||||
" return r.json().then(d => ({\n" +
|
|
||||||
" brand: b,\n" +
|
|
||||||
" alive: d?.hits?.hits?.[0]?.source?.alive || false,\n" +
|
|
||||||
" error: null\n" +
|
|
||||||
" }));\n" +
|
|
||||||
" })\n" +
|
|
||||||
" .catch(e => ({\n" +
|
|
||||||
" brand: b,\n" +
|
|
||||||
" alive: false,\n" +
|
|
||||||
" error: e.name + ': ' + e.message\n" +
|
|
||||||
" }))\n" +
|
|
||||||
")).then(callback);";
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Map<String, Object>> results = (List<Map<String, Object>>)
|
|
||||||
((JavascriptExecutor) driver).executeAsyncScript(script);
|
|
||||||
|
|
||||||
Map<String, Object> item = results.get(0);
|
|
||||||
String error = (String) item.get("error");
|
|
||||||
|
|
||||||
if (error != null && (
|
|
||||||
error.contains("HTTP 403") ||
|
|
||||||
error.contains("Failed to fetch") ||
|
|
||||||
error.contains("NetworkError") ||
|
|
||||||
error.contains("TypeError") ||
|
|
||||||
error.contains("script timeout"))) {
|
|
||||||
|
|
||||||
System.err.println(brand + " 查询失败(" + (retryCount + 1) + "/" + maxRetries + "): " + error + ",切换代理...");
|
|
||||||
|
|
||||||
// 切换代理
|
|
||||||
try { driver.quit(); } catch (Exception e) {}
|
|
||||||
driver = null;
|
driver = null;
|
||||||
retryCount++;
|
retryCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 成功或非网络错误
|
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
Boolean alive = (Boolean) item.get("alive");
|
@SuppressWarnings("unchecked") List<Map<String, Object>> hits = (List<Map<String, Object>>) result.get("hits");
|
||||||
resultMap.put(brand, alive);
|
String input = normalize(brand);
|
||||||
System.out.println(brand + " -> " + (alive ? "✓ 已注册" : "✗ 未注册"));
|
boolean registered = false;
|
||||||
|
|
||||||
|
for (Map<String, Object> hit : hits) {
|
||||||
|
@SuppressWarnings("unchecked") Map<String, Object> source = (Map<String, Object>) hit.get("source");
|
||||||
|
if (source != null && input.equals(normalize((String) source.get("wordmark")))) {
|
||||||
|
Number code = (Number) source.get("statusCode");
|
||||||
|
if (code != null && (code.intValue() == 688 || code.intValue() == 700)) {
|
||||||
|
registered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultMap.put(brand, registered);
|
||||||
|
System.out.println(brand + " -> " + (registered ? "✓" : "✗"));
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} else {
|
||||||
System.err.println(brand + " -> [查询失败: " + error + "]");
|
System.err.println(brand + " -> [查询失败: " + error + "]");
|
||||||
resultMap.put(brand, false); // 失败也记录为未注册
|
resultMap.put(brand, true);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/" + maxRetries + "): " + e.getMessage());
|
System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/3): " + e.getMessage());
|
||||||
try { driver.quit(); } catch (Exception ex) {}
|
if (driver != null) driver.quit();
|
||||||
driver = null;
|
driver = null;
|
||||||
retryCount++;
|
retryCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(brand + " -> [查询失败: 已重试" + maxRetries + "次]");
|
System.err.println(brand + " -> [查询失败: 已重试3次]");
|
||||||
resultMap.put(brand, false); // 失败也记录为未注册
|
resultMap.put(brand, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void closeDriver() {
|
|
||||||
if (driver != null) {
|
|
||||||
try { driver.quit(); } catch (Exception e) {}
|
|
||||||
driver = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
closeDriver();
|
if (driver != null) driver.quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user