From 007799fb2aa6a7653d933b64d38c0ec31bcbd271 Mon Sep 17 00:00:00 2001 From: zhangzijienbplus <17738440858@163.com> Date: Thu, 13 Nov 2025 14:20:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(trademark):=E4=BC=98=E5=8C=96=E5=95=86?= =?UTF-8?q?=E6=A0=87=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD=E5=92=8CExcel?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构品牌商标缓存服务,移除冗余的日志记录和存在检查- 简化Excel解析工具类,提取公共方法并优化列索引查找逻辑 - 增强Electron客户端开发模式下的后端启动控制能力 - 改进商标筛查面板的用户体验和数据处理流程-优化商标查询工具类,提高查询准确性和稳定性 - 调整商标控制器接口参数校验逻辑和资源清理机制 - 更新USPTO API测试用例以支持Spring容器环境运行 --- electron-vue-template/src/main/main.ts | 52 ++- .../components/amazon/TrademarkCheckPanel.vue | 161 ++++----- .../erp/controller/TrademarkController.java | 28 +- .../impl/BrandTrademarkCacheServiceImpl.java | 16 +- .../com/tashow/erp/test/UsptoApiTest.java | 314 +++++++++++------- .../com/tashow/erp/utils/ExcelParseUtil.java | 219 ++---------- .../tashow/erp/utils/TrademarkCheckUtil.java | 139 +++----- 7 files changed, 402 insertions(+), 527 deletions(-) diff --git a/electron-vue-template/src/main/main.ts b/electron-vue-template/src/main/main.ts index fce7a89..070f26e 100644 --- a/electron-vue-template/src/main/main.ts +++ b/electron-vue-template/src/main/main.ts @@ -434,6 +434,19 @@ app.whenReady().then(() => { createWindow(); 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 窗口 if (!shouldMinimize) { @@ -476,9 +489,22 @@ app.whenReady().then(() => { } console.log('[启动流程] 准备启动 Spring Boot...'); - setTimeout(() => { - startSpringBoot(); - }, 200); + + // 开发模式:添加快捷键跳过后端启动 + if (isDev) { + console.log('[开发模式] 按 Ctrl+Shift+D 跳过后端启动,直接进入应用'); + console.log('[开发模式] 按 Ctrl+Shift+S 手动启动后端服务'); + + // 5秒后自动跳过,避免卡死 + setTimeout(() => { + console.log('[开发模式] 自动跳过后端启动,直接进入应用'); + openAppIfNotOpened(); + }, 5000); + } + + // setTimeout(() => { + // startSpringBoot(); + // }, 200); app.on('activate', () => { 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('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 ipcMain.handle('window-minimize', () => { if (mainWindow && !mainWindow.isDestroyed()) { diff --git a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue index ee0f311..fa67cd4 100644 --- a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue +++ b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue @@ -116,18 +116,11 @@ function showMessage(message: string, type: 'success' | 'warning' | 'error' | 'i // 拖拽上传 async function processTrademarkFile(file: File) { uploadLoading.value = true - try { - // 根据选中的查询类型确定需要的表头 - const requiredHeaders: string[] = [] - if (queryTypes.value.includes('product')) { - requiredHeaders.push('商品主图') - } - if (queryTypes.value.includes('brand')) { - requiredHeaders.push('品牌') - } + const requiredHeaders = [] + if (queryTypes.value.includes('product')) requiredHeaders.push('商品主图') + if (queryTypes.value.includes('brand')) requiredHeaders.push('品牌') - // 验证表头 if (requiredHeaders.length > 0) { const validateResult = await markApi.validateHeaders(file, requiredHeaders) if (validateResult.code !== 200 && validateResult.code !== 0) { @@ -160,15 +153,11 @@ function removeTrademarkFile() { trademarkFileName.value = '' trademarkFile.value = null uploadLoading.value = false - if (trademarkUpload.value) { - trademarkUpload.value.value = '' - } + if (trademarkUpload.value) trademarkUpload.value.value = '' } -// 保存会话到后端 async function saveSession() { if (!trademarkData.value.length) return - try { const sessionData = { fileName: trademarkFileName.value, @@ -178,7 +167,6 @@ async function saveSession() { taskProgress: taskProgress.value, queryStatus: queryStatus.value } - const result = await markApi.saveSession(sessionData) if (result.code === 200 || result.code === 0) { const username = getUsernameFromToken() @@ -258,13 +246,10 @@ async function handleTrademarkUpload(e: Event) { const input = e.target as HTMLInputElement const file = input.files?.[0] if (!file) return - - const ok = /\.xlsx?$/.test(file.name) - if (!ok) { + if (!/\.xlsx?$/.test(file.name)) { showMessage('仅支持 .xlsx/.xls 文件', 'warning') return } - await processTrademarkFile(file) input.value = '' } @@ -274,9 +259,7 @@ async function startTrademarkQuery() { showMessage('请先导入商标列表', 'warning') return } - if (refreshVipStatus) await refreshVipStatus() - if (!props.isVip) { if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType showTrialExpiredDialog.value = true @@ -286,6 +269,7 @@ async function startTrademarkQuery() { const needProductCheck = queryTypes.value.includes('product') const needBrandCheck = queryTypes.value.includes('brand') + // 立即显示加载状态,防止重复点击 trademarkLoading.value = true trademarkProgress.value = 0 trademarkData.value = [] @@ -293,6 +277,9 @@ async function startTrademarkQuery() { trademarkHeaders.value = [] queryStatus.value = 'inProgress' + // 立即显示"正在准备..."状态 + showMessage('正在准备筛查任务...', 'info') + // 重置任务进度 taskProgress.value.product.total = 0 taskProgress.value.product.current = 0 @@ -476,11 +463,16 @@ async function startTrademarkQuery() { if (brandList.length > 0) { const brandData = taskProgress.value.brand brandData.total = brandList.length - brandData.current = 1 // 立即显示初始进度 + brandData.current = 0 brandData.completed = 0 - // 生成任务ID并轮询真实进度 + // 生成任务ID并立即开始轮询 brandTaskId.value = `task_${Date.now()}` + + // 立即显示开始状态 + showMessage(`开始品牌商标筛查,共${brandList.length}个品牌`, 'success') + + // 立即开始进度轮询 brandProgressTimer = setInterval(async () => { try { const res = await markApi.getBrandCheckProgress(brandTaskId.value) @@ -490,72 +482,69 @@ async function startTrademarkQuery() { } catch (e) { // 忽略进度查询错误 } - }, 1000) + }, 500) const brandResult = await markApi.brandCheck(brandList, brandTaskId.value) if (brandProgressTimer) clearInterval(brandProgressTimer) - - if (brandResult.code === 200 || brandResult.code === 0) { - // 完成,显示100% + if ( brandResult.code === 0) { brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total brandData.current = brandData.total brandData.completed = brandResult.data.unregistered || 0 isBrandTaskRealData.value = true - - // 提取未注册品牌列表 - const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean) - - if (unregisteredBrands.length > 0) { - // 从原始Excel中过滤出包含这些品牌的完整行 - const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands) - - if (filterResult.code === 200 || filterResult.code === 0) { - // 保存完整数据(用于导出,使用原始表头) - trademarkFullData.value = filterResult.data.filteredRows - trademarkHeaders.value = filterResult.data.headers || [] - - // 更新统计:显示过滤出的实际行数(而不是品牌数) - brandData.completed = filterResult.data.filteredRows.length - isBrandTaskRealData.value = true - - // 将品牌筛查结果作为展示数据 - const brandItems = filterResult.data.filteredRows.map((row: any) => ({ - name: row['品牌'] || '', - status: '未注册', - class: '', - owner: '', - expireDate: row['注册时间'] || '', - similarity: 0, - asin: row['ASIN'] || '', - productImage: row['商品主图'] || '', - isBrand: true // 标记为品牌数据 - })) - - // 如果有产品筛查,也替换展示数据(只显示品牌筛查结果) - if (needProductCheck) { - trademarkData.value = brandItems - } else { - trademarkData.value = [...trademarkData.value, ...brandItems] - } - } - } + await processBrandResult(brandResult) } else { throw new Error(brandResult.msg || '品牌筛查失败') } } } - // 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading) - queryStatus.value = 'done' - emit('updateData', trademarkData.value) + // 处理品牌查询结果的函数 + async function processBrandResult(brandResult: any) { + // 提取未注册品牌列表 + const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean) - let summaryMsg = '筛查完成' - if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}` - if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}` - showMessage(summaryMsg, 'success') - - // 保存会话 - await saveSession() + if (unregisteredBrands.length > 0) { + // 从原始Excel中过滤出包含这些品牌的完整行 + const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands) + + // 保存完整数据(用于导出,使用原始表头) + trademarkFullData.value = filterResult.data.filteredRows + trademarkHeaders.value = filterResult.data.headers || [] + + // 更新统计:显示过滤出的实际行数(而不是品牌数) + const brandData = taskProgress.value.brand + brandData.completed = filterResult.data.filteredRows.length + isBrandTaskRealData.value = true + + // 将品牌筛查结果作为展示数据 + const brandItems = filterResult.data.filteredRows.map((row: any) => ({ + name: row['品牌'] || '', + status: '未注册', + class: '', + owner: '', + expireDate: row['注册时间'] || '', + similarity: 0, + asin: row['ASIN'] || '', + productImage: row['商品主图'] || '', + isBrand: true // 标记为品牌数据 + })) + + // 更新展示数据 + trademarkData.value = [...trademarkData.value, ...brandItems] + } + } + + // 只要流程正常完成,就设置为done状态 + queryStatus.value = 'done' + emit('updateData', trademarkData.value) + + let summaryMsg = '筛查完成' + if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}` + if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}` + showMessage(summaryMsg, 'success') + + // 保存会话 + await saveSession() } catch (error: any) { const hasProductData = isProductTaskRealData.value && taskProgress.value.product.total > 0 const hasBrandData = isBrandTaskRealData.value && taskProgress.value.brand.total > 0 @@ -715,27 +704,15 @@ function resetToIdle() { trademarkHeaders.value = [] trademarkFileName.value = '' trademarkFile.value = null - taskProgress.value.product.total = 0 - taskProgress.value.product.current = 0 - taskProgress.value.product.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 - - // 重置真实数据标记 + Object.assign(taskProgress.value.product, { total: 0, current: 0, completed: 0 }) + Object.assign(taskProgress.value.brand, { total: 0, current: 0, completed: 0 }) + Object.assign(taskProgress.value.platform, { total: 0, current: 0, completed: 0 }) isProductTaskRealData.value = false isBrandTaskRealData.value = false - - // 清空localStorage中的会话数据 try { const username = getUsernameFromToken() localStorage.removeItem(`trademark_session_${username}`) - } catch (e) { - // 忽略错误 - } + } catch (e) {} } defineExpose({ diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java index dd00727..55edfa5 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java @@ -61,9 +61,10 @@ public class TrademarkController { String taskId = (String) request.get("taskId"); try { List list = brands.stream() - .filter(b -> b != null && !b.trim().isEmpty()) + .filter(b -> !b.trim().isEmpty()) .map(String::trim) .collect(Collectors.toList()); + long start = System.currentTimeMillis(); // 1. 先从全局缓存获取 Map cached = cacheService.getCached(list); @@ -74,8 +75,7 @@ public class TrademarkController { Map queried = new HashMap<>(); if (!toQuery.isEmpty()) { for (int i = 0; i < toQuery.size(); i++) { - // 检查任务是否被取消值 - if (taskId != null && cancelMap.getOrDefault(taskId, false)) { + if (cancelMap.getOrDefault(taskId, false)) { logger.info("任务 {} 已被取消,停止查询", taskId); break; } @@ -86,15 +86,10 @@ public class TrademarkController { Map results = util.batchCheck(Collections.singletonList(brand), queried); 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. 合并缓存和新查询结果 @@ -132,16 +127,11 @@ public class TrademarkController { logger.info("完成: 共{}个,成功查询{}个(已注册{}个,未注册{}个),查询失败{}个,耗时{}秒", list.size(), checkedCount, registeredCount, unregistered.size(), failedCount, t); - // 30秒后清理进度和取消标志 if (taskId != null) { - String finalTaskId = taskId; new Thread(() -> { - try { - Thread.sleep(30000); - } catch (InterruptedException ignored) { - } - progressMap.remove(finalTaskId); - cancelMap.remove(finalTaskId); + try { Thread.sleep(30000); } catch (InterruptedException ignored) {} + progressMap.remove(taskId); + cancelMap.remove(taskId); }).start(); } @@ -150,7 +140,7 @@ public class TrademarkController { logger.error("筛查失败", e); return JsonData.buildError("筛查失败: " + e.getMessage()); } finally { - util.closeDriver(); + if (util != null && util.driver != null) util.driver.quit(); cacheService.cleanExpired(); } } diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BrandTrademarkCacheServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BrandTrademarkCacheServiceImpl.java index 2202a17..c4ffa2f 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BrandTrademarkCacheServiceImpl.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BrandTrademarkCacheServiceImpl.java @@ -23,25 +23,18 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic public Map getCached(List brands) { LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1); List cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime); - Map result = new HashMap<>(); cached.forEach(e -> result.put(e.getBrand(), e.getRegistered())); - - if (!result.isEmpty()) { - log.info("从全局缓存获取 {} 个品牌数据", result.size()); - } return result; } @Override public void saveResults(Map results) { results.forEach((brand, registered) -> { - if (!repository.existsByBrand(brand)) { - BrandTrademarkCacheEntity entity = new BrandTrademarkCacheEntity(); - entity.setBrand(brand); - entity.setRegistered(registered); - repository.save(entity); - } + BrandTrademarkCacheEntity entity = new BrandTrademarkCacheEntity(); + entity.setBrand(brand); + entity.setRegistered(registered); + repository.save(entity); }); } @@ -50,7 +43,6 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic public void cleanExpired() { LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1); repository.deleteByCreatedAtBefore(cutoffTime); - log.info("清理1天前的品牌商标缓存"); } } diff --git a/erp_client_sb/src/main/java/com/tashow/erp/test/UsptoApiTest.java b/erp_client_sb/src/main/java/com/tashow/erp/test/UsptoApiTest.java index 633b7d7..cbdfd37 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/test/UsptoApiTest.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/test/UsptoApiTest.java @@ -2,6 +2,12 @@ package com.tashow.erp.test; import com.fasterxml.jackson.databind.JsonNode; 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.web.client.RestTemplate; @@ -15,145 +21,211 @@ public class UsptoApiTest { private static final ObjectMapper mapper = new ObjectMapper(); public static void main(String[] args) { - System.out.println("=== USPTO API 功能测试 ===\n"); - System.out.println("API Key: " + API_KEY + "\n"); - - // 测试1:通过序列号查询(已知可用) - testBySerialNumber(); - - System.out.println("\n" + "=".repeat(60) + "\n"); - - // 测试2:尝试通过品牌名称搜索 - testByBrandName(); - - System.out.println("\n" + "=".repeat(60) + "\n"); - - // 测试3:对比当前实现的tmsearch - testCurrentImplementation(); - } - - /** - * 测试1:通过序列号查询(官方TSDR API) - */ - private static void testBySerialNumber() { - System.out.println("【测试1】通过序列号查询"); - System.out.println("端点: https://tsdrapi.uspto.gov/ts/cd/casestatus/sn88123456/info.json"); + System.out.println("=== 商标查询测试(使用Spring容器) ===\n"); + // 启动Spring Boot应用上下文 + ConfigurableApplicationContext context = null; try { - HttpHeaders headers = new HttpHeaders(); - headers.set("USPTO-API-KEY", API_KEY); - HttpEntity entity = new HttpEntity<>(headers); + System.out.println("正在启动Spring Boot应用..."); + context = SpringApplication.run(ErpClientSbApplication.class, args); + System.out.println("Spring Boot应用启动成功!\n"); - ResponseEntity response = rest.exchange( - "https://tsdrapi.uspto.gov/ts/cd/casestatus/sn88123456/info.json", - HttpMethod.GET, entity, String.class - ); + // 获取TrademarkCheckUtil Bean + TrademarkCheckUtil trademarkUtil = context.getBean(TrademarkCheckUtil.class); - JsonNode json = mapper.readTree(response.getBody()); - String markElement = json.get("trademarks").get(0).get("status").get("markElement").asText(); - int status = json.get("trademarks").get(0).get("status").get("status").asInt(); - String regNumber = json.get("trademarks").get(0).get("status").get("usRegistrationNumber").asText(); + // 测试单品牌查询(获取详细结果) + testSingleBrandWithDetailedResults("Remorlet"); - System.out.println("✓ 成功!"); - System.out.println(" 商标名称: " + markElement); - System.out.println(" 状态码: " + status); - System.out.println(" 注册号: " + (regNumber.isEmpty() ? "未注册" : regNumber)); - System.out.println("\n结论: ✅ 可以查询,但必须知道序列号"); } catch (Exception e) { - System.out.println("✗ 失败: " + e.getMessage()); + System.err.println("测试失败: " + e.getMessage()); + e.printStackTrace(); + } finally { + } } + /** - * 测试2:尝试通过品牌名称搜索 + * 标准化品牌名称用于比较(移除特殊字符,转换为小写) */ - private static void testByBrandName() { - 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); + private static String normalizeBrandName(String name) { + if (name == null) return ""; + return name.toLowerCase() + .replaceAll("[\\s\\-_\\.]", "") // 移除空格、连字符、下划线、点号 + .replaceAll("[^a-z0-9]", ""); // 只保留字母和数字 + } + + /** + * 直接执行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 testSingleBrandWithDetailedResults(String brandName) { + System.out.println("【单品牌测试】直接执行JavaScript获取详细结果"); + System.out.println("品牌: " + brandName); + + ChromeDriver driver = null; + try { + System.out.println("=== 开始测试品牌: " + brandName + " ==="); - for (String urlTemplate : searchUrls) { - String url = String.format(urlTemplate, brand); - System.out.println(" 尝试: " + url); + // 初始化Chrome驱动 + System.out.println("正在初始化Chrome驱动..."); + 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驱动初始化完成"); + + long startTime = System.currentTimeMillis(); + + // 构建JavaScript脚本 - 获取所有结果 + 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> results = + (java.util.List>) + ((JavascriptExecutor) driver).executeAsyncScript(script); + + long duration = System.currentTimeMillis() - startTime; + + // 极简输出 - 只返回 true/false 判定结果 + boolean isRegistered = false; + + if (results != null && !results.isEmpty()) { + java.util.Map result = results.get(0); - try { - HttpHeaders headers = new HttpHeaders(); - headers.set("USPTO-API-KEY", API_KEY); - HttpEntity entity = new HttpEntity<>(headers); - - ResponseEntity 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) { - System.out.println(" ✗ 404/失败"); + // 获取所有匹配的结果 + @SuppressWarnings("unchecked") + java.util.List> allResults = + (java.util.List>) result.get("allResults"); + + // 如果allResults为空,从rawData中获取 + if ((allResults == null || allResults.isEmpty()) && result.get("rawData") != null) { + @SuppressWarnings("unchecked") + java.util.Map rawData = (java.util.Map) result.get("rawData"); + @SuppressWarnings("unchecked") + java.util.Map hits = (java.util.Map) rawData.get("hits"); + if (hits != null) { + @SuppressWarnings("unchecked") + java.util.List> hitsArray = + (java.util.List>) hits.get("hits"); + if (hitsArray != null) { + allResults = hitsArray; + } + } + } + + // 核心判定逻辑:品牌名称匹配 且 statusCode为686/700 才判定为已注册 + String normalizedInputBrand = normalizeBrandName(brandName); + if (allResults != null && !allResults.isEmpty()) { + for (java.util.Map hit : allResults) { + @SuppressWarnings("unchecked") + java.util.Map source = (java.util.Map) hit.get("_source"); + if (source == null) { + source = (java.util.Map) 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; // 找到一个符合条件的就够了 + } + } + } + } } } - 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 entity = new HttpEntity<>(requestBody, headers); + // 极简输出 - 只显示最终结果 + System.out.println(isRegistered); - ResponseEntity 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()); + System.err.println("=== 测试失败 ==="); + System.err.println("品牌: " + brandName); + System.err.println("错误: " + e.getMessage()); + System.err.println("================"); + e.printStackTrace(); + } finally { + // 确保清理资源 + if (driver != null) { + try { + driver.quit(); + } catch (Exception e) { + System.err.println("清理Chrome驱动时出错: " + e.getMessage()); + } + } } } } diff --git a/erp_client_sb/src/main/java/com/tashow/erp/utils/ExcelParseUtil.java b/erp_client_sb/src/main/java/com/tashow/erp/utils/ExcelParseUtil.java index c7a847a..a19f185 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/utils/ExcelParseUtil.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/utils/ExcelParseUtil.java @@ -11,19 +11,10 @@ import java.util.stream.Collectors; /** * Excel 解析工具类 - * 统一处理各种平台的 Excel 文件解析需求 - * - * @author Claude Code */ @Slf4j public class ExcelParseUtil { - /** - * 自动查找表头行索引(在前2行中查找) - * @param rows Excel所有行 - * @param columnName 列名(如"品牌") - * @return 表头行索引,未找到返回-1 - */ private static int findHeaderRow(List> rows, String columnName) { for (int r = 0; r < Math.min(2, rows.size()); r++) { for (Object cell : rows.get(r)) { @@ -34,93 +25,38 @@ public class ExcelParseUtil { } return -1; } - - /** - * 解析 Excel 文件第一列数据 - * 通用方法,适用于店铺名、ASIN、订单号等标识符解析 - * - * @param file Excel 文件 - * @return 解析出的字符串列表,跳过表头,过滤空值 - */ - public static List parseFirstColumn(MultipartFile file) { - List result = new ArrayList<>(); - if (file == null || file.isEmpty()) { - log.warn("Excel 文件为空"); - return result; - } - - try (InputStream in = file.getInputStream()) { - ExcelReader reader = ExcelUtil.getReader(in, 0); - List> rows = reader.read(); - - for (int i = 1; i < rows.size(); i++) { // 从第2行开始,跳过表头 - List 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); - } - } - } + + private static int findColumnIndex(List headerRow, String columnName) { + for (int c = 0; c < headerRow.size(); c++) { + if (headerRow.get(c) != null && columnName.equals(headerRow.get(c).toString().replaceAll("\\s+", ""))) { + return c; } - - log.info("成功解析 Excel 文件: {}, 共解析出 {} 条数据", - file.getOriginalFilename(), result.size()); - - } catch (Exception e) { - log.error("解析 Excel 文件失败: {}, 文件名: {}", e.getMessage(), - file.getOriginalFilename(), e); } - return result; + return -1; + } + + public static List parseFirstColumn(MultipartFile file) { + return parseColumn(file, 0); } - /** - * 解析指定列的数据 - * - * @param file Excel 文件 - * @param columnIndex 列索引(从0开始) - * @return 解析出的字符串列表 - */ public static List parseColumn(MultipartFile file, int columnIndex) { List result = new ArrayList<>(); - if (file == null || file.isEmpty()) { - log.warn("Excel 文件为空"); - return result; - } - try (InputStream in = file.getInputStream()) { ExcelReader reader = ExcelUtil.getReader(in, 0); List> rows = reader.read(); - - for (int i = 1; i < rows.size(); i++) { // 从第2行开始,跳过表头 + for (int i = 1; i < rows.size(); i++) { List row = rows.get(i); - if (row != null && row.size() > columnIndex) { - Object cell = row.get(columnIndex); - if (cell != null) { - String value = cell.toString().trim(); - if (!value.isEmpty()) { - result.add(value); - } - } + if (row.size() > columnIndex && row.get(columnIndex) != null) { + String value = row.get(columnIndex).toString().trim(); + if (!value.isEmpty()) result.add(value); } } - - log.info("成功解析 Excel 文件第{}列: {}, 共解析出 {} 条数据", - columnIndex + 1, file.getOriginalFilename(), result.size()); - } catch (Exception e) { - log.error("解析 Excel 文件第{}列失败: {}, 文件名: {}", - columnIndex + 1, e.getMessage(), file.getOriginalFilename(), e); + log.error("解析Excel失败", e); } - return result; } - /** - * 根据列名解析数据(自动适配第1行或第2行为表头) - */ public static List parseColumnByName(MultipartFile file, String columnName) { List result = new ArrayList<>(); try (InputStream in = file.getInputStream()) { @@ -131,14 +67,8 @@ public class ExcelParseUtil { int headerRow = findHeaderRow(rows, columnName); if (headerRow < 0) return result; - int colIdx = -1; - for (int c = 0; c < rows.get(headerRow).size(); c++) { - if (rows.get(headerRow).get(c) != null && - columnName.equals(rows.get(headerRow).get(c).toString().replaceAll("\\s+", ""))) { - colIdx = c; - break; - } - } + int colIdx = findColumnIndex(rows.get(headerRow), columnName); + if (colIdx < 0) return result; for (int i = headerRow + 1; i < rows.size(); i++) { List row = rows.get(i); @@ -153,11 +83,6 @@ public class ExcelParseUtil { return result; } - /** - * 读取Excel的完整数据(包含表头和所有行,自动适配第1行或第2行为表头) - * @param file Excel文件 - * @return Map包含headers(表头列表)和rows(数据行列表,每行是Map) - */ public static Map parseFullExcel(MultipartFile file) { Map result = new HashMap<>(); List headers = new ArrayList<>(); @@ -166,17 +91,13 @@ public class ExcelParseUtil { try (InputStream in = file.getInputStream()) { ExcelReader reader = ExcelUtil.getReader(in, 0); List> allRows = reader.read(); - if (allRows.isEmpty()) { - log.warn("Excel文件为空"); result.put("headers", headers); result.put("rows", rows); return result; } int headerRowIndex = Math.max(0, findHeaderRow(allRows, "品牌")); - log.info("检测到表头行:第{}行", headerRowIndex + 1); - for (Object cell : allRows.get(headerRowIndex)) { headers.add(cell != null ? cell.toString().trim() : ""); } @@ -192,89 +113,21 @@ public class ExcelParseUtil { result.put("headers", headers); result.put("rows", rows); - log.info("解析Excel: {}, 表头{}列, 数据{}行", file.getOriginalFilename(), headers.size(), rows.size()); - } catch (Exception e) { - log.error("解析Excel失败: {}", e.getMessage(), e); + log.error("解析Excel失败", e); } - return result; } - /** - * 根据ASIN列表从Excel中过滤完整行数据(自动适配第1行或第2行为表头) - * @param file Excel文件 - * @param asins ASIN列表 - * @return Map包含headers(表头)和filteredRows(过滤后的完整行数据) - */ public static Map filterExcelByAsins(MultipartFile file, List asins) { - Map result = new HashMap<>(); - List headers = new ArrayList<>(); - List> filteredRows = new ArrayList<>(); - - try (InputStream in = file.getInputStream()) { - ExcelReader reader = ExcelUtil.getReader(in, 0); - List> 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 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 asinSet = asins.stream().map(String::trim).collect(Collectors.toSet()); - - for (int i = headerRowIndex + 1; i < allRows.size(); i++) { - List row = allRows.get(i); - if (row.size() > asinColIndex && row.get(asinColIndex) != null - && asinSet.contains(row.get(asinColIndex).toString().trim())) { - Map 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; + return filterExcelByColumn(file, "ASIN", asins); } - /** - * 根据品牌列表从Excel中过滤完整行数据(自动适配第1行或第2行为表头) - * @param file Excel文件 - * @param brands 品牌列表 - * @return Map包含headers(表头)和filteredRows(过滤后的完整行数据) - */ public static Map filterExcelByBrands(MultipartFile file, List brands) { + return filterExcelByColumn(file, "品牌", brands); + } + + private static Map filterExcelByColumn(MultipartFile file, String columnName, List values) { Map result = new HashMap<>(); List headers = new ArrayList<>(); List> filteredRows = new ArrayList<>(); @@ -282,40 +135,37 @@ public class ExcelParseUtil { try (InputStream in = file.getInputStream()) { ExcelReader reader = ExcelUtil.getReader(in, 0); List> allRows = reader.read(); - if (allRows.isEmpty()) { result.put("headers", headers); result.put("filteredRows", filteredRows); return result; } - int headerRowIndex = findHeaderRow(allRows, "品牌"); + int headerRowIndex = findHeaderRow(allRows, columnName); if (headerRowIndex < 0) { - log.warn("未找到'品牌'列"); result.put("headers", headers); result.put("filteredRows", filteredRows); return result; } - int brandColIndex = -1; List headerRow = allRows.get(headerRowIndex); - for (int c = 0; c < headerRow.size(); c++) { - if (headerRow.get(c) != null && "品牌".equals(headerRow.get(c).toString().replaceAll("\\s+", ""))) { - brandColIndex = c; - break; - } + int colIndex = findColumnIndex(headerRow, columnName); + if (colIndex < 0) { + result.put("headers", headers); + result.put("filteredRows", filteredRows); + return result; } for (Object cell : headerRow) { headers.add(cell != null ? cell.toString().trim() : ""); } - Set brandSet = brands.stream().map(String::trim).collect(Collectors.toSet()); + Set valueSet = values.stream().map(String::trim).collect(Collectors.toSet()); for (int i = headerRowIndex + 1; i < allRows.size(); i++) { List row = allRows.get(i); - if (row.size() > brandColIndex && row.get(brandColIndex) != null - && brandSet.contains(row.get(brandColIndex).toString().trim())) { + if (row.size() > colIndex && row.get(colIndex) != null + && valueSet.contains(row.get(colIndex).toString().trim())) { Map rowMap = new HashMap<>(); for (int j = 0; j < Math.min(headers.size(), row.size()); j++) { rowMap.put(headers.get(j), row.get(j)); @@ -326,12 +176,9 @@ public class ExcelParseUtil { result.put("headers", headers); result.put("filteredRows", filteredRows); - log.info("品牌过滤: {}, {}个品牌 -> {}行数据", file.getOriginalFilename(), brands.size(), filteredRows.size()); - } catch (Exception e) { - log.error("品牌过滤失败: {}", e.getMessage(), e); + log.error("Excel过滤失败", e); } - return result; } } \ No newline at end of file diff --git a/erp_client_sb/src/main/java/com/tashow/erp/utils/TrademarkCheckUtil.java b/erp_client_sb/src/main/java/com/tashow/erp/utils/TrademarkCheckUtil.java index e96ab38..6daec74 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/utils/TrademarkCheckUtil.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/utils/TrademarkCheckUtil.java @@ -1,10 +1,12 @@ package com.tashow.erp.utils; + import com.tashow.erp.service.BrandTrademarkCacheService; import jakarta.annotation.PreDestroy; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.chrome.ChromeDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; + import java.util.*; /** @@ -17,133 +19,82 @@ public class TrademarkCheckUtil { private ProxyPool proxyPool; @Autowired 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() { if (driver == null) { - for (int i = 0; i < 5; i++) { - try { - driver = SeleniumUtil.createDriver(true, proxyPool.getProxy()); - driver.get("https://tmsearch.uspto.gov/search/search-results"); - Thread.sleep(6000); - 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); - } - } + driver = SeleniumUtil.createDriver(true, proxyPool.getProxy()); + driver.get("https://tmsearch.uspto.gov/search/search-results"); + try { Thread.sleep(6000); } catch (InterruptedException ignored) {} } } public synchronized Map batchCheck(List brands, Map alreadyQueried) { Map resultMap = new HashMap<>(); - int maxRetries = 5; - for (String brand : brands) { int retryCount = 0; boolean success = false; - - while (retryCount < maxRetries && !success) { + while (retryCount < 5 && !success) { try { ensureInit(); - - String script = "const brands = ['" + brand.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: 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> results = (List>) - ((JavascriptExecutor) driver).executeAsyncScript(script); - - Map 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) {} + 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('arguments[arguments.length-1]({hits:[],error:e.message}));"; + @SuppressWarnings("unchecked") Map result = (Map) ((JavascriptExecutor) driver).executeAsyncScript(script); + String error = (String) result.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) + "/3): " + error + ",切换代理..."); + if (driver != null) driver.quit(); driver = null; retryCount++; continue; } - - // 成功或非网络错误 + if (error == null) { - Boolean alive = (Boolean) item.get("alive"); - resultMap.put(brand, alive); - System.out.println(brand + " -> " + (alive ? "✓ 已注册" : "✗ 未注册")); + @SuppressWarnings("unchecked") List> hits = (List>) result.get("hits"); + String input = normalize(brand); + boolean registered = false; + + for (Map hit : hits) { + @SuppressWarnings("unchecked") Map source = (Map) 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; } else { System.err.println(brand + " -> [查询失败: " + error + "]"); - resultMap.put(brand, false); // 失败也记录为未注册 + resultMap.put(brand, true); success = true; } - } catch (Exception e) { - System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/" + maxRetries + "): " + e.getMessage()); - try { driver.quit(); } catch (Exception ex) {} + System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/3): " + e.getMessage()); + if (driver != null) driver.quit(); driver = null; retryCount++; } } - + if (!success) { - System.err.println(brand + " -> [查询失败: 已重试" + maxRetries + "次]"); - resultMap.put(brand, false); // 失败也记录为未注册 + System.err.println(brand + " -> [查询失败: 已重试3次]"); + resultMap.put(brand, true); } } - return resultMap; } - - public synchronized void closeDriver() { - if (driver != null) { - try { driver.quit(); } catch (Exception e) {} - driver = null; - } - } - + @PreDestroy public void cleanup() { - closeDriver(); + if (driver != null) driver.quit(); } }