Initial commit
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
```
|
||||
接收API请求,提交内容管理任务(刷新/预热/分发)到配管系统。
|
||||
|
||||
【输入】ContentManageRequestParam
|
||||
├─ contentURLs: List<UrlMd5> // URL列表(可能包含MD5)
|
||||
├─ dirs: List<String> // 目录列表(目录刷新用)
|
||||
├─ callBackUrl: String // 回调地址
|
||||
└─ enterprise: String // 企业ID
|
||||
|
||||
【阶段1】verifyParam (行1094-1137)
|
||||
↓ 校验URL数量、合法性、企业额度
|
||||
↓ 从Redis批量获取域名缓存信息
|
||||
└─ 输出: Tuple4<URL列表, URL→域名映射, 域名集合, 缓存域名信息>
|
||||
|
||||
【阶段2】checkValidDomainRefresh (行985-1078)
|
||||
↓ 校验域名归属、状态、平面信息
|
||||
↓ 精准域名(数据库) + 广义域名(泛域名匹配) + 冲突域名处理
|
||||
↓ 缓存miss时查DB,写回Redis(TTL 1800秒)
|
||||
└─ 输出: Map<域名, 平面JSON字符串>
|
||||
|
||||
【阶段3】buildContentTask (行856-930)
|
||||
↓ 查询企业信息(cpId, 创建者, 省份)
|
||||
↓ 构建BPM任务列表(每个URL一个task)
|
||||
↓ 如果有回调URL,构建回调任务PO
|
||||
└─ 输出: Tuple3<BPM请求, 回调任务PO, 回调URL列表PO>
|
||||
|
||||
【阶段4】sendContentTask (行787-848)
|
||||
↓ 根据操作类型调用BPM接口:
|
||||
│ INSERT → contentReload (预热)
|
||||
│ DISTRIBUTE → contentDistribution (分发)
|
||||
│ 其他 → contentRefresh (刷新)
|
||||
↓ 成功后:
|
||||
│ 1. 扣减企业额度
|
||||
│ 2. 保存回调任务到DB (content_customize_task)
|
||||
│ 3. 保存回调URL明细到DB (content_customize_url)
|
||||
└─ 输出: taskId
|
||||
|
||||
---
|
||||
三、核心数据表结构
|
||||
|
||||
1. content_customize_task (回调任务主表)
|
||||
|
||||
关键字段:
|
||||
- id 主键
|
||||
- cpId 企业cpId (纯数字)
|
||||
- taskId 任务ID (UUID)
|
||||
- callBackUrl 回调地址
|
||||
- operationType 操作类型枚举
|
||||
- state 任务状态 (0=待回调)
|
||||
- callBack 回调状态 (0=未回调, 1=已回调)
|
||||
- callBackSeparate 是否单独回调
|
||||
|
||||
用途: 记录需要异步回调的任务,供定时任务扫描并回调客户接口。
|
||||
|
||||
2. content_customize_url (回调URL明细表)
|
||||
|
||||
关键字段:
|
||||
- id 主键
|
||||
- taskId 任务ID (外键关联task表)
|
||||
- url 具体URL
|
||||
- operateType 操作类型 (0=预热,1=刷新,3=分发,4=目录刷新)
|
||||
- urlIndex URL序号
|
||||
- issuePlainInfo 平面信息JSON (List<Integer>)
|
||||
- status URL执行状态
|
||||
- nodeTotal 节点总数
|
||||
- nodeSuccessCount 成功节点数
|
||||
|
||||
用途: 记录每个URL的执行详情,回调时提供细粒度状态。
|
||||
|
||||
---
|
||||
四、校验规则详解
|
||||
|
||||
1. URL数量校验 (verifyParam)
|
||||
|
||||
普通操作(刷新/预热/分发): 单次最大 maxDir(配置,默认2000)
|
||||
目录刷新: 单次最大 maxDirLength(配置,默认100)
|
||||
|
||||
2. URL合法性校验 (checkURL方法,未展示)
|
||||
|
||||
- URL格式验证
|
||||
- 协议检查(http/https)
|
||||
- 域名提取
|
||||
|
||||
3. 企业额度校验 (enterpriseContentNumberMaxNum)
|
||||
|
||||
每日默认额度:
|
||||
- 普通操作: 2000次 (Constants.CONTENT_MAXOPERATION_DAY)
|
||||
- 目录刷新: 100次 (Constants.CONTENT_MAXDIRS_DAY)
|
||||
校验逻辑: 已用额度 + 本次请求数量 <= 企业配额
|
||||
|
||||
4. 域名归属校验 (checkValidDomainRefresh:1069-1072)
|
||||
|
||||
if (!enterpriseId.equals(domainInfo.getTenantId())) {
|
||||
throw new PlatformException("域名:" + domain + "不存在");
|
||||
}
|
||||
关键: 防止企业操作其他企业的域名。
|
||||
|
||||
5. 域名状态校验 (checkValidDomainRefresh:1064-1067)
|
||||
|
||||
允许的域名状态:
|
||||
- 已生效
|
||||
- 部署中
|
||||
- 部署失败
|
||||
- 启用中
|
||||
|
||||
校验方法: DomainTicketStateEnum.judgeDomainState(domainInfo.getDomainState())
|
||||
|
||||
6. 域名类型处理
|
||||
|
||||
- 精准域名: 完全匹配 (http://www.example.com)
|
||||
- 广义域名: 泛域名匹配 (*.example.com)
|
||||
- 冲突域名: 多企业共用域名,通过cpDomain字段区分真实域名
|
||||
|
||||
---
|
||||
五、关键业务逻辑
|
||||
|
||||
1. 域名缓存策略 (行1118-1133)
|
||||
|
||||
查询顺序:
|
||||
Redis批量查询 → 缓存命中直接用
|
||||
→ 缓存miss查DB → 写回Redis(TTL 30分钟)
|
||||
|
||||
广义域名永远查内存(不缓存到Redis的key)
|
||||
|
||||
问题: 广义域名每次都查全量,如果广义域名数量巨大会有性能问题。
|
||||
|
||||
2. 平面信息处理
|
||||
|
||||
平面 = CDN节点分组概念 (可能是地域/运营商等维度)
|
||||
|
||||
domainPlain.get(urlDomainMap.get(url)) // JSON字符串 "[1,2,3]"
|
||||
↓ 解析
|
||||
List<Integer> plains = JSONArray.parseArray(...) // [1,2,3]
|
||||
|
||||
每个URL会下发到对应的平面节点。
|
||||
|
||||
3. 操作类型路由 (handleContentTask:819-829)
|
||||
|
||||
switch (operationType) {
|
||||
case INSERT: → bpm.contentReload() // 预热
|
||||
case DISTRIBUTE: → bpm.contentDistribution() // 分发
|
||||
default: → bpm.contentRefresh() // 刷新/其他
|
||||
}
|
||||
|
||||
注意: DIRUPDATE(目录刷新)走的是default分支,即contentRefresh()。
|
||||
|
||||
---
|
||||
六、异常处理机制
|
||||
|
||||
任何阶段失败 → 抛出PlatformException → 整个任务失败
|
||||
→ 不会保存任何DB记录
|
||||
→ 不会扣减企业额度
|
||||
|
||||
成功下发BPM后 → 立即扣减额度 + 保存回调任务
|
||||
→ 如果回调任务保存失败?代码没处理这种情况!(bug风险)
|
||||
|
||||
---
|
||||
七、代码问题清单
|
||||
|
||||
🔴 严重问题
|
||||
|
||||
4. Tuple滥用: Tuple4/Tuple3完全不可读,应该用命名类
|
||||
5. 重复遍历:
|
||||
- verifyParam遍历urlHostList构建urlHostMap
|
||||
- buildContentTask又遍历contentUrlList
|
||||
- 同样的数据被多次转换
|
||||
6. 异常处理不完整:
|
||||
|
||||
// 行838-848
|
||||
postSuccessActions(...) {
|
||||
updateEnterpriseContentNumber(...); // 如果这里失败?
|
||||
contentCustomizeTaskDao.save(...); // 如果这里失败?
|
||||
contentCustomizeUrlDao.saveAll(...); // 如果这里失败?
|
||||
}
|
||||
这三个操作应该在一个事务里!
|
||||
|
||||
7. 缓存一致性风险:
|
||||
- Redis缓存30分钟
|
||||
- 如果DB中域名状态变更,缓存不会主动失效
|
||||
- 可能导致已下线域名仍能操作
|
||||
|
||||
🟡 中等问题
|
||||
|
||||
8. 日志信息不足:
|
||||
- 缺少域名校验失败的详细日志
|
||||
- 缺少企业额度扣减的审计日志
|
||||
9. 魔法数字:
|
||||
- Redis TTL硬编码1800L
|
||||
- 应该定义常量
|
||||
10. 空指针风险:
|
||||
// 行884
|
||||
user = enterpriseInfo.getAccounts().get(0); // 如果accounts为空?
|
||||
|
||||
🟢 小问题
|
||||
|
||||
11. 变量命名混乱:
|
||||
- urlPlain 实际是 domainPlainMap
|
||||
- urlHost 实际是 validationResult
|
||||
12. 注释质量差:
|
||||
- "校验url数量、url合法性..." 这不是废话吗?
|
||||
|
||||
---
|
||||
八、数据依赖关系
|
||||
|
||||
EnterpriseInfo (企业表)
|
||||
└─ cpId, accounts, 额度信息
|
||||
|
||||
SelfServiceDomainConfig (域名配置表)
|
||||
├─ domain (域名)
|
||||
├─ cpDomain (冲突域名真实值)
|
||||
├─ tenantId (所属企业)
|
||||
├─ domainState (域名状态)
|
||||
└─ domainPlain (平面信息JSON)
|
||||
|
||||
Redis缓存
|
||||
key: content:domain:{domain}:{enterpriseId}
|
||||
value: DomainEnterpriseInfoRedisPO
|
||||
ttl: 1800秒
|
||||
```
|
||||
@@ -0,0 +1,440 @@
|
||||
```
|
||||
|
||||
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<String, DomainEnterpriseInfoRedisPO> preciseDomainMap = new HashMap<>();
|
||||
if (!MapUtils.isEmpty(cacheDomain)) {
|
||||
preciseDomainMap.putAll(cacheDomain); // 把Redis缓存的域名先放进来
|
||||
}
|
||||
|
||||
目的: 构建一个"精准域名查找表",包含缓存+DB查询的完整域名信息。
|
||||
|
||||
---
|
||||
阶段2: 处理未缓存的精准域名 (行1000-1035)
|
||||
|
||||
这是整段代码最复杂的部分,因为要处理冲突域名。
|
||||
|
||||
2.1 数据库查询
|
||||
|
||||
List<DomainEnterpriseInfoTmp> 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<String, Info> 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<String, Info> 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<String, DomainEnterpriseInfoRedisPO> extensiveDomainMap = new HashMap<>();
|
||||
|
||||
for (String domain : domains) { // 遍历所有域名
|
||||
DomainEnterpriseInfoRedisPO domainInfo = preciseDomainMap.get(domain);
|
||||
|
||||
if (domainInfo == null) { // 精准域名没找到
|
||||
// 延迟加载:只在第一次需要时才查询广义域名
|
||||
if (extensiveDomainMap.isEmpty()) {
|
||||
List<Info> 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<String> 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<Info> extensiveDomains = service.findAllExtensiveDomain();
|
||||
// 查询全量泛域名!如果有1000个泛域名,全部加载到内存
|
||||
|
||||
问题:
|
||||
- 每次处理任务时,只要有一个域名需要泛域名匹配,就查询全部
|
||||
- 没有缓存到Redis(只缓存匹配结果)
|
||||
- 如果泛域名数量巨大,这是性能炸弹
|
||||
|
||||
改进:
|
||||
// 泛域名应该缓存到Redis或本地内存
|
||||
private static final Cache<String, List<Info>> 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<Info> 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<Info> 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<Info> preciseDomains = dao.findDomainInfoByListDomain(absentDomain, enterpriseId);
|
||||
// 允许查询结果为空,部分域名可能是泛域名
|
||||
if (!CollectionUtils.isEmpty(preciseDomains)) {
|
||||
// 处理查到的精准域名
|
||||
// ...
|
||||
}
|
||||
// 未查到的域名留给后面的泛域名匹配
|
||||
}
|
||||
|
||||
---
|
||||
🟡 中等问题
|
||||
|
||||
5. 方法命名误导
|
||||
|
||||
private Map<String, String> checkValidDomainRefresh(...)
|
||||
|
||||
方法名叫checkValidDomainRefresh,但实际上:
|
||||
- 不仅校验,还查询、转换、缓存
|
||||
- 不仅适用于Refresh,INSERT/DISTRIBUTE也用这个方法
|
||||
|
||||
应该叫:
|
||||
private Map<String, String> resolveDomainPlainsWithValidation(...)
|
||||
|
||||
6. 日志不足
|
||||
|
||||
缺少关键日志:
|
||||
- 冲突域名处理的日志
|
||||
- 泛域名匹配的日志
|
||||
- Redis缓存命中率日志
|
||||
|
||||
---
|
||||
【Linus式总结】
|
||||
|
||||
核心判断
|
||||
|
||||
这段代码在解决真实问题吗? → ✅ 是的
|
||||
- CDN系统确实需要支持精准域名+泛域名+多租户隔离
|
||||
- 缓存策略也是必要的(减少DB压力)
|
||||
|
||||
数据结构设计合理吗? → ⚠️ 凑合
|
||||
- 用两个Map处理冲突域名,思路正确但实现有bug
|
||||
- 延迟加载广义域名是好的优化
|
||||
|
||||
有特殊情况需要消除吗? → 🔴 有!
|
||||
- 冲突域名的特殊处理应该封装成独立方法
|
||||
- 精准域名和泛域名的处理应该统一
|
||||
|
||||
---
|
||||
改进方案
|
||||
|
||||
如果让我重写这段代码:
|
||||
|
||||
private Map<String, String> resolveDomainPlains(
|
||||
Set<String> domains,
|
||||
String enterpriseId,
|
||||
Map<String, DomainInfo> cached) {
|
||||
|
||||
DomainResolver resolver = DomainResolver.builder()
|
||||
.withCache(cached)
|
||||
.withEnterpriseId(enterpriseId)
|
||||
.build();
|
||||
|
||||
Map<String, String> 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. 冲突域名处理思路正确
|
||||
|
||||
"这代码能用,但有定时炸弹。那个精准域名查询为空就抛异常的逻辑,需要立即修复。"
|
||||
|
||||
---
|
||||
现在清楚这段域名校验逻辑了吗?有哪里不明白的?
|
||||
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
# 2.1 跨客户订购迁移流程自动化(涉及子系统与CPID字段解耦)
|
||||
|
||||
## 需求评审
|
||||
|
||||
NRWLXT-30203
|
||||
|
||||
**功能说明:** 针对不同 cpid 的冲突域名,预热刷新及 URL 封禁, 我们把 A 转为 A^,底层回调给我们的也按照 A^回调,展示给客户的结果按照 A 展示。
|
||||
|
||||
**需求来源:** 集团-高旭、韩齐雅
|
||||
|
||||
**需求背景:**
|
||||
|
||||
政企侧 2025 年订购迁移需求呈现出密集、不定期态势,主要为从【总部政企】迁移至【省公司政企】需求。
|
||||
|
||||
政企侧要求订购迁移时,保障业务服务不中断、计费数据不受影响(一般需要某月 1 号 00:00 分新订购生效计费,前一日 23:59 分旧订购中止)。
|
||||
|
||||
由于订购迁移主要为【订购ID】和【CPID】变更,当前各系统和平面与 CPID 字段耦合严重,导致订购迁移工作组织消耗大量人力。
|
||||
|
||||
当前不同场景订购迁移各系统需配合的工作,主要涉及:IBS、CRS、集中配管、自研平面、中兴平面、华为平面,以及客户侧配合变更 AKSK 等。
|
||||
|
||||
**具体描述:**
|
||||
|
||||
1、预热刷新:针对冲突域名,不同 CPID 时,客户下发预热刷新任务时(接口、页面),需将URL中的原始域名修改为加后缀的冲突域名给下游系统。查询任务时,将待后缀的冲突域名转换回原始域名给客户展示。
|
||||
|
||||
2、[CDN运营平台接口规范-客户版-20250519](https://www.kdocs.cn/office/w/cnS6vYEirmwO?sub_file_id=AEITSOBCADQHU&attachment_store_type=upload_ks3&disablePlugins&readonly) 涉及 4 内容管理接口、8.1 解封禁接口、8.2 URL 封禁/解封任务查询接口。
|
||||
|
||||
3、相同 cpid 下的不做任何逻辑处理,子账号做同步处理。
|
||||
|
||||
4、场景说明
|
||||
|
||||
(1)A:冲突a 展示原始a
|
||||
|
||||
(2)A:冲突a+冲突2a 展示冲突a+冲突2a
|
||||
|
||||
(3)A:冲突a+冲突b 展示原始a +原始b
|
||||
|
||||
(4)A:原始a+冲突b 展示原始a +原始b
|
||||
|
||||
(5)A:原始a+冲突a 展示原始a+冲突a
|
||||
|
||||
## 需求备注
|
||||
|
||||
老接口:com.cmcc.cdn.api.service.impl.ContentApiServiceImpl#submitContentTasksFromApi
|
||||
|
||||
正哥写的新接口:
|
||||
|
||||
com.cmcc.cdn.api.service.impl.ContentApiServiceImpl#submitContentTasksFromApiNew
|
||||
|
||||
## 需求开发
|
||||
|
||||
```
|
||||
/**
|
||||
* 根据企业ID集合查询活跃域名信息
|
||||
* 返回包含企业ID和域名的投影对象列表
|
||||
*/
|
||||
@Query("select s.tenantId as tenantId, s.domain as domain from SelfServiceDomainConfigPO s "
|
||||
+ "where s.tenantId in :tenantIds and s.deleted = false and s.state in :states")
|
||||
List<TenantDomainProjection> findActiveDomainsByTenantIds(@Param("tenantIds") Collection<String> tenantIds,
|
||||
@Param("states") Collection<DomainTicketStateEnum> states);
|
||||
```
|
||||
|
||||
1、 客户同时拥有冲突域名和真实域名,当在缓存失效内,删除了真实域名,这时候处理冲突域名的时候会存在获取异常。
|
||||
|
||||
2、 4.5 支持域名和 URL 维度查询。历史没有校验直接给配管。现在因为要做冲突域名转换。如果没有查到域名信息和 rul 中的 hsot 信息,我们要进行异常抛出吗?
|
||||
|
||||
URL 解封禁途径:
|
||||
|
||||
```
|
||||
deny_allow_task 库表存储
|
||||
|
||||
|
||||
更新(网宿URL封禁/解禁回调入口):/v1/url/ban/callback
|
||||
新增(URL封禁/解禁,网速封禁):/api/normal/url/ban
|
||||
|
||||
新增
|
||||
/action?commandType=urlbanorunban
|
||||
/api/toutiao/url/ban
|
||||
/api/url/ban(8.1)
|
||||
|
||||
|
||||
更新:/actionpm?commandType=pipeDomainCallBack
|
||||
/v1/pipe/config/callback
|
||||
```
|
||||
|
||||
# 用例评审
|
||||
|
||||
# 冒烟自测
|
||||
|
||||
## API 接口
|
||||
|
||||
**URL预热**
|
||||
|
||||
```
|
||||
https://apppkg-p02-bg-drcn.hihonorcdn.com/diff/com.zhongyuedu.zhongyuzhongyi_183_19_186_0_e6f007_3d1930_1761718044875.diff
|
||||
```
|
||||
|
||||
**内容刷新**
|
||||
|
||||
```
|
||||
https://contentplatform-drcn.hihonorcdn.com.wskam.com/AdPlatformDelivery/landingPagePkgName/com.jzxy.heisu.json
|
||||
```
|
||||
|
||||
**目录刷新**
|
||||
|
||||
```
|
||||
https://s-file-1.ykt.cbern.com.cn/
|
||||
```
|
||||
Reference in New Issue
Block a user