197 lines
6.2 KiB
Markdown
197 lines
6.2 KiB
Markdown
```
|
||
|
||
|
||
---
|
||
【核心判断】
|
||
|
||
🔴 这是垃圾代码 - 复杂、低效、有隐藏的递归炸弹
|
||
|
||
---
|
||
【流程分析: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." - 现在代码给你了,你看着办吧。
|
||
|
||
需要我帮你重构吗?还是你有其他问题?
|
||
```
|