``` 1. 精准域名 - www.example.com 必须完全匹配 2. 冲突域名 - 多个企业可能配置同一个域名(联通/电信/移动各有一套) 3. 泛域名 - *.example.com 匹配所有子域名 "数据结构是什么?" 这才是关键: 【输入】 domains: [www.a.com, b.b.com, sub.c.com] // 用户请求的域名列表 absentDomain: [www.a.com, b.b.com] // Redis缓存未命中的域名 cacheDomain: {sub.c.com -> 域名信息PO} // Redis缓存命中的域名 【输出】 Map<域名, 平面JSON> = { "www.a.com" -> "[1,2,3]", // 该域名可以下发到平面1,2,3 "b.b.com" -> "[1,2,5]", "sub.c.com" -> "[1,2,3,4,5,6,7,8]" } --- 【完整流程拆解】 阶段1: 初始化 (行993-998) Map preciseDomainMap = new HashMap<>(); if (!MapUtils.isEmpty(cacheDomain)) { preciseDomainMap.putAll(cacheDomain); // 把Redis缓存的域名先放进来 } 目的: 构建一个"精准域名查找表",包含缓存+DB查询的完整域名信息。 --- 阶段2: 处理未缓存的精准域名 (行1000-1035) 这是整段代码最复杂的部分,因为要处理冲突域名。 2.1 数据库查询 List preciseDomains = selfServiceDomainConfigDao.findDomainInfoByListDomain(absentDomain, enterpriseId); SQL伪代码: SELECT * FROM domain_config WHERE domain IN ('www.a.com', 'b.b.com') AND tenant_id = '企业123' 重要: 这里查询会返回两种情况的数据: 4. domain字段就是真实域名: {domain: 'www.a.com', cpDomain: null} 5. cpDomain字段是真实域名: {domain: 'www.a.com_conflict', cpDomain: 'www.a.com'} --- 2.2 构建两个Map (行1009-1014) - 关键设计 // Map1: 以domain字段为key Map realDomainMap = { "www.a.com" -> Info{domain='www.a.com', cpDomain=null, ...} "b.b.com_conflict" -> Info{domain='b.b.com_conflict', cpDomain='b.b.com', ...} } // Map2: 以cpDomain字段为key (只包含冲突域名) Map cpDomainMap = { "b.b.com" -> Info{domain='b.b.com_conflict', cpDomain='b.b.com', ...} } 为什么需要两个Map? 因为用户请求的是真实域名b.b.com,但数据库存储的可能是b.b.com_conflict! --- 2.3 遍历处理每个域名 (行1017-1034) - 双重匹配逻辑 for (String domain : absentDomain) { // domain = "b.b.com" DomainEnterpriseInfoRedisPO redisPO = null; // 第一次尝试: 直接匹配domain字段 DomainEnterpriseInfoTmp tmpInfo = realDomainMap.get(domain); if (tmpInfo != null) { redisPO = convert(tmpInfo); } else { // 第二次尝试: 从冲突域名Map中匹配cpDomain字段 tmpInfo = cpDomainMap.get(domain); // 用b.b.com查cpDomain if (tmpInfo != null) { redisPO = convert(tmpInfo); redisPO.setDomain(domain); // 🔴 关键:覆盖domain为真实域名 } } if (redisPO != null) { preciseDomainMap.put(domain, redisPO); redis.set(cacheKey, redisPO, 1800秒); // 写回缓存 } } 举例说明冲突域名处理: 数据库记录: ┌─────────────────────┬──────────────┬────────────┐ │ domain │ cpDomain │ tenantId │ ├─────────────────────┼──────────────┼────────────┤ │ www.a.com │ NULL │ 企业123 │ // 普通域名 │ b.b.com_unicom │ b.b.com │ 企业123 │ // 冲突域名(联通) │ b.b.com_telecom │ b.b.com │ 企业456 │ // 冲突域名(电信) └─────────────────────┴──────────────┴────────────┘ 用户请求: ["www.a.com", "b.b.com"] 处理流程: 6. www.a.com → realDomainMap.get("www.a.com") ✅ 直接命中 7. b.b.com → realDomainMap.get("b.b.com") ❌ 未命中 → cpDomainMap.get("b.b.com") ✅ 命中 b.b.com_unicom → 覆盖domain字段为 "b.b.com" → Redis缓存key用真实域名 "b.b.com" 设计意图: - 数据库用domain_conflict存储避免主键冲突 - 但对用户透明,用户永远使用真实域名 - 通过企业ID区分不同企业的同名域名 --- 阶段3: 处理广义域名(泛域名) (行1037-1061) Map extensiveDomainMap = new HashMap<>(); for (String domain : domains) { // 遍历所有域名 DomainEnterpriseInfoRedisPO domainInfo = preciseDomainMap.get(domain); if (domainInfo == null) { // 精准域名没找到 // 延迟加载:只在第一次需要时才查询广义域名 if (extensiveDomainMap.isEmpty()) { List extensiveDomains = service.findAllExtensiveDomain(); extensiveDomainMap = toMap(extensiveDomains); // 可能包含: {"*.example.com", "*.test.com"} } // 泛域名匹配 String matchedDomain = matchExtensiveDomain(domain, extensiveDomainMap.keySet()); // 例: domain="sub.example.com" → 匹配到 "*.example.com" if (StringUtils.isBlank(matchedDomain)) { throw new PlatformException("未查询到域名 " + domain); } domainInfo = extensiveDomainMap.get(matchedDomain); // 使用泛域名的配置 redis.set(cacheKey, domainInfo, 1800秒); // 也缓存到Redis } // ... 后续校验 } 泛域名匹配算法 (CdniContextVO.matchExtensiveDomain) public static String matchExtensiveDomain(String domain, Collection extensiveDomains) { // domain = "sub.example.com" // extensiveDomains = ["*.example.com", "*.test.com"] int index = domain.indexOf('.'); // index = 3 if (index != -1) { String match = "*" + domain.substring(index); // match = "*.example.com" for (String s : extensiveDomains) { if (match.equals(s)) { return s; // 返回 "*.example.com" } } } return null; } 匹配逻辑: sub.example.com → *.example.com ✅ a.b.example.com → *.b.example.com (如果存在) ✅ → 否则匹配失败 ❌ 注意: 只匹配第一级子域名! 延迟加载优化: if (extensiveDomainMap.isEmpty()) { // 只在第一次遇到泛域名需求时才查询 // 如果所有域名都是精准域名,这个查询就省了 } --- 阶段4: 校验域名状态和企业归属 (行1063-1074) // 4.1 状态校验 if (!DomainTicketStateEnum.judgeDomainState(domainInfo.getDomainState())) { throw new PlatformException("域名状态无效"); } // 允许的状态: 已生效、部署中、部署失败、启用中 // 4.2 企业归属校验 if (!enterpriseId.equals(domainInfo.getTenantId())) { throw new PlatformException("域名不存在"); // 实际是权限不足 } // 4.3 提取平面信息 result.put(domain, domainInfo.getDomainPlain()); // domainPlain 是JSON字符串: "[1,2,3,4,5]" --- 【完整数据流示例】 假设场景: 用户请求: domains = ["www.a.com", "sub.b.com", "c.example.com"] enterpriseId = "企业123" Redis缓存: 命中: {"www.a.com" -> Info{...}} 未命中: ["sub.b.com", "c.example.com"] 数据库 domain_config: ┌──────────────────┬────────────┬──────────────┬──────────────┐ │ domain │ cpDomain │ tenantId │ domainPlain │ ├──────────────────┼────────────┼──────────────┼──────────────┤ │ www.a.com │ NULL │ 企业123 │ [1,2,3] │ │ sub.b.com │ NULL │ 企业123 │ [1,2,5] │ │ *.example.com │ NULL │ 企业123 │ [1,2,3,4,5] │ └──────────────────┴────────────┴──────────────┴──────────────┘ 执行流程: ┌─ 阶段1: 初始化 ─────────────────────────────────┐ │ preciseDomainMap = {"www.a.com" -> Info{...}} │ // 来自Redis └──────────────────────────────────────────────┘ ┌─ 阶段2: 查询DB ──────────────────────────────────┐ │ absentDomain = ["sub.b.com", "c.example.com"] │ │ ↓ DB查询 (精准域名) │ │ 结果: [Info{domain='sub.b.com', ...}] │ │ c.example.com 未找到 │ │ ↓ 处理 │ │ preciseDomainMap.put("sub.b.com", ...) │ │ Redis.set("sub.b.com", ..., 1800秒) │ └──────────────────────────────────────────────┘ ┌─ 阶段3: 泛域名匹配 ────────────────────────────────┐ │ 遍历: ["www.a.com", "sub.b.com", "c.example.com"]│ │ │ │ www.a.com → preciseDomainMap.get() ✅ │ │ sub.b.com → preciseDomainMap.get() ✅ │ │ c.example.com → preciseDomainMap.get() ❌ │ │ ↓ 触发广义域名查询 │ │ extensiveDomainMap = {"*.example.com"│-> Info} │ ↓ 匹配 │ │ matchExtensiveDomain("c.example.com")│ │ → "*.example.com" ✅ │ │ ↓ 使用泛域名配置 │ │ domainInfo = Info{domain='*.example.com', plain='[1,2,3,4,5]'} └──────────────────────────────────────────────┘ ┌─ 阶段4: 校验 + 输出 ─────────────────────────────┐ │ result = { │ │ "www.a.com" -> "[1,2,3]", │ │ "sub.b.com" -> "[1,2,5]", │ │ "c.example.com" -> "[1,2,3,4,5]" // 泛域名的平面│ │ } │ └──────────────────────────────────────────────┘ --- 【代码问题分析】 🔴 严重问题 1. 冲突域名处理的设计缺陷 // 行1026 redisPO.setDomain(domain); // 直接修改对象 这个操作污染了数据!如果tmpInfo被其他地方引用,也会被修改。应该: redisPO = tmpInfo.cloneAndSetDomain(domain); // 创建新对象 2. 广义域名查询性能问题 // 行1045 List extensiveDomains = service.findAllExtensiveDomain(); // 查询全量泛域名!如果有1000个泛域名,全部加载到内存 问题: - 每次处理任务时,只要有一个域名需要泛域名匹配,就查询全部 - 没有缓存到Redis(只缓存匹配结果) - 如果泛域名数量巨大,这是性能炸弹 改进: // 泛域名应该缓存到Redis或本地内存 private static final Cache> EXTENSIVE_DOMAIN_CACHE = CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .build(); 3. 缓存一致性问题 // 行1032、1060 redisUtil.setJSONString(key, redisPO, 1800L); // 30分钟 问题场景: 4. 域名状态从"已生效"变更为"已下线" 5. Redis缓存还有30分钟才过期 6. 用户仍然可以操作已下线的域名! 解决方案: - 域名状态变更时主动清除Redis缓存(事件总线) - 或者缩短TTL到5分钟 4. 重复查询问题 // 行1002 List preciseDomains = dao.findDomainInfoByListDomain(absentDomain, enterpriseId); // 如果absentDomain = ["a.com", "b.com", "c.com"] // 而数据库只有 a.com 和 b.com // c.com 不存在,直接抛异常 (行1005) 问题: 无法区分"域名不存在"还是"需要泛域名匹配"。 正确流程: 5. 查询精准域名,允许部分未找到 6. 未找到的再尝试泛域名匹配 7. 最后仍未找到才抛异常 但当前代码: - 如果查询结果为空,直接抛异常 - 导致泛域名逻辑(行1037)永远无法执行! 等等,我看错了吗?让我重新看代码... // 行1002 List preciseDomains = dao.findDomainInfoByListDomain(absentDomain, enterpriseId); if (CollectionUtils.isEmpty(preciseDomains)) { log.error("域名不存在: {}", absentDomain); throw new PlatformException("未查询到域名信息"); // 🔴 这里直接抛异常了! } 妈的,这是个BUG! 如果absentDomain中全是泛域名的子域名(比如sub.example.com),数据库查不到精准记录,会直接抛异常,永远走不到泛域名匹配逻辑(行1037)! 正确逻辑应该是: if (!CollectionUtils.isEmpty(absentDomain)) { List preciseDomains = dao.findDomainInfoByListDomain(absentDomain, enterpriseId); // 允许查询结果为空,部分域名可能是泛域名 if (!CollectionUtils.isEmpty(preciseDomains)) { // 处理查到的精准域名 // ... } // 未查到的域名留给后面的泛域名匹配 } --- 🟡 中等问题 5. 方法命名误导 private Map checkValidDomainRefresh(...) 方法名叫checkValidDomainRefresh,但实际上: - 不仅校验,还查询、转换、缓存 - 不仅适用于Refresh,INSERT/DISTRIBUTE也用这个方法 应该叫: private Map resolveDomainPlainsWithValidation(...) 6. 日志不足 缺少关键日志: - 冲突域名处理的日志 - 泛域名匹配的日志 - Redis缓存命中率日志 --- 【Linus式总结】 核心判断 这段代码在解决真实问题吗? → ✅ 是的 - CDN系统确实需要支持精准域名+泛域名+多租户隔离 - 缓存策略也是必要的(减少DB压力) 数据结构设计合理吗? → ⚠️ 凑合 - 用两个Map处理冲突域名,思路正确但实现有bug - 延迟加载广义域名是好的优化 有特殊情况需要消除吗? → 🔴 有! - 冲突域名的特殊处理应该封装成独立方法 - 精准域名和泛域名的处理应该统一 --- 改进方案 如果让我重写这段代码: private Map resolveDomainPlains( Set domains, String enterpriseId, Map cached) { DomainResolver resolver = DomainResolver.builder() .withCache(cached) .withEnterpriseId(enterpriseId) .build(); Map result = new HashMap<>(); for (String domain : domains) { DomainInfo info = resolver.resolve(domain); // 内部统一处理: 缓存查询 → 精准域名 → 冲突域名 → 泛域名 info.validateState(); info.validateOwnership(enterpriseId); result.put(domain, info.getPlains()); } return result; } 消除特殊情况,用统一的Resolver模式。 --- 最终评价 品味评分: 🟡 凑合 问题: 1. 有个严重BUG(行1003-1005的异常处理) 2. 缓存一致性风险 3. 泛域名查询性能隐患 优点: 4. 延迟加载广义域名是个好主意 5. 冲突域名处理思路正确 "这代码能用,但有定时炸弹。那个精准域名查询为空就抛异常的逻辑,需要立即修复。" --- 现在清楚这段域名校验逻辑了吗?有哪里不明白的? ```