refactor(utils):优化商标查询重试机制- 将初始化失败重试次数从3次增加到5次
- 改进批量查询逻辑,支持单个品牌独立重试- 添加针对HTTP 403、网络错误等异常的代理切换机制-优化查询脚本构造方式,提高执行稳定性 - 增强错误处理和日志输出信息 - 移除控制器中冗余的注释描述
This commit is contained in:
@@ -21,7 +21,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
/**
|
/**
|
||||||
* 商标检查控制器 - 极速版(浏览器内并发)
|
* 商标检查控制器
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/trademark")
|
@RequestMapping("/api/trademark")
|
||||||
@@ -63,12 +63,9 @@ public class TrademarkController {
|
|||||||
.filter(b -> b != null && !b.trim().isEmpty())
|
.filter(b -> b != null && !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);
|
||||||
|
|
||||||
// 2. 找出缓存未命中的品牌
|
// 2. 找出缓存未命中的品牌
|
||||||
List<String> toQuery = list.stream()
|
List<String> toQuery = list.stream()
|
||||||
.filter(b -> !cached.containsKey(b))
|
.filter(b -> !cached.containsKey(b))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class TrademarkCheckUtil {
|
|||||||
Thread.sleep(6000);
|
Thread.sleep(6000);
|
||||||
return; // 成功则返回
|
return; // 成功则返回
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("初始化失败(尝试" + (i+1) + "/3): " + e.getMessage());
|
System.err.println("初始化失败(尝试" + (i+1) + "/5): " + e.getMessage());
|
||||||
if (driver != null) {
|
if (driver != null) {
|
||||||
try { driver.quit(); } catch (Exception ex) {}
|
try { driver.quit(); } catch (Exception ex) {}
|
||||||
driver = null;
|
driver = null;
|
||||||
@@ -40,106 +40,99 @@ public class TrademarkCheckUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
ensureInit();
|
Map<String, Boolean> resultMap = new HashMap<>();
|
||||||
|
int maxRetries = 5;
|
||||||
|
|
||||||
// 构建批量查询脚本(带错误诊断)
|
for (String brand : brands) {
|
||||||
String script = """
|
int retryCount = 0;
|
||||||
const brands = arguments[0];
|
boolean success = false;
|
||||||
const callback = arguments[arguments.length - 1];
|
|
||||||
|
|
||||||
Promise.all(brands.map(brand =>
|
while (retryCount < maxRetries && !success) {
|
||||||
fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch', {
|
try {
|
||||||
method: 'POST',
|
ensureInit();
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: {bool: {must: [{bool: {should: [
|
|
||||||
{match_phrase: {WM: {query: brand, boost: 5}}},
|
|
||||||
{match: {WM: {query: brand, boost: 2}}},
|
|
||||||
{match_phrase: {PM: {query: brand, boost: 2}}}
|
|
||||||
]}}]}},
|
|
||||||
size: 1, _source: ['alive']
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
if (!r.ok) {
|
|
||||||
return {brand, alive: false, error: `HTTP ${r.status}: ${r.statusText}`};
|
|
||||||
}
|
|
||||||
return r.json().then(d => ({
|
|
||||||
brand,
|
|
||||||
alive: d?.hits?.hits?.[0]?.source?.alive || false,
|
|
||||||
error: null
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.catch(e => ({
|
|
||||||
brand,
|
|
||||||
alive: false,
|
|
||||||
error: e.name + ': ' + e.message
|
|
||||||
}))
|
|
||||||
)).then(callback);
|
|
||||||
""";
|
|
||||||
|
|
||||||
try {
|
String script = "const brands = ['" + brand.replace("'", "\\'") + "'];\n" +
|
||||||
@SuppressWarnings("unchecked")
|
"const callback = arguments[arguments.length - 1];\n" +
|
||||||
List<Map<String, Object>> results = (List<Map<String, Object>>)
|
"Promise.all(brands.map(b => \n" +
|
||||||
((JavascriptExecutor) driver).executeAsyncScript(script, brands);
|
" 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);";
|
||||||
|
|
||||||
// 检测是否有网络错误(包括403、Failed to fetch等)
|
@SuppressWarnings("unchecked")
|
||||||
boolean hasNetworkError = results.stream()
|
List<Map<String, Object>> results = (List<Map<String, Object>>)
|
||||||
.anyMatch(item -> {
|
((JavascriptExecutor) driver).executeAsyncScript(script);
|
||||||
|
|
||||||
|
Map<String, Object> item = results.get(0);
|
||||||
String error = (String) item.get("error");
|
String error = (String) item.get("error");
|
||||||
return error != null && (
|
|
||||||
|
if (error != null && (
|
||||||
error.contains("HTTP 403") ||
|
error.contains("HTTP 403") ||
|
||||||
error.contains("Failed to fetch") ||
|
error.contains("Failed to fetch") ||
|
||||||
error.contains("NetworkError") ||
|
error.contains("NetworkError") ||
|
||||||
error.contains("TypeError")
|
error.contains("TypeError") ||
|
||||||
);
|
error.contains("script timeout"))) {
|
||||||
});
|
|
||||||
|
|
||||||
// 如果有网络错误,切换代理并重试
|
System.err.println(brand + " 查询失败(" + (retryCount + 1) + "/" + maxRetries + "): " + error + ",切换代理...");
|
||||||
if (hasNetworkError) {
|
|
||||||
System.err.println("检测到网络错误,切换代理并重试...");
|
|
||||||
|
|
||||||
// 切换代理前保存已查询的品牌
|
// 切换代理
|
||||||
if (alreadyQueried != null && !alreadyQueried.isEmpty()) {
|
try { driver.quit(); } catch (Exception e) {}
|
||||||
try {
|
driver = null;
|
||||||
cacheService.saveResults(alreadyQueried);
|
retryCount++;
|
||||||
System.out.println("代理切换,已保存 " + alreadyQueried.size() + " 个品牌到缓存");
|
continue;
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("保存缓存失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try { driver.quit(); } catch (Exception e) {}
|
// 成功或非网络错误
|
||||||
driver = null;
|
if (error == null) {
|
||||||
ensureInit();
|
Boolean alive = (Boolean) item.get("alive");
|
||||||
|
resultMap.put(brand, alive);
|
||||||
|
System.out.println(brand + " -> " + (alive ? "✓ 已注册" : "✗ 未注册"));
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
System.err.println(brand + " -> [查询失败: " + error + "]");
|
||||||
|
resultMap.put(brand, false); // 失败也记录为未注册
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 重新执行查询
|
} catch (Exception e) {
|
||||||
@SuppressWarnings("unchecked")
|
System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/" + maxRetries + "): " + e.getMessage());
|
||||||
List<Map<String, Object>> retryResults = (List<Map<String, Object>>)
|
try { driver.quit(); } catch (Exception ex) {}
|
||||||
((JavascriptExecutor) driver).executeAsyncScript(script, brands);
|
driver = null;
|
||||||
results = retryResults;
|
retryCount++;
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Boolean> resultMap = new HashMap<>();
|
|
||||||
for (Map<String, Object> item : results) {
|
|
||||||
String brand = (String) item.get("brand");
|
|
||||||
Boolean alive = (Boolean) item.get("alive");
|
|
||||||
String error = (String) item.get("error");
|
|
||||||
|
|
||||||
if (error != null) {
|
|
||||||
// 查询失败,不放入结果,只打印错误
|
|
||||||
System.err.println(brand + " -> [查询失败: " + error + "]");
|
|
||||||
} else {
|
|
||||||
// 查询成功,放入结果
|
|
||||||
resultMap.put(brand, alive);
|
|
||||||
System.out.println(brand + " -> " + (alive ? "✓ 已注册" : "✗ 未注册"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultMap;
|
|
||||||
} catch (Exception e) {
|
if (!success) {
|
||||||
System.err.println("批量查询失败: " + e.getMessage());
|
System.err.println(brand + " -> [查询失败: 已重试" + maxRetries + "次]");
|
||||||
return new HashMap<>();
|
resultMap.put(brand, false); // 失败也记录为未注册
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void closeDriver() {
|
public synchronized void closeDriver() {
|
||||||
|
|||||||
Reference in New Issue
Block a user