feat(trademark): 支持商标筛查任务取消状态及优化错误处理- 新增商标筛查取消状态的UI展示和处理逻辑

-优化错误提示信息,区分网络错误、超时和风控场景
- 改进任务进度计算逻辑,支持更准确的完成状态判断
- 调整品牌提取逻辑,从Excel中直接读取品牌列数据
- 增强后端403错误检测和代理自动切换机制
- 更新前端组件样式和交互逻辑以匹配新状态
-修复部分条件判断逻辑以提升稳定性- 调整文件上传大小限制至50MB以支持更大文件- 优化Excel解析工具类,支持自动识别表头行位置
This commit is contained in:
2025-11-05 10:16:14 +08:00
parent a62d7b6147
commit 4e2ce48934
9 changed files with 264 additions and 179 deletions

View File

@@ -1,19 +1,20 @@
package com.tashow.erp.controller;
import com.tashow.erp.utils.ExcelParseUtil;
import com.tashow.erp.utils.JsonData;
import com.tashow.erp.utils.LoggerUtil;
import com.tashow.erp.utils.TrademarkCheckUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/**
* 商标检查控制器 - 极速版(浏览器内并发)
*/
@RestController
@RequestMapping("/api/trademark")
@CrossOrigin
public class TrademarkController {
private static final Logger logger = LoggerUtil.getLogger(TrademarkController.class);
@@ -31,10 +32,7 @@ public class TrademarkController {
.map(String::trim)
.distinct()
.collect(Collectors.toList());
logger.info("开始检查 {}个品牌", list.size());
long start = System.currentTimeMillis();
// 串行查询(不加延迟)
List<Map<String, Object>> unregistered = new ArrayList<>();
int checkedCount = 0;
@@ -87,4 +85,21 @@ public class TrademarkController {
util.closeDriver();
}
}
/**
* 从Excel提取品牌列表
*/
@PostMapping("/extractBrands")
public JsonData extractBrands(@RequestParam("file") MultipartFile file) {
try {
List<String> brands = ExcelParseUtil.parseColumnByName(file, "品牌");
if (brands.isEmpty()) return JsonData.buildError("未找到品牌列或品牌数据为空");
Map<String, Object> result = new HashMap<>();
result.put("total", brands.size());
result.put("brands", brands);
return JsonData.buildSuccess(result);
} catch (Exception e) {
return JsonData.buildError("提取失败: " + e.getMessage());
}
}
}

View File

@@ -56,7 +56,6 @@ public class ExcelParseUtil {
log.error("解析 Excel 文件失败: {}, 文件名: {}", e.getMessage(),
file.getOriginalFilename(), e);
}
return result;
}
@@ -101,4 +100,45 @@ public class ExcelParseUtil {
return result;
}
/**
* 根据列名解析数据自动适配第1行或第2行为表头
*/
public static List<String> parseColumnByName(MultipartFile file, String columnName) {
List<String> result = new ArrayList<>();
try (InputStream in = file.getInputStream()) {
ExcelReader reader = ExcelUtil.getReader(in, 0);
List<List<Object>> rows = reader.read();
if (rows.isEmpty()) return result;
// 查找表头行和列索引
int headerRow = -1, colIdx = -1;
for (int r = 0; r < Math.min(2, rows.size()); r++) {
for (int c = 0; c < rows.get(r).size(); c++) {
String col = rows.get(r).get(c).toString().replaceAll("\\s+", "");
if (col.equals(columnName)) {
headerRow = r;
colIdx = c;
break;
}
}
if (colIdx != -1) break;
}
if (colIdx == -1) return result;
// 从表头下一行开始读数据
for (int i = headerRow + 1; i < rows.size(); i++) {
List<Object> row = rows.get(i);
if (row.size() > colIdx && row.get(colIdx) != null) {
String val = row.get(colIdx).toString().trim();
if (!val.isEmpty()) result.add(val);
}
}
} catch (Exception e) {
log.error("解析失败", e);
}
return result;
}
}

View File

@@ -6,26 +6,23 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 商标检查工具
* 支持批量并发查询每100次切换代理
* 检测到403时自动切换代理并重试
*/
@Component
public class TrademarkCheckUtil {
@Autowired
private ProxyPool proxyPool;
private ChromeDriver driver;
private final AtomicInteger checkCount = new AtomicInteger(0);
private static final int PROXY_SWITCH_THRESHOLD = 100;
private synchronized void ensureInit() {
if (driver == null) {
for (int i = 0; i < 3; i++) {
for (int i = 0; i < 5; i++) {
try {
driver = SeleniumUtil.createDriver(false, proxyPool.getProxy());
driver.get("https://tmsearch.uspto.gov/search/search-results");
Thread.sleep(5000);
Thread.sleep(6000);
return; // 成功则返回
} catch (Exception e) {
System.err.println("初始化失败(尝试" + (i+1) + "/3: " + e.getMessage());
@@ -42,14 +39,6 @@ public class TrademarkCheckUtil {
public synchronized Map<String, Boolean> batchCheck(List<String> brands) {
ensureInit();
// 每100个切换代理
if (checkCount.addAndGet(brands.size()) >= PROXY_SWITCH_THRESHOLD) {
try { driver.quit(); } catch (Exception e) {}
driver = null;
checkCount.set(0);
ensureInit();
}
// 构建批量查询脚本(带错误诊断)
String script = """
const brands = arguments[0];
@@ -91,6 +80,27 @@ public class TrademarkCheckUtil {
List<Map<String, Object>> results = (List<Map<String, Object>>)
((JavascriptExecutor) driver).executeAsyncScript(script, brands);
// 检测是否有403错误
boolean has403 = results.stream()
.anyMatch(item -> {
String error = (String) item.get("error");
return error != null && error.contains("HTTP 403");
});
// 如果有403切换代理并重试
if (has403) {
System.err.println("检测到403切换代理并重试...");
try { driver.quit(); } catch (Exception e) {}
driver = null;
ensureInit();
// 重新执行查询
@SuppressWarnings("unchecked")
List<Map<String, Object>> retryResults = (List<Map<String, Object>>)
((JavascriptExecutor) driver).executeAsyncScript(script, brands);
results = retryResults;
}
Map<String, Boolean> resultMap = new HashMap<>();
for (Map<String, Object> item : results) {
String brand = (String) item.get("brand");
@@ -117,7 +127,6 @@ public class TrademarkCheckUtil {
if (driver != null) {
try { driver.quit(); } catch (Exception e) {}
driver = null;
checkCount.set(0);
}
}

View File

@@ -10,6 +10,10 @@ javafx:
spring:
main:
lazy-initialization: true
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
datasource:
url: jdbc:sqlite:./data/erp-cache.db?journal_mode=WAL&synchronous=NORMAL&cache_size=10000&temp_store=memory&busy_timeout=30000
driver-class-name: org.sqlite.JDBC