6.2 KiB
6.2 KiB
---
【核心判断】
🔴 这是垃圾代码 - 复杂、低效、有隐藏的递归炸弹
---
【流程分析:a.b.c.tttt.cn 每层都没备案会怎样】
假设主域名集合包含 cn,那么:
第1轮:直接查询
查询: a.b.c.tttt.cn
返回: false (未备案)
↓
计算主域名: tttt.cn
生成子域名列表: [b.c.tttt.cn, c.tttt.cn, tttt.cn]
进入第2轮 ↓
第2轮:递归查子域名(第258行的递归陷阱)
查询: [b.c.tttt.cn, c.tttt.cn, tttt.cn]
全部返回: false
↓ 分别处理:
b.c.tttt.cn → 生成 [c.tttt.cn, tttt.cn]
c.tttt.cn → 生成 [tttt.cn]
tttt.cn → 生成 [] (空!递归终止条件)
合并后查询列表: [c.tttt.cn, tttt.cn, tttt.cn] ← 注意重复!
进入第3轮 ↓
第3轮:继续递归
查询: [c.tttt.cn, tttt.cn, tttt.cn] ← tttt.cn被查了2次!
全部返回: false
↓
c.tttt.cn → 生成 [tttt.cn]
tttt.cn → 生成 []
tttt.cn → 生成 []
合并后查询列表: [tttt.cn]
进入第4轮 ↓
第4轮:最后一次
查询: [tttt.cn]
返回: false
↓
tttt.cn → 生成 []
递归结束!
最终结果
icpMap = {
"a.b.c.tttt.cn": "false" // 186行的逻辑会用最后一次子域名查询结果覆盖
}
---
【致命问题】
1️⃣ 隐藏的递归炸弹 (XinAnApiService.java:258)
Map<String, String> domainsICP = getDomainsIcp(subDomains); // 递归!
这他妈是在干什么?
- 方法 getDomainsIcp 调用 getSubDomainIcp
- getSubDomainIcp 又调用回 getDomainsIcp
- 形成递归,但没有任何注释说明!
- 递归深度 = 域名层级数 - 1
对于 a.b.c.d.e.f.g.tttt.cn(8层域名):
- 会递归 7 次
- 总查询次数:1 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 28 次调用信安接口!
- 而且 tttt.cn 会被重复查询 7 次!
2️⃣ 数据结构错了 - 没有去重!
List<String> subDomains = Lists.newArrayList();
subDomainMap.values().forEach(subDomains::addAll); // 256-257行:直接addAll,没去重!
tttt.cn 会在 subDomains 列表中出现多次,每次都要查询信安接口。
"Bad programmers worry about the code. Good programmers worry about data structures."
- 你需要的是 Set<String>,而不是 List<String>
- 或者在查询前去重:subDomains.stream().distinct().collect(Collectors.toList())
3️⃣ 复杂度爆炸 - 3层缩进是底线,这代码有多少层?
看看第147-158行的嵌套:
for (Map.Entry<String, String> entry : icpMap.entrySet()) { // 第1层
if (StringUtils.isEmpty(icp) || FALSE_ICP.getValue().equals(icp)) { // 第2层
if (CollectionUtils.isEmpty(subDomains)) { // 第3层
continue;
}
}
}
还有异常处理里又重复了一遍逻辑(159-170行)—— 这是什么鬼设计?
4️⃣ getSubDomain 的诡异逻辑 (XinAnApiService.java:358-367)
temp.forEach(var ->
subDomains.addFirst(StringUtils.join(Arrays.asList(var, subDomains.getFirst()), SEPARATOR2))
);
subDomains.remove(subDomains.getFirst()); // 为什么要先加再删?
为什么不一开始就不加原域名?
- 这是在解决不存在的问题
- 应该直接构建正确的列表,而不是"先加后删"
---
【Linus式重构方案】
核心思想:消除递归,用迭代 + 去重
public Map<String, String> getDomainsIcp(List<String> domains) {
if (CollectionUtils.isEmpty(domains)) {
return Maps.newHashMap();
}
// 第1步:收集所有需要查询的域名(包括子域名),用 Set 去重
Set<String> allDomainsToQuery = new LinkedHashSet<>();
Map<String, List<String>> domainHierarchy = new HashMap<>();
for (String domain : domains) {
allDomainsToQuery.add(domain);
String mainDomain = getDomainParam(domain);
List<String> subDomains = getSubDomain(mainDomain, domain);
allDomainsToQuery.addAll(subDomains); // Set自动去重
domainHierarchy.put(domain, subDomains);
}
// 第2步:一次性查询所有域名(去重后)
Map<String, String> allIcpResults = queryDomainsIcpByGroup(new ArrayList<>(allDomainsToQuery));
// 第3步:为每个原始域名找最近的有备案的父域名
Map<String, String> result = new HashMap<>();
for (String domain : domains) {
String icp = allIcpResults.get(domain);
// 如果自己有备案,直接用
if (icp != null && !icp.equals(FALSE_ICP.getValue()) && !icp.isEmpty()) {
result.put(domain, icp);
continue;
}
// 否则从子域名列表中找第一个有备案的
List<String> subDomains = domainHierarchy.get(domain);
boolean found = false;
if (subDomains != null) {
for (String subDomain : subDomains) {
String subIcp = allIcpResults.get(subDomain);
if (subIcp != null && !subIcp.equals(FALSE_ICP.getValue()) && !subIcp.isEmpty()) {
result.put(domain, subIcp);
found = true;
break;
}
}
}
if (!found) {
result.put(domain, icp != null ? icp : "");
}
}
return result;
}
改进效果:
| 指标 | 原代码 | 重构后 |
|--------------|---------------|-----------|
| 递归次数 | N层 | 0 |
| 接口调用次数(8层域名) | 28次 | 8次(去重后) |
| 重复查询 | 是 | 否 |
| 代码行数 | ~80行(分散在3个方法) | ~40行(单方法) |
| 可读性 | 需要追踪递归 | 一目了然 |
---
【最后警告】
这段代码有三个经典反模式:
1. "聪明"代码 - 递归隐藏在方法调用里,不看完整代码根本发现不了
2. 重复逻辑 - 异常处理块里复制粘贴了主逻辑
3. 过度抽象 - getSubDomain 方法可以简化成3行,但写成了10行
"Talk is cheap. Show me the code." - 现在代码给你了,你看着办吧。
需要我帮你重构吗?还是你有其他问题?