feat(trademark): 支持商标筛查任务取消状态及优化错误处理- 新增商标筛查取消状态的UI展示和处理逻辑
-优化错误提示信息,区分网络错误、超时和风控场景 - 改进任务进度计算逻辑,支持更准确的完成状态判断 - 调整品牌提取逻辑,从Excel中直接读取品牌列数据 - 增强后端403错误检测和代理自动切换机制 - 更新前端组件样式和交互逻辑以匹配新状态 -修复部分条件判断逻辑以提升稳定性- 调整文件上传大小限制至50MB以支持更大文件- 优化Excel解析工具类,支持自动识别表头行位置
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user