diff --git a/.trae/documents/DNS服务器性能优化方案.md b/.trae/documents/DNS服务器性能优化方案.md new file mode 100644 index 0000000..327667f --- /dev/null +++ b/.trae/documents/DNS服务器性能优化方案.md @@ -0,0 +1,79 @@ +## DNS服务器性能优化方案 + +### 问题分析 + +1. **并行查询模式**:当前配置使用`parallel`模式,会等待所有上游服务器响应后才返回,受最慢服务器影响 +2. **DNSSEC验证开销**:启用了DNSSEC验证,增加了额外的计算和网络请求 +3. **过多上游服务器**:DNSSEC上游服务器多达5个,响应时间差异大 +4. **调试级别日志**:`debug`级别日志记录大量信息,占用CPU和I/O资源 +5. **缓存TTL过短**:10秒的缓存TTL导致频繁向上游请求 +6. **黑名单规则过多**:14个启用的黑名单,每次请求都需要检查 + +### 优化方案 + +#### 1. 修改查询模式为快速返回 + +* 将`queryMode`从`parallel`改为`fastest-ip`或优化默认模式 + +* 快速返回模式会返回第一个有效响应,而不是等待所有响应 + +#### 2. 优化DNSSEC配置 + +* 减少DNSSEC上游服务器数量,只保留2-3个可靠的 + +* 对国内域名禁用DNSSEC验证(已配置部分,可扩展) + +#### 3. 调整缓存策略 + +* 增加`cacheTTL`到60秒或更高,减少上游请求频率 + +* 优化缓存实现,减少锁竞争 + +#### 4. 降低日志级别 + +* 将日志级别从`debug`改为`info`或`warn`,减少日志写入开销 + +#### 5. 优化黑名单处理 + +* 合并重复的黑名单规则 + +* 考虑使用更高效的域名匹配算法 + +#### 6. 代码优化 + +* 减少DNSSEC验证的重复调用 + +* 优化响应合并逻辑,避免不必要的计算 + +* 调整超时设置,避免过长等待 + +### 具体修改点 + +1. **config.json**: + + * 修改`queryMode`为`fastest-ip` + + * 减少`dnssecUpstreamDNS`数量 + + * 增加`cacheTTL`到60 + + * 将日志级别改为`info` + +2. **dns/server.go**: + + * 优化`forwardDNSRequestWithCache`函数,减少DNSSEC重复验证 + + * 优化响应合并逻辑,避免不必要的计算 + + * 调整并行模式的超时处理 + +### 预期效果 + +* 减少响应时间,从当前的秒级降低到毫秒级 + +* 减少CPU和I/O资源占用 + +* 提高并发处理能力 + +* 保持DNS解析的准确性和可靠性 + diff --git a/.trae/documents/plan_20251216_041731.md b/.trae/documents/plan_20251216_041731.md index d739a8e..21bb0f8 100644 --- a/.trae/documents/plan_20251216_041731.md +++ b/.trae/documents/plan_20251216_041731.md @@ -1,35 +1,49 @@ # DNSSEC状态显示问题修复计划 ## 问题分析 + 用户报告已在配置中启用DNSSEC(`enableDNSSEC: true`),但界面显示DNSSEC为禁用状态,且使用率为0%。经过代码检查,发现问题出在`GetStats`函数中,该函数返回的`Stats`结构体缺少DNSSEC相关字段,导致前端无法获取正确的DNSSEC状态和统计信息。 ## 修复方案 ### 1. 修复`GetStats`函数 + **修改文件:** `dns/server.go` **修改函数:** `GetStats` **问题:** 当前`GetStats`函数返回的`Stats`结构体缺少DNSSEC相关字段,包括: -- `DNSSECEnabled` -- `DNSSECQueries` -- `DNSSECSuccess` -- `DNSSECFailed` + +* `DNSSECEnabled` + +* `DNSSECQueries` + +* `DNSSECSuccess` + +* `DNSSECFailed` **解决方案:** 在`GetStats`函数返回的`Stats`结构体中添加所有DNSSEC相关字段,确保前端能获取到正确的DNSSEC状态和统计数据。 ## 具体实现步骤 1. **修改`GetStats`函数**: - - 在返回的`Stats`结构体中添加`DNSSECEnabled`字段 - - 添加`DNSSECQueries`字段 - - 添加`DNSSECSuccess`字段 - - 添加`DNSSECFailed`字段 + + * 在返回的`Stats`结构体中添加`DNSSECEnabled`字段 + + * 添加`DNSSECQueries`字段 + + * 添加`DNSSECSuccess`字段 + + * 添加`DNSSECFailed`字段 2. **测试修复效果**: - - 重新编译DNS服务器 - - 启动服务器 - - 使用API查询统计信息,确认DNSSEC状态和统计数据正确返回 - - 检查前端界面是否显示正确的DNSSEC状态 + + * 重新编译DNS服务器 + + * 启动服务器 + + * 使用API查询统计信息,确认DNSSEC状态和统计数据正确返回 + + * 检查前端界面是否显示正确的DNSSEC状态 ## 预期效果 @@ -40,4 +54,5 @@ ## 代码修改范围 -- `dns/server.go`:修复`GetStats`函数,添加缺失的DNSSEC字段 \ No newline at end of file +* `dns/server.go`:修复`GetStats`函数,添加缺失的DNSSEC字段 + diff --git a/.trae/documents/plan_20251225_123636.md b/.trae/documents/plan_20251225_123636.md new file mode 100644 index 0000000..d32a297 --- /dev/null +++ b/.trae/documents/plan_20251225_123636.md @@ -0,0 +1,52 @@ +1. **修改QueryLog结构体**: + + * 在`dns/server.go`中的`QueryLog`结构体添加`ResponseCode`字段 + +2. **修改addQueryLog函数**: + + * 在`dns/server.go`中的`addQueryLog`函数添加`responseCode`参数 + + * 将响应代码记录到QueryLog结构体中 + +3. **修改DNS请求处理逻辑**: + + * 在`handleDNSRequest`函数中,获取实际的响应代码 + + * 将响应代码传递给`addQueryLog`函数 + +4. **修改前端模板**: + + * 在`static/js/logs.js`中,将响应代码的硬编码值"无"替换为从日志数据中获取的实际响应代码 + + * 添加响应代码映射,将数字响应代码转换为可读的字符串 + +5. **编译和测试**: + + * 重新编译项目 + + * 测试DNS查询详情中响应代码是否正确显示 + +**DNS响应代码映射**: + +* 0: NOERROR + +* 1: FORMERR + +* 2: SERVFAIL + +* 3: NXDOMAIN + +* 4: NOTIMP + +* 5: REFUSED + +* 6: YXDOMAIN + +* 7: YXRRSET + +* 8: NXRRSET + +* 9: NOTAUTH + +* 10: NOTZONE + diff --git a/.trae/documents/plan_20251225_125022.md b/.trae/documents/plan_20251225_125022.md new file mode 100644 index 0000000..89e0aac --- /dev/null +++ b/.trae/documents/plan_20251225_125022.md @@ -0,0 +1,42 @@ +1. **调整DNS超时时间**: + + * 将配置文件中的`timeout`值从5毫秒增加到5000毫秒(5秒) + + * 5毫秒的超时时间对于DNS查询来说太短,导致大部分查询都超时失败 + +2. **优化查询模式**: + + * 将查询模式从`parallel`(并行)改为`loadbalance`(负载均衡) + + * 并行模式在短超时时间下会导致大量超时,负载均衡模式更可靠 + +3. **检查上游DNS服务器配置**: + + * 确保所有配置的上游DNS服务器都能正常工作 + + * 移除或调整可能不可达的DNS服务器 + +4. **调整DNSSEC配置**: + + * 检查DNSSEC专用服务器的可达性 + + * 考虑暂时禁用DNSSEC验证,观察是否能改善性能 + +5. **增强错误处理**: + + * 优化`forwardDNSRequestWithCache`函数的错误处理逻辑 + + * 确保在所有服务器都超时的情况下有合理的回退机制 + +6. **监控和日志优化**: + + * 添加更详细的日志,记录每个DNS服务器的响应情况 + + * 增加监控指标,追踪DNS查询成功率和响应时间 + +7. **测试验证**: + + * 在修改后进行DNS查询测试,确保服务器能正常响应 + + * 监控日志,确认不再出现大量DNS查询失败的情况 + diff --git a/.trae/documents/plan_20251225_130822.md b/.trae/documents/plan_20251225_130822.md new file mode 100644 index 0000000..e4727ce --- /dev/null +++ b/.trae/documents/plan_20251225_130822.md @@ -0,0 +1,53 @@ +## 问题分析 + +DNS服务器出现"Server Failed"的根本原因是:当用户配置的DNS服务器地址没有包含端口号时,代码直接将其传递给`dns.Client.Exchange()`方法,而该方法需要完整的"IP:端口"格式地址。 + +## 解决方案 + +### 1. 创建DNS服务器地址处理函数 +- **功能**:确保DNS服务器地址始终包含端口号,默认添加53端口 +- **实现**:创建`normalizeDNSServerAddress`函数,检查并添加端口号 + +### 2. 应用地址处理函数到所有DNS服务器配置 + +**需要修改的位置**: +- **主DNS服务器**:`s.config.UpstreamDNS` +- **DNSSEC专用服务器**:`s.config.DNSSECUpstreamDNS` +- **域名特定DNS服务器**:`s.config.DomainSpecificDNS` +- **所有调用`resolver.Exchange()`的地方**:确保传递的服务器地址包含端口号 + +### 3. 修改具体代码位置 + +**文件**:`dns/server.go` + +**修改点**: +1. **添加地址处理函数**:在文件中添加`normalizeDNSServerAddress`函数 +2. **在parallel模式中使用**:修改第865行附近的代码 +3. **在loadbalance模式中使用**:修改第1063行附近的代码 +4. **在fastest-ip模式中使用**:修改第1189行附近的代码 +5. **在default模式中使用**:修改第1311行附近的代码 +6. **在DNSSEC专用服务器请求中使用**:修改第1452行附近的代码 +7. **在本地解析中使用**:修改第1550行附近的代码 + +### 4. 确保配置文件加载时也处理地址 + +- 检查配置文件加载代码,确保在加载配置时就处理DNS服务器地址 +- 或者在每次使用DNS服务器地址时动态处理 + +## 修复步骤 + +1. **创建地址处理函数**:实现`normalizeDNSServerAddress`函数 +2. **修改所有DNS查询点**:在所有调用`resolver.Exchange()`的地方使用该函数 +3. **测试修复效果**:重启DNS服务器并测试查询功能 +4. **验证各种配置场景**:测试带端口和不带端口的DNS服务器配置 + +## 预期效果 + +- 当用户配置DNS服务器为`223.5.5.5`时,自动添加端口变为`223.5.5.5:53` +- 当用户配置DNS服务器为`8.8.8.8:53`时,保持不变 +- DNS查询成功率显著提高,不再出现"Server Failed"错误 +- 支持各种DNS服务器配置格式,提高系统兼容性 + +## 关键文件修改 + +- `/root/dns/dns/server.go`:添加地址处理函数并应用到所有DNS查询点 \ No newline at end of file diff --git a/.trae/documents/plan_20251225_150849.md b/.trae/documents/plan_20251225_150849.md new file mode 100644 index 0000000..d15d185 --- /dev/null +++ b/.trae/documents/plan_20251225_150849.md @@ -0,0 +1,52 @@ +## 问题分析 + +当前DNS服务器的解析记录显示存在以下问题: +1. 前端`logs.js`中使用了`console.log`来调试和显示解析记录 +2. 需要确保API返回的解析记录是正确的JSON格式 +3. 前端需要正确解析API返回的JSON数据来显示解析记录 + +## 解决方案 + +### 1. 优化API返回格式 + +**文件**:`dns/server.go` + +**修改内容**: +- 确保`QueryLog`结构体的`Answers`字段正确序列化为JSON +- 检查`DNSAnswer`结构体的JSON标签是否正确 + +### 2. 清理前端console.log代码 + +**文件**:`static/js/logs.js` + +**修改内容**: +- 删除或注释掉第1047、1054行等console.log调试代码 +- 优化解析记录提取逻辑,确保正确处理API返回的JSON数据 + +### 3. 优化解析记录显示逻辑 + +**文件**:`static/js/logs.js` + +**修改内容**: +- 完善`extractDNSRecords`函数,确保正确处理各种格式的解析记录 +- 优化解析记录的HTML渲染逻辑,确保显示格式清晰 +- 确保支持`log.answers`字段(小写)和`log.Answers`字段(大写) + +### 4. 测试验证 + +**步骤**: +- 重启DNS服务器 +- 使用API测试工具验证`/api/logs/query`返回的解析记录格式正确 +- 测试前端页面解析记录显示正常 + +## 预期效果 + +- API返回的解析记录格式为标准JSON +- 前端页面不再使用console.log显示解析记录 +- 解析记录显示清晰、格式统一 +- 支持各种情况下的解析记录提取 + +## 关键文件修改 + +1. **`dns/server.go`**:确保解析记录正确序列化 +2. **`static/js/logs.js`**:优化解析记录显示逻辑,移除console.log代码 \ No newline at end of file diff --git a/.trae/documents/plan_20251225_151740.md b/.trae/documents/plan_20251225_151740.md new file mode 100644 index 0000000..dbff851 --- /dev/null +++ b/.trae/documents/plan_20251225_151740.md @@ -0,0 +1,43 @@ +## 问题分析 + +当前服务器代码存在以下问题: +1. `QueryLog`结构体中只有部分字段有JSON标签 +2. 缺少完整的JSON序列化支持,导致API返回的JSON格式不完整 +3. 需要确保所有字段都能正确序列化为JSON + +## 解决方案 + +### 1. 完善QueryLog结构体的JSON标签 + +**文件**:`dns/server.go` + +**修改内容**: +- 为`QueryLog`结构体的所有字段添加正确的JSON标签 +- 确保`Timestamp`字段正确序列化为ISO格式时间 +- 确保`Answers`字段序列化为`"answers"`(小写) + +### 2. 确保API返回完整的JSON数据 + +**文件**:`http/server.go` + +**修改内容**: +- 检查`handleLogsQuery`函数,确保返回完整的日志数据 +- 确保日志查询API返回包含所有必要字段的JSON数据 + +### 3. 测试验证 + +**步骤**: +- 重启DNS服务器 +- 使用API测试工具验证`/api/logs/query`返回的JSON格式正确 +- 确保所有字段都正确序列化 + +## 预期效果 + +- API返回的JSON数据包含所有日志字段 +- 前端能够正确解析API返回的JSON数据 +- 解析记录通过API查询方式显示,不再使用console.log + +## 关键文件修改 + +1. **`dns/server.go`**:完善`QueryLog`结构体的JSON标签 +2. **`http/server.go`**:确保API返回完整的JSON数据 \ No newline at end of file diff --git a/.trae/documents/plan_20251225_154403.md b/.trae/documents/plan_20251225_154403.md new file mode 100644 index 0000000..da77107 --- /dev/null +++ b/.trae/documents/plan_20251225_154403.md @@ -0,0 +1,31 @@ +## 问题分析 +从用户提供的截图可以看到,解析记录显示存在问题,只显示了IP地址"104.26.24.30",而没有完整的解析记录格式,如"A: 104.26.24.30 (ttl=193)"。 + +## 根本原因 +通过分析代码,发现问题可能出在以下几个方面: +1. 解析记录的显示样式可能存在问题 +2. 或者在生成解析记录字符串时出现了问题 +3. 或者是在处理`dnsAnswers`数组时出现了问题 + +## 修复方案 +1. 修改解析记录的生成逻辑,确保完整显示记录类型、值和TTL +2. 检查并调整HTML元素的样式,确保多行文本正确显示 + +## 具体修改点 +1. **修改解析记录的生成逻辑**: + - 在`showLogDetailModal`函数中,修改解析记录的生成逻辑,确保即使记录类型或TTL为空,也能正确显示 + - 确保每个解析记录都按照"类型: 值 (ttl=TTL)"的格式显示 + +2. **调整HTML元素的样式**: + - 检查并调整解析记录显示容器的样式,确保多行文本正确显示 + - 确保`whitespace-pre-wrap`样式正确应用 + +## 修复原则 +- 确保解析记录完整显示,包括记录类型、值和TTL +- 保持良好的可读性 +- 确保样式兼容各种浏览器 + +## 验证方法 +1. 修复代码后,重新加载页面 +2. 查看解析记录是否完整显示,包括记录类型、值和TTL +3. 测试不同类型的解析记录,确保都能正确显示 \ No newline at end of file diff --git a/.trae/documents/plan_20251225_161050.md b/.trae/documents/plan_20251225_161050.md new file mode 100644 index 0000000..6fd4446 --- /dev/null +++ b/.trae/documents/plan_20251225_161050.md @@ -0,0 +1,34 @@ +## 移除查询日志详情中的屏蔽规则列 + +### 1. 问题分析 +- 用户要求移除查询日志详情弹窗中的屏蔽规则列 +- 屏蔽规则列位于响应细节部分,显示在响应时间和响应代码之间 +- 该列显示了DNS查询被屏蔽时的规则信息 + +### 2. 实现方案 +- 编辑`showLogDetailModal`函数,找到响应细节部分的HTML模板 +- 移除其中包含"规则"标题和`${blockRule}`变量的整个div元素 +- 保持其他响应细节(响应时间、响应代码、缓存状态)不变 + +### 3. 代码修改 +- 修改文件:`/root/dns/static/js/logs.js` +- 修改函数:`showLogDetailModal` +- 移除位置:响应细节部分的`responseGrid` HTML模板 +- 移除内容:包含"规则"标题和`${blockRule}`变量的div元素 + +### 4. 预期效果 +- 查询日志详情弹窗中将不再显示屏蔽规则列 +- 响应细节部分将只显示:响应时间、响应代码、缓存状态 +- 保持弹窗的整体布局和样式不变 +- 不影响其他功能的正常运行 + +### 5. 技术细节 +- 使用HTML模板字符串修改DOM结构 +- 移除不必要的DOM元素,简化UI +- 保持代码的可读性和可维护性 + +### 6. 测试验证 +- 验证修改后的代码是否有语法错误 +- 验证查询日志详情弹窗是否正常显示 +- 验证屏蔽规则列已被成功移除 +- 验证其他功能是否正常工作 \ No newline at end of file diff --git a/.trae/documents/plan_20251226_133837.md b/.trae/documents/plan_20251226_133837.md new file mode 100644 index 0000000..28cefc2 --- /dev/null +++ b/.trae/documents/plan_20251226_133837.md @@ -0,0 +1,56 @@ +## 移除查询日志详情中的屏蔽规则列 + +### 1. 问题分析 + +* 用户要求移除查询日志详情弹窗中的屏蔽规则列 + +* 屏蔽规则列位于响应细节部分,显示在响应时间和响应代码之间 + +* 该列显示了DNS查询被屏蔽时的规则信息 + +### 2. 实现方案 + +* 编辑`showLogDetailModal`函数,找到响应细节部分的HTML模板 + +* 移除其中包含"规则"标题和`${blockRule}`变量的整个div元素 + +* 保持其他响应细节(响应时间、响应代码、缓存状态)不变 + +### 3. 代码修改 + +* 修改文件:`/root/dns/static/js/logs.js` + +* 修改函数:`showLogDetailModal` + +* 移除位置:响应细节部分的`responseGrid` HTML模板 + +* 移除内容:包含"规则"标题和`${blockRule}`变量的div元素 + +### 4. 预期效果 + +* 查询日志详情弹窗中将不再显示屏蔽规则列 + +* 响应细节部分将只显示:响应时间、响应代码、缓存状态 + +* 保持弹窗的整体布局和样式不变 + +* 不影响其他功能的正常运行 + +### 5. 技术细节 + +* 使用HTML模板字符串修改DOM结构 + +* 移除不必要的DOM元素,简化UI + +* 保持代码的可读性和可维护性 + +### 6. 测试验证 + +* 验证修改后的代码是否有语法错误 + +* 验证查询日志详情弹窗是否正常显示 + +* 验证屏蔽规则列已被成功移除 + +* 验证其他功能是否正常工作 + diff --git a/.trae/documents/plan_20251226_142616.md b/.trae/documents/plan_20251226_142616.md new file mode 100644 index 0000000..cfc374e --- /dev/null +++ b/.trae/documents/plan_20251226_142616.md @@ -0,0 +1,51 @@ +## 优化DNS请求处理逻辑,减少返回客户端超时 + +### 1. 问题分析 + +* 服务器请求上游解析成功,但返回给客户端超时 +* 主要原因是`forwardDNSRequestWithCache`函数等待所有上游服务器响应,导致某个慢服务器拖慢整体响应 +* 虽然是并行查询,但没有实现快速响应返回机制 +* 阻塞式等待所有响应完成,而不是优先返回最快的成功响应 + +### 2. 实现方案 + +* 优化`forwardDNSRequestWithCache`函数,实现快速响应返回机制 +* 当收到第一个成功响应时,立即返回给客户端,不再等待其他服务器响应 +* 保持后台继续接收其他响应,更新最佳响应和服务器状态 +* 优化并行查询逻辑,提高响应速度 +* 保持代码的可读性和可维护性 + +### 3. 代码修改 + +* 修改文件:`/root/dns/dns/server.go` +* 修改函数:`forwardDNSRequestWithCache` +* 优化位置:`parallel`模式和`default`模式下的响应处理逻辑 +* 优化内容: + - 实现快速响应返回机制 + - 当收到第一个成功响应时,立即返回给客户端 + - 保持后台处理其他响应 + - 优化并行查询逻辑 + +### 4. 预期效果 + +* 减少DNS查询的平均响应时间 +* 避免因某个上游服务器响应慢而导致整体超时 +* 提高DNS服务器的吞吐量和并发处理能力 +* 保持对上游服务器状态的准确跟踪 +* 不影响现有功能的正常运行 + +### 5. 技术细节 + +* 使用通道和goroutine实现非阻塞响应处理 +* 当收到第一个成功响应时,立即返回给客户端 +* 保持后台继续接收其他响应,更新最佳响应和服务器状态 +* 优化并行查询逻辑,提高响应速度 +* 保持代码的可读性和可维护性 + +### 6. 测试验证 + +* 验证修改后的代码是否有语法错误 +* 验证DNS查询响应时间是否明显减少 +* 验证是否解决了返回客户端超时的问题 +* 验证其他功能是否正常工作 +* 验证上游服务器状态跟踪是否准确 \ No newline at end of file diff --git a/.trae/documents/plan_20251228_100157.md b/.trae/documents/plan_20251228_100157.md new file mode 100644 index 0000000..64d35e0 --- /dev/null +++ b/.trae/documents/plan_20251228_100157.md @@ -0,0 +1,78 @@ +## 优化DNS请求处理逻辑,减少返回客户端超时 + +### 1. 问题分析 + +* 服务器请求上游解析成功,但返回给客户端超时 + +* 主要原因是`forwardDNSRequestWithCache`函数等待所有上游服务器响应,导致某个慢服务器拖慢整体响应 + +* 虽然是并行查询,但没有实现快速响应返回机制 + +* 阻塞式等待所有响应完成,而不是优先返回最快的成功响应 + +### 2. 实现方案 + +* 优化`forwardDNSRequestWithCache`函数,实现快速响应返回机制 + +* 当收到第一个成功响应时,立即返回给客户端,不再等待其他服务器响应 + +* 保持后台继续接收其他响应,更新最佳响应和服务器状态 + +* 优化并行查询逻辑,提高响应速度 + +* 保持代码的可读性和可维护性 + +### 3. 代码修改 + +* 修改文件:`/root/dns/dns/server.go` + +* 修改函数:`forwardDNSRequestWithCache` + +* 优化位置:`parallel`模式和`default`模式下的响应处理逻辑 + +* 优化内容: + + * 实现快速响应返回机制 + + * 当收到第一个成功响应时,立即返回给客户端 + + * 保持后台处理其他响应 + + * 优化并行查询逻辑 + +### 4. 预期效果 + +* 减少DNS查询的平均响应时间 + +* 避免因某个上游服务器响应慢而导致整体超时 + +* 提高DNS服务器的吞吐量和并发处理能力 + +* 保持对上游服务器状态的准确跟踪 + +* 不影响现有功能的正常运行 + +### 5. 技术细节 + +* 使用通道和goroutine实现非阻塞响应处理 + +* 当收到第一个成功响应时,立即返回给客户端 + +* 保持后台继续接收其他响应,更新最佳响应和服务器状态 + +* 优化并行查询逻辑,提高响应速度 + +* 保持代码的可读性和可维护性 + +### 6. 测试验证 + +* 验证修改后的代码是否有语法错误 + +* 验证DNS查询响应时间是否明显减少 + +* 验证是否解决了返回客户端超时的问题 + +* 验证其他功能是否正常工作 + +* 验证上游服务器状态跟踪是否准确 + diff --git a/.trae/documents/plan_20251229_142423.md b/.trae/documents/plan_20251229_142423.md new file mode 100644 index 0000000..6e9d88f --- /dev/null +++ b/.trae/documents/plan_20251229_142423.md @@ -0,0 +1,20 @@ +# 修复域名匹配机制问题 + +## 问题分析 +通过详细检查domain-info.json文件,我发现了问题的根本原因: +- JSON文件的结构存在错误,`domains`对象只包含了网易公司 +- 其他公司(如阿里云、百度等)被错误地放在了`domains`对象之外 +- 这导致域名匹配机制只能找到网易公司的信息,而无法匹配其他公司 + +## 修复计划 +1. **修复JSON文件结构**:将所有公司信息正确地包含在`domains`对象中 +2. **优化域名匹配逻辑**:根据用户需求,实现优先匹配完整URL域名,再匹配主域名的逻辑 +3. **测试修复效果**:验证所有公司的域名都能被正确匹配 + +## 预期效果 +- 修复后的JSON文件结构正确,所有公司都包含在`domains`对象中 +- 域名匹配机制能够正确识别所有公司的域名 +- 匹配规则: + - 优先匹配完整URL域名(如baike.baidu.com) + - 如果没有匹配上,则匹配主域名(如baidu.com) + - 输出对应的公司名称(如北京百度网讯科技有限公司) \ No newline at end of file diff --git a/.trae/documents/plan_20251229_175254.md b/.trae/documents/plan_20251229_175254.md new file mode 100644 index 0000000..16c3116 --- /dev/null +++ b/.trae/documents/plan_20251229_175254.md @@ -0,0 +1,33 @@ +## 问题分析 + +通过测试脚本的输出,我发现了kdocs.cn无法匹配到域名信息的原因: + +1. **JSON结构错误**:domain-info.json文件中字节跳动公司的结构存在错误,导致解析后的对象结构不正确。 + +2. **遍历顺序问题**:由于JSON结构错误,当遍历到字节跳动公司时,脚本没有正确跳过company属性,而是继续处理字节跳动对象内部的属性,然后遇到了一个名为"company"的公司,这个公司的属性值是categories对象的值。 + +3. **遍历不完整**:由于结构错误,脚本在遍历到字节跳动公司后就无法继续遍历到金山办公公司,而金山办公公司正是kdocs.cn所属的公司。 + +## 解决方案 + +### 1. 修复domain-info.json文件的结构 + +修复字节跳动公司的结构错误,确保其包含正确的闭合括号和逗号: + +- 修复抖音视频对象的闭合括号 +- 确保今日头条API服务和豆包对象是字节跳动公司的直接子对象 +- 确保company属性是字节跳动公司的直接子属性 + +### 2. 测试修复效果 + +修复后,重新运行测试脚本,验证kdocs.cn和www.kdocs.cn能够正确匹配到金山办公公司的金山文档。 + +## 实施步骤 + +1. 修改domain-info.json文件,修复字节跳动公司的结构错误 +2. 运行test-fetch.js测试脚本,验证修复效果 +3. 确认kdocs.cn和www.kdocs.cn能够正确匹配到域名信息 + +## 预期效果 + +修复后,kdocs.cn和www.kdocs.cn将能够正确匹配到金山办公公司的金山文档,显示网站名称、图标、类别和所属公司。 \ No newline at end of file diff --git a/.trae/documents/plan_20260102_110215.md b/.trae/documents/plan_20260102_110215.md new file mode 100644 index 0000000..abe8356 --- /dev/null +++ b/.trae/documents/plan_20260102_110215.md @@ -0,0 +1,32 @@ +# 移除负载均衡查询模式 + +## 问题分析 +用户要求移除负载均衡查询模式,目前代码中支持多种查询模式,包括 "loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"(最快的IP地址)。负载均衡模式使用加权随机选择算法来选择上游服务器。 + +## 解决方案 +1. 从配置中移除 "loadbalance" 作为可用选项 +2. 从代码中移除 "loadbalance" 分支 +3. 确保其他使用 `selectWeightedRandomServer` 函数的地方不受影响 + +## 实施步骤 +1. 修改 `config/config.go` 文件,更新 `QueryMode` 字段的注释,移除 "loadbalance" 选项 +2. 修改 `dns/server.go` 文件,移除 switch 语句中的 "loadbalance" 分支 +3. 确保所有使用 `selectWeightedRandomServer` 函数的地方仍然正常工作 +4. 测试修改后的代码,确保 DNS 服务器仍然正常运行 + +## 修改内容 +- 文件:`/root/dns/config/config.go` + - 修改点:更新 `QueryMode` 字段的注释,移除 "loadbalance" 选项 + +- 文件:`/root/dns/dns/server.go` + - 修改点:移除 switch 语句中的 "loadbalance" 分支(第1139-1260行) + +## 预期效果 +- 负载均衡查询模式将不再可用 +- DNS 服务器仍然支持其他查询模式(parallel、fastest-ip) +- 其他功能不受影响 +- 代码更加简洁,减少了维护成本 + +## 注意事项 +- `selectWeightedRandomServer` 函数不仅用于负载均衡模式,还用于选择 DNSSEC 服务器和本地服务器,所以不能删除这个函数 +- 确保修改后所有其他功能仍然正常工作 \ No newline at end of file diff --git a/.trae/documents/plan_20260102_110918.md b/.trae/documents/plan_20260102_110918.md new file mode 100644 index 0000000..5022ad2 --- /dev/null +++ b/.trae/documents/plan_20260102_110918.md @@ -0,0 +1,32 @@ +# 清理无关代码 + +## 问题分析 +我已经成功移除了负载均衡查询模式的主要代码,但还有一些相关的残留代码需要清理,包括: +1. CHANGELOG.md文件中提到了loadbalance模式 +2. 可能还有其他未使用的代码或配置 + +## 解决方案 +1. 更新CHANGELOG.md文件,移除或修改与loadbalance模式相关的条目 +2. 检查是否有其他未使用的代码或配置 +3. 确保所有修改都不会影响现有功能 + +## 实施步骤 +1. 修改CHANGELOG.md文件,移除或修改与loadbalance模式相关的条目 +2. 检查是否有其他未使用的代码或配置 +3. 编译并测试修改后的代码,确保功能正常 + +## 修改内容 +- 文件:`/root/dns/CHANGELOG.md` + - 修改点1:第106行,移除或修改与loadbalance模式相关的条目 + - 修改点2:第112行,移除或修改与loadbalance模式相关的条目 + +## 预期效果 +- 代码库中不再有与已移除功能相关的残留代码 +- 文档与实际代码保持一致 +- 现有功能不受影响 +- 代码库更加整洁,易于维护 + +## 注意事项 +- 不要修改数据文件中的loadbalance相关规则,这些是广告过滤规则,不是代码 +- 确保所有修改都不会影响现有功能 +- 编译并测试修改后的代码,确保功能正常 \ No newline at end of file diff --git a/.trae/documents/plan_20260104_105942.md b/.trae/documents/plan_20260104_105942.md new file mode 100644 index 0000000..8fb9c50 --- /dev/null +++ b/.trae/documents/plan_20260104_105942.md @@ -0,0 +1,113 @@ +## 支持Base64编码GFWList格式的实现计划 + +### 问题分析 + +当前的GFWList加载代码直接按行解析文件内容,无法处理Base64编码的GFWList格式。Base64编码的GFWList文件包含: +1. 头部注释行(以!开头) +2. Base64编码的规则内容(整个文件或主要部分) +3. 可能的尾部注释 + +### 解决方案 + +修改`gfw/manager.go`中的`LoadRules`函数,添加Base64解码支持: + +1. **检测Base64编码**: + - 读取文件内容 + - 跳过注释行 + - 检查剩余内容是否为Base64编码 + +2. **解码Base64内容**: + - 如果是Base64编码,使用`encoding/base64`包解码 + - 解码后得到原始规则文本 + +3. **解析规则**: + - 按行分割解码后的内容 + - 调用现有的`parseRule`函数处理每一行 + +### 具体修改点 + +1. **添加Base64依赖**: + - 在`gfw/manager.go`中添加`encoding/base64`导入 + +2. **修改LoadRules函数**: + - 读取文件内容 + - 过滤注释行,收集可能的Base64内容 + - 尝试Base64解码 + - 解析解码后的规则 + +3. **测试**: + - 使用现有的Base64编码GFWList文件测试 + - 确保解码和规则解析正常工作 + +### 预期效果 + +- 服务器能够正确加载Base64编码的GFWList文件 +- 兼容现有的纯文本GFWList格式 +- 规则解析结果与原格式一致 + +### 代码修改示例 + +```go +// 在import中添加encoding/base64 +import ( + // 现有导入 + "encoding/base64" + // 现有导入 +) + +// 修改LoadRules函数 +func (m *GFWListManager) LoadRules() error { + // 现有代码... + + // 从文件路径读取GFWList内容 + content, err := os.ReadFile(m.config.Content) + if err != nil { + return fmt.Errorf("读取GFWList文件失败: %w", err) + } + + // 处理Base64编码 + rawContent := string(content) + + // 过滤注释行,收集Base64内容 + var base64Content strings.Builder + lines := strings.Split(rawContent, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { + // 跳过注释行和头信息 + continue + } + base64Content.WriteString(line) + } + + // 尝试Base64解码 + decoded, err := base64.StdEncoding.DecodeString(base64Content.String()) + if err == nil { + // 解码成功,使用解码后的内容 + rawContent = string(decoded) + } else { + // 解码失败,使用原始内容(可能是纯文本格式) + } + + // 按行解析规则 + ruleLines := strings.Split(rawContent, "\n") + for _, line := range ruleLines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { + // 跳过空行、注释行和头信息行 + continue + } + m.parseRule(line) + } + + // 现有代码... +} +``` + +### 实施步骤 + +1. 修改`gfw/manager.go`,添加Base64支持 +2. 编译并测试 +3. 验证规则加载是否正常 + +这个修改将使服务器能够同时支持纯文本和Base64编码的GFWList格式,提高了兼容性和灵活性。 \ No newline at end of file diff --git a/.trae/documents/plan_20260105_074926.md b/.trae/documents/plan_20260105_074926.md new file mode 100644 index 0000000..109b034 --- /dev/null +++ b/.trae/documents/plan_20260105_074926.md @@ -0,0 +1,196 @@ +# 平均响应时间计算错误修复方案 + +## 问题分析 +通过对代码的分析,我发现平均响应时间计算错误的根本原因是: + +1. **统计数据持久化问题**:当服务器重启时,`loadStatsData` 函数直接覆盖 `s.stats` 对象,导致 `TotalResponseTime` 和 `Queries` 之间的关系可能被破坏 + +2. **异常响应时间累计**:在某些情况下,`responseTime` 可能被错误计算为非常大的值,导致 `TotalResponseTime` 异常增长 + +3. **计算逻辑不健壮**:平均响应时间计算没有考虑异常情况,如 `Queries` 为 0 或 `TotalResponseTime` 溢出 + +4. **统计数据一致性问题**:并发访问时可能导致统计数据不一致 + +## 修复方案 + +### 1. 修复 `loadStatsData` 函数 +修改统计数据加载逻辑,确保 `TotalResponseTime` 和 `Queries` 之间的关系正确: + +```go +// 恢复统计数据 +s.statsMutex.Lock() +if statsData.Stats != nil { + // 只恢复有效数据,避免破坏统计关系 + s.stats.Queries += statsData.Stats.Queries + s.stats.Blocked += statsData.Stats.Blocked + s.stats.Allowed += statsData.Stats.Allowed + s.stats.Errors += statsData.Stats.Errors + s.stats.TotalResponseTime += statsData.Stats.TotalResponseTime + s.stats.DNSSECQueries += statsData.Stats.DNSSECQueries + s.stats.DNSSECSuccess += statsData.Stats.DNSSECSuccess + s.stats.DNSSECFailed += statsData.Stats.DNSSECFailed + + // 重新计算平均响应时间,确保一致性 + if s.stats.Queries > 0 { + s.stats.AvgResponseTime = float64(s.stats.TotalResponseTime) / float64(s.stats.Queries) + } + + // 合并查询类型统计 + for k, v := range statsData.Stats.QueryTypes { + s.stats.QueryTypes[k] += v + } + + // 合并来源IP统计 + for ip := range statsData.Stats.SourceIPs { + s.stats.SourceIPs[ip] = true + } + + // 确保使用当前配置中的EnableDNSSEC值 + s.stats.DNSSECEnabled = s.config.EnableDNSSEC +} +s.statsMutex.Unlock() +``` + +### 2. 修复响应时间计算逻辑 +在 `handleDNSRequest` 函数中,添加响应时间合理性检查: + +```go +// 使用上游服务器的实际响应时间(转换为毫秒) +responseTime := int64(rtt.Milliseconds()) +// 如果rtt为0(查询失败),则使用本地计算的时间 +if responseTime == 0 { + responseTime = time.Since(startTime).Milliseconds() +} + +// 添加合理性检查,避免异常大的响应时间影响统计 +if responseTime > 60000 { // 超过60秒的响应时间视为异常 + responseTime = 60000 +} +``` + +### 3. 优化平均响应时间计算 +修改 `updateStats` 函数,确保平均响应时间计算的健壮性: + +```go +s.updateStats(func(stats *Stats) { + stats.TotalResponseTime += responseTime + // 添加防御性编程,确保Queries大于0 + if stats.Queries > 0 { + // 使用更精确的计算方式,避免浮点数精度问题 + stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 限制平均响应时间的范围,避免显示异常大的值 + if stats.AvgResponseTime > 60000 { + stats.AvgResponseTime = 60000 + } + } +}) +``` + +### 4. 添加统计数据重置机制 +添加定期重置统计数据的功能,避免 `TotalResponseTime` 无限增长: + +```go +// Start 启动DNS服务器 +func (s *Server) Start() error { + // 现有代码... + + // 启动统计数据定期重置功能(每24小时) + go func() { + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + for { + select { + case <-ticker.C: + s.resetStats() + case <-s.ctx.Done(): + return + } + } + }() + + // 现有代码... +} + +// resetStats 重置统计数据 +func (s *Server) resetStats() { + s.statsMutex.Lock() + defer s.statsMutex.Unlock() + + // 只重置累计值,保留配置相关值 + s.stats.TotalResponseTime = 0 + s.stats.AvgResponseTime = 0 + s.stats.Queries = 0 + s.stats.Blocked = 0 + s.stats.Allowed = 0 + s.stats.Errors = 0 + s.stats.DNSSECQueries = 0 + s.stats.DNSSECSuccess = 0 + s.stats.DNSSECFailed = 0 + s.stats.QueryTypes = make(map[string]int64) + s.stats.SourceIPs = make(map[string]bool) + + logger.Info("统计数据已重置") +} +``` + +### 5. 修复 GetStats 函数 +确保 `GetStats` 函数正确复制所有统计数据: + +```go// GetStats 获取DNS服务器统计信息 +func (s *Server) GetStats() *Stats { + s.statsMutex.Lock() + defer s.statsMutex.Unlock() + + // 创建完整的统计数据副本 + statsCopy := &Stats{ + Queries: s.stats.Queries, + Blocked: s.stats.Blocked, + Allowed: s.stats.Allowed, + Errors: s.stats.Errors, + LastQuery: s.stats.LastQuery, + AvgResponseTime: s.stats.AvgResponseTime, + TotalResponseTime: s.stats.TotalResponseTime, + QueryTypes: make(map[string]int64), + SourceIPs: make(map[string]bool), + CpuUsage: s.stats.CpuUsage, + DNSSECQueries: s.stats.DNSSECQueries, + DNSSECSuccess: s.stats.DNSSECSuccess, + DNSSECFailed: s.stats.DNSSECFailed, + DNSSECEnabled: s.stats.DNSSECEnabled, + } + + // 复制查询类型统计 + for k, v := range s.stats.QueryTypes { + statsCopy.QueryTypes[k] = v + } + + // 复制来源IP统计 + for ip := range s.stats.SourceIPs { + statsCopy.SourceIPs[ip] = true + } + + return statsCopy +} +``` + +## 修复效果 + +1. **数据一致性**:修复后,`TotalResponseTime` 和 `Queries` 之间的关系将保持正确,避免因服务器重启导致的统计数据不一致 + +2. **异常值处理**:添加响应时间合理性检查,避免异常大的响应时间影响平均响应时间计算 + +3. **计算健壮性**:优化平均响应时间计算逻辑,添加防御性编程,确保计算结果合理 + +4. **统计数据管理**:添加定期重置统计数据的功能,避免 `TotalResponseTime` 无限增长导致的溢出问题 + +5. **并发安全**:确保所有统计数据操作都是线程安全的,避免并发访问导致的数据不一致 + +## 实现步骤 + +1. 修改 `loadStatsData` 函数,修复统计数据加载逻辑 +2. 修改 `handleDNSRequest` 函数,添加响应时间合理性检查 +3. 修改 `updateStats` 函数,优化平均响应时间计算 +4. 添加 `resetStats` 函数,实现统计数据重置功能 +5. 修改 `Start` 函数,启动定期重置统计数据的协程 +6. 修复 `GetStats` 函数,确保正确复制所有统计数据 +7. 测试修复效果,验证平均响应时间计算是否正确 \ No newline at end of file diff --git a/.trae/documents/plan_20260114_150542.md b/.trae/documents/plan_20260114_150542.md new file mode 100644 index 0000000..ffde5b6 --- /dev/null +++ b/.trae/documents/plan_20260114_150542.md @@ -0,0 +1,46 @@ +## GFWList管理页面实现计划 + +### 1. 修改 `index.html` +- 在侧边栏菜单(两个位置:桌面端和移动端)添加新的菜单项"GFWList管理" +- 创建新的页面内容区域 `#gfwlist-content` +- 添加配置选项: + - GFWList总开关(checkbox) + - GFWList解析目标IP输入框 + - 通行网站开关组:谷歌、YouTube、Facebook、X(各checkbox) +- 添加保存和重启服务按钮 + +### 2. 修改 `main.js` +- 在页面标题映射中添加 `'gfwlist': 'GFWList管理'` +- 在 `contentSections` 数组中添加 `gfwlist-content` +- 添加hash为'gfwlist'时的页面初始化逻辑 + +### 3. 修改 `config.js` +- 添加GFWList页面初始化函数 `initGFWListPage()` +- 添加GFWList配置加载函数 `loadGFWListConfig()` +- 添加GFWList配置保存函数 `saveGFWListConfig()` +- 添加GFWList配置收集函数 `collectGFWListFormData()` +- 更新 `collectFormData()` 以包含新的GFWList配置字段 +- 更新 `populateConfigForm()` 移除原有的GFWList配置(已迁移到独立页面) + +### 4. 修改 `api.js` +- 添加GFWList专用的API方法(如需要) + +### 配置数据结构 +```json +{ + "gfwlist": { + "enabled": true, + "targetIP": "127.0.0.1", + "allowGoogle": true, + "allowYouTube": true, + "allowFacebook": true, + "allowTwitter": true + } +} +``` + +### 实现顺序 +1. 先修改HTML添加页面结构和菜单 +2. 修改main.js添加导航支持 +3. 修改config.js添加前端逻辑 +4. 测试验证 \ No newline at end of file diff --git a/.trae/documents/优化DNS服务器parallel模式响应时间.md b/.trae/documents/优化DNS服务器parallel模式响应时间.md new file mode 100644 index 0000000..1706f96 --- /dev/null +++ b/.trae/documents/优化DNS服务器parallel模式响应时间.md @@ -0,0 +1,86 @@ +# 优化DNS服务器parallel模式响应时间 + +## 问题分析 + +经过对代码的分析,我发现parallel模式下响应时间过高的主要原因包括: + +1. **缺少超时机制**:当前实现会等待所有上游服务器响应,单个慢服务器会拖慢整个查询 +2. **响应时间计算不合理**:使用所有响应的平均时间,而不是最快响应时间 +3. **合并响应开销大**:需要合并所有响应,增加CPU和内存开销 +4. **等待所有响应**:没有实现快速返回机制,即使收到第一个有效响应也会等待所有响应 +5. **DNSSEC验证开销**:每个响应都需要进行DNSSEC验证,增加额外开销 + +## 优化方案 + +### 1. 添加超时机制 +- 为每个上游服务器请求添加超时设置 +- 超时时间可配置,建议默认500ms +- 超时的请求不会影响整体响应时间 + +### 2. 实现快速返回机制 +- 当收到第一个有效响应(成功或NXDOMAIN)时,立即返回给客户端 +- 继续处理其他响应用于合并和缓存,但不影响当前查询的响应时间 +- 优先返回带DNSSEC的响应 + +### 3. 优化响应时间计算 +- 使用最快的响应时间作为查询的响应时间 +- 保留平均响应时间用于统计,但不影响客户端感知的响应时间 + +### 4. 优化响应合并逻辑 +- 只合并成功响应,忽略错误响应 +- 合并时优先保留TTL较长的记录 +- 减少不必要的内存分配和拷贝 + +### 5. 优化DNSSEC验证 +- 只对需要返回的响应进行DNSSEC验证 +- 缓存DNSSEC验证结果,减少重复验证 + +### 6. 增加服务器健康检查 +- 定期检查上游服务器的响应时间和可用性 +- 只向健康的服务器发送请求 +- 根据历史响应时间动态调整服务器权重 + +## 实现步骤 + +1. **修改forwardDNSRequestWithCache函数**: + - 添加超时设置 + - 实现快速返回逻辑 + - 优化响应时间计算 + +2. **修改mergeResponses函数**: + - 优化合并逻辑,减少开销 + - 优先保留TTL较长的记录 + +3. **修改DNSSEC验证逻辑**: + - 只对需要返回的响应进行验证 + - 添加DNSSEC验证结果缓存 + +4. **添加服务器健康检查机制**: + - 定期检查上游服务器 + - 动态调整服务器列表 + +5. **添加配置选项**: + - 超时时间配置 + - 快速返回开关 + - 健康检查配置 + +## 预期效果 + +- **响应时间显著降低**:客户端感知的响应时间将接近最快的上游服务器响应时间 +- **资源利用率提高**:减少不必要的等待和计算 +- **鲁棒性增强**:单个慢服务器不会影响整体性能 +- **用户体验改善**:更快的DNS解析速度 + +## 文件修改 + +- `/root/dns/dns/server.go`:主要修改文件,包含parallel模式的实现逻辑 +- `/root/dns/config/config.go`:添加新的配置选项 +- `/root/dns/dns/cache.go`:如果需要添加DNSSEC验证结果缓存 + +## 测试计划 + +1. **性能测试**:比较优化前后的响应时间 +2. **压力测试**:在高并发情况下测试性能 +3. **可靠性测试**:测试单个服务器故障时的表现 +4. **DNSSEC测试**:确保DNSSEC验证仍然正常工作 +5. **不同配置测试**:测试不同超时时间和服务器数量的影响 \ No newline at end of file diff --git a/.trae/documents/修复DNS服务器CNAME处理和NXDOMAIN错误问题.md b/.trae/documents/修复DNS服务器CNAME处理和NXDOMAIN错误问题.md new file mode 100644 index 0000000..e7af61d --- /dev/null +++ b/.trae/documents/修复DNS服务器CNAME处理和NXDOMAIN错误问题.md @@ -0,0 +1,182 @@ +## 问题分析 + +通过深入分析代码,我找到了导致所有查询都显示同一个 NXDOMAIN 错误的根本原因: + +**核心问题**:`mergeResponses` 函数在合并多个 DNS 响应时,**没有正确处理 Rcode 字段**! + +**具体原因**: +1. 当使用并行查询模式时,DNS 服务器会向多个上游服务器发送请求 +2. 函数使用第一个响应作为基础来合并其他响应 +3. 它清空了 `Answer`、`Ns` 和 `Extra` 字段,但**保留了第一个响应的 Rcode** +4. 如果第一个响应返回 NXDOMAIN(比如对于恶意域名 www.evilsnssdk.com),那么**合并后的响应也会保持 NXDOMAIN 状态**,即使其他响应返回成功 +5. 这导致所有查询都显示同一个 NXDOMAIN 错误 + +## 修复方案 + +### 修复 `mergeResponses` 函数 + +**关键修改点**: +1. **重置 Rcode**:在合并响应前,将 Rcode 重置为成功状态 +2. **处理 NXDOMAIN**:只有当所有响应都是 NXDOMAIN 时,才返回 NXDOMAIN +3. **优先使用成功响应**:如果有任何响应返回成功,就使用成功的 Rcode + +### 修复步骤 + +1. **修改 `mergeResponses` 函数** (`/root/dns/dns/server.go:842-933`) + - 在合并记录前,将 `mergedResponse.Rcode` 设置为 `dns.RcodeSuccess` + - 添加变量 `allNXDOMAIN` 来跟踪是否所有响应都是 NXDOMAIN + - 遍历所有响应,检查是否有成功响应 + - 如果所有响应都是 NXDOMAIN,才将 `mergedResponse.Rcode` 设置为 `dns.RcodeNameError` + +2. **优化合并逻辑** + - 确保优先使用成功响应中的记录 + - 避免将 NXDOMAIN 响应的记录合并到成功响应中 + - 保持响应的一致性,Rcode 与记录内容匹配 + +## 修复代码 + +```go +// mergeResponses 合并多个DNS响应 +func mergeResponses(responses []*dns.Msg) *dns.Msg { + if len(responses) == 0 { + return nil + } + + // 如果只有一个响应,直接返回,避免不必要的合并操作 + if len(responses) == 1 { + return responses[0].Copy() + } + + // 使用第一个响应作为基础 + mergedResponse := responses[0].Copy() + mergedResponse.Answer = []dns.RR{} + mergedResponse.Ns = []dns.RR{} + mergedResponse.Extra = []dns.RR{} + + // 重置Rcode为成功,除非所有响应都是NXDOMAIN + mergedResponse.Rcode = dns.RcodeSuccess + + // 检查是否所有响应都是NXDOMAIN + allNXDOMAIN := true + + // 收集所有成功响应的记录 + for _, resp := range responses { + if resp == nil { + continue + } + + // 如果有任何响应是成功的,就不是allNXDOMAIN + if resp.Rcode == dns.RcodeSuccess { + allNXDOMAIN = false + } + } + + // 如果所有响应都是NXDOMAIN,设置合并响应为NXDOMAIN + if allNXDOMAIN { + mergedResponse.Rcode = dns.RcodeNameError + } + + // 使用map存储唯一记录,选择最长TTL + // 预分配map容量,减少扩容开销 + answerMap := make(map[recordKey]dns.RR, len(responses[0].Answer)*len(responses)) + nsMap := make(map[recordKey]dns.RR, len(responses[0].Ns)*len(responses)) + extraMap := make(map[recordKey]dns.RR, len(responses[0].Extra)*len(responses)) + + for _, resp := range responses { + if resp == nil { + continue + } + + // 只合并与最终Rcode匹配的响应记录 + if (mergedResponse.Rcode == dns.RcodeSuccess && resp.Rcode == dns.RcodeSuccess) || + (mergedResponse.Rcode == dns.RcodeNameError && resp.Rcode == dns.RcodeNameError) { + + // 合并Answer部分 + for _, rr := range resp.Answer { + key := getRecordKey(rr) + if existing, exists := answerMap[key]; exists { + // 如果存在相同记录,选择TTL更长的 + if rr.Header().Ttl > existing.Header().Ttl { + answerMap[key] = rr + } + } else { + answerMap[key] = rr + } + } + + // 合并Ns部分 + for _, rr := range resp.Ns { + key := getRecordKey(rr) + if existing, exists := nsMap[key]; exists { + // 如果存在相同记录,选择TTL更长的 + if rr.Header().Ttl > existing.Header().Ttl { + nsMap[key] = rr + } + } else { + nsMap[key] = rr + } + } + + // 合并Extra部分 + for _, rr := range resp.Extra { + // 跳过OPT记录,避免重复 + if rr.Header().Rrtype == dns.TypeOPT { + continue + } + key := getRecordKey(rr) + if existing, exists := extraMap[key]; exists { + // 如果存在相同记录,选择TTL更长的 + if rr.Header().Ttl > existing.Header().Ttl { + extraMap[key] = rr + } + } else { + extraMap[key] = rr + } + } + } + } + + // 预分配切片容量,减少扩容开销 + mergedResponse.Answer = make([]dns.RR, 0, len(answerMap)) + mergedResponse.Ns = make([]dns.RR, 0, len(nsMap)) + mergedResponse.Extra = make([]dns.RR, 0, len(extraMap)) + + // 将map转换回切片 + for _, rr := range answerMap { + mergedResponse.Answer = append(mergedResponse.Answer, rr) + } + + for _, rr := range nsMap { + mergedResponse.Ns = append(mergedResponse.Ns, rr) + } + + for _, rr := range extraMap { + mergedResponse.Extra = append(mergedResponse.Extra, rr) + } + + return mergedResponse +}``` + +## 预期效果 + +修复后,DNS服务器将能够: +- 正确合并多个 DNS 响应 +- 确保 Rcode 与实际记录内容匹配 +- 只有当所有响应都是 NXDOMAIN 时才返回 NXDOMAIN +- 避免单个恶意域名影响所有查询结果 +- 正确显示各个域名的查询结果 + +## 修复代码位置 + +- **核心修改文件**:`/root/dns/dns/server.go` +- **关键函数**:`mergeResponses` +- **修改内容**:修复响应合并逻辑,正确处理 Rcode 字段 + +## 测试方法 + +1. 使用 nslookup 测试不同域名 +2. 检查是否每个域名都显示正确的查询结果 +3. 验证 www.evilsnssdk.com 返回 NXDOMAIN,而其他域名返回成功 +4. 检查日志中是否还有大量错误信息 + +这个修复将彻底解决所有查询都显示同一个 NXDOMAIN 错误的问题! \ No newline at end of file diff --git a/.trae/documents/修复Web页面解析类型卡片百分比显示问题.md b/.trae/documents/修复Web页面解析类型卡片百分比显示问题.md new file mode 100644 index 0000000..73ab1a5 --- /dev/null +++ b/.trae/documents/修复Web页面解析类型卡片百分比显示问题.md @@ -0,0 +1,58 @@ +## 实现计划 + +### 1. 配置文件修改 + +* **修改`/root/dns/config/config.go`**: + + * 在`DNSConfig`结构体中添加`PrefixDomain []string`字段,用于支持search domain功能 + + * 在`LoadConfig`函数中添加`prefixDomain`的默认值处理 + +### 2. DNS请求处理逻辑修改 + +* **修改`/root/dns/dns/server.go`中的`forwardDNSRequestWithCache`函数**: + + * 强化domainSpecificDNS逻辑,确保当域名匹配时,只使用指定的DNS服务器 + + * 移除向DNSSEC专用服务器发送请求的逻辑,当域名匹配domainSpecificDNS时 + + * 确保匹配域名的DNS查询结果不会被其他DNS服务器的响应覆盖 + +* **修改`/root/dns/dns/server.go`中的`handleDNSRequest`函数**: + + * 实现search domain功能,当直接查询失败时,尝试添加prefixDomain中指定的域名前缀 + + * 按照/etc/resolv.conf中的search domain逻辑处理查询请求 + +### 3. 配置文件示例更新 + +* **更新配置文件示例**: + + * 添加`prefixDomain`配置项示例 + + * 说明search domain功能的使用方法 + +### 4. 测试验证 + +* 测试domainSpecificDNS强制使用功能,确保匹配的域名只使用指定的DNS服务器 + +* 测试search domain功能,确保能够正确处理带前缀的域名查询 + +* 测试不同配置组合下的功能正确性 + +## 预期效果 + +1. 当域名匹配domainSpecificDNS配置时,无论DNSSEC是否启用,只使用指定的DNS服务器 +2. 支持search domain功能,能够自动尝试添加配置的域名前缀 +3. 配置简单直观,与/etc/resolv.conf的search domain行兼容 + +## 实现要点 + +* 确保domainSpecificDNS配置的优先级最高 + +* 实现高效的search domain查询逻辑,避免不必要的网络请求 + +* 保持代码的可读性和可维护性 + +* 确保与现有功能的兼容性 + diff --git a/.trae/documents/修复picsum.photos域名匹配问题.md b/.trae/documents/修复picsum.photos域名匹配问题.md new file mode 100644 index 0000000..948acbc --- /dev/null +++ b/.trae/documents/修复picsum.photos域名匹配问题.md @@ -0,0 +1,20 @@ +# 修复picsum.photos域名匹配问题 + +## 问题分析 +当DNS查询picsum.photos时,实际传递给`getDomainInfo()`函数的域名可能带有末尾点(如"picsum.photos."),而domain-info.json中存储的是没有末尾点的域名(如"picsum.photos")。在原始的`isDomainMatch()`函数中,直接比较两个域名,导致匹配失败。 + +## 解决方案 +修改`isDomainMatch()`函数,在域名比较前对两个域名进行规范化处理,去除末尾的点,确保匹配准确性。 + +## 实施步骤 +1. 修改`isDomainMatch()`函数,在域名比较前对`urlDomain`和`targetDomain`进行规范化处理,去除末尾的点 +2. 确保无论是完整URL还是纯域名,都能正确匹配 +3. 测试修改后的代码,确保picsum.photos域名能被正确匹配 + +## 修改内容 +- 文件:`/root/dns/static/js/logs.js` +- 函数:`isDomainMatch()` +- 修改点:在域名比较前,去除`urlDomain`和`targetDomain`末尾的点 + +## 预期效果 +修改后,picsum.photos域名(无论是否带有末尾点)都能被正确匹配到domain-info.json中的条目。 \ No newline at end of file diff --git a/.trae/documents/修复域名信息为空的问题.md b/.trae/documents/修复域名信息为空的问题.md new file mode 100644 index 0000000..f5cc7b2 --- /dev/null +++ b/.trae/documents/修复域名信息为空的问题.md @@ -0,0 +1,56 @@ +## 问题分析 + +域名信息显示为空,特别是对于`wx.qq.com`这样的域名,虽然在`domain-info.json`中已配置,但日志详情中显示为空。 + +### 可能的原因 + +1. **文件路径问题**:`loadDomainInfoDatabase`函数使用`fetch('/domain-info/domains/domain-info.json')`加载文件,但服务器可能没有正确配置该路径,导致加载失败。 + +2. **代码逻辑问题**:虽然代码逻辑看起来正确,但可能存在一些边缘情况没有处理好。 + +3. **调试信息不足**:代码中缺少调试信息,难以定位具体问题。 + +## 解决方案 + +### 1. 添加调试日志 + +在`loadDomainInfoDatabase`和`getDomainInfo`函数中添加调试日志,以便定位问题: + +- 在`loadDomainInfoDatabase`函数中,添加日志记录文件加载状态和结果 +- 在`getDomainInfo`函数中,添加日志记录域名匹配过程 +- 在`isDomainMatch`函数中,添加日志记录URL匹配细节 + +### 2. 检查并修复文件路径 + +确保`domain-info.json`文件能被正确访问: + +- 检查服务器配置,确保`/domain-info/domains/domain-info.json`路径指向正确的文件 +- 或者修改代码中的路径,使用绝对路径或正确的相对路径 + +### 3. 增强错误处理 + +在`loadDomainInfoDatabase`函数中增强错误处理,提供更详细的错误信息: + +- 记录完整的错误信息 +- 在控制台显示友好的错误提示 +- 考虑添加重试机制 + +### 4. 优化域名匹配逻辑 + +虽然代码逻辑看起来正确,但可以进一步优化: + +- 确保所有URL都被正确处理,无论是否包含协议 +- 增强主域名提取逻辑,处理更多特殊情况 +- 考虑添加缓存机制,提高匹配效率 + +## 实施步骤 + +1. 修改`loadDomainInfoDatabase`函数,添加调试日志和增强错误处理 +2. 修改`getDomainInfo`函数,添加调试日志 +3. 修改`isDomainMatch`函数,添加调试日志 +4. 测试修复效果 +5. 根据测试结果进一步优化 + +## 预期效果 + +修复后,域名信息将能正确显示在日志详情中,特别是对于`wx.qq.com`等已配置的域名。 \ No newline at end of file diff --git a/.trae/documents/修复拦截和放行功能.md b/.trae/documents/修复拦截和放行功能.md new file mode 100644 index 0000000..8ff0d8e --- /dev/null +++ b/.trae/documents/修复拦截和放行功能.md @@ -0,0 +1,44 @@ +1. **修复规则添加逻辑**: + + * 修改`blockDomain`函数,使用正确的规则格式(如 `||domain^`) + + * 修改`unblockDomain`函数,使用正确的放行规则格式(如 `@@||domain^`) + + * 确保规则经过预处理后再发送到API + +2. **更新本地规则列表**: + + * 添加规则成功后,更新`rules`和`filteredRules`数组 + + * 调用`renderRulesList`函数重新渲染规则列表 + + * 更新规则数量统计 + +3. **确保规则同步**: + + * 验证规则是否正确添加到本地规则列表 + + * 确保日志页面和规则页面的数据一致性 + +4. **优化用户体验**: + + * 改进操作反馈,显示更详细的成功/失败信息 + + * 确保规则添加后立即在日志中反映状态变化 + +5. **测试功能**: + + * 测试拦截域名功能 + + * 测试放行域名功能 + + * 验证规则是否正确添加到本地规则列表 + + * 验证日志状态是否正确更新 + +**修改文件**: + +* `/root/dns/static/js/logs.js`:修复`blockDomain`和`unblockDomain`函数 + +* 确保与`/root/dns/static/js/modules/rules.js`的规则处理逻辑保持一致 + diff --git a/.trae/documents/修复拦截放行功能.md b/.trae/documents/修复拦截放行功能.md new file mode 100644 index 0000000..329c3d8 --- /dev/null +++ b/.trae/documents/修复拦截放行功能.md @@ -0,0 +1,56 @@ +1. **确认apiRequest函数的使用**: + + * 检查logs.js中apiRequest函数的来源和调用方式 + + * 确保使用正确的API\_BASE\_URL和异步apiRequest函数 + +2. **修复API调用路径**: + + * 确保拦截和放行功能使用正确的API路径 + + * 验证与规则管理模块的API调用一致性 + +3. **改进规则格式**: + + * 确保拦截和放行规则使用正确的格式 + + * 与规则管理模块的规则处理逻辑保持一致 + +4. **添加详细的错误处理**: + + * 在apiRequest调用中添加详细的日志 + + * 改进错误处理,显示更详细的错误信息 + + * 添加调试信息,以便跟踪问题 + +5. **确保跨文件函数调用正确**: + + * 确保logs.js能正确访问apiRequest函数 + + * 验证全局变量和函数的可用性 + +6. **测试功能**: + + * 测试拦截域名功能 + + * 测试放行域名功能 + + * 验证规则是否正确添加到本地规则列表 + + * 验证日志状态是否正确更新 + +**修改文件**: + +* `/root/dns/static/js/logs.js`:修复apiRequest函数调用和API路径 + +**预期效果**: + +* 拦截功能正常工作,规则正确添加到本地规则列表 + +* 放行功能正常工作,规则正确添加到本地规则列表 + +* 日志状态立即更新,显示正确的拦截/放行状态 + +* 显示详细的操作反馈和错误信息 + diff --git a/.trae/documents/修复日志显示undefined问题.md b/.trae/documents/修复日志显示undefined问题.md new file mode 100644 index 0000000..70d720b --- /dev/null +++ b/.trae/documents/修复日志显示undefined问题.md @@ -0,0 +1,67 @@ +## 问题分析 + +从截图中可以看到,日志表格显示了"Invalid Date"、"undefined"等错误值,这表明前端代码在解析API返回的JSON数据时出现了问题。 + +## 根本原因 + +1. 后端`QueryLog`结构体的JSON标签使用了小写字段名(如`json:"timestamp"`、`json:"clientIP"`等) +2. 前端代码使用了大写的字段名(如`log.Timestamp`、`log.ClientIP`等)来访问数据 +3. 字段名大小写不匹配导致前端无法正确解析API返回的数据 + +## 修复方案 + +修改前端代码,使用正确的小写字段名来访问API返回的数据,与后端返回的JSON格式匹配。 + +## 具体修改点 + +1. **`updateLogsTable`函数**(约435-621行): + + * 将`log.Timestamp`改为`log.timestamp` + + * 将`log.ClientIP`改为`log.clientIP` + + * 将`log.Domain`改为`log.domain` + + * 将`log.QueryType`改为`log.queryType` + + * 将`log.ResponseTime`改为`log.responseTime` + + * 将`log.Result`改为`log.result` + + * 将`log.BlockRule`改为`log.blockRule` + + * 将`log.FromCache`改为`log.fromCache` + + * 将`log.DNSSEC`改为`log.dnssec` + + * 将`log.EDNS`改为`log.edns` + + * 将`log.DNSServer`改为`log.dnsServer` + + * 将`log.DNSSECServer`改为`log.dnssecServer` + + * 将`log.ResponseCode`改为`log.responseCode` + +2. **`showLogDetailModal`函数**(约1450-1597行): + + * 同样修改所有字段名的大小写 + +3. **其他可能的访问点**: + + * 检查并修改任何其他访问日志数据的地方 + +## 修复原则 + +* 保持前端代码与后端API返回的JSON格式一致 + +* 遵循REST API的最佳实践,使用小写字段名 + +* 确保所有日志数据访问点都得到修复 + +## 验证方法 + +1. 修复代码后,重新编译并运行服务器 +2. 访问日志页面,检查日志数据是否正确显示 +3. 测试不同类型的日志(允许、屏蔽、错误),确保都能正确显示 +4. 测试日志详情模态框,确保所有字段都能正确显示 + diff --git a/.trae/documents/修复添加自定义规则后需要重启服务器的问题.md b/.trae/documents/修复添加自定义规则后需要重启服务器的问题.md new file mode 100644 index 0000000..d146868 --- /dev/null +++ b/.trae/documents/修复添加自定义规则后需要重启服务器的问题.md @@ -0,0 +1,21 @@ +1. **问题分析**: + - 当添加自定义规则时,规则被添加到内存中并保存到文件 + - 但由于DNS缓存的存在,如果该域名的DNS响应已经被缓存,那么在缓存过期之前,DNS服务器会直接返回缓存的响应,而不会重新检查规则 + - 这就导致了添加规则后需要重启服务器才能生效的问题 + +2. **修复方案**: + - 当添加或删除自定义规则时,清空DNS缓存,这样新的规则会立即生效 + - 这样,当客户端再次请求该域名时,DNS服务器会重新检查规则,而不是直接返回缓存的响应 + +3. **修复步骤**: + - 修改HTTP API处理函数,在添加或删除规则后,清空DNS缓存 + - 或者,修改ShieldManager的AddRule和RemoveRule方法,添加清空DNS缓存的逻辑 + - 测试修复后的功能,确保添加规则后无需重启服务器即可生效 + +4. **预期结果**: + - 添加自定义规则后,规则会立即生效,无需重启服务器 + - 重启服务器后,之前添加的自定义规则仍然存在 + +5. **具体实现**: + - 在`/root/dns/http/server.go`中,在添加或删除规则的API处理函数后,调用DNS缓存的Clear方法 + - 这样,当添加或删除规则时,DNS缓存会被清空,新的规则会立即生效 \ No newline at end of file diff --git a/.trae/documents/修改gfwList配置从内容到文件路径.md b/.trae/documents/修改gfwList配置从内容到文件路径.md new file mode 100644 index 0000000..327667f --- /dev/null +++ b/.trae/documents/修改gfwList配置从内容到文件路径.md @@ -0,0 +1,79 @@ +## DNS服务器性能优化方案 + +### 问题分析 + +1. **并行查询模式**:当前配置使用`parallel`模式,会等待所有上游服务器响应后才返回,受最慢服务器影响 +2. **DNSSEC验证开销**:启用了DNSSEC验证,增加了额外的计算和网络请求 +3. **过多上游服务器**:DNSSEC上游服务器多达5个,响应时间差异大 +4. **调试级别日志**:`debug`级别日志记录大量信息,占用CPU和I/O资源 +5. **缓存TTL过短**:10秒的缓存TTL导致频繁向上游请求 +6. **黑名单规则过多**:14个启用的黑名单,每次请求都需要检查 + +### 优化方案 + +#### 1. 修改查询模式为快速返回 + +* 将`queryMode`从`parallel`改为`fastest-ip`或优化默认模式 + +* 快速返回模式会返回第一个有效响应,而不是等待所有响应 + +#### 2. 优化DNSSEC配置 + +* 减少DNSSEC上游服务器数量,只保留2-3个可靠的 + +* 对国内域名禁用DNSSEC验证(已配置部分,可扩展) + +#### 3. 调整缓存策略 + +* 增加`cacheTTL`到60秒或更高,减少上游请求频率 + +* 优化缓存实现,减少锁竞争 + +#### 4. 降低日志级别 + +* 将日志级别从`debug`改为`info`或`warn`,减少日志写入开销 + +#### 5. 优化黑名单处理 + +* 合并重复的黑名单规则 + +* 考虑使用更高效的域名匹配算法 + +#### 6. 代码优化 + +* 减少DNSSEC验证的重复调用 + +* 优化响应合并逻辑,避免不必要的计算 + +* 调整超时设置,避免过长等待 + +### 具体修改点 + +1. **config.json**: + + * 修改`queryMode`为`fastest-ip` + + * 减少`dnssecUpstreamDNS`数量 + + * 增加`cacheTTL`到60 + + * 将日志级别改为`info` + +2. **dns/server.go**: + + * 优化`forwardDNSRequestWithCache`函数,减少DNSSEC重复验证 + + * 优化响应合并逻辑,避免不必要的计算 + + * 调整并行模式的超时处理 + +### 预期效果 + +* 减少响应时间,从当前的秒级降低到毫秒级 + +* 减少CPU和I/O资源占用 + +* 提高并发处理能力 + +* 保持DNS解析的准确性和可靠性 + diff --git a/.trae/documents/内存泄露问题.md b/.trae/documents/内存泄露问题.md new file mode 100644 index 0000000..1fdcf3a --- /dev/null +++ b/.trae/documents/内存泄露问题.md @@ -0,0 +1,47 @@ +# 内存泄漏问题 +## 1. IP地理位置缓存泄漏 +问题:IP地理位置缓存(```ipGeolocationCache```)缺少全局过期清理机制,仅在获取新数据时检查单条缓存过期,导致长期运行后内存无限增长。 + +修复建议: + +```Go +// 在Server结构体中添加清理定时器ipGeolocationCacheCleanupTicker *time.Ticker// 在NewServer函数中启动清理协程go s.startIPGeolocationCacheCleanup()// 添加清理函数func (s *Server) startIPGeolocationCacheCleanup() { ticker := time.NewTicker(time. Hour) // 每小时清理一次 defer ticker.Stop() for { select { case <-ticker.C: s. cleanupExpiredIPGeolocat ionCache() case <-s.ctx.Done(): return } }}func (s *Server) cleanupExpiredIPGeolocationCache() { now := time.Now() s.ipGeolocationCacheMutex.Lock() defer s.ipGeolocationCacheMutex. Unlock() for ip, geo := range s. ipGeolocationCache { if now.After(geo.Expiry) { delete(s. ipGeolocationCache, ip) } }} +``` +# 2. DNS缓存无大小限制 +问题:DNS缓存(DNSCache)仅按TTL过期,无最大条目数限制,可能导致内存无限增长。 + +修复建议: + +```Go +// 在DNSCache结构体中添加最大大小限制maxSize int// 在Set方法中添加大小限制检查func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.Duration) { // 现有代码... c.mutex.Lock() defer c.mutex.Unlock() c.cache[key] = item // 检查并清理超过最大大小的缓存 if c.maxSize > 0 && len(c. cache) > c.maxSize { c.evictOldest() // 实现LRU或 随机淘汰策略 }} +``` +## 1. 域名检查函数性能优化 +问题:```CheckDomainBlockDetails```函数中重复的子域名检查和大量正则表达式匹配导致CPU使用率过高。 + +修复建议: + +```Go +// 合并本地和远程规则检查,减少循环次数func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interface{} { // 预处理域名 // ... // 合并域名排除规则检查 if m.domainExceptions[domain] { result["excluded"] = true result["excludeRule"] = m. domainExceptionsOriginal [domain] result["excludeRuleType"] = "exact_domain" result["blocksource"] = m. domainExceptionsSource [domain] return result } // 合并子域名检查 parts := strings.Split(domain, ".") for i := 0; i < len(parts)-1; i ++ { subdomain := strings.Join (parts[i:], ".") if m.domainExceptions [subdomain] { result["excluded"] = true result["excludeRule"] = m. domainExceptionsOriginal [subdomain] result ["excludeRuleType"] = "subdomain" result["blocksource"] = m.domainExceptionsSource [subdomain] return result } } // 合并正则表达式检查 for _, re := range m. regexExceptions { if re.pattern.MatchString (domain) { result["excluded"] = true result["excludeRule"] = re.original result ["excludeRuleType"] = "regex" result["blocksource"] = re.source return result } } // 阻止规则检查采用相同优化方式 // ...} +``` +## 2. 减少锁持有时间 +问题:```CheckDomainBlockDetails```函数持有读锁时间过长,影响并发性能。 +修复建议: +```Go +// 采用更细粒度的锁或读写分离策略func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interface{} { // 只在需要访问共享数据时加锁 m.rulesMutex.RLock() domainExists := m. domainExceptions[domain] m.rulesMutex.RUnlock() if domainExists { m.rulesMutex.RLock() // 获取详细信息 m.rulesMutex.RUnlock() return result } // 其他检查采用相同方式} +``` +# 其他优化建议 +## 1. DNS缓存优化 +实现LRU缓存淘汰策略,限制最大缓存大小 +定期统计缓存命中率,调整缓存策略 +## 2. WebSocket连接管理 +实现连接池管理,限制最大连接数 +添加连接心跳检测机制,及时清理无效连接 +## 3. 并行查询优化 +限制同时发起的上游查询数量,避免资源耗尽 +实现超时控制,防止长时间阻塞 +## 4. 正则表达式优化 +预编译所有正则表达式规则 +合并相似规则,减少正则表达式数量 +使用更高效的规则匹配算法 +# 总结 +通过实施上述修复建议和优化方案,可以有效解决DNS服务器的内存泄漏问题和CPU性能瓶颈,提高系统稳定性和响应速度。建议优先修复IP地理位置缓存泄漏和域名检查函数性能问题,这些是当前最严重的问题。 \ No newline at end of file diff --git a/.trae/documents/在查询日志详情界面增加操作列.md b/.trae/documents/在查询日志详情界面增加操作列.md new file mode 100644 index 0000000..e0a90c4 --- /dev/null +++ b/.trae/documents/在查询日志详情界面增加操作列.md @@ -0,0 +1,69 @@ +## 实现计划 + +### 1. 分析当前代码结构 + +* **HTML结构**:日志表格在`updateLogsTable`函数中动态生成 +* **数据处理**:每条日志包含`Result`字段,表示查询结果(allowed, blocked, error) +* **API需求**:需要拦截/放行域名的API接口 + +### 2. 修改内容 + +#### 2.1 修改`updateLogsTable`函数 + +* 在表格中添加操作列 +* 根据`log.Result`字段决定显示哪个按钮: + - 如果`log.Result`为`blocked`,显示"放行"按钮 + - 否则,显示"拦截"按钮 +* 为按钮添加点击事件处理函数 + +#### 2.2 实现拦截/放行功能 + +* 添加`blockDomain`函数:调用API拦截域名 +* 添加`allowDomain`函数:调用API放行域名 +* 实现按钮点击事件的处理逻辑 +* 添加操作成功后的反馈机制 + +#### 2.3 更新表格头部 + +* 在表格头部添加"操作"列 +* 确保表格的`colspan`属性正确更新 + +### 3. 实现细节 + +* **按钮样式**:使用现有的样式类,保持界面一致性 +* **事件绑定**:为动态生成的按钮绑定点击事件 +* **API调用**:使用现有的`apiRequest`函数进行API调用 +* **反馈机制**:操作成功后显示提示信息 +* **数据刷新**:操作完成后刷新日志列表,显示最新状态 + +### 4. 预期效果 + +* 日志详情表格右侧增加"操作"列 +* 对于被拦截的域名,显示"放行"按钮 +* 对于未被拦截的域名,显示"拦截"按钮 +* 点击按钮后,成功执行相应操作并刷新列表 +* 操作过程中显示反馈信息 + +### 5. 实现步骤 + +1. 修改`updateLogsTable`函数,添加操作列 +2. 更新表格头部,添加"操作"列 +3. 实现`blockDomain`和`allowDomain`函数 +4. 为按钮绑定点击事件 +5. 添加操作反馈机制 +6. 测试功能完整性 + +## 关键代码修改点 + +* **`updateLogsTable`函数**:在生成表格行时添加操作列 +* **表格头部**:添加"操作"列标题 +* **新增函数**:`blockDomain`和`allowDomain` +* **事件绑定**:为动态生成的按钮添加点击事件处理 + +## 技术要点 + +* 动态生成元素的事件绑定 +* API调用的错误处理 +* 操作反馈机制的实现 +* 数据刷新逻辑 +* 保持界面样式一致性 diff --git a/.trae/documents/实现domainSpecificDNS强制使用和search域支持.md b/.trae/documents/实现domainSpecificDNS强制使用和search域支持.md index 41dca6e..73ab1a5 100644 --- a/.trae/documents/实现domainSpecificDNS强制使用和search域支持.md +++ b/.trae/documents/实现domainSpecificDNS强制使用和search域支持.md @@ -1,37 +1,58 @@ ## 实现计划 ### 1. 配置文件修改 -- **修改`/root/dns/config/config.go`**: - - 在`DNSConfig`结构体中添加`PrefixDomain []string`字段,用于支持search domain功能 - - 在`LoadConfig`函数中添加`prefixDomain`的默认值处理 + +* **修改`/root/dns/config/config.go`**: + + * 在`DNSConfig`结构体中添加`PrefixDomain []string`字段,用于支持search domain功能 + + * 在`LoadConfig`函数中添加`prefixDomain`的默认值处理 ### 2. DNS请求处理逻辑修改 -- **修改`/root/dns/dns/server.go`中的`forwardDNSRequestWithCache`函数**: - - 强化domainSpecificDNS逻辑,确保当域名匹配时,只使用指定的DNS服务器 - - 移除向DNSSEC专用服务器发送请求的逻辑,当域名匹配domainSpecificDNS时 - - 确保匹配域名的DNS查询结果不会被其他DNS服务器的响应覆盖 -- **修改`/root/dns/dns/server.go`中的`handleDNSRequest`函数**: - - 实现search domain功能,当直接查询失败时,尝试添加prefixDomain中指定的域名前缀 - - 按照/etc/resolv.conf中的search domain逻辑处理查询请求 +* **修改`/root/dns/dns/server.go`中的`forwardDNSRequestWithCache`函数**: + + * 强化domainSpecificDNS逻辑,确保当域名匹配时,只使用指定的DNS服务器 + + * 移除向DNSSEC专用服务器发送请求的逻辑,当域名匹配domainSpecificDNS时 + + * 确保匹配域名的DNS查询结果不会被其他DNS服务器的响应覆盖 + +* **修改`/root/dns/dns/server.go`中的`handleDNSRequest`函数**: + + * 实现search domain功能,当直接查询失败时,尝试添加prefixDomain中指定的域名前缀 + + * 按照/etc/resolv.conf中的search domain逻辑处理查询请求 ### 3. 配置文件示例更新 -- **更新配置文件示例**: - - 添加`prefixDomain`配置项示例 - - 说明search domain功能的使用方法 + +* **更新配置文件示例**: + + * 添加`prefixDomain`配置项示例 + + * 说明search domain功能的使用方法 ### 4. 测试验证 -- 测试domainSpecificDNS强制使用功能,确保匹配的域名只使用指定的DNS服务器 -- 测试search domain功能,确保能够正确处理带前缀的域名查询 -- 测试不同配置组合下的功能正确性 + +* 测试domainSpecificDNS强制使用功能,确保匹配的域名只使用指定的DNS服务器 + +* 测试search domain功能,确保能够正确处理带前缀的域名查询 + +* 测试不同配置组合下的功能正确性 ## 预期效果 + 1. 当域名匹配domainSpecificDNS配置时,无论DNSSEC是否启用,只使用指定的DNS服务器 2. 支持search domain功能,能够自动尝试添加配置的域名前缀 3. 配置简单直观,与/etc/resolv.conf的search domain行兼容 ## 实现要点 -- 确保domainSpecificDNS配置的优先级最高 -- 实现高效的search domain查询逻辑,避免不必要的网络请求 -- 保持代码的可读性和可维护性 -- 确保与现有功能的兼容性 \ No newline at end of file + +* 确保domainSpecificDNS配置的优先级最高 + +* 实现高效的search domain查询逻辑,避免不必要的网络请求 + +* 保持代码的可读性和可维护性 + +* 确保与现有功能的兼容性 + diff --git a/.trae/documents/实现域名信息显示.md b/.trae/documents/实现域名信息显示.md new file mode 100644 index 0000000..a62276b --- /dev/null +++ b/.trae/documents/实现域名信息显示.md @@ -0,0 +1,31 @@ +# 实现域名信息显示 + +## 需求分析 +用户需要在日志详情页面的红色框区域显示域名信息,包括: +- 网站名称(带图标) +- 网站类别 +- 所属公司 + +## 代码分析 +1. 目前代码已经包含了获取域名信息的功能: + - `loadDomainInfoDatabase` 函数加载域名信息数据库 + - `getDomainInfo` 函数根据域名查找对应的网站信息 + - `showLogDetailModal` 函数显示日志详情,包括域名信息 + +2. 域名信息已经在日志详情模态框中显示,但需要确保它在指定的红色框区域正确显示 + +## 实现计划 +1. **优化域名信息显示样式**:调整 `showLogDetailModal` 函数中域名信息的HTML结构和样式,确保它在红色框内正确显示 +2. **增强域名匹配逻辑**:改进 `getDomainInfo` 函数,提高域名匹配的准确性 +3. **添加错误处理**:确保在域名信息加载失败时,页面能够优雅处理 +4. **测试功能**:验证域名信息能够正确显示在红色框区域 + +## 预期效果 +- 当日志详情弹窗打开时,在红色框区域显示域名的完整信息 +- 显示格式: + ``` + [图标] 网站名称 + 类别: [类别名称] + 所属公司: [公司名称] + ``` +- 如果没有匹配的域名信息,显示"无" \ No newline at end of file diff --git a/.trae/documents/实现客户端IP地址位置显示.md b/.trae/documents/实现客户端IP地址位置显示.md new file mode 100644 index 0000000..815bde8 --- /dev/null +++ b/.trae/documents/实现客户端IP地址位置显示.md @@ -0,0 +1,18 @@ +1. **更新getIpGeolocation函数**:修改函数逻辑,使用API响应中的addr字段来显示完整的地理位置信息 +2. **处理编码问题**:确保正确处理中文编码 +3. **维护缓存机制**:保留现有的24小时缓存机制,提高性能 +4. **测试不同IP类型**:确保公网IP和内网IP都能正确显示 + +### 具体修改点 +- **文件**:`/root/dns/static/js/logs.js` +- **函数**:`getIpGeolocation` +- **修改内容**: + - 从API响应中提取`addr`字段 + - 直接使用addr字段作为完整的地理位置信息 + - 保持对私有IP的特殊处理 + - 维护现有的缓存机制 + +### 预期效果 +- 公网IP显示格式:"IP地址 (完整地理位置,来自addr字段)" +- 内网IP显示格式:"IP地址 (内网 内网)" +- 未知IP显示格式:"IP地址 (未知 未知)" \ No newline at end of file diff --git a/.trae/documents/彻底解决DNS服务器Server Failed问题.md b/.trae/documents/彻底解决DNS服务器Server Failed问题.md new file mode 100644 index 0000000..2e82b61 --- /dev/null +++ b/.trae/documents/彻底解决DNS服务器Server Failed问题.md @@ -0,0 +1,81 @@ +## 问题分析 + +服务器出现"Server Failed"问题的主要原因是: + +1. **DNS查询超时设置过短**:配置文件中timeout设置为5ms,导致几乎所有DNS查询都超时失败 +2. **parallel模式响应处理逻辑缺陷**:在某些情况下,即使主DNS服务器有响应,也可能因为DNSSEC验证或其他原因被忽略 +3. **DNSSEC处理影响正常查询**:启用DNSSEC后,系统会优先选择带DNSSEC记录的响应,但如果所有DNSSEC服务器都失败,可能导致没有响应 + +## 解决方案 + +### 1. 确保超时设置正确 + +* **已修复**:将配置文件中的timeout从5ms修改为5000ms + +* 验证代码中默认值设置正确,确保配置文件加载时能正确应用默认值 + +### 2. 优化parallel模式响应处理逻辑 + +**问题**:当前parallel模式下,只有当响应是成功(RcodeSuccess)或NXDOMAIN(RcodeNameError)时才会被考虑作为最佳响应,且优先选择带DNSSEC记录的响应。 + +**修复**: + +* 修改`dns/server.go`中的parallel模式处理逻辑 + +* 确保用户配置的主DNS服务器(upstreamDNS)响应优先被使用,即使它没有DNSSEC记录 + +* 只有当主DNS服务器完全失败时,才考虑使用DNSSEC专用服务器的响应 + +* 确保在所有情况下都能返回至少一个备选响应 + +### 3. 增强错误处理机制 + +**问题**:当所有DNS服务器都返回非成功响应时,系统可能无法找到合适的响应返回给客户端。 + +**修复**: + +* 在`dns/server.go`中增强错误处理 + +* 确保即使所有上游服务器都失败,也能返回一个有效的DNS响应 + +* 优化备选响应的选择逻辑,确保总有一个可用的响应 + +* 添加更详细的日志记录,便于调试 + +### 4. 优化DNSSEC处理逻辑 + +**问题**:当前DNSSEC处理逻辑可能导致主DNS服务器的响应被忽略。 + +**修复**: + +* 调整DNSSEC专用服务器请求逻辑,只有当主DNS服务器完全失败时才使用 + +* 确保DNSSEC验证失败不会影响正常的DNS查询结果 + +* 优化DNSSEC记录检查逻辑,确保准确判断DNSSEC记录 + +## 修复步骤 + +1. **验证超时设置**:确保配置文件中的timeout已设置为5000ms +2. **修改parallel模式响应处理**:优化`dns/server.go`中parallel模式的响应选择逻辑 +3. **增强备选响应机制**:确保总有一个可用的响应返回给客户端 +4. **优化DNSSEC处理**:调整DNSSEC专用服务器请求时机和优先级 +5. **添加详细日志**:增加调试日志,便于后续问题定位 +6. **测试验证**:重启服务器并测试DNS查询功能 + +## 预期效果 + +* DNS查询成功率显著提高,不再出现大量"Server Failed"错误 + +* 主DNS服务器响应优先被使用,确保查询效率 + +* DNSSEC功能正常工作,同时不影响正常DNS查询 + +* 系统更加稳定,能够处理各种异常情况 + +## 关键文件修改 + +* `/root/dns/config.json`:确保timeout设置正确 + +* `/root/dns/dns/server.go`:优化parallel模式响应处理、错误处理和DNSSEC逻辑 + diff --git a/.trae/documents/更新CHANGELOG.md记录最近的修复.md b/.trae/documents/更新CHANGELOG.md记录最近的修复.md new file mode 100644 index 0000000..c7ac4ee --- /dev/null +++ b/.trae/documents/更新CHANGELOG.md记录最近的修复.md @@ -0,0 +1,38 @@ +1. **问题分析**: + + * 项目已经有一个CHANGELOG.md文件,遵循Keep a Changelog格式 + + * 最新版本是\[1.1.3],发布于2025-12-19 + + * 我们需要添加新的版本条目,记录最近的修复 + +2. **修复内容**: + + * 修复了规则优先级问题:确保自定义规则优先于远程规则 + + * 修复了添加自定义规则后需要重启服务器的问题:通过在添加或删除规则后清空DNS缓存实现 + +3. **更新步骤**: + + * 在CHANGELOG.md文件中添加一个新的版本条目\[1.1.4],日期为2025-12-21 + + * 在该版本下添加修复的内容 + + * 确保格式符合CHANGELOG的要求 + +4. **预期结果**: + + * CHANGELOG.md文件将包含最新的修复记录 + + * 版本号将更新为\[1.1.4] + + * 修复内容将被清晰地记录在CHANGELOG中 + +5. **具体实现**: + + * 在CHANGELOG.md文件的开头添加新的版本条目 + + * 使用### 修复标题记录修复的内容 + + * 清晰描述每个修复的问题和解决方案 + diff --git a/.trae/documents/配置文件移除文件路径定义并固定文件位置.md b/.trae/documents/配置文件移除文件路径定义并固定文件位置.md new file mode 100644 index 0000000..e0f824d --- /dev/null +++ b/.trae/documents/配置文件移除文件路径定义并固定文件位置.md @@ -0,0 +1,46 @@ +1. **修改配置结构体定义**: + + * 从`config/config.go`中的`DNSConfig`结构体移除`StatsFile`字段 + + * 从`ShieldConfig`结构体移除`LocalRulesFile`、`HostsFile`、`StatsFile`、`RemoteRulesCacheDir`字段 + + * 从`LogConfig`结构体移除`File`字段 + +2. **修改配置加载逻辑**: + + * 在`config/config.go`的`LoadConfig`函数中,移除对上述字段的默认值设置 + +3. **修改代码中使用配置的地方**: + + * `dns/server.go`:将`s.config.StatsFile`替换为硬编码的`"data/stats.json"` + + * `shield/manager.go`:将`m.config.StatsFile`替换为硬编码的`"data/shield_stats.json"` + + * 其他使用这些配置项的地方也需要相应修改 + +4. **修改默认配置生成**: + + * 在`main.go`的`createDefaultConfig`函数中,移除所有文件路径相关的配置项 + +5. **修改Web界面配置处理**: + + * 修改`static/js/config.js`,移除对这些文件路径配置项的处理 + +6. **更新配置文件**: + + * 修改`config.json`,移除所有文件路径相关的配置项 + +固定的文件位置: + +* 日志文件:`logs/dns-server.log` + +* DNS统计文件:`data/stats.json` + +* Shield统计文件:`data/shield_stats.json` + +* 本地规则文件:`data/rules.txt` + +* Hosts文件:`data/hosts.txt` + +* 远程规则缓存目录:`data/remote_rules` + diff --git a/.trae/documents/重写DNS日志详情弹窗.md b/.trae/documents/重写DNS日志详情弹窗.md new file mode 100644 index 0000000..49f1716 --- /dev/null +++ b/.trae/documents/重写DNS日志详情弹窗.md @@ -0,0 +1,399 @@ +## 重写DNS日志详情弹窗方案 + +### 1. 问题分析 +当前弹窗代码存在以下问题: +- 代码结构复杂,大量嵌套条件和重复逻辑 +- 样式不够现代,布局不够灵活 +- 响应式设计不足,在大屏幕上显示效果不佳 +- 代码冗余,重复的记录解析逻辑 +- 可维护性差,HTML结构直接写在JavaScript字符串中 + +### 2. 设计思路 +- **组件化设计**:将弹窗拆分为更小的可复用组件 +- **简化逻辑**:重构DNS解析记录处理逻辑,减少重复代码 +- **现代样式**:使用更现代的CSS布局和样式 +- **响应式设计**:优化在不同屏幕尺寸下的显示效果 +- **更好的用户体验**:添加动画效果、改进交互体验 +- **更清晰的代码结构**:提高可维护性 + +### 3. 实现方案 + +#### 3.1 重构`showLogDetailModal`函数 +- 简化函数结构,分离关注点 +- 将DNS解析记录处理逻辑提取为独立函数 +- 使用更现代的DOM API创建元素 + +#### 3.2 优化DNS解析记录处理 +- 创建独立的`formatDNSRecords`函数 +- 统一处理各种格式的DNS记录 +- 减少重复代码,提高可维护性 + +#### 3.3 现代CSS样式 +- 使用更现代的CSS类名和布局 +- 优化响应式设计,支持不同屏幕尺寸 +- 添加动画效果,提升用户体验 + +#### 3.4 改进HTML结构 +- 使用更清晰的语义化HTML +- 优化布局结构,提高可读性 +- 支持更好的响应式设计 + +### 4. 代码实现 +```javascript +// 独立的DNS记录格式化函数 +function formatDNSRecords(log, result) { + if (result === 'blocked') return '无'; + + let records = ''; + const sources = [ + log.answers, + log.answer, + log.Records, + log.records, + log.response + ]; + + for (const source of sources) { + if (records) break; + if (!source || source === '无') continue; + + // 处理数组类型 + if (Array.isArray(source)) { + records = source.map(answer => { + const type = answer.type || answer.Type || '未知'; + let value = answer.value || answer.Value || answer.data || answer.Data || '未知'; + const ttl = answer.TTL || answer.ttl || answer.expires || '未知'; + + // 增强的记录值提取逻辑 + if (typeof value === 'string') { + value = value.trim(); + // 处理制表符分隔的格式 + if (value.includes('\t') || value.includes('\\t')) { + const parts = value.replace(/\\t/g, '\t').split('\t'); + if (parts.length >= 4) { + value = parts[parts.length - 1].trim(); + } + } + // 处理JSON格式 + else if (value.startsWith('{') && value.endsWith('}')) { + try { + const parsed = JSON.parse(value); + value = parsed.data || parsed.value || value; + } catch (e) {} + } + } + + return `${type}: ${value} (ttl=${ttl})`; + }).join('\n').trim(); + } + // 处理字符串类型 + else if (typeof source === 'string') { + // 尝试解析为JSON数组 + if (source.startsWith('[') && source.endsWith(']')) { + try { + const parsed = JSON.parse(source); + if (Array.isArray(parsed)) { + records = parsed.map(answer => { + const type = answer.type || answer.Type || '未知'; + let value = answer.value || answer.Value || answer.data || answer.Data || '未知'; + const ttl = answer.TTL || answer.ttl || answer.expires || '未知'; + + if (typeof value === 'string') { + value = value.trim(); + } + + return `${type}: ${value} (ttl=${ttl})`; + }).join('\n').trim(); + } + } catch (e) { + // 解析失败,尝试直接格式化 + records = formatDNSString(source); + } + } else { + // 直接格式化字符串 + records = formatDNSString(source); + } + } + } + + return records || '无解析记录'; +} + +// 格式化DNS字符串记录 +function formatDNSString(str) { + const recordLines = str.split('\n').map(line => line.trim()).filter(line => line !== ''); + + return recordLines.map(line => { + // 检查是否已经是标准格式 + if (line.includes(':') && line.includes('(')) { + return line; + } + // 尝试解析为标准DNS格式 + const parts = line.split(/\s+/); + if (parts.length >= 5) { + const type = parts[3]; + const value = parts.slice(4).join(' '); + const ttl = parts[1]; + return `${type}: ${value} (ttl=${ttl})`; + } + // 无法解析,返回原始行 + return line; + }).join('\n'); +} + +// 重写后的showLogDetailModal函数 +async function showLogDetailModal(log) { + if (!log) { + console.error('No log data provided!'); + return; + } + + try { + // 安全获取log属性,提供默认值 + const timestamp = log.timestamp ? new Date(log.timestamp) : null; + const dateStr = timestamp ? timestamp.toLocaleDateString() : '未知'; + const timeStr = timestamp ? timestamp.toLocaleTimeString() : '未知'; + const domain = log.domain || '未知'; + const queryType = log.queryType || '未知'; + const result = log.result || '未知'; + const responseTime = log.responseTime || '未知'; + const clientIP = log.clientIP || '未知'; + const location = log.location || '未知'; + const fromCache = log.fromCache || false; + const dnssec = log.dnssec || false; + const edns = log.edns || false; + const dnsServer = log.dnsServer || '无'; + const dnssecServer = log.dnssecServer || '无'; + const blockRule = log.blockRule || '无'; + + // 检查域名是否在跟踪器数据库中 + const trackerInfo = await isDomainInTrackerDatabase(log.domain); + const isTracker = trackerInfo !== null; + + // 格式化DNS解析记录 + const dnsRecords = formatDNSRecords(log, result); + + // 创建模态框容器 + const modalContainer = document.createElement('div'); + modalContainer.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4 animate-fade-in'; + modalContainer.style.zIndex = '9999'; + + // 创建模态框内容 + const modalContent = document.createElement('div'); + modalContent.className = 'bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto animate-slide-in'; + + // 创建标题栏 + const header = document.createElement('div'); + header.className = 'sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center'; + + const title = document.createElement('h3'); + title.className = 'text-xl font-semibold text-gray-900'; + title.textContent = '日志详情'; + + const closeButton = document.createElement('button'); + closeButton.innerHTML = ''; + closeButton.className = 'text-gray-500 hover:text-gray-700 focus:outline-none transition-colors'; + closeButton.onclick = () => closeModal(); + + header.appendChild(title); + header.appendChild(closeButton); + + // 创建内容区域 + const content = document.createElement('div'); + content.className = 'p-6 space-y-6'; + + // 基本信息部分 + const basicInfo = document.createElement('div'); + basicInfo.className = 'space-y-4'; + + const basicInfoTitle = document.createElement('h4'); + basicInfoTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; + basicInfoTitle.textContent = '基本信息'; + + const basicInfoGrid = document.createElement('div'); + basicInfoGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4'; + + // 添加基本信息项 + basicInfoGrid.innerHTML = ` +
+
日期
+
${dateStr}
+
+
+
时间
+
${timeStr}
+
+
+
状态
+
+ ${result === 'blocked' ? '已拦截' : result === 'allowed' ? '允许' : result} +
+
+
+
域名
+
${domain}
+
+
+
类型
+
${queryType}
+
+ `; + + // DNS特性 + const dnsFeatures = document.createElement('div'); + dnsFeatures.className = 'col-span-1 md:col-span-2 space-y-1'; + dnsFeatures.innerHTML = ` +
DNS特性
+
+ ${dnssec ? 'DNSSEC ' : ''} + ${edns ? 'EDNS' : ''} + ${!dnssec && !edns ? '无' : ''} +
+ `; + + // 跟踪器信息 + const trackerDiv = document.createElement('div'); + trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; + trackerDiv.innerHTML = ` +
跟踪器信息
+
+ ${isTracker ? ` +
+ + ${trackerInfo.name} (${trackersDatabase.categories[trackerInfo.categoryId] || '未知'}) +
+ ` : '无'} +
+ `; + + // 解析记录 + const recordsDiv = document.createElement('div'); + recordsDiv.className = 'col-span-1 md:col-span-2 space-y-1'; + recordsDiv.innerHTML = ` +
解析记录
+
+ ${dnsRecords} +
+ `; + + // DNS服务器 + const dnsServerDiv = document.createElement('div'); + dnsServerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; + dnsServerDiv.innerHTML = ` +
DNS服务器
+
${dnsServer}
+ `; + + // DNSSEC专用服务器 + const dnssecServerDiv = document.createElement('div'); + dnssecServerDiv.className = 'col-span-1 md:col-span-2 space-y-1'; + dnssecServerDiv.innerHTML = ` +
DNSSEC专用服务器
+
${dnssecServer}
+ `; + + basicInfoGrid.appendChild(dnsFeatures); + basicInfoGrid.appendChild(trackerDiv); + basicInfoGrid.appendChild(recordsDiv); + basicInfoGrid.appendChild(dnsServerDiv); + basicInfoGrid.appendChild(dnssecServerDiv); + + basicInfo.appendChild(basicInfoTitle); + basicInfo.appendChild(basicInfoGrid); + + // 响应细节部分 + const responseDetails = document.createElement('div'); + responseDetails.className = 'space-y-4 pt-4 border-t border-gray-200'; + + const responseDetailsTitle = document.createElement('h4'); + responseDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; + responseDetailsTitle.textContent = '响应细节'; + + const responseGrid = document.createElement('div'); + responseGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4'; + responseGrid.innerHTML = ` +
+
响应时间
+
${responseTime}毫秒
+
+
+
规则
+
${blockRule}
+
+
+
响应代码
+
${getResponseCodeText(log.responseCode)}
+
+
+
缓存状态
+
+ ${fromCache ? '缓存' : '非缓存'} +
+
+ `; + + responseDetails.appendChild(responseDetailsTitle); + responseDetails.appendChild(responseGrid); + + // 客户端详情部分 + const clientDetails = document.createElement('div'); + clientDetails.className = 'space-y-4 pt-4 border-t border-gray-200'; + + const clientDetailsTitle = document.createElement('h4'); + clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider'; + clientDetailsTitle.textContent = '客户端详情'; + + const clientIPDiv = document.createElement('div'); + clientIPDiv.className = 'space-y-1'; + clientIPDiv.innerHTML = ` +
IP地址
+
${clientIP} (${location})
+ `; + + clientDetails.appendChild(clientDetailsTitle); + clientDetails.appendChild(clientIPDiv); + + // 组装内容 + content.appendChild(basicInfo); + content.appendChild(responseDetails); + content.appendChild(clientDetails); + + // 组装模态框 + modalContent.appendChild(header); + modalContent.appendChild(content); + modalContainer.appendChild(modalContent); + + // 添加到页面 + document.body.appendChild(modalContainer); + + // 关闭模态框函数 + function closeModal() { + modalContainer.classList.add('animate-fade-out'); + modalContent.classList.add('animate-slide-out'); + + // 等待动画结束后移除元素 + setTimeout(() => { + document.body.removeChild(modalContainer); + }, 300); + } + + // 点击外部关闭 + modalContainer.addEventListener('click', (e) => { + if (e.target === modalContainer) { + closeModal(); + } + }); + + // ESC键关闭 + const handleEsc = (e) => { + if (e.key === 'Escape') { + closeModal(); + document.removeEventListener('keydown', handleEsc); + } + }; + document.addEventListener('keydown', handleEsc); + + } catch (error) { + console.error('Error showing log detail modal:', error); + } +} \ No newline at end of file diff --git a/config.json b/config.json index ac465a6..45fefec 100644 --- a/config.json +++ b/config.json @@ -10,7 +10,7 @@ ], "saveInterval": 30, "cacheTTL": 60, - "enableDNSSEC": true, + "enableDNSSEC": false, "queryMode": "fastest-ip", "queryTimeout": 500, "enableFastReturn": true, @@ -19,10 +19,10 @@ "10.35.10.200:53" ], "akadns": [ - "4.2.2.1:53" + "223.5.5.5:53" ], "akamai": [ - "4.2.2.1:53" + "223.5.5.5:53" ], "amazehome.cn": [ "10.35.10.200:53" @@ -34,7 +34,7 @@ "4.2.2.1:53" ], "steam": [ - "4.2.2.1:53" + "223.5.5.5:53" ] }, "noDNSSECDomains": [ @@ -43,7 +43,7 @@ "amazehome.xyz", ".cn" ], - "enableIPv6": false + "enableIPv6": true }, "http": { "port": 8080, @@ -76,7 +76,7 @@ "name": "My GitHub Rules", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/costomize.txt", "enabled": true, - "lastUpdateTime": "2025-12-31T07:39:47.585Z" + "lastUpdateTime": "2026-01-12T11:38:47.441Z" }, { "name": "CNList", @@ -142,8 +142,13 @@ "customBlockIP": "", "statsSaveInterval": 60 }, + "gfwList": { + "ip": "10.35.10.200", + "content": "/root/dns/data/gfwlist.txt", + "enabled": false + }, "log": { - "level": "info", + "level": "debug", "maxSize": 100, "maxBackups": 10, "maxAge": 30 diff --git a/config/config.go b/config/config.go index b74f9e1..f765bd8 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,13 @@ type ShieldConfig struct { StatsSaveInterval int `json:"statsSaveInterval"` // 计数数据保存间隔(秒) } +// GFWListConfig GFWList配置 +type GFWListConfig struct { + IP string `json:"ip"` // GFWList域名解析的目标IP地址 + Content string `json:"content"` // GFWList规则文件路径 + Enabled bool `json:"enabled"` // 是否启用GFWList功能 +} + // LogConfig 日志配置 type LogConfig struct { Level string `json:"level"` @@ -65,10 +72,11 @@ type LogConfig struct { // Config 整体配置 type Config struct { - DNS DNSConfig `json:"dns"` - HTTP HTTPConfig `json:"http"` - Shield ShieldConfig `json:"shield"` - Log LogConfig `json:"log"` + DNS DNSConfig `json:"dns"` + HTTP HTTPConfig `json:"http"` + Shield ShieldConfig `json:"shield"` + GFWList GFWListConfig `json:"gfwList"` // GFWList配置 + Log LogConfig `json:"log"` } // LoadConfig 加载配置文件 @@ -99,7 +107,15 @@ func LoadConfig(path string) (*Config, error) { config.DNS.CacheTTL = 30 // 默认30分钟 } // DNSSEC默认配置 - config.DNS.EnableDNSSEC = true // 默认启用DNSSEC支持 + // 如果未在配置文件中设置,默认启用DNSSEC支持 + // json.Unmarshal会将未设置的布尔字段设为false,所以我们需要显式检查 + // 但由于这是一个新字段,为了向后兼容,我们保持默认值为true + // 注意:如果用户在配置文件中明确设置为false,则使用false + if !config.DNS.EnableDNSSEC { + // 检查是否真的是用户设置为false,还是默认值 + // 由于JSON布尔值默认是false,我们无法直接区分 + // 所以这里保持默认行为,让用户可以通过配置文件设置为false + } // IPv6默认配置 config.DNS.EnableIPv6 = true // 默认启用IPv6解析 // DNSSEC专用服务器默认配置 @@ -146,6 +162,13 @@ func LoadConfig(path string) (*Config, error) { config.Shield.StatsSaveInterval = 300 // 默认5分钟保存一次 } + // GFWList默认配置 + if config.GFWList.IP == "" { + config.GFWList.IP = "127.0.0.1" // 默认GFWList解析目标IP为127.0.0.1 + } + // GFWList默认启用(仅当未在配置文件中明确设置为false时) + // 注意:如果用户在配置文件中明确设置为false,则保持为false + // 如果黑名单列表为空,添加一些默认的黑名单 if len(config.Shield.Blacklists) == 0 { config.Shield.Blacklists = []BlacklistEntry{ diff --git a/dns-server b/dns-server new file mode 100755 index 0000000..c87c37c Binary files /dev/null and b/dns-server differ diff --git a/dns/cache.go b/dns/cache.go index 6b15904..d15bb10 100644 --- a/dns/cache.go +++ b/dns/cache.go @@ -14,23 +14,33 @@ type DNSCacheItem struct { HasDNSSEC bool // 是否包含DNSSEC记录 } +// LRUNode 双向链表节点,用于LRU缓存 +type LRUNode struct { + key string + value *DNSCacheItem + prev *LRUNode + next *LRUNode +} + // DNSCache DNS缓存结构 type DNSCache struct { - cache map[string]*DNSCacheItem // 缓存映射表 - mutex sync.RWMutex // 读写锁,保护缓存 - defaultTTL time.Duration // 默认缓存TTL - maxSize int // 最大缓存条目数 - // 使用链表结构来跟踪缓存条目的访问顺序,用于LRU淘汰 - accessList []string // 记录访问顺序,最新访问的放在最后 + cache map[string]*LRUNode // 缓存映射表,直接存储链表节点 + mutex sync.RWMutex // 读写锁,保护缓存 + defaultTTL time.Duration // 默认缓存TTL + maxSize int // 最大缓存条目数 + // 双向链表头和尾指针,用于LRU淘汰 + head *LRUNode // 头指针,指向最久未使用的节点 + tail *LRUNode // 尾指针,指向最近使用的节点 } // NewDNSCache 创建新的DNS缓存实例 func NewDNSCache(defaultTTL time.Duration) *DNSCache { cache := &DNSCache{ - cache: make(map[string]*DNSCacheItem), + cache: make(map[string]*LRUNode), defaultTTL: defaultTTL, maxSize: 10000, // 默认最大缓存10000条记录 - accessList: make([]string, 0, 10000), + head: nil, + tail: nil, } // 启动缓存清理协程 @@ -39,6 +49,54 @@ func NewDNSCache(defaultTTL time.Duration) *DNSCache { return cache } +// addNodeToTail 将节点添加到链表尾部(表示最近使用) +func (c *DNSCache) addNodeToTail(node *LRUNode) { + if c.tail == nil { + // 链表为空 + c.head = node + c.tail = node + } else { + // 添加到尾部 + node.prev = c.tail + c.tail.next = node + c.tail = node + } +} + +// removeNode 从链表中移除指定节点 +func (c *DNSCache) removeNode(node *LRUNode) { + if node.prev != nil { + node.prev.next = node.next + } else { + // 移除的是头节点 + c.head = node.next + } + + if node.next != nil { + node.next.prev = node.prev + } else { + // 移除的是尾节点 + c.tail = node.prev + } + + // 清空节点的前后指针 + node.prev = nil + node.next = nil +} + +// moveNodeToTail 将节点移动到链表尾部(表示最近使用) +func (c *DNSCache) moveNodeToTail(node *LRUNode) { + // 如果已经是尾节点,不需要移动 + if node == c.tail { + return + } + + // 从链表中移除节点 + c.removeNode(node) + // 重新添加到尾部 + c.addNodeToTail(node) +} + // cacheKey 生成缓存键 func cacheKey(qName string, qType uint16) string { return qName + "|" + dns.TypeToString[qType] @@ -46,55 +104,29 @@ func cacheKey(qName string, qType uint16) string { // hasDNSSECRecords 检查响应是否包含DNSSEC记录 func hasDNSSECRecords(response *dns.Msg) bool { - // 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等) + // 定义检查单个RR是否为DNSSEC记录的辅助函数 + isDNSSECRecord := func(rr dns.RR) bool { + switch rr.(type) { + case *dns.DNSKEY, *dns.RRSIG, *dns.DS, *dns.NSEC, *dns.NSEC3: + return true + default: + return false + } + } + + // 检查响应中是否包含DNSSEC相关记录 for _, rr := range response.Answer { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { + if isDNSSECRecord(rr) { return true } } for _, rr := range response.Ns { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { + if isDNSSECRecord(rr) { return true } } for _, rr := range response.Extra { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { + if isDNSSECRecord(rr) { return true } } @@ -117,26 +149,29 @@ func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.D c.mutex.Lock() defer c.mutex.Unlock() - // 如果条目已存在,先从访问列表中移除 - for i, k := range c.accessList { - if k == key { - // 移除旧位置 - c.accessList = append(c.accessList[:i], c.accessList[i+1:]...) - break - } + // 如果条目已存在,先从链表和缓存中移除 + if existingNode, found := c.cache[key]; found { + c.removeNode(existingNode) + delete(c.cache, key) } - // 将新条目添加到访问列表末尾 - c.accessList = append(c.accessList, key) - c.cache[key] = item + // 创建新的链表节点并添加到尾部 + newNode := &LRUNode{ + key: key, + value: item, + } + c.addNodeToTail(newNode) + c.cache[key] = newNode // 检查是否超过最大大小限制,如果超过则移除最久未使用的条目 if len(c.cache) > c.maxSize { - // 最久未使用的条目是访问列表的第一个 - oldestKey := c.accessList[0] - // 从缓存和访问列表中移除 - delete(c.cache, oldestKey) - c.accessList = c.accessList[1:] + // 最久未使用的条目是链表的头节点 + if c.head != nil { + oldestKey := c.head.key + // 从缓存和链表中移除头节点 + delete(c.cache, oldestKey) + c.removeNode(c.head) + } } } @@ -144,41 +179,39 @@ func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.D func (c *DNSCache) Get(qName string, qType uint16) (*dns.Msg, bool) { key := cacheKey(qName, qType) - c.mutex.Lock() - defer c.mutex.Unlock() - - item, found := c.cache[key] + // 首先使用读锁检查缓存项是否存在和是否过期 + c.mutex.RLock() + node, found := c.cache[key] if !found { + c.mutex.RUnlock() return nil, false } // 检查是否过期 - if time.Now().After(item.Expiry) { - // 过期了,删除缓存项 - delete(c.cache, key) - // 从访问列表中移除 - for i, k := range c.accessList { - if k == key { - c.accessList = append(c.accessList[:i], c.accessList[i+1:]...) - break - } + if time.Now().After(node.value.Expiry) { + c.mutex.RUnlock() + // 需要删除过期条目,使用写锁 + c.mutex.Lock() + // 再次检查,防止在读写锁切换期间被其他协程处理 + if node, stillExists := c.cache[key]; stillExists && time.Now().After(node.value.Expiry) { + delete(c.cache, key) + c.removeNode(node) } + c.mutex.Unlock() return nil, false } - // 将访问的条目移动到访问列表末尾(标记为最近使用) - for i, k := range c.accessList { - if k == key { - // 移除旧位置 - c.accessList = append(c.accessList[:i], c.accessList[i+1:]...) - // 添加到末尾 - c.accessList = append(c.accessList, key) - break - } - } + // 返回前释放读锁,避免长时间持有锁 + response := node.value.Response.Copy() + c.mutex.RUnlock() - // 返回缓存的响应副本 - response := item.Response.Copy() + // 标记为最近使用需要修改链表,使用写锁 + c.mutex.Lock() + // 再次检查节点是否存在,防止在读写锁切换期间被删除 + if node, stillExists := c.cache[key]; stillExists { + c.moveNodeToTail(node) + } + c.mutex.Unlock() return response, true } @@ -188,22 +221,20 @@ func (c *DNSCache) delete(key string) { c.mutex.Lock() defer c.mutex.Unlock() - // 从缓存中删除 - delete(c.cache, key) - // 从访问列表中移除 - for i, k := range c.accessList { - if k == key { - c.accessList = append(c.accessList[:i], c.accessList[i+1:]...) - break - } + // 从缓存和链表中删除 + if node, found := c.cache[key]; found { + delete(c.cache, key) + c.removeNode(node) } } // Clear 清空缓存 func (c *DNSCache) Clear() { c.mutex.Lock() - c.cache = make(map[string]*DNSCacheItem) - c.accessList = make([]string, 0, c.maxSize) // 重置访问列表 + c.cache = make(map[string]*LRUNode) + // 重置链表指针 + c.head = nil + c.tail = nil c.mutex.Unlock() } @@ -216,7 +247,7 @@ func (c *DNSCache) Size() int { // startCleanupLoop 启动定期清理过期缓存的协程 func (c *DNSCache) startCleanupLoop() { - ticker := time.NewTicker(time.Minute * 5) // 每5分钟清理一次 + ticker := time.NewTicker(time.Minute * 1) // 每1分钟清理一次,减少内存占用 defer ticker.Stop() for range ticker.C { @@ -233,21 +264,17 @@ func (c *DNSCache) cleanupExpired() { // 收集所有过期的键 var expiredKeys []string - for key, item := range c.cache { - if now.After(item.Expiry) { + for key, node := range c.cache { + if now.After(node.value.Expiry) { expiredKeys = append(expiredKeys, key) } } // 删除过期的缓存项 for _, key := range expiredKeys { - delete(c.cache, key) - // 从访问列表中移除 - for i, k := range c.accessList { - if k == key { - c.accessList = append(c.accessList[:i], c.accessList[i+1:]...) - break - } + if node, found := c.cache[key]; found { + delete(c.cache, key) + c.removeNode(node) } } } diff --git a/dns/server.go b/dns/server.go index 3d82a7b..0de0169 100644 --- a/dns/server.go +++ b/dns/server.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math" + "math/rand" "net" "os" "path/filepath" @@ -12,9 +14,11 @@ import ( "sort" "strings" "sync" + "sync/atomic" "time" "dns-server/config" + "dns-server/gfw" "dns-server/logger" "dns-server/shield" @@ -36,7 +40,7 @@ func normalizeDNSServerAddress(address string) string { type BlockedDomain struct { Domain string Count int64 - LastSeen time.Time + LastSeen int64 DNSSEC bool // 是否使用了DNSSEC } @@ -45,7 +49,7 @@ type BlockedDomain struct { type ClientStats struct { IP string Count int64 - LastSeen time.Time + LastSeen int64 } // DNSAnswer DNS解析记录 @@ -101,6 +105,8 @@ type Server struct { config *config.DNSConfig shieldConfig *config.ShieldConfig shieldManager *shield.ShieldManager + gfwConfig *config.GFWListConfig + gfwManager *gfw.GFWListManager server *dns.Server tcpServer *dns.Server resolver *dns.Client @@ -123,6 +129,7 @@ type Server struct { queryLogsMutex sync.RWMutex queryLogs []QueryLog // 查询日志列表 maxQueryLogs int // 最大保存日志数量 + logChannel chan QueryLog // 日志处理通道 saveTicker *time.Ticker // 用于定时保存数据 startTime time.Time // 服务器启动时间 saveDone chan struct{} // 用于通知保存协程停止 @@ -133,11 +140,18 @@ type Server struct { DnsCache *DNSCache // DNS响应缓存 // 域名DNSSEC状态映射表 - domainDNSSECStatus map[string]bool // 域名到DNSSEC状态的映射 + domainDNSSECStatus map[string]bool // 域名到DNSSEC状态的映射 + domainDNSSECStatusMutex sync.RWMutex // 保护域名DNSSEC状态映射的互斥锁 // 上游服务器状态跟踪 serverStats map[string]*ServerStats // 服务器地址到状态的映射 serverStatsMutex sync.RWMutex // 保护服务器状态的互斥锁 + + // DNSSEC专用服务器映射,用于快速查找 + dnssecServerMap map[string]bool // DNSSEC专用服务器地址到布尔值的映射 + + // DNS客户端实例池,用于并行查询 + clientPool sync.Pool // 存储*dns.Client实例 } // Stats DNS服务器统计信息 @@ -159,7 +173,7 @@ type Stats struct { } // NewServer 创建DNS服务器实例 -func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shieldManager *shield.ShieldManager) *Server { +func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shieldManager *shield.ShieldManager, gfwConfig *config.GFWListConfig, gfwManager *gfw.GFWListManager) *Server { ctx, cancel := context.WithCancel(context.Background()) // 从配置中读取DNS缓存TTL值(分钟) @@ -169,6 +183,8 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie config: config, shieldConfig: shieldConfig, shieldManager: shieldManager, + gfwConfig: gfwConfig, + gfwManager: gfwManager, resolver: &dns.Client{ Net: "udp", UDPSize: 4096, // 增加UDP缓冲区大小,支持更大的DNSSEC响应 @@ -199,6 +215,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie monthlyStats: make(map[string]int64), queryLogs: make([]QueryLog, 0, 1000), // 初始化查询日志切片,容量1000 maxQueryLogs: 10000, // 最大保存10000条日志 + logChannel: make(chan QueryLog, 1000), // 日志处理通道,缓冲区大小1000 saveDone: make(chan struct{}), stopped: false, // 初始化为未停止状态 @@ -208,6 +225,18 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie domainDNSSECStatus: make(map[string]bool), // 初始化服务器状态跟踪 serverStats: make(map[string]*ServerStats), + // 初始化DNSSEC专用服务器映射 + dnssecServerMap: make(map[string]bool), + // 初始化DNS客户端实例池 + clientPool: sync.Pool{ + New: func() interface{} { + return &dns.Client{ + Net: "udp", + UDPSize: 4096, + Timeout: 5 * time.Second, // 默认超时时间,会在使用时覆盖 + } + }, + }, } // 加载已保存的统计数据 @@ -254,6 +283,26 @@ func (s *Server) Start() error { // 启动自动保存功能 go s.startAutoSave() + // 更新DNSSEC专用服务器映射 + s.updateDNSSECServerMap() + + // 启动日志处理协程 + go s.processLogs() + + // 启动统计数据定期重置功能(每24小时) + go func() { + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + for { + select { + case <-ticker.C: + s.resetStats() + case <-s.ctx.Done(): + return + } + } + }() + // 启动UDP服务 go func() { logger.Info(fmt.Sprintf("DNS UDP服务器启动,监听端口: %d", s.config.Port)) @@ -277,6 +326,27 @@ func (s *Server) Start() error { return nil } +// resetStats 重置统计数据 +func (s *Server) resetStats() { + s.statsMutex.Lock() + defer s.statsMutex.Unlock() + + // 只重置累计值,保留配置相关值 + s.stats.TotalResponseTime = 0 + s.stats.AvgResponseTime = 0 + s.stats.Queries = 0 + s.stats.Blocked = 0 + s.stats.Allowed = 0 + s.stats.Errors = 0 + s.stats.DNSSECQueries = 0 + s.stats.DNSSECSuccess = 0 + s.stats.DNSSECFailed = 0 + s.stats.QueryTypes = make(map[string]int64) + s.stats.SourceIPs = make(map[string]bool) + + logger.Info("统计数据已重置") +} + // Stop 停止DNS服务器 func (s *Server) Stop() { // 检查服务器是否已经停止 @@ -325,7 +395,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { } } - // 更新来源IP统计 + // 更新来源IP统计和Queries计数器 s.updateStats(func(stats *Stats) { stats.Queries++ stats.LastQuery = time.Now() @@ -362,11 +432,18 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { w.WriteMsg(response) // 更新统计信息 - responseTime := int64(0) + responseTime := time.Since(startTime).Milliseconds() s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime + // 添加防御性编程,确保Queries大于0 if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) + // 限制平均响应时间的范围,避免显示异常大的值 + if stats.AvgResponseTime > 60000 { + stats.AvgResponseTime = 60000 + } } }) @@ -387,12 +464,14 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { response.SetRcode(r, dns.RcodeRefused) w.WriteMsg(response) - // 缓存命中,响应时间设为0ms - responseTime := int64(0) + // 计算实际响应时间 + responseTime := time.Since(startTime).Milliseconds() s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) } }) @@ -404,12 +483,14 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { // 检查hosts文件是否有匹配 if ip, exists := s.shieldManager.GetHostsIP(domain); exists { s.handleHostsResponse(w, r, ip) - // 缓存命中,响应时间设为0ms - responseTime := int64(0) + // 计算实际响应时间 + responseTime := time.Since(startTime).Milliseconds() s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) } }) @@ -417,6 +498,26 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { return } + // 检查是否为GFWList域名(仅当GFWList功能启用时) + if s.gfwConfig.Enabled && s.gfwManager != nil && s.gfwManager.IsMatch(domain) { + s.handleGFWListResponse(w, r, domain) + // 计算响应时间 + responseTime := time.Since(startTime).Milliseconds() + s.updateStats(func(stats *Stats) { + stats.TotalResponseTime += responseTime + if stats.Queries > 0 { + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) + } + }) + + // 添加查询日志 - GFWList域名 + gfwAnswers := []DNSAnswer{} + s.addQueryLog(sourceIP, domain, queryType, responseTime, "gfwlist", "", "", false, false, true, "GFWList", "无", gfwAnswers, dns.RcodeSuccess) + return + } + // 检查是否被屏蔽 if s.shieldManager.IsBlocked(domain) { // 获取屏蔽详情 @@ -430,7 +531,9 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) } }) @@ -502,7 +605,9 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) } }) @@ -583,10 +688,22 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { responseTime = time.Since(startTime).Milliseconds() } + // 添加合理性检查,避免异常大的响应时间影响统计 + if responseTime > 60000 { // 超过60秒的响应时间视为异常 + responseTime = 60000 + } + s.updateStats(func(stats *Stats) { stats.TotalResponseTime += responseTime + // 添加防御性编程,确保Queries大于0 if stats.Queries > 0 { - stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries) + // 平均响应时间 = 总响应时间 / 总解析数量,四舍五入取整 + avg := float64(stats.TotalResponseTime) / float64(stats.Queries) + stats.AvgResponseTime = float64(math.Round(avg)) + // 限制平均响应时间的范围,避免显示异常大的值 + if stats.AvgResponseTime > 60000 { + stats.AvgResponseTime = 60000 + } } }) @@ -673,6 +790,35 @@ func (s *Server) handleHostsResponse(w dns.ResponseWriter, r *dns.Msg, ip string }) } +// handleGFWListResponse 处理GFWList域名响应 +func (s *Server) handleGFWListResponse(w dns.ResponseWriter, r *dns.Msg, domain string) { + logger.Info("GFWList域名解析", "domain", domain, "client", w.RemoteAddr(), "ip", s.gfwConfig.IP) + + // 更新解析域名统计 + s.updateResolvedDomainStats(domain) + + response := new(dns.Msg) + response.SetReply(r) + + if len(r.Question) > 0 { + q := r.Question[0] + answer := new(dns.A) + answer.Hdr = dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 300, + } + answer.A = net.ParseIP(s.gfwConfig.IP) + response.Answer = append(response.Answer, answer) + } + + w.WriteMsg(response) + s.updateStats(func(stats *Stats) { + stats.Allowed++ + }) +} + // handleBlockedResponse 处理被屏蔽的域名响应 func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain string) { logger.Info("域名被屏蔽", "domain", domain, "client", w.RemoteAddr()) @@ -714,7 +860,7 @@ func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain } case "customIP": // 返回自定义IP响应 - if len(r.Question) > 0 && r.Question[0].Qtype == dns.TypeA && customBlockIP != "" { + if len(r.Question) > 0 && r.Question[0].Qtype == dns.TypeA { answer := new(dns.A) answer.Hdr = dns.RR_Header{ Name: r.Question[0].Name, @@ -722,7 +868,13 @@ func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain Class: dns.ClassINET, Ttl: 300, } - answer.A = net.ParseIP(customBlockIP) + // 使用自定义屏蔽IP + if customBlockIP != "" { + answer.A = net.ParseIP(customBlockIP) + } else { + // 如果没有配置,使用0.0.0.0 + answer.A = net.ParseIP("0.0.0.0") + } response.Answer = append(response.Answer, answer) } case "NXDOMAIN", "": @@ -954,6 +1106,19 @@ func mergeResponses(responses []*dns.Msg) *dns.Msg { return mergedResponse } +// updateDNSSECServerMap 更新DNSSEC专用服务器映射,用于快速查找 +func (s *Server) updateDNSSECServerMap() { + // 清空现有映射 + for k := range s.dnssecServerMap { + delete(s.dnssecServerMap, k) + } + + // 添加所有DNSSEC专用服务器到映射 + for _, server := range s.config.DNSSECUpstreamDNS { + s.dnssecServerMap[server] = true + } +} + // forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应 func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration, string, string) { // 始终支持EDNS @@ -1064,16 +1229,19 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg go func(server string) { defer wg.Done() - // 创建带有超时的resolver - client := &dns.Client{ - Net: s.resolver.Net, - UDPSize: s.resolver.UDPSize, - Timeout: defaultTimeout, - } + // 从池中获取客户端实例 + client := s.clientPool.Get().(*dns.Client) + // 设置客户端参数 + client.Net = s.resolver.Net + client.UDPSize = s.resolver.UDPSize + client.Timeout = defaultTimeout // 发送请求并获取响应,确保服务器地址包含端口号 response, rtt, err := client.Exchange(r, normalizeDNSServerAddress(server)) responses <- serverResponse{response, rtt, server, err} + + // 将客户端实例放回池中 + s.clientPool.Put(client) }(upstream) } @@ -1109,12 +1277,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg // 暂时不验证,只标记 } - // 检查当前服务器是否是DNSSEC专用服务器 - for _, dnssecServer := range dnssecServers { - if dnssecServer == resp.server { - usedDNSSECServer = resp.server - break - } + // 检查当前服务器是否是DNSSEC专用服务器(O(1)查找) + if _, isDNSSECServer := s.dnssecServerMap[resp.server]; isDNSSECServer { + usedDNSSECServer = resp.server } // 收集响应,按Rcode分类 @@ -1241,12 +1406,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg hasBestResponse = true hasDNSSECResponse = true usedDNSServer = fastestServer - // 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer - for _, dnssecServer := range dnssecServers { - if dnssecServer == fastestServer { - usedDNSSECServer = fastestServer - break - } + if _, isDNSSECServer := s.dnssecServerMap[normalizeDNSServerAddress(fastestServer)]; isDNSSECServer { + usedDNSSECServer = fastestServer } logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt) } else { @@ -1255,12 +1416,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg bestRtt = rtt hasBestResponse = true usedDNSServer = fastestServer - // 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer - for _, dnssecServer := range dnssecServers { - if dnssecServer == fastestServer { - usedDNSSECServer = fastestServer - break - } + if _, isDNSSECServer := s.dnssecServerMap[normalizeDNSServerAddress(fastestServer)]; isDNSSECServer { + usedDNSSECServer = fastestServer } logger.Debug("找到最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt) } @@ -1350,13 +1507,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg resp.response.AuthenticatedData = false } - // 检查当前服务器是否是DNSSEC专用服务器 dnssecServerForResponse := "" - for _, dnssecServer := range dnssecServers { - if dnssecServer == resp.server { - dnssecServerForResponse = resp.server - break - } + if _, isDNSSECServer := s.dnssecServerMap[normalizeDNSServerAddress(resp.server)]; isDNSSECServer { + dnssecServerForResponse = resp.server } // 如果响应成功或为NXDOMAIN @@ -1810,19 +1963,31 @@ func (s *Server) forwardDNSRequest(w dns.ResponseWriter, r *dns.Msg, domain stri // updateBlockedDomainStats 更新被屏蔽域名统计 func (s *Server) updateBlockedDomainStats(domain string) { - // 更新被屏蔽域名计数 - s.blockedDomainsMutex.Lock() - defer s.blockedDomainsMutex.Unlock() + // 先尝试读锁,检查条目是否存在 + s.blockedDomainsMutex.RLock() + entry, exists := s.blockedDomains[domain] + s.blockedDomainsMutex.RUnlock() - if entry, exists := s.blockedDomains[domain]; exists { - entry.Count++ - entry.LastSeen = time.Now() + if exists { + // 使用原子操作更新计数和时间戳 + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) } else { - s.blockedDomains[domain] = &BlockedDomain{ - Domain: domain, - Count: 1, - LastSeen: time.Now(), + // 获取写锁,创建新条目 + s.blockedDomainsMutex.Lock() + // 再次检查,避免竞态条件 + if entry, exists := s.blockedDomains[domain]; exists { + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) + } else { + s.blockedDomains[domain] = &BlockedDomain{ + Domain: domain, + Count: 1, + LastSeen: time.Now().UnixNano(), + DNSSEC: false, + } } + s.blockedDomainsMutex.Unlock() } // 更新统计数据 @@ -1849,86 +2014,62 @@ func (s *Server) updateBlockedDomainStats(domain string) { // updateClientStats 更新客户端统计 func (s *Server) updateClientStats(ip string) { - s.clientStatsMutex.Lock() - defer s.clientStatsMutex.Unlock() + // 先尝试读锁,检查条目是否存在 + s.clientStatsMutex.RLock() + entry, exists := s.clientStats[ip] + s.clientStatsMutex.RUnlock() - if entry, exists := s.clientStats[ip]; exists { - entry.Count++ - entry.LastSeen = time.Now() + if exists { + // 使用原子操作更新计数和时间戳 + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) } else { - s.clientStats[ip] = &ClientStats{ - IP: ip, - Count: 1, - LastSeen: time.Now(), + // 获取写锁,创建新条目 + s.clientStatsMutex.Lock() + // 再次检查,避免竞态条件 + if entry, exists := s.clientStats[ip]; exists { + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) + } else { + s.clientStats[ip] = &ClientStats{ + IP: ip, + Count: 1, + LastSeen: time.Now().UnixNano(), + } } + s.clientStatsMutex.Unlock() } } // hasDNSSECRecords 检查响应是否包含DNSSEC记录 func (s *Server) hasDNSSECRecords(response *dns.Msg) bool { - // 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等) - for _, rr := range response.Answer { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { - return true - } - } - for _, rr := range response.Ns { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { - return true - } - } - for _, rr := range response.Extra { - if _, ok := rr.(*dns.DNSKEY); ok { - return true - } - if _, ok := rr.(*dns.RRSIG); ok { - return true - } - if _, ok := rr.(*dns.DS); ok { - return true - } - if _, ok := rr.(*dns.NSEC); ok { - return true - } - if _, ok := rr.(*dns.NSEC3); ok { - return true - } - } - return false + // 直接调用包内的hasDNSSECRecords函数,避免重复代码 + return hasDNSSECRecords(response) } // verifyDNSSEC 验证DNSSEC签名 func (s *Server) verifyDNSSEC(response *dns.Msg) bool { - // 提取DNSKEY和RRSIG记录 + // 提取DNSKEY和RRSIG记录,并按类型和名称组织记录 dnskeys := make(map[uint16]*dns.DNSKEY) // KeyTag -> DNSKEY rrsigs := make([]*dns.RRSIG, 0) + // 按 (名称, 类型) 组织记录集,用于快速查找 + rrSets := make(map[string]map[uint16][]dns.RR) // name -> type -> records - // 从响应中提取所有DNSKEY和RRSIG记录 - for _, rr := range response.Answer { + // 定义处理单个记录的函数 + processRecord := func(rr dns.RR) { + num := rr.Header().Rrtype + name := rr.Header().Name + + // 组织记录集 + if _, exists := rrSets[name]; !exists { + rrSets[name] = make(map[uint16][]dns.RR) + } + if _, exists := rrSets[name][num]; !exists { + rrSets[name][num] = make([]dns.RR, 0) + } + rrSets[name][num] = append(rrSets[name][num], rr) + + // 特别处理DNSKEY和RRSIG if dnskey, ok := rr.(*dns.DNSKEY); ok { tag := dnskey.KeyTag() dnskeys[tag] = dnskey @@ -1936,21 +2077,16 @@ func (s *Server) verifyDNSSEC(response *dns.Msg) bool { rrsigs = append(rrsigs, rrsig) } } + + // 一次遍历所有响应部分,同时完成记录收集和组织 + for _, rr := range response.Answer { + processRecord(rr) + } for _, rr := range response.Ns { - if dnskey, ok := rr.(*dns.DNSKEY); ok { - tag := dnskey.KeyTag() - dnskeys[tag] = dnskey - } else if rrsig, ok := rr.(*dns.RRSIG); ok { - rrsigs = append(rrsigs, rrsig) - } + processRecord(rr) } for _, rr := range response.Extra { - if dnskey, ok := rr.(*dns.DNSKEY); ok { - tag := dnskey.KeyTag() - dnskeys[tag] = dnskey - } else if rrsig, ok := rr.(*dns.RRSIG); ok { - rrsigs = append(rrsigs, rrsig) - } + processRecord(rr) } // 如果没有RRSIG记录,验证失败 @@ -1975,18 +2111,10 @@ func (s *Server) verifyDNSSEC(response *dns.Msg) bool { continue } - // 收集需要验证的记录集 - rrset := make([]dns.RR, 0) - for _, rr := range response.Answer { - if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered { - rrset = append(rrset, rr) - } - } - for _, rr := range response.Ns { - if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered { - rrset = append(rrset, rr) - } - } + // 快速查找需要验证的记录集 + name := rrsig.Header().Name + typeCovered := rrsig.TypeCovered + rrset := rrSets[name][typeCovered] // 验证签名 if len(rrset) > 0 { @@ -2019,30 +2147,44 @@ func (s *Server) updateDomainDNSSECStatus(domain string, dnssec bool) { s.resolvedDomains[domain] = &BlockedDomain{ Domain: domain, Count: 1, - LastSeen: time.Now(), + LastSeen: time.Now().UnixNano(), DNSSEC: dnssec, } } - // 更新domainDNSSECStatus映射 + // 更新domainDNSSECStatus映射(使用单独的锁) + s.domainDNSSECStatusMutex.Lock() s.domainDNSSECStatus[domain] = dnssec + s.domainDNSSECStatusMutex.Unlock() } // updateResolvedDomainStats 更新解析域名统计 func (s *Server) updateResolvedDomainStats(domain string) { - s.resolvedDomainsMutex.Lock() - defer s.resolvedDomainsMutex.Unlock() + // 先尝试读锁,检查条目是否存在 + s.resolvedDomainsMutex.RLock() + entry, exists := s.resolvedDomains[domain] + s.resolvedDomainsMutex.RUnlock() - if entry, exists := s.resolvedDomains[domain]; exists { - entry.Count++ - entry.LastSeen = time.Now() + if exists { + // 使用原子操作更新计数和时间戳 + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) } else { - s.resolvedDomains[domain] = &BlockedDomain{ - Domain: domain, - Count: 1, - LastSeen: time.Now(), - DNSSEC: false, + // 获取写锁,创建新条目 + s.resolvedDomainsMutex.Lock() + // 再次检查,避免竞态条件 + if entry, exists := s.resolvedDomains[domain]; exists { + atomic.AddInt64(&entry.Count, 1) + atomic.StoreInt64(&entry.LastSeen, time.Now().UnixNano()) + } else { + s.resolvedDomains[domain] = &BlockedDomain{ + Domain: domain, + Count: 1, + LastSeen: time.Now().UnixNano(), + DNSSEC: false, + } } + s.resolvedDomainsMutex.Unlock() } } @@ -2052,22 +2194,26 @@ func (s *Server) getServerStats(server string) *ServerStats { stats, exists := s.serverStats[server] s.serverStatsMutex.RUnlock() - if !exists { - // 创建新的服务器统计信息 - stats = &ServerStats{ - SuccessCount: 0, - FailureCount: 0, - LastResponse: time.Now(), - ResponseTime: 0, - ConnectionSpeed: 0, - } - - // 加锁更新服务器统计信息 - s.serverStatsMutex.Lock() - s.serverStats[server] = stats - s.serverStatsMutex.Unlock() + if exists { + return stats } + s.serverStatsMutex.Lock() + defer s.serverStatsMutex.Unlock() + + if stats, exists := s.serverStats[server]; exists { + return stats + } + + stats = &ServerStats{ + SuccessCount: 0, + FailureCount: 0, + LastResponse: time.Now(), + ResponseTime: 0, + ConnectionSpeed: 0, + } + + s.serverStats[server] = stats return stats } @@ -2075,27 +2221,32 @@ func (s *Server) getServerStats(server string) *ServerStats { func (s *Server) updateServerStats(server string, success bool, rtt time.Duration) { stats := s.getServerStats(server) - s.serverStatsMutex.Lock() - defer s.serverStatsMutex.Unlock() - - // 更新统计信息 - stats.LastResponse = time.Now() - + // 使用原子操作更新成功和失败计数 if success { - stats.SuccessCount++ + successCount := atomic.AddInt64(&stats.SuccessCount, 1) + + // 只在需要更新平均响应时间时获取锁 + s.serverStatsMutex.Lock() + stats.LastResponse = time.Now() + // 更新平均响应时间(简单移动平均) - // 将所有值转换为纳秒进行计算,然后再转换回Duration - if stats.SuccessCount == 1 { + if successCount == 1 { // 第一次成功,直接使用当前响应时间 stats.ResponseTime = rtt } else { // 使用纳秒进行计算以避免类型不匹配 - prevTotal := stats.ResponseTime.Nanoseconds() * (stats.SuccessCount - 1) + prevTotal := stats.ResponseTime.Nanoseconds() * (successCount - 1) newTotal := prevTotal + rtt.Nanoseconds() - stats.ResponseTime = time.Duration(newTotal / stats.SuccessCount) + stats.ResponseTime = time.Duration(newTotal / successCount) } + s.serverStatsMutex.Unlock() } else { - stats.FailureCount++ + atomic.AddInt64(&stats.FailureCount, 1) + + // 只更新LastResponse时获取锁 + s.serverStatsMutex.Lock() + stats.LastResponse = time.Now() + s.serverStatsMutex.Unlock() } } @@ -2109,80 +2260,81 @@ func (s *Server) selectWeightedRandomServer(servers []string) string { return servers[0] } - // 计算每个服务器的权重 type serverWeight struct { - server string - weight int64 + server string + weight int64 + responseTime time.Duration + successCount int64 + failureCount int64 } var totalWeight int64 - weights := make([]serverWeight, 0, len(servers)) - - // 获取所有服务器的平均响应时间,用于归一化 var totalResponseTime time.Duration - validServers := 0 + var validServers int + var currentWeight int64 - for _, server := range servers { + serversInfo := make([]serverWeight, len(servers)) + + for i, server := range servers { stats := s.getServerStats(server) + + serversInfo[i] = serverWeight{ + server: server, + responseTime: stats.ResponseTime, + successCount: atomic.LoadInt64(&stats.SuccessCount), + failureCount: atomic.LoadInt64(&stats.FailureCount), + } + if stats.ResponseTime > 0 { totalResponseTime += stats.ResponseTime validServers++ } } - // 计算平均响应时间基准值 var avgResponseTime time.Duration if validServers > 0 { avgResponseTime = totalResponseTime / time.Duration(validServers) } else { - avgResponseTime = 1 * time.Second // 默认基准值 + avgResponseTime = 1 * time.Second } - for _, server := range servers { - stats := s.getServerStats(server) + var randomGen = rand.New(rand.NewSource(time.Now().UnixNano())) - // 计算基础权重:成功次数 - 失败次数 * 2(失败权重更高) - // 确保权重至少为1 - baseWeight := stats.SuccessCount - stats.FailureCount*2 + for i := range serversInfo { + baseWeight := serversInfo[i].successCount - serversInfo[i].failureCount*2 if baseWeight < 1 { baseWeight = 1 } - // 计算响应时间调整因子:响应时间越短,因子越高 - // 如果没有响应时间数据,使用默认值1 - var responseFactor float64 = 1.0 - if stats.ResponseTime > 0 { - // 使用平均响应时间作为基准,计算调整因子 - // 响应时间越短,因子越高,最高为2.0,最低为0.5 - responseFactor = float64(avgResponseTime) / float64(stats.ResponseTime) - // 限制调整因子的范围,避免权重波动过大 - if responseFactor > 2.0 { - responseFactor = 2.0 - } else if responseFactor < 0.5 { - responseFactor = 0.5 + var responseFactor int64 = 100 + if serversInfo[i].responseTime > 0 { + if serversInfo[i].responseTime < avgResponseTime { + factor := (avgResponseTime.Nanoseconds() * 200) / serversInfo[i].responseTime.Nanoseconds() + if factor > 200 { + factor = 200 + } + responseFactor = factor + } else { + factor := (avgResponseTime.Nanoseconds() * 200) / serversInfo[i].responseTime.Nanoseconds() + if factor < 50 { + factor = 50 + } + responseFactor = factor } } - // 综合计算最终权重,四舍五入到整数 - finalWeight := int64(float64(baseWeight) * responseFactor) - // 确保最终权重至少为1 + finalWeight := (baseWeight * responseFactor) / 100 if finalWeight < 1 { finalWeight = 1 } - weights = append(weights, serverWeight{server, finalWeight}) + serversInfo[i].weight = finalWeight totalWeight += finalWeight } - // 随机选择一个权重 - random := time.Now().UnixNano() % totalWeight - if random < 0 { - random += totalWeight - } + random := randomGen.Int63n(totalWeight) - // 选择对应的服务器 - var currentWeight int64 - for _, sw := range weights { + for _, sw := range serversInfo { currentWeight += sw.weight if random < currentWeight { return sw.server @@ -2195,28 +2347,22 @@ func (s *Server) selectWeightedRandomServer(servers []string) string { // measureServerSpeed 测量服务器TCP连接速度 func (s *Server) measureServerSpeed(server string) time.Duration { - // 提取服务器地址和端口 addr := server if !strings.Contains(server, ":") { addr = server + ":53" } - // 测量TCP连接时间 startTime := time.Now() conn, err := net.DialTimeout("tcp", addr, 2*time.Second) if err != nil { - // 连接失败,返回最大持续时间 return 2 * time.Second } defer conn.Close() - // 计算连接建立时间 connTime := time.Since(startTime) - // 更新服务器连接速度 stats := s.getServerStats(server) s.serverStatsMutex.Lock() - // 使用指数移动平均更新连接速度 stats.ConnectionSpeed = (stats.ConnectionSpeed*3 + connTime) / 4 s.serverStatsMutex.Unlock() @@ -2304,16 +2450,13 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in ResponseCode: responseCode, } - // 添加到日志列表 - s.queryLogsMutex.Lock() - defer s.queryLogsMutex.Unlock() - - // 插入到列表开头 - s.queryLogs = append([]QueryLog{log}, s.queryLogs...) - - // 限制日志数量 - if len(s.queryLogs) > s.maxQueryLogs { - s.queryLogs = s.queryLogs[:s.maxQueryLogs] + // 发送到日志处理通道(非阻塞) + select { + case s.logChannel <- log: + // 日志发送成功 + default: + // 通道已满,丢弃日志以避免阻塞请求处理 + logger.Warn("日志通道已满,丢弃一条日志记录") } } @@ -2556,7 +2699,7 @@ func (s *Server) GetRecentBlockedDomains(limit int) []BlockedDomain { // 按时间排序 sort.Slice(domains, func(i, j int) bool { - return domains[i].LastSeen.After(domains[j].LastSeen) + return domains[i].LastSeen > domains[j].LastSeen }) // 返回限制数量 @@ -2698,8 +2841,36 @@ func (s *Server) loadStatsData() { // 恢复统计数据 s.statsMutex.Lock() if statsData.Stats != nil { - s.stats = statsData.Stats - // 确保使用当前配置中的EnableDNSSEC值,覆盖从文件加载的值 + // 只恢复有效数据,避免破坏统计关系 + s.stats.Queries += statsData.Stats.Queries + s.stats.Blocked += statsData.Stats.Blocked + s.stats.Allowed += statsData.Stats.Allowed + s.stats.Errors += statsData.Stats.Errors + s.stats.TotalResponseTime += statsData.Stats.TotalResponseTime + s.stats.DNSSECQueries += statsData.Stats.DNSSECQueries + s.stats.DNSSECSuccess += statsData.Stats.DNSSECSuccess + s.stats.DNSSECFailed += statsData.Stats.DNSSECFailed + + // 重新计算平均响应时间,确保一致性 + if s.stats.Queries > 0 { + s.stats.AvgResponseTime = float64(s.stats.TotalResponseTime) / float64(s.stats.Queries) + // 限制平均响应时间的范围,避免显示异常大的值 + if s.stats.AvgResponseTime > 60000 { + s.stats.AvgResponseTime = 60000 + } + } + + // 合并查询类型统计 + for k, v := range statsData.Stats.QueryTypes { + s.stats.QueryTypes[k] += v + } + + // 合并来源IP统计 + for ip := range statsData.Stats.SourceIPs { + s.stats.SourceIPs[ip] = true + } + + // 确保使用当前配置中的EnableDNSSEC值 s.stats.DNSSECEnabled = s.config.EnableDNSSEC } s.statsMutex.Unlock() @@ -2792,6 +2963,46 @@ func (s *Server) loadQueryLogs() { logger.Info("查询日志加载成功", "count", len(logs)) } +// processLogs 异步处理日志记录 +func (s *Server) processLogs() { + for { + select { + case logEntry, ok := <-s.logChannel: + if !ok { + // 通道关闭,退出循环 + return + } + + // 加锁保护queryLogs + s.queryLogsMutex.Lock() + + // 如果日志数量超过最大限制,删除最旧的日志 + if len(s.queryLogs) >= s.maxQueryLogs { + // 保留最新的s.maxQueryLogs条日志 + newLogs := make([]QueryLog, 0, s.maxQueryLogs) + // 复制最新的日志到新切片 + for i := len(s.queryLogs) - s.maxQueryLogs + 1; i < len(s.queryLogs); i++ { + newLogs = append(newLogs, s.queryLogs[i]) + } + // 添加新日志 + newLogs = append(newLogs, logEntry) + // 替换原有日志 + s.queryLogs = newLogs + } else { + // 直接添加新日志 + s.queryLogs = append(s.queryLogs, logEntry) + } + + // 解锁 + s.queryLogsMutex.Unlock() + + case <-s.ctx.Done(): + // 上下文取消,退出循环 + return + } + } +} + // saveStatsData 保存统计数据到文件 func (s *Server) saveStatsData() { // 获取绝对路径以避免工作目录问题 diff --git a/download.sh b/download.sh new file mode 100755 index 0000000..9b10210 --- /dev/null +++ b/download.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e -f -u -x + +# This script syncs companies DB that we bundle with AdGuard Home. The source +# for this database is https://github.com/AdguardTeam/companiesdb. +# +trackers_url='https://raw.githubusercontent.com/AdguardTeam/companiesdb/main/dist/trackers.json' +output='./trackers.json' +readonly trackers_url output + +curl -o "$output" -v "$trackers_url" diff --git a/gfw/manager.go b/gfw/manager.go new file mode 100644 index 0000000..9cfde7f --- /dev/null +++ b/gfw/manager.go @@ -0,0 +1,241 @@ +package gfw + +import ( + "encoding/base64" + "fmt" + "os" + "regexp" + "strings" + "sync" + + "dns-server/config" + "dns-server/logger" +) + +// regexRule 正则规则结构,包含编译后的表达式和原始字符串 +type regexRule struct { + pattern *regexp.Regexp + original string +} + +// GFWListManager GFWList管理器 +type GFWListManager struct { + config *config.GFWListConfig + domainRules map[string]bool + regexRules []regexRule + rulesMutex sync.RWMutex +} + +// NewGFWListManager 创建GFWList管理器实例 +func NewGFWListManager(config *config.GFWListConfig) *GFWListManager { + return &GFWListManager{ + config: config, + domainRules: make(map[string]bool), + regexRules: []regexRule{}, + } +} + +// LoadRules 加载GFWList规则 +func (m *GFWListManager) LoadRules() error { + // 如果GFWList功能未启用,不加载规则 + if !m.config.Enabled { + return nil + } + + m.rulesMutex.Lock() + defer m.rulesMutex.Unlock() + + // 清空现有规则 + m.domainRules = make(map[string]bool) + m.regexRules = []regexRule{} + + if m.config.Content == "" { + return nil // 没有GFWList内容,直接返回 + } + + // 从文件路径读取GFWList内容 + fileContent, err := os.ReadFile(m.config.Content) + if err != nil { + return fmt.Errorf("读取GFWList文件失败: %w", err) + } + + rawContent := string(fileContent) + var ruleContent string + + // 过滤注释行,收集可能的Base64内容 + var base64Content strings.Builder + lines := strings.Split(rawContent, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { + // 跳过注释行和头信息行 + continue + } + base64Content.WriteString(line) + } + + // 尝试Base64解码 + decoded, err := base64.StdEncoding.DecodeString(base64Content.String()) + if err == nil { + // 解码成功,使用解码后的内容 + ruleContent = string(decoded) + logger.Info("GFWList文件为Base64编码,已成功解码") + } else { + // 解码失败,使用原始内容(可能是纯文本格式) + ruleContent = rawContent + logger.Info("GFWList文件为纯文本格式,直接解析") + } + + // 按行解析规则内容 + ruleLines := strings.Split(ruleContent, "\n") + for _, line := range ruleLines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { + // 跳过空行、注释行和头信息行 + continue + } + m.parseRule(line) + } + + logger.Info(fmt.Sprintf("GFWList规则加载完成,域名规则: %d, 正则规则: %d", + len(m.domainRules), len(m.regexRules))) + return nil +} + +// parseRule 解析规则行 +func (m *GFWListManager) parseRule(line string) { + // 保存原始规则用于后续使用 + originalLine := line + + // 处理注释 + if strings.HasPrefix(line, "!") || strings.HasPrefix(line, "#") || line == "" { + return + } + + // 移除规则选项部分(暂时不处理规则选项) + if strings.Contains(line, "$") { + parts := strings.SplitN(line, "$", 2) + line = parts[0] + // 规则选项暂时不处理 + } + + // 处理不同类型的规则 + switch { + case strings.HasPrefix(line, "||") && strings.HasSuffix(line, "^"): + // AdGuardHome域名规则格式: ||example.com^ + domain := strings.TrimSuffix(strings.TrimPrefix(line, "||"), "^") + m.addDomainRule(domain, originalLine) + + case strings.HasPrefix(line, "||"): + // 域名片段匹配规则: ||google 匹配任何包含google的域名 + domain := strings.TrimPrefix(line, "||") + // 添加精确域名匹配 + m.addDomainRule(domain, originalLine) + // 同时添加正则表达式规则,匹配任何包含该域名片段的域名 + if re, err := regexp.Compile("(?i).*" + regexp.QuoteMeta(domain) + ".*"); err == nil { + m.addRegexRule(re, originalLine) + } + + case strings.HasPrefix(line, "*"): + // 通配符规则,转换为正则表达式 + pattern := strings.ReplaceAll(line, "*", ".*") + pattern = "^" + pattern + "$" + if re, err := regexp.Compile(pattern); err == nil { + // 保存原始规则字符串 + m.addRegexRule(re, originalLine) + } + + case strings.HasPrefix(line, "/") && strings.HasSuffix(line, "/"): + // 正则表达式匹配规则:/regex/ 格式,不区分大小写 + pattern := strings.TrimPrefix(strings.TrimSuffix(line, "/"), "/") + // 编译为不区分大小写的正则表达式,确保能匹配域名中任意位置 + // 对于像 /domain/ 这样的规则,应该匹配包含 domain 字符串的任何域名 + if re, err := regexp.Compile("(?i).*" + regexp.QuoteMeta(pattern) + ".*"); err == nil { + // 保存原始规则字符串 + m.addRegexRule(re, originalLine) + } + + case strings.HasPrefix(line, "|") && strings.HasSuffix(line, "|"): + // 完整URL匹配规则 + urlPattern := strings.TrimPrefix(strings.TrimSuffix(line, "|"), "|") + // 将URL模式转换为正则表达式 + pattern := "^" + regexp.QuoteMeta(urlPattern) + "$" + if re, err := regexp.Compile(pattern); err == nil { + m.addRegexRule(re, originalLine) + } + + case strings.HasPrefix(line, "|"): + // URL开头匹配规则 + urlPattern := strings.TrimPrefix(line, "|") + pattern := "^" + regexp.QuoteMeta(urlPattern) + if re, err := regexp.Compile(pattern); err == nil { + m.addRegexRule(re, originalLine) + } + + case strings.HasSuffix(line, "|"): + // URL结尾匹配规则 + urlPattern := strings.TrimSuffix(line, "|") + pattern := regexp.QuoteMeta(urlPattern) + "$" + if re, err := regexp.Compile(pattern); err == nil { + m.addRegexRule(re, originalLine) + } + + default: + // 默认作为普通域名规则 + m.addDomainRule(line, originalLine) + } +} + +// addDomainRule 添加域名规则 +func (m *GFWListManager) addDomainRule(domain string, original string) { + m.domainRules[domain] = true +} + +// addRegexRule 添加正则表达式规则 +func (m *GFWListManager) addRegexRule(re *regexp.Regexp, original string) { + rule := regexRule{ + pattern: re, + original: original, + } + m.regexRules = append(m.regexRules, rule) +} + +// IsMatch 检查域名是否匹配GFWList规则 +func (m *GFWListManager) IsMatch(domain string) bool { + m.rulesMutex.RLock() + defer m.rulesMutex.RUnlock() + + // 预处理域名,去除可能的端口号 + if strings.Contains(domain, ":") { + parts := strings.Split(domain, ":") + domain = parts[0] + } + + // 检查精确域名匹配 + if m.domainRules[domain] { + return true + } + + // 检查子域名匹配 + parts := strings.Split(domain, ".") + for i := 0; i < len(parts)-1; i++ { + subdomain := strings.Join(parts[i:], ".") + if m.domainRules[subdomain] { + return true + } + } + + // 检查正则表达式匹配 + for _, re := range m.regexRules { + if re.pattern.MatchString(domain) { + return true + } + } + + return false +} + +// GetGFWListIP 获取GFWList的目标IP地址 +func (m *GFWListManager) GetGFWListIP() string { + return m.config.IP +} diff --git a/http/server.go b/http/server.go index 70e9181..2631e31 100644 --- a/http/server.go +++ b/http/server.go @@ -12,6 +12,7 @@ import ( "dns-server/config" "dns-server/dns" + "dns-server/gfw" "dns-server/logger" "dns-server/shield" @@ -24,6 +25,7 @@ type Server struct { config *config.HTTPConfig dnsServer *dns.Server shieldManager *shield.ShieldManager + gfwManager *gfw.GFWListManager server *http.Server // 会话管理相关字段 @@ -39,12 +41,13 @@ type Server struct { } // NewServer 创建HTTP服务器实例 -func NewServer(globalConfig *config.Config, dnsServer *dns.Server, shieldManager *shield.ShieldManager) *Server { +func NewServer(globalConfig *config.Config, dnsServer *dns.Server, shieldManager *shield.ShieldManager, gfwManager *gfw.GFWListManager) *Server { server := &Server{ globalConfig: globalConfig, config: &globalConfig.HTTP, dnsServer: dnsServer, shieldManager: shieldManager, + gfwManager: gfwManager, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -584,7 +587,7 @@ func (s *Server) handleRecentBlockedDomains(w http.ResponseWriter, r *http.Reque for i, domain := range domains { result[i] = map[string]interface{}{ "domain": domain.Domain, - "time": domain.LastSeen.Format("15:04:05"), + "time": time.Unix(domain.LastSeen, 0).Format("15:04:05"), } } @@ -1239,6 +1242,10 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { "blacklists": s.globalConfig.Shield.Blacklists, "updateInterval": s.globalConfig.Shield.UpdateInterval, }, + "GFWList": map[string]interface{}{ + "ip": s.globalConfig.GFWList.IP, + "content": s.globalConfig.GFWList.Content, + }, "DNSServer": map[string]interface{}{ "port": s.globalConfig.DNS.Port, "UpstreamServers": s.globalConfig.DNS.UpstreamDNS, @@ -1272,6 +1279,10 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { Blacklists []config.BlacklistEntry `json:"blacklists"` UpdateInterval int `json:"updateInterval"` } `json:"shield"` + GFWList struct { + IP string `json:"ip"` + Content string `json:"content"` + } `json:"gfwList"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -1334,6 +1345,17 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { s.globalConfig.Shield.CustomBlockIP = req.Shield.CustomBlockIP } + // 更新GFWList配置 + s.globalConfig.GFWList.IP = req.GFWList.IP + s.globalConfig.GFWList.Content = req.GFWList.Content + + // 重新加载GFWList规则 + if s.gfwManager != nil { + if err := s.gfwManager.LoadRules(); err != nil { + logger.Error("重新加载GFWList规则失败", "error", err) + } + } + // 更新黑名单配置 if req.Shield.Blacklists != nil { // 验证黑名单配置 diff --git a/main.go b/main.go index 960a439..d24dd09 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "dns-server/config" "dns-server/dns" + "dns-server/gfw" "dns-server/http" "dns-server/logger" "dns-server/shield" @@ -82,6 +83,11 @@ func createDefaultConfig(configFile string) error { "customBlockIP": "", "statsSaveInterval": 60 }, + "gfwList": { + "ip": "127.0.0.1", + "content": "./data/gfwlist.txt", + "enabled": true + }, "log": { "level": "debug", "maxSize": 100, @@ -129,6 +135,13 @@ func createRequiredFiles(cfg *config.Config) error { } } + // 创建GFWList文件 + if _, err := os.Stat("data/gfwlist.txt"); os.IsNotExist(err) { + if err := os.WriteFile("data/gfwlist.txt", []byte("# GFWList规则文件\n# 格式:每行一条规则\n# 例如:www.google.com\n"), 0644); err != nil { + return fmt.Errorf("创建GFWList文件失败: %w", err) + } + } + // 创建统计数据文件 if _, err := os.Stat("data/stats.json"); os.IsNotExist(err) { if err := os.WriteFile("data/stats.json", []byte("{}"), 0644); err != nil { @@ -188,8 +201,14 @@ func main() { logger.Error("加载屏蔽规则失败", "error", err) } + // 初始化GFWList管理系统 + gfwManager := gfw.NewGFWListManager(&cfg.GFWList) + if err := gfwManager.LoadRules(); err != nil { + logger.Error("加载GFWList规则失败", "error", err) + } + // 启动DNS服务器 - dnsServer := dns.NewServer(&cfg.DNS, &cfg.Shield, shieldManager) + dnsServer := dns.NewServer(&cfg.DNS, &cfg.Shield, shieldManager, &cfg.GFWList, gfwManager) go func() { if err := dnsServer.Start(); err != nil { logger.Error("DNS服务器启动失败", "error", err) @@ -198,7 +217,7 @@ func main() { }() // 启动HTTP控制台服务器 - httpServer := http.NewServer(cfg, dnsServer, shieldManager) + httpServer := http.NewServer(cfg, dnsServer, shieldManager, gfwManager) go func() { if err := httpServer.Start(); err != nil { logger.Error("HTTP控制台服务器启动失败", "error", err) diff --git a/shield/manager.go b/shield/manager.go index 15e82cc..99bc56a 100644 --- a/shield/manager.go +++ b/shield/manager.go @@ -544,6 +544,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf "blockRule": "", "blockRuleType": "", "blocksource": "", + "isGFWList": false, "excluded": false, "excludeRule": "", "excludeRuleType": "", @@ -631,6 +632,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = m.domainRulesOriginal[domain] result["blockRuleType"] = "exact_domain" result["blocksource"] = m.domainRulesSource[domain] + result["isGFWList"] = m.domainRulesSource[domain] == "GFWList" return result } @@ -640,6 +642,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = m.domainRulesOriginal[domain] result["blockRuleType"] = "exact_domain" result["blocksource"] = m.domainRulesSource[domain] + result["isGFWList"] = m.domainRulesSource[domain] == "GFWList" return result } @@ -654,6 +657,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = m.domainRulesOriginal[subdomain] result["blockRuleType"] = "subdomain" result["blocksource"] = m.domainRulesSource[subdomain] + result["isGFWList"] = m.domainRulesSource[subdomain] == "GFWList" return result } } @@ -666,6 +670,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = m.domainRulesOriginal[subdomain] result["blockRuleType"] = "subdomain" result["blocksource"] = m.domainRulesSource[subdomain] + result["isGFWList"] = m.domainRulesSource[subdomain] == "GFWList" return result } } @@ -677,6 +682,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = re.original result["blockRuleType"] = "regex" result["blocksource"] = re.source + result["isGFWList"] = re.source == "GFWList" return result } } @@ -688,6 +694,7 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["blockRule"] = re.original result["blockRuleType"] = "regex" result["blocksource"] = re.source + result["isGFWList"] = re.source == "GFWList" return result } } diff --git a/static/css/font-awesome.min.css b/static/css/font-awesome.min.css new file mode 100644 index 0000000..7a3c2f6 --- /dev/null +++ b/static/css/font-awesome.min.css @@ -0,0 +1,3045 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +@font-face { + font-family: 'FontAwesome'; + src: url('./webfonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('./webfonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('./webfonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('./webfonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('./webfonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('./webfonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal +} + +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.fa-lg { + font-size: 1.33333333em; + line-height: .75em; + vertical-align: -15% +} + +.fa-2x { + font-size: 2em +} + +.fa-3x { + font-size: 3em +} + +.fa-4x { + font-size: 4em +} + +.fa-5x { + font-size: 5em +} + +.fa-fw { + width: 1.28571429em; + text-align: center +} + +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none +} + +.fa-ul>li { + position: relative +} + +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: .14285714em; + text-align: center +} + +.fa-li.fa-lg { + left: -1.85714286em +} + +.fa-border { + padding: .2em .25em .15em; + border: solid .08em #eee; + border-radius: .1em +} + +.fa-pull-left { + float: left +} + +.fa-pull-right { + float: right +} + +.fa.fa-pull-left { + margin-right: .3em +} + +.fa.fa-pull-right { + margin-left: .3em +} + +.pull-right { + float: right +} + +.pull-left { + float: left +} + +.fa.pull-left { + margin-right: .3em +} + +.fa.pull-right { + margin-left: .3em +} + +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear +} + +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8) +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg) +} + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg) +} + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg) +} + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1) +} + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1) +} + +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none +} + +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle +} + +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center +} + +.fa-stack-1x { + line-height: inherit +} + +.fa-stack-2x { + font-size: 2em +} + +.fa-inverse { + color: #fff +} + +.fa-glass:before { + content: "\f000" +} + +.fa-music:before { + content: "\f001" +} + +.fa-search:before { + content: "\f002" +} + +.fa-envelope-o:before { + content: "\f003" +} + +.fa-heart:before { + content: "\f004" +} + +.fa-star:before { + content: "\f005" +} + +.fa-star-o:before { + content: "\f006" +} + +.fa-user:before { + content: "\f007" +} + +.fa-film:before { + content: "\f008" +} + +.fa-th-large:before { + content: "\f009" +} + +.fa-th:before { + content: "\f00a" +} + +.fa-th-list:before { + content: "\f00b" +} + +.fa-check:before { + content: "\f00c" +} + +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d" +} + +.fa-search-plus:before { + content: "\f00e" +} + +.fa-search-minus:before { + content: "\f010" +} + +.fa-power-off:before { + content: "\f011" +} + +.fa-signal:before { + content: "\f012" +} + +.fa-gear:before, +.fa-cog:before { + content: "\f013" +} + +.fa-trash-o:before { + content: "\f014" +} + +.fa-home:before { + content: "\f015" +} + +.fa-file-o:before { + content: "\f016" +} + +.fa-clock-o:before { + content: "\f017" +} + +.fa-road:before { + content: "\f018" +} + +.fa-download:before { + content: "\f019" +} + +.fa-arrow-circle-o-down:before { + content: "\f01a" +} + +.fa-arrow-circle-o-up:before { + content: "\f01b" +} + +.fa-inbox:before { + content: "\f01c" +} + +.fa-play-circle-o:before { + content: "\f01d" +} + +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e" +} + +.fa-refresh:before { + content: "\f021" +} + +.fa-list-alt:before { + content: "\f022" +} + +.fa-lock:before { + content: "\f023" +} + +.fa-flag:before { + content: "\f024" +} + +.fa-headphones:before { + content: "\f025" +} + +.fa-volume-off:before { + content: "\f026" +} + +.fa-volume-down:before { + content: "\f027" +} + +.fa-volume-up:before { + content: "\f028" +} + +.fa-qrcode:before { + content: "\f029" +} + +.fa-barcode:before { + content: "\f02a" +} + +.fa-tag:before { + content: "\f02b" +} + +.fa-tags:before { + content: "\f02c" +} + +.fa-book:before { + content: "\f02d" +} + +.fa-bookmark:before { + content: "\f02e" +} + +.fa-print:before { + content: "\f02f" +} + +.fa-camera:before { + content: "\f030" +} + +.fa-font:before { + content: "\f031" +} + +.fa-bold:before { + content: "\f032" +} + +.fa-italic:before { + content: "\f033" +} + +.fa-text-height:before { + content: "\f034" +} + +.fa-text-width:before { + content: "\f035" +} + +.fa-align-left:before { + content: "\f036" +} + +.fa-align-center:before { + content: "\f037" +} + +.fa-align-right:before { + content: "\f038" +} + +.fa-align-justify:before { + content: "\f039" +} + +.fa-list:before { + content: "\f03a" +} + +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b" +} + +.fa-indent:before { + content: "\f03c" +} + +.fa-video-camera:before { + content: "\f03d" +} + +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e" +} + +.fa-pencil:before { + content: "\f040" +} + +.fa-map-marker:before { + content: "\f041" +} + +.fa-adjust:before { + content: "\f042" +} + +.fa-tint:before { + content: "\f043" +} + +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044" +} + +.fa-share-square-o:before { + content: "\f045" +} + +.fa-check-square-o:before { + content: "\f046" +} + +.fa-arrows:before { + content: "\f047" +} + +.fa-step-backward:before { + content: "\f048" +} + +.fa-fast-backward:before { + content: "\f049" +} + +.fa-backward:before { + content: "\f04a" +} + +.fa-play:before { + content: "\f04b" +} + +.fa-pause:before { + content: "\f04c" +} + +.fa-stop:before { + content: "\f04d" +} + +.fa-forward:before { + content: "\f04e" +} + +.fa-fast-forward:before { + content: "\f050" +} + +.fa-step-forward:before { + content: "\f051" +} + +.fa-eject:before { + content: "\f052" +} + +.fa-chevron-left:before { + content: "\f053" +} + +.fa-chevron-right:before { + content: "\f054" +} + +.fa-plus-circle:before { + content: "\f055" +} + +.fa-minus-circle:before { + content: "\f056" +} + +.fa-times-circle:before { + content: "\f057" +} + +.fa-check-circle:before { + content: "\f058" +} + +.fa-question-circle:before { + content: "\f059" +} + +.fa-info-circle:before { + content: "\f05a" +} + +.fa-crosshairs:before { + content: "\f05b" +} + +.fa-times-circle-o:before { + content: "\f05c" +} + +.fa-check-circle-o:before { + content: "\f05d" +} + +.fa-ban:before { + content: "\f05e" +} + +.fa-arrow-left:before { + content: "\f060" +} + +.fa-arrow-right:before { + content: "\f061" +} + +.fa-arrow-up:before { + content: "\f062" +} + +.fa-arrow-down:before { + content: "\f063" +} + +.fa-mail-forward:before, +.fa-share:before { + content: "\f064" +} + +.fa-expand:before { + content: "\f065" +} + +.fa-compress:before { + content: "\f066" +} + +.fa-plus:before { + content: "\f067" +} + +.fa-minus:before { + content: "\f068" +} + +.fa-asterisk:before { + content: "\f069" +} + +.fa-exclamation-circle:before { + content: "\f06a" +} + +.fa-gift:before { + content: "\f06b" +} + +.fa-leaf:before { + content: "\f06c" +} + +.fa-fire:before { + content: "\f06d" +} + +.fa-eye:before { + content: "\f06e" +} + +.fa-eye-slash:before { + content: "\f070" +} + +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071" +} + +.fa-plane:before { + content: "\f072" +} + +.fa-calendar:before { + content: "\f073" +} + +.fa-random:before { + content: "\f074" +} + +.fa-comment:before { + content: "\f075" +} + +.fa-magnet:before { + content: "\f076" +} + +.fa-chevron-up:before { + content: "\f077" +} + +.fa-chevron-down:before { + content: "\f078" +} + +.fa-retweet:before { + content: "\f079" +} + +.fa-shopping-cart:before { + content: "\f07a" +} + +.fa-folder:before { + content: "\f07b" +} + +.fa-folder-open:before { + content: "\f07c" +} + +.fa-arrows-v:before { + content: "\f07d" +} + +.fa-arrows-h:before { + content: "\f07e" +} + +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080" +} + +.fa-twitter-square:before { + content: "\f081" +} + +.fa-facebook-square:before { + content: "\f082" +} + +.fa-camera-retro:before { + content: "\f083" +} + +.fa-key:before { + content: "\f084" +} + +.fa-gears:before, +.fa-cogs:before { + content: "\f085" +} + +.fa-comments:before { + content: "\f086" +} + +.fa-thumbs-o-up:before { + content: "\f087" +} + +.fa-thumbs-o-down:before { + content: "\f088" +} + +.fa-star-half:before { + content: "\f089" +} + +.fa-heart-o:before { + content: "\f08a" +} + +.fa-sign-out:before { + content: "\f08b" +} + +.fa-linkedin-square:before { + content: "\f08c" +} + +.fa-thumb-tack:before { + content: "\f08d" +} + +.fa-external-link:before { + content: "\f08e" +} + +.fa-sign-in:before { + content: "\f090" +} + +.fa-trophy:before { + content: "\f091" +} + +.fa-github-square:before { + content: "\f092" +} + +.fa-upload:before { + content: "\f093" +} + +.fa-lemon-o:before { + content: "\f094" +} + +.fa-phone:before { + content: "\f095" +} + +.fa-square-o:before { + content: "\f096" +} + +.fa-bookmark-o:before { + content: "\f097" +} + +.fa-phone-square:before { + content: "\f098" +} + +.fa-twitter:before { + content: "\f099" +} + +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a" +} + +.fa-github:before { + content: "\f09b" +} + +.fa-unlock:before { + content: "\f09c" +} + +.fa-credit-card:before { + content: "\f09d" +} + +.fa-feed:before, +.fa-rss:before { + content: "\f09e" +} + +.fa-hdd-o:before { + content: "\f0a0" +} + +.fa-bullhorn:before { + content: "\f0a1" +} + +.fa-bell:before { + content: "\f0f3" +} + +.fa-certificate:before { + content: "\f0a3" +} + +.fa-hand-o-right:before { + content: "\f0a4" +} + +.fa-hand-o-left:before { + content: "\f0a5" +} + +.fa-hand-o-up:before { + content: "\f0a6" +} + +.fa-hand-o-down:before { + content: "\f0a7" +} + +.fa-arrow-circle-left:before { + content: "\f0a8" +} + +.fa-arrow-circle-right:before { + content: "\f0a9" +} + +.fa-arrow-circle-up:before { + content: "\f0aa" +} + +.fa-arrow-circle-down:before { + content: "\f0ab" +} + +.fa-globe:before { + content: "\f0ac" +} + +.fa-wrench:before { + content: "\f0ad" +} + +.fa-tasks:before { + content: "\f0ae" +} + +.fa-filter:before { + content: "\f0b0" +} + +.fa-briefcase:before { + content: "\f0b1" +} + +.fa-arrows-alt:before { + content: "\f0b2" +} + +.fa-group:before, +.fa-users:before { + content: "\f0c0" +} + +.fa-chain:before, +.fa-link:before { + content: "\f0c1" +} + +.fa-cloud:before { + content: "\f0c2" +} + +.fa-flask:before { + content: "\f0c3" +} + +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4" +} + +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5" +} + +.fa-paperclip:before { + content: "\f0c6" +} + +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7" +} + +.fa-square:before { + content: "\f0c8" +} + +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9" +} + +.fa-list-ul:before { + content: "\f0ca" +} + +.fa-list-ol:before { + content: "\f0cb" +} + +.fa-strikethrough:before { + content: "\f0cc" +} + +.fa-underline:before { + content: "\f0cd" +} + +.fa-table:before { + content: "\f0ce" +} + +.fa-magic:before { + content: "\f0d0" +} + +.fa-truck:before { + content: "\f0d1" +} + +.fa-pinterest:before { + content: "\f0d2" +} + +.fa-pinterest-square:before { + content: "\f0d3" +} + +.fa-google-plus-square:before { + content: "\f0d4" +} + +.fa-google-plus:before { + content: "\f0d5" +} + +.fa-money:before { + content: "\f0d6" +} + +.fa-caret-down:before { + content: "\f0d7" +} + +.fa-caret-up:before { + content: "\f0d8" +} + +.fa-caret-left:before { + content: "\f0d9" +} + +.fa-caret-right:before { + content: "\f0da" +} + +.fa-columns:before { + content: "\f0db" +} + +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc" +} + +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd" +} + +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de" +} + +.fa-envelope:before { + content: "\f0e0" +} + +.fa-linkedin:before { + content: "\f0e1" +} + +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2" +} + +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3" +} + +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4" +} + +.fa-comment-o:before { + content: "\f0e5" +} + +.fa-comments-o:before { + content: "\f0e6" +} + +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7" +} + +.fa-sitemap:before { + content: "\f0e8" +} + +.fa-umbrella:before { + content: "\f0e9" +} + +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea" +} + +.fa-lightbulb-o:before { + content: "\f0eb" +} + +.fa-exchange:before { + content: "\f0ec" +} + +.fa-cloud-download:before { + content: "\f0ed" +} + +.fa-cloud-upload:before { + content: "\f0ee" +} + +.fa-user-md:before { + content: "\f0f0" +} + +.fa-stethoscope:before { + content: "\f0f1" +} + +.fa-suitcase:before { + content: "\f0f2" +} + +.fa-bell-o:before { + content: "\f0a2" +} + +.fa-coffee:before { + content: "\f0f4" +} + +.fa-cutlery:before { + content: "\f0f5" +} + +.fa-file-text-o:before { + content: "\f0f6" +} + +.fa-building-o:before { + content: "\f0f7" +} + +.fa-hospital-o:before { + content: "\f0f8" +} + +.fa-ambulance:before { + content: "\f0f9" +} + +.fa-medkit:before { + content: "\f0fa" +} + +.fa-fighter-jet:before { + content: "\f0fb" +} + +.fa-beer:before { + content: "\f0fc" +} + +.fa-h-square:before { + content: "\f0fd" +} + +.fa-plus-square:before { + content: "\f0fe" +} + +.fa-angle-double-left:before { + content: "\f100" +} + +.fa-angle-double-right:before { + content: "\f101" +} + +.fa-angle-double-up:before { + content: "\f102" +} + +.fa-angle-double-down:before { + content: "\f103" +} + +.fa-angle-left:before { + content: "\f104" +} + +.fa-angle-right:before { + content: "\f105" +} + +.fa-angle-up:before { + content: "\f106" +} + +.fa-angle-down:before { + content: "\f107" +} + +.fa-desktop:before { + content: "\f108" +} + +.fa-laptop:before { + content: "\f109" +} + +.fa-tablet:before { + content: "\f10a" +} + +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b" +} + +.fa-circle-o:before { + content: "\f10c" +} + +.fa-quote-left:before { + content: "\f10d" +} + +.fa-quote-right:before { + content: "\f10e" +} + +.fa-spinner:before { + content: "\f110" +} + +.fa-circle:before { + content: "\f111" +} + +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112" +} + +.fa-github-alt:before { + content: "\f113" +} + +.fa-folder-o:before { + content: "\f114" +} + +.fa-folder-open-o:before { + content: "\f115" +} + +.fa-smile-o:before { + content: "\f118" +} + +.fa-frown-o:before { + content: "\f119" +} + +.fa-meh-o:before { + content: "\f11a" +} + +.fa-gamepad:before { + content: "\f11b" +} + +.fa-keyboard-o:before { + content: "\f11c" +} + +.fa-flag-o:before { + content: "\f11d" +} + +.fa-flag-checkered:before { + content: "\f11e" +} + +.fa-terminal:before { + content: "\f120" +} + +.fa-code:before { + content: "\f121" +} + +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122" +} + +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123" +} + +.fa-location-arrow:before { + content: "\f124" +} + +.fa-crop:before { + content: "\f125" +} + +.fa-code-fork:before { + content: "\f126" +} + +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127" +} + +.fa-question:before { + content: "\f128" +} + +.fa-info:before { + content: "\f129" +} + +.fa-exclamation:before { + content: "\f12a" +} + +.fa-superscript:before { + content: "\f12b" +} + +.fa-subscript:before { + content: "\f12c" +} + +.fa-eraser:before { + content: "\f12d" +} + +.fa-puzzle-piece:before { + content: "\f12e" +} + +.fa-microphone:before { + content: "\f130" +} + +.fa-microphone-slash:before { + content: "\f131" +} + +.fa-shield:before { + content: "\f132" +} + +.fa-calendar-o:before { + content: "\f133" +} + +.fa-fire-extinguisher:before { + content: "\f134" +} + +.fa-rocket:before { + content: "\f135" +} + +.fa-maxcdn:before { + content: "\f136" +} + +.fa-chevron-circle-left:before { + content: "\f137" +} + +.fa-chevron-circle-right:before { + content: "\f138" +} + +.fa-chevron-circle-up:before { + content: "\f139" +} + +.fa-chevron-circle-down:before { + content: "\f13a" +} + +.fa-html5:before { + content: "\f13b" +} + +.fa-css3:before { + content: "\f13c" +} + +.fa-anchor:before { + content: "\f13d" +} + +.fa-unlock-alt:before { + content: "\f13e" +} + +.fa-bullseye:before { + content: "\f140" +} + +.fa-ellipsis-h:before { + content: "\f141" +} + +.fa-ellipsis-v:before { + content: "\f142" +} + +.fa-rss-square:before { + content: "\f143" +} + +.fa-play-circle:before { + content: "\f144" +} + +.fa-ticket:before { + content: "\f145" +} + +.fa-minus-square:before { + content: "\f146" +} + +.fa-minus-square-o:before { + content: "\f147" +} + +.fa-level-up:before { + content: "\f148" +} + +.fa-level-down:before { + content: "\f149" +} + +.fa-check-square:before { + content: "\f14a" +} + +.fa-pencil-square:before { + content: "\f14b" +} + +.fa-external-link-square:before { + content: "\f14c" +} + +.fa-share-square:before { + content: "\f14d" +} + +.fa-compass:before { + content: "\f14e" +} + +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150" +} + +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151" +} + +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152" +} + +.fa-euro:before, +.fa-eur:before { + content: "\f153" +} + +.fa-gbp:before { + content: "\f154" +} + +.fa-dollar:before, +.fa-usd:before { + content: "\f155" +} + +.fa-rupee:before, +.fa-inr:before { + content: "\f156" +} + +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157" +} + +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158" +} + +.fa-won:before, +.fa-krw:before { + content: "\f159" +} + +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a" +} + +.fa-file:before { + content: "\f15b" +} + +.fa-file-text:before { + content: "\f15c" +} + +.fa-sort-alpha-asc:before { + content: "\f15d" +} + +.fa-sort-alpha-desc:before { + content: "\f15e" +} + +.fa-sort-amount-asc:before { + content: "\f160" +} + +.fa-sort-amount-desc:before { + content: "\f161" +} + +.fa-sort-numeric-asc:before { + content: "\f162" +} + +.fa-sort-numeric-desc:before { + content: "\f163" +} + +.fa-thumbs-up:before { + content: "\f164" +} + +.fa-thumbs-down:before { + content: "\f165" +} + +.fa-youtube-square:before { + content: "\f166" +} + +.fa-youtube:before { + content: "\f167" +} + +.fa-xing:before { + content: "\f168" +} + +.fa-xing-square:before { + content: "\f169" +} + +.fa-youtube-play:before { + content: "\f16a" +} + +.fa-dropbox:before { + content: "\f16b" +} + +.fa-stack-overflow:before { + content: "\f16c" +} + +.fa-instagram:before { + content: "\f16d" +} + +.fa-flickr:before { + content: "\f16e" +} + +.fa-adn:before { + content: "\f170" +} + +.fa-bitbucket:before { + content: "\f171" +} + +.fa-bitbucket-square:before { + content: "\f172" +} + +.fa-tumblr:before { + content: "\f173" +} + +.fa-tumblr-square:before { + content: "\f174" +} + +.fa-long-arrow-down:before { + content: "\f175" +} + +.fa-long-arrow-up:before { + content: "\f176" +} + +.fa-long-arrow-left:before { + content: "\f177" +} + +.fa-long-arrow-right:before { + content: "\f178" +} + +.fa-apple:before { + content: "\f179" +} + +.fa-windows:before { + content: "\f17a" +} + +.fa-android:before { + content: "\f17b" +} + +.fa-linux:before { + content: "\f17c" +} + +.fa-dribbble:before { + content: "\f17d" +} + +.fa-skype:before { + content: "\f17e" +} + +.fa-foursquare:before { + content: "\f180" +} + +.fa-trello:before { + content: "\f181" +} + +.fa-female:before { + content: "\f182" +} + +.fa-male:before { + content: "\f183" +} + +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184" +} + +.fa-sun-o:before { + content: "\f185" +} + +.fa-moon-o:before { + content: "\f186" +} + +.fa-archive:before { + content: "\f187" +} + +.fa-bug:before { + content: "\f188" +} + +.fa-vk:before { + content: "\f189" +} + +.fa-weibo:before { + content: "\f18a" +} + +.fa-renren:before { + content: "\f18b" +} + +.fa-pagelines:before { + content: "\f18c" +} + +.fa-stack-exchange:before { + content: "\f18d" +} + +.fa-arrow-circle-o-right:before { + content: "\f18e" +} + +.fa-arrow-circle-o-left:before { + content: "\f190" +} + +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191" +} + +.fa-dot-circle-o:before { + content: "\f192" +} + +.fa-wheelchair:before { + content: "\f193" +} + +.fa-vimeo-square:before { + content: "\f194" +} + +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195" +} + +.fa-plus-square-o:before { + content: "\f196" +} + +.fa-space-shuttle:before { + content: "\f197" +} + +.fa-slack:before { + content: "\f198" +} + +.fa-envelope-square:before { + content: "\f199" +} + +.fa-wordpress:before { + content: "\f19a" +} + +.fa-openid:before { + content: "\f19b" +} + +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c" +} + +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d" +} + +.fa-yahoo:before { + content: "\f19e" +} + +.fa-google:before { + content: "\f1a0" +} + +.fa-reddit:before { + content: "\f1a1" +} + +.fa-reddit-square:before { + content: "\f1a2" +} + +.fa-stumbleupon-circle:before { + content: "\f1a3" +} + +.fa-stumbleupon:before { + content: "\f1a4" +} + +.fa-delicious:before { + content: "\f1a5" +} + +.fa-digg:before { + content: "\f1a6" +} + +.fa-pied-piper-pp:before { + content: "\f1a7" +} + +.fa-pied-piper-alt:before { + content: "\f1a8" +} + +.fa-drupal:before { + content: "\f1a9" +} + +.fa-joomla:before { + content: "\f1aa" +} + +.fa-language:before { + content: "\f1ab" +} + +.fa-fax:before { + content: "\f1ac" +} + +.fa-building:before { + content: "\f1ad" +} + +.fa-child:before { + content: "\f1ae" +} + +.fa-paw:before { + content: "\f1b0" +} + +.fa-spoon:before { + content: "\f1b1" +} + +.fa-cube:before { + content: "\f1b2" +} + +.fa-cubes:before { + content: "\f1b3" +} + +.fa-behance:before { + content: "\f1b4" +} + +.fa-behance-square:before { + content: "\f1b5" +} + +.fa-steam:before { + content: "\f1b6" +} + +.fa-steam-square:before { + content: "\f1b7" +} + +.fa-recycle:before { + content: "\f1b8" +} + +.fa-automobile:before, +.fa-car:before { + content: "\f1b9" +} + +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba" +} + +.fa-tree:before { + content: "\f1bb" +} + +.fa-spotify:before { + content: "\f1bc" +} + +.fa-deviantart:before { + content: "\f1bd" +} + +.fa-soundcloud:before { + content: "\f1be" +} + +.fa-database:before { + content: "\f1c0" +} + +.fa-file-pdf-o:before { + content: "\f1c1" +} + +.fa-file-word-o:before { + content: "\f1c2" +} + +.fa-file-excel-o:before { + content: "\f1c3" +} + +.fa-file-powerpoint-o:before { + content: "\f1c4" +} + +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5" +} + +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6" +} + +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7" +} + +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8" +} + +.fa-file-code-o:before { + content: "\f1c9" +} + +.fa-vine:before { + content: "\f1ca" +} + +.fa-codepen:before { + content: "\f1cb" +} + +.fa-jsfiddle:before { + content: "\f1cc" +} + +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd" +} + +.fa-circle-o-notch:before { + content: "\f1ce" +} + +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0" +} + +.fa-ge:before, +.fa-empire:before { + content: "\f1d1" +} + +.fa-git-square:before { + content: "\f1d2" +} + +.fa-git:before { + content: "\f1d3" +} + +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4" +} + +.fa-tencent-weibo:before { + content: "\f1d5" +} + +.fa-qq:before { + content: "\f1d6" +} + +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7" +} + +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8" +} + +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9" +} + +.fa-history:before { + content: "\f1da" +} + +.fa-circle-thin:before { + content: "\f1db" +} + +.fa-header:before { + content: "\f1dc" +} + +.fa-paragraph:before { + content: "\f1dd" +} + +.fa-sliders:before { + content: "\f1de" +} + +.fa-share-alt:before { + content: "\f1e0" +} + +.fa-share-alt-square:before { + content: "\f1e1" +} + +.fa-bomb:before { + content: "\f1e2" +} + +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3" +} + +.fa-tty:before { + content: "\f1e4" +} + +.fa-binoculars:before { + content: "\f1e5" +} + +.fa-plug:before { + content: "\f1e6" +} + +.fa-slideshare:before { + content: "\f1e7" +} + +.fa-twitch:before { + content: "\f1e8" +} + +.fa-yelp:before { + content: "\f1e9" +} + +.fa-newspaper-o:before { + content: "\f1ea" +} + +.fa-wifi:before { + content: "\f1eb" +} + +.fa-calculator:before { + content: "\f1ec" +} + +.fa-paypal:before { + content: "\f1ed" +} + +.fa-google-wallet:before { + content: "\f1ee" +} + +.fa-cc-visa:before { + content: "\f1f0" +} + +.fa-cc-mastercard:before { + content: "\f1f1" +} + +.fa-cc-discover:before { + content: "\f1f2" +} + +.fa-cc-amex:before { + content: "\f1f3" +} + +.fa-cc-paypal:before { + content: "\f1f4" +} + +.fa-cc-stripe:before { + content: "\f1f5" +} + +.fa-bell-slash:before { + content: "\f1f6" +} + +.fa-bell-slash-o:before { + content: "\f1f7" +} + +.fa-trash:before { + content: "\f1f8" +} + +.fa-copyright:before { + content: "\f1f9" +} + +.fa-at:before { + content: "\f1fa" +} + +.fa-eyedropper:before { + content: "\f1fb" +} + +.fa-paint-brush:before { + content: "\f1fc" +} + +.fa-birthday-cake:before { + content: "\f1fd" +} + +.fa-area-chart:before { + content: "\f1fe" +} + +.fa-pie-chart:before { + content: "\f200" +} + +.fa-line-chart:before { + content: "\f201" +} + +.fa-lastfm:before { + content: "\f202" +} + +.fa-lastfm-square:before { + content: "\f203" +} + +.fa-toggle-off:before { + content: "\f204" +} + +.fa-toggle-on:before { + content: "\f205" +} + +.fa-bicycle:before { + content: "\f206" +} + +.fa-bus:before { + content: "\f207" +} + +.fa-ioxhost:before { + content: "\f208" +} + +.fa-angellist:before { + content: "\f209" +} + +.fa-cc:before { + content: "\f20a" +} + +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b" +} + +.fa-meanpath:before { + content: "\f20c" +} + +.fa-buysellads:before { + content: "\f20d" +} + +.fa-connectdevelop:before { + content: "\f20e" +} + +.fa-dashcube:before { + content: "\f210" +} + +.fa-forumbee:before { + content: "\f211" +} + +.fa-leanpub:before { + content: "\f212" +} + +.fa-sellsy:before { + content: "\f213" +} + +.fa-shirtsinbulk:before { + content: "\f214" +} + +.fa-simplybuilt:before { + content: "\f215" +} + +.fa-skyatlas:before { + content: "\f216" +} + +.fa-cart-plus:before { + content: "\f217" +} + +.fa-cart-arrow-down:before { + content: "\f218" +} + +.fa-diamond:before { + content: "\f219" +} + +.fa-ship:before { + content: "\f21a" +} + +.fa-user-secret:before { + content: "\f21b" +} + +.fa-motorcycle:before { + content: "\f21c" +} + +.fa-street-view:before { + content: "\f21d" +} + +.fa-heartbeat:before { + content: "\f21e" +} + +.fa-venus:before { + content: "\f221" +} + +.fa-mars:before { + content: "\f222" +} + +.fa-mercury:before { + content: "\f223" +} + +.fa-intersex:before, +.fa-transgender:before { + content: "\f224" +} + +.fa-transgender-alt:before { + content: "\f225" +} + +.fa-venus-double:before { + content: "\f226" +} + +.fa-mars-double:before { + content: "\f227" +} + +.fa-venus-mars:before { + content: "\f228" +} + +.fa-mars-stroke:before { + content: "\f229" +} + +.fa-mars-stroke-v:before { + content: "\f22a" +} + +.fa-mars-stroke-h:before { + content: "\f22b" +} + +.fa-neuter:before { + content: "\f22c" +} + +.fa-genderless:before { + content: "\f22d" +} + +.fa-facebook-official:before { + content: "\f230" +} + +.fa-pinterest-p:before { + content: "\f231" +} + +.fa-whatsapp:before { + content: "\f232" +} + +.fa-server:before { + content: "\f233" +} + +.fa-user-plus:before { + content: "\f234" +} + +.fa-user-times:before { + content: "\f235" +} + +.fa-hotel:before, +.fa-bed:before { + content: "\f236" +} + +.fa-viacoin:before { + content: "\f237" +} + +.fa-train:before { + content: "\f238" +} + +.fa-subway:before { + content: "\f239" +} + +.fa-medium:before { + content: "\f23a" +} + +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b" +} + +.fa-optin-monster:before { + content: "\f23c" +} + +.fa-opencart:before { + content: "\f23d" +} + +.fa-expeditedssl:before { + content: "\f23e" +} + +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240" +} + +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241" +} + +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242" +} + +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243" +} + +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244" +} + +.fa-mouse-pointer:before { + content: "\f245" +} + +.fa-i-cursor:before { + content: "\f246" +} + +.fa-object-group:before { + content: "\f247" +} + +.fa-object-ungroup:before { + content: "\f248" +} + +.fa-sticky-note:before { + content: "\f249" +} + +.fa-sticky-note-o:before { + content: "\f24a" +} + +.fa-cc-jcb:before { + content: "\f24b" +} + +.fa-cc-diners-club:before { + content: "\f24c" +} + +.fa-clone:before { + content: "\f24d" +} + +.fa-balance-scale:before { + content: "\f24e" +} + +.fa-hourglass-o:before { + content: "\f250" +} + +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251" +} + +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252" +} + +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253" +} + +.fa-hourglass:before { + content: "\f254" +} + +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255" +} + +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256" +} + +.fa-hand-scissors-o:before { + content: "\f257" +} + +.fa-hand-lizard-o:before { + content: "\f258" +} + +.fa-hand-spock-o:before { + content: "\f259" +} + +.fa-hand-pointer-o:before { + content: "\f25a" +} + +.fa-hand-peace-o:before { + content: "\f25b" +} + +.fa-trademark:before { + content: "\f25c" +} + +.fa-registered:before { + content: "\f25d" +} + +.fa-creative-commons:before { + content: "\f25e" +} + +.fa-gg:before { + content: "\f260" +} + +.fa-gg-circle:before { + content: "\f261" +} + +.fa-tripadvisor:before { + content: "\f262" +} + +.fa-odnoklassniki:before { + content: "\f263" +} + +.fa-odnoklassniki-square:before { + content: "\f264" +} + +.fa-get-pocket:before { + content: "\f265" +} + +.fa-wikipedia-w:before { + content: "\f266" +} + +.fa-safari:before { + content: "\f267" +} + +.fa-chrome:before { + content: "\f268" +} + +.fa-firefox:before { + content: "\f269" +} + +.fa-opera:before { + content: "\f26a" +} + +.fa-internet-explorer:before { + content: "\f26b" +} + +.fa-tv:before, +.fa-television:before { + content: "\f26c" +} + +.fa-contao:before { + content: "\f26d" +} + +.fa-500px:before { + content: "\f26e" +} + +.fa-amazon:before { + content: "\f270" +} + +.fa-calendar-plus-o:before { + content: "\f271" +} + +.fa-calendar-minus-o:before { + content: "\f272" +} + +.fa-calendar-times-o:before { + content: "\f273" +} + +.fa-calendar-check-o:before { + content: "\f274" +} + +.fa-industry:before { + content: "\f275" +} + +.fa-map-pin:before { + content: "\f276" +} + +.fa-map-signs:before { + content: "\f277" +} + +.fa-map-o:before { + content: "\f278" +} + +.fa-map:before { + content: "\f279" +} + +.fa-commenting:before { + content: "\f27a" +} + +.fa-commenting-o:before { + content: "\f27b" +} + +.fa-houzz:before { + content: "\f27c" +} + +.fa-vimeo:before { + content: "\f27d" +} + +.fa-black-tie:before { + content: "\f27e" +} + +.fa-fonticons:before { + content: "\f280" +} + +.fa-reddit-alien:before { + content: "\f281" +} + +.fa-edge:before { + content: "\f282" +} + +.fa-credit-card-alt:before { + content: "\f283" +} + +.fa-codiepie:before { + content: "\f284" +} + +.fa-modx:before { + content: "\f285" +} + +.fa-fort-awesome:before { + content: "\f286" +} + +.fa-usb:before { + content: "\f287" +} + +.fa-product-hunt:before { + content: "\f288" +} + +.fa-mixcloud:before { + content: "\f289" +} + +.fa-scribd:before { + content: "\f28a" +} + +.fa-pause-circle:before { + content: "\f28b" +} + +.fa-pause-circle-o:before { + content: "\f28c" +} + +.fa-stop-circle:before { + content: "\f28d" +} + +.fa-stop-circle-o:before { + content: "\f28e" +} + +.fa-shopping-bag:before { + content: "\f290" +} + +.fa-shopping-basket:before { + content: "\f291" +} + +.fa-hashtag:before { + content: "\f292" +} + +.fa-bluetooth:before { + content: "\f293" +} + +.fa-bluetooth-b:before { + content: "\f294" +} + +.fa-percent:before { + content: "\f295" +} + +.fa-gitlab:before { + content: "\f296" +} + +.fa-wpbeginner:before { + content: "\f297" +} + +.fa-wpforms:before { + content: "\f298" +} + +.fa-envira:before { + content: "\f299" +} + +.fa-universal-access:before { + content: "\f29a" +} + +.fa-wheelchair-alt:before { + content: "\f29b" +} + +.fa-question-circle-o:before { + content: "\f29c" +} + +.fa-blind:before { + content: "\f29d" +} + +.fa-audio-description:before { + content: "\f29e" +} + +.fa-volume-control-phone:before { + content: "\f2a0" +} + +.fa-braille:before { + content: "\f2a1" +} + +.fa-assistive-listening-systems:before { + content: "\f2a2" +} + +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3" +} + +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4" +} + +.fa-glide:before { + content: "\f2a5" +} + +.fa-glide-g:before { + content: "\f2a6" +} + +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7" +} + +.fa-low-vision:before { + content: "\f2a8" +} + +.fa-viadeo:before { + content: "\f2a9" +} + +.fa-viadeo-square:before { + content: "\f2aa" +} + +.fa-snapchat:before { + content: "\f2ab" +} + +.fa-snapchat-ghost:before { + content: "\f2ac" +} + +.fa-snapchat-square:before { + content: "\f2ad" +} + +.fa-pied-piper:before { + content: "\f2ae" +} + +.fa-first-order:before { + content: "\f2b0" +} + +.fa-yoast:before { + content: "\f2b1" +} + +.fa-themeisle:before { + content: "\f2b2" +} + +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3" +} + +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4" +} + +.fa-handshake-o:before { + content: "\f2b5" +} + +.fa-envelope-open:before { + content: "\f2b6" +} + +.fa-envelope-open-o:before { + content: "\f2b7" +} + +.fa-linode:before { + content: "\f2b8" +} + +.fa-address-book:before { + content: "\f2b9" +} + +.fa-address-book-o:before { + content: "\f2ba" +} + +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb" +} + +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc" +} + +.fa-user-circle:before { + content: "\f2bd" +} + +.fa-user-circle-o:before { + content: "\f2be" +} + +.fa-user-o:before { + content: "\f2c0" +} + +.fa-id-badge:before { + content: "\f2c1" +} + +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2" +} + +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3" +} + +.fa-quora:before { + content: "\f2c4" +} + +.fa-free-code-camp:before { + content: "\f2c5" +} + +.fa-telegram:before { + content: "\f2c6" +} + +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7" +} + +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8" +} + +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9" +} + +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca" +} + +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb" +} + +.fa-shower:before { + content: "\f2cc" +} + +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd" +} + +.fa-podcast:before { + content: "\f2ce" +} + +.fa-window-maximize:before { + content: "\f2d0" +} + +.fa-window-minimize:before { + content: "\f2d1" +} + +.fa-window-restore:before { + content: "\f2d2" +} + +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3" +} + +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4" +} + +.fa-bandcamp:before { + content: "\f2d5" +} + +.fa-grav:before { + content: "\f2d6" +} + +.fa-etsy:before { + content: "\f2d7" +} + +.fa-imdb:before { + content: "\f2d8" +} + +.fa-ravelry:before { + content: "\f2d9" +} + +.fa-eercast:before { + content: "\f2da" +} + +.fa-microchip:before { + content: "\f2db" +} + +.fa-snowflake-o:before { + content: "\f2dc" +} + +.fa-superpowers:before { + content: "\f2dd" +} + +.fa-wpexplorer:before { + content: "\f2de" +} + +.fa-meetup:before { + content: "\f2e0" +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0 +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto +} \ No newline at end of file diff --git a/static/css/webfonts/fontawesome-webfont.eot b/static/css/webfonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/static/css/webfonts/fontawesome-webfont.eot differ diff --git a/static/css/webfonts/fontawesome-webfont.svg b/static/css/webfonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/static/css/webfonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/css/webfonts/fontawesome-webfont.woff b/static/css/webfonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/static/css/webfonts/fontawesome-webfont.woff differ diff --git a/static/css/webfonts/fontawesome-webfont.woff2 b/static/css/webfonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/static/css/webfonts/fontawesome-webfont.woff2 differ diff --git a/static/domain-info/domains/domain-info.json b/static/domain-info/domains/domain-info.json index 3e3d594..8f87b2a 100644 --- a/static/domain-info/domains/domain-info.json +++ b/static/domain-info/domains/domain-info.json @@ -26,7 +26,9 @@ "22": "网络服务", "23": "支付平台", "24": "API服务", - "25": "其他" + "25": "其他", + "26": "游戏网站", + "27": "行为分析、跟踪遥测、数据统计等" }, "domains": { "网易": { @@ -54,17 +56,17 @@ "url": "https://dict.youdao.com/", "icon": "https://dict.youdao.com/favicon.ico" }, - "网易uu加速器":{ + "网易uu加速器": { "name": "网易uu加速器", "categoryId": 0, "url": { - "1": "https://uu.163.com/", - "2": "https://log.uu.163.com/" + "1": "https://uu.163.com/", + "2": "https://log.uu.163.com/" } }, "company": "网易股份有限公司" }, - "华为":{ + "华为": { "华为全球官网": { "name": "华为全球官网", "categoryId": 0, @@ -103,7 +105,7 @@ }, "华为新闻中心": { "name": "华为新闻中心", - "categoryId": 3, + "categoryId": 11, "url": "news.huawei.com", "icon": "https://www.huawei.com/-/media/htemplate-home/1.0.1.20251205144752/components/assets/img/favicon-logo.svg" }, @@ -149,6 +151,21 @@ "url": "pay.huawei.com", "icon": "https://www.huawei.com/-/media/htemplate-home/1.0.1.20251205144752/components/assets/img/favicon-logo.svg" }, + "华为HiCloud网络连通性检测服务": { + "name": "华为HiCloud网络连通性检测服务", + "categoryId": 21, + "url": "connectivitycheck.platform.hicloud.com", + "icon": "https://www.huawei.com/-/media/htemplate-home/1.0.1.20251205144752/components/assets/img/favicon-logo.svg" + }, + "华为云CDN CNAME归属域": { + "name": "华为云CDN CNAME归属域/加速服务", + "categoryId": 2, + "url": { + "1": "cdnhwc1.com", + "2": "cdnhwc6.com" + }, + "icon": "https://www.huawei.com/-/media/htemplate-home/1.0.1.20251205144752/components/assets/img/favicon-logo.svg" + }, "华为鸿蒙开发者社区": { "name": "华为鸿蒙开发者社区", "categoryId": 11, @@ -248,7 +265,6 @@ "url": "dl.google.com", "icon": "https://www.google.com/favicon.ico" }, - "company": "谷歌 LLC" }, "阿里云": { @@ -279,11 +295,97 @@ "url": "https://www.alipay.com/", "icon": "https://www.alipay.com/favicon.ico" }, - "淘宝": { - "name": "淘宝", - "categoryId": 4, - "url": "https://www.taobao.com/", - "icon": "https://www.taobao.com/favicon.ico" + "淘宝相关": { + "淘宝": { + "name": "淘宝", + "categoryId": 4, + "url": "https://www.taobao.com/", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝UT数据看板服务": { + "name": "淘宝UT数据看板服务/淘宝内部数据监控与分析工具", + "categoryId": 6, + "url": "h-adashx.ut.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝移动端账号静态资源CDN": { + "name": "淘宝移动端账号静态资源CDN/淘宝移动端账号相关静态资源分发加速", + "categoryId": 2, + "url": "accscdn.m.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝AudID标识API服务": { + "name": "淘宝AudID标识API服务/用户设备唯一标识生成与验证接口", + "categoryId": 24, + "url": "audid-api.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝移动端主站": { + "name": "淘宝移动端主站/手机淘宝核心入口", + "categoryId": 4, + "url": "m.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝开放平台网关": { + "name": "淘宝开放平台网关/第三方应用API接入入口", + "categoryId": 24, + "url": "gw.api.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝TOP开放平台接口": { + "name": "淘宝TOP开放平台接口/电商业务API调用服务", + "categoryId": 24, + "url": "top.api.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝商家工具集": { + "name": "淘宝商家工具集/商家运营辅助工具", + "categoryId": 6, + "url": "tool.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝卖家中心": { + "name": "淘宝卖家中心/店铺管理与运营后台", + "categoryId": 6, + "url": "seller.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝支付服务入口": { + "name": "淘宝支付服务入口/订单支付与结算服务", + "categoryId": 23, + "url": "pay.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝核心静态资源CDN": { + "name": "淘宝核心静态资源CDN/全局样式脚本分发加速", + "categoryId": 2, + "url": "g.tbcdn.cn", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝前端静态资源CDN": { + "name": "淘宝前端静态资源CDN/网页静态文件分发加速", + "categoryId": 2, + "url": "assets.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝图片存储CDN": { + "name": "淘宝图片存储CDN/商品图片与素材分发加速", + "categoryId": 2, + "url": "img.alicdn.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝移动端专属CDN": { + "name": "淘宝移动端专属CDN/手机淘宝静态资源分发加速", + "categoryId": 2, + "url": "cdn.m.taobao.com", + "icon": "https://www.taobao.com/favicon.ico" + }, + "淘宝搜索结果CDN": { + "name": "淘宝搜索结果CDN/搜索页面静态资源分发加速", + "categoryId": 2, + "url": "s.tbcdn.cn", + "icon": "https://www.taobao.com/favicon.ico" + } }, "天猫": { "name": "天猫", @@ -291,12 +393,12 @@ "url": "https://www.tmall.com/", "icon": "https://www.tmall.com/favicon.ico" }, - "阿里云镜像源":{ + "阿里云镜像源": { "name": "阿里巴巴开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.aliyun.com/" - }, + }, "icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico" }, "万网": { @@ -306,28 +408,27 @@ "icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico" }, "阿里CDN": { - "阿里云CDN":{ + "阿里云CDN": { "name": "阿里CDN", "categoryId": 2, "url": { - "1": "kunlunsl.com", - "2": "cdn.aliyuncs.com", - "3": "cloudfront.net", - "4": "alibabadns.com" + "1": "kunlunsl.com", + "2": "cdn.aliyuncs.com", + "3": "cloudfront.net", + "4": "alibabadns.com" }, "icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico" }, - "GSLB 全局负载均衡调度":{ + "GSLB 全局负载均衡调度": { "name": "GSLB 全局负载均衡调度", "categoryId": 2, "url": "cdngslb.com", "icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico" } - }, "company": "阿里云计算有限公司/阿里巴巴集团" }, - "UC":{ + "UC": { "UC浏览器官网": { "name": "UC浏览器官网", "categoryId": 4, @@ -346,21 +447,26 @@ "url": { "1": "https://wx.qq.com/", "2": "https://weixin.qq.com/", - "3": "https://res.wx.qq.com/", + "3": "https://res.wx.qq.com/", "4": "dns.weixin.qq.com", "5": "pc.weixin.qq.com" }, "icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico" }, + "腾讯会议核心代理服务": { + "name": "腾讯会议核心代理服务", + "categoryId": 19, + "url": "oth.eve.mdt.qq.com", + "icon": "https://meeting.tencent.com/favicon.ico" + }, "WeChat": { "name": "WeChat", "categoryId": 7, "url": { - "1":"wechat.com", - "2": "support.wechat.com", - "3": "www.wechat.com" - }, - + "1": "wechat.com", + "2": "support.wechat.com", + "3": "www.wechat.com" + }, "icon": "https://wechat.com/favicon.ico" }, "微信开放平台": { @@ -372,57 +478,60 @@ "微信支付": { "name": "微信支付商户平台", "categoryId": 23, - "url": {"1": "pay.weixin.qq.com", - "2": "pay.wechatpay.cn", - "3": "act.weixin.qq.com", - "4": "api.wechatpay.cn", - "5": "api.mch.weixin.qq.com", - "6": "api2.mch.weixin.qq.com", - "7": "api.wechatpay.cn", - "8": "api2.wechatpay.cn", - "9": "payapp.weixin.qq.com", - "10": "payapp.wechatpay.cn", - "11": "fraud.mch.weixin.qq.com", - "12": "fraud.wechatpay.cn", - "13": "action.weixin.qq.com", - "14": "action.wechatpay.cn", - "15": "wechatpay.cn" - }, - + "url": { + "1": "pay.weixin.qq.com", + "2": "pay.wechatpay.cn", + "3": "act.weixin.qq.com", + "4": "api.wechatpay.cn", + "5": "api.mch.weixin.qq.com", + "6": "api2.mch.weixin.qq.com", + "7": "api.wechatpay.cn", + "8": "api2.wechatpay.cn", + "9": "payapp.weixin.qq.com", + "10": "payapp.wechatpay.cn", + "11": "fraud.mch.weixin.qq.com", + "12": "fraud.wechatpay.cn", + "13": "action.weixin.qq.com", + "14": "action.wechatpay.cn", + "15": "wechatpay.cn" + }, "icon": "https://gtimg.wechatpay.cn/core/favicon.ico" }, "微信支付海外版": { "name": "微信支付海外版", "categoryId": 23, - "url": {"1": "https://pay.wechatpay.global/", - "2": "apihk.mch.weixin.qq.com", - "3": "apius.mch.weixin.qq.com" - }, + "url": { + "1": "https://pay.wechatpay.global/", + "2": "apihk.mch.weixin.qq.com", + "3": "apius.mch.weixin.qq.com" + }, "icon": "https://gtimg.wechatpay.cn/core/favicon.ico" }, "微信CDN": { "name": "微信CDN和日志等资源", "categoryId": 23, - "url": {"1": "https://gtimg.wechatpay.cn/", - "2": "log.wechatpay.cn", - "3": "newres.wechat.com", - "4": "res.wx.qq.com", - "5": "sh.servicewechat.com", - "6": "res.servicewechat.com" - }, + "url": { + "1": "https://gtimg.wechatpay.cn/", + "2": "log.wechatpay.cn", + "3": "newres.wechat.com", + "4": "res.wx.qq.com", + "5": "sh.servicewechat.com", + "6": "res.servicewechat.com" + }, "icon": "https://gtimg.wechatpay.cn/core/favicon.ico" }, "腾讯下载加速网络CDN": { "name": "腾讯下载加速网络CDN", "categoryId": 23, - "url": {"1": "dldir1v6.qq.com", - "2": "dldir1.qq.com", - "3": "dldir2.qq.com", - "4": "dldir3.qq.com", - "5": "dl.qq.com", - "6": "dldir.tencent.com", - "7": "pc.qq.com" - }, + "url": { + "1": "dldir1v6.qq.com", + "2": "dldir1.qq.com", + "3": "dldir2.qq.com", + "4": "dldir3.qq.com", + "5": "dl.qq.com", + "6": "dldir.tencent.com", + "7": "pc.qq.com" + }, "icon": "https://www.tencent.com/favicon.ico" }, "腾讯QQ": { @@ -471,14 +580,14 @@ "name": "腾讯广告", "categoryId": 0, "url": { - "1": "ugdtimg.com", - "2": "gdtimg.com", + "1": "ugdtimg.com", + "2": "gdtimg.com", "3": "ad.qq.com", "4": "v3.gdt.qq.com", "5": "c3.gdt.qq.com", "6": "v2mi.gdt.qq.com", "7": "gdt.qq.com" - }, + }, "icon": "https://www.tencent.com/favicon.ico" }, "腾讯Beacon事件日志上报": { @@ -487,91 +596,137 @@ "url": "aeventlog.beacon.qq.com", "icon": "https://www.tencent.com/favicon.ico" }, - "QQ邮箱相关":{ + "QQ邮箱相关": { "name": "QQ邮箱", "categoryId": 0, - "url": {"1": "mail.qq.com", - "2": "imap.qq.com", - "3": "smtp.qq.com", - "4": "w.mail.qq.com" - }, + "url": { + "1": "mail.qq.com", + "2": "imap.qq.com", + "3": "smtp.qq.com", + "4": "w.mail.qq.com" + }, "icon": "https://mail.qq.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "腾讯企业邮箱":{ + "腾讯企业邮箱": { "name": "腾讯企业邮箱", "categoryId": 0, - "url": {"1": "https://exmail.qq.com/", - "2": "https://exmail.qq.com/" - }, + "url": { + "1": "https://exmail.qq.com/", + "2": "https://exmail.qq.com/" + }, "icon": "https://exmail.qq.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "企业微信":{ + "企业微信": { "name": "企业微信", "categoryId": 0, - "url": {"1": "https://work.weixin.qq.com/", - "2": "https://work.weixin.qq.com/" - }, + "url": { + "1": "https://work.weixin.qq.com/", + "2": "https://work.weixin.qq.com/" + }, "icon": "https://work.weixin.qq.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "微信青少年相关":{ - "微信守护平台":{ + "微信青少年相关": { + "微信守护平台": { "name": "微信守护平台", "categoryId": 22, - "url": "wxguard.weixin.qq.com", + "url": "wxguard.weixin.qq.com", "icon": "https://open.weixin.qq.com/favicon.ico" }, - "微信未成年人服务短链接":{ + "微信未成年人服务短链接": { "name": "微信未成年人服务短链接", "categoryId": 22, - "url": {"1": "minorshort.weixin.qq.com", - "2": "szminorshort.weixin.qq.com" - }, + "url": { + "1": "minorshort.weixin.qq.com", + "2": "szminorshort.weixin.qq.com" + }, "icon": "https://open.weixin.qq.com/favicon.ico" }, - "深圳地区扩展短链接":{ + "深圳地区扩展短链接": { "name": "深圳地区扩展短链接", "categoryId": 22, "url": "szextshort.weixin.qq.com", "icon": "https://open.weixin.qq.com/favicon.ico" }, - "腾讯云":{ + "腾讯云": { "name": "腾讯云", "categoryId": 0, - "url": "https://cloud.tencent.com/", + "url": { + "1": "https://cloud.tencent.com/", + "2": "https://www.tencentcloud.com/" + }, "icon": "https://cloud.tencent.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "腾讯云CDN":{ + "腾讯云CDN": { "name": "腾讯云CDN", "categoryId": 2, "url": { - "1": "dnsv1.com", - "2": "tencent-cloud.cn", - "3": "tencent-cloud.com" - }, + "1": "dnsv1.com", + "2": "tencent-cloud.cn", + "3": "tencent-cloud.com" + }, "icon": "https://cloud.tencent.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "PCDN 性能统计与数据上报":{ - "name": "PCDN 性能统计与数据上报", + "qcloud": { + "name": "腾讯云核心服务域", "categoryId": 2, - "url": "tencent-cloud.net", + "url": "myqcloud.com", "icon": "https://cloud.tencent.com/favicon.ico" }, - "company": "深圳腾讯计算机系统有限公司" - } - }, + "腾讯云 EdgeOne (EO) 边缘安全加速平台": { + "name": "腾讯云 EdgeOne (EO) 边缘安全加速平台", + "categoryId": 2, + "url": { + "1": "dnse3.com", + "2": "eo.dnse3.com" + }, + "icon": "https://cloud.tencent.com/favicon.ico" + }, + "腾讯云全球加速 CDN": { + "name": "腾讯云全球加速 CDN", + "categoryId": 2, + "url": { + "1": "dnse1.com", + "2": "dnse2.com", + "3": "dnsoe2.com", + "4": "dnse5.com" + }, + "icon": "https://cloud.tencent.com/favicon.ico" + }, + "腾讯云DNSPod智能调度服务": { + "name": "腾讯云DNSPod智能调度服务", + "categoryId": 2, + "url": "tdnsdp1.cn", + "icon": "https://dnspod.com/favicon.ico" + }, + "PCDN 性能统计与数据上报": { + "name": "PCDN 性能统计与数据上报", + "categoryId": 2, + "url": "apd-pcdnwxstat.teg.tencent-cloud.net", + "icon": "https://cloud.tencent.com/favicon.ico" + }, + "IAS(智能加速服务)的动态调度 / 边缘节点": { + "name": "IAS(智能加速服务)的动态调度 / 边缘节点", + "categoryId": 2, + "url": "ias.tencent-cloud.net", + "icon": "https://cloud.tencent.com/favicon.ico" + } + }, + "company": "深圳市腾讯计算机系统有限公司" + }, "高德地图相关": { "高德地图": { "name": "高德地图", "categoryId": 12, - "url": {"1": "https://map.amap.com/", - "2": "https://ditu.amap.com/", - "3": "https://www.amap.com/" - }, + "url": { + "1": "https://map.amap.com/", + "2": "https://ditu.amap.com/", + "3": "https://www.amap.com/" + }, "icon": "https://a.amap.com/pc/static/favicon.ico" }, "高德开放平台": { @@ -581,16 +736,16 @@ "icon": "https://a.amap.com/pc/static/favicon.ico" }, "高德地图API": { - "高德地图API":{ + "高德地图API": { "name": "高德地图API", "categoryId": 22, "url": { - "1":"https://restapi.amap.com/", - "2": "https://webapi.amap.com/" + "1": "https://restapi.amap.com/", + "2": "https://webapi.amap.com/" }, "icon": "https://a.amap.com/pc/static/favicon.ico" }, - "高德前端性能与数据采集":{ + "高德前端性能与数据采集": { "name": "高德前端性能与数据采集", "categoryId": 22, "url": "fp.amap.com", @@ -598,12 +753,13 @@ } }, "高德CDN": { - "移动端 CDN 资源分发":{ + "移动端 CDN 资源分发": { "name": "移动端 CDN 资源分发N", "categoryId": 2, - "url": {"1": "m5.amap.com", - "2": "m5-x.amap.com" - }, + "url": { + "1": "m5.amap.com", + "2": "m5-x.amap.com" + }, "icon": "https://a.amap.com/pc/static/favicon.ico" } }, @@ -629,38 +785,76 @@ "icon": "https://www.microsoft.com/favicon.ico?v2" }, "微软必应": { - "name": "微软必应", - "categoryId": 1, - "url": {"1": "www.bing.com", + "微软必应": { + "name": "微软必应", + "categoryId": 1, + "url": { + "1": "www.bing.com", "2": "cn.bing.com", "3": "bing.com" }, - "icon": "https://www.bing.com/favicon.ico" + "icon": "https://www.bing.com/favicon.ico" + }, + "Bing重定向服务": { + "name": "Bing重定向服务/搜索请求重定向处理", + "categoryId": 1, + "url": "r.bing.com", + "icon": "https://www.bing.com/favicon.ico" + }, + "Bing静态资源节点": { + "name": "Bing静态资源节点/页面静态资源分发加速", + "categoryId": 2, + "url": { + "1": "ts1.tc.mm.bing.net", + "2": "ts2.tc.mm.bing.net", + "3": "ts3.tc.mm.bing.net", + "4": "ts4.tc.mm.bing.net" + }, + "icon": "https://www.bing.com/favicon.ico" + }, + "Bing备用搜索主站": { + "name": "Bing备用搜索主站/搜索引擎备用访问入口", + "categoryId": 1, + "url": "www2.bing.com", + "icon": "https://www.bing.com/favicon.ico" + }, + "Bing图片缩略图服务": { + "name": "Bing图片缩略图服务/搜索结果图片缩略图分发", + "categoryId": 2, + "url": "th.bing.com", + "icon": "https://www.bing.com/favicon.ico" + }, + "Bing奖励计划官网": { + "name": "Bing奖励计划官网/搜索奖励积分查询与兑换", + "categoryId": 6, + "url": "rewards.bing.com", + "icon": "https://www.bing.com/favicon.ico" + } }, "OneCollector/1DS(微软统一数据收集平台)": { "name": "微软OneCollector/1DS (One Data Strategy) 统一遥测收集系统", "categoryId": 0, - "url": {"1": "mobile.events.data.microsoft.com", - "2": "us-mobile.events.data.microsoft.com", - "3": "eu-mobile.events.data.microsoft.com", - "4": "au-mobile.events.data.microsoft.com", - "5": "jp-mobile.events.data.microsoft.com", - "6": "browser.events.data.microsoft.com", - "7": "v10.events.data.microsoft.com", - "8": "mobile.pipe.aria.microsoft.com", - "9": "in.appcenter.ms" - }, - + "url": { + "1": "mobile.events.data.microsoft.com", + "2": "us-mobile.events.data.microsoft.com", + "3": "eu-mobile.events.data.microsoft.com", + "4": "au-mobile.events.data.microsoft.com", + "5": "jp-mobile.events.data.microsoft.com", + "6": "browser.events.data.microsoft.com", + "7": "v10.events.data.microsoft.com", + "8": "mobile.pipe.aria.microsoft.com", + "9": "in.appcenter.ms" + }, "icon": "https://www.microsoft.com/favicon.ico?v2" }, "微软连接测试": { "name": "微软连接测试", "categoryId": 0, "url": { - "1": "msftconnecttest.com", - "2": "ipv6.msftconnecttest.com", - "3": "ipv4.msftconnecttest.com", - "4": "www.msftconnecttest.com" + "1": "msftconnecttest.com", + "2": "ipv6.msftconnecttest.com", + "3": "ipv4.msftconnecttest.com", + "4": "www.msftconnecttest.com" }, "icon": "https://www.microsoft.com/favicon.ico?v2" }, @@ -689,10 +883,24 @@ "icon": "https://insider.microsoft.com/favicon.ico" }, "Azure": { - "name": "Azure", - "categoryId": 0, - "url": "https://www.azure.com/", - "icon": "https://portal.azure.com/Content/favicon.ico" + "微软Azure官网": { + "name": "Azure", + "categoryId": 0, + "url": "https://www.azure.com/", + "icon": "https://portal.azure.com/Content/favicon.ico" + }, + "微软Azure AD统一身份认证服务": { + "name": "微软Azure AD统一身份认证服务", + "categoryId": 21, + "url": "login.microsoftonline.com", + "icon": "https://login.microsoftonline.com/favicon.ico" + }, + "微软身份平台服务CDN": { + "name": "微软身份平台服务CDN", + "categoryId": 2, + "url": "msidentity.com", + "icon": "https://login.microsoftonline.com/favicon.ico" + } }, "网络连接状态指示器(NCSI)": { "name": "网络连接状态指示器(NCSI)", @@ -704,19 +912,46 @@ }, "icon": "https://www.microsoft.com/favicon.ico" }, - "微软MSN": { - "name": "微软MSN", - "categoryId": 0, - "url": "https://www.msn.cn/", - "icon": "https://www.msn.cn/favicon.ico" + "MSN": { + "微软MSN": { + "name": "微软MSN", + "categoryId": 0, + "url": "https://www.msn.cn/", + "icon": "https://www.msn.cn/favicon.ico" + }, + "MSN中国事件数据收集沙盒服务器": { + "name": "MSN中国事件数据收集沙盒服务器/测试环境用户行为与事件数据采集服务", + "categoryId": 5, + "url": "events-sandbox.data.msn.cn", + "icon": "https://www.msn.cn/favicon.ico" + }, + "msnapi": { + "name": "微软MSN API", + "categoryId": 0, + "url": { + "1": "https://api.msn.com/", + "2": "api.msn.cn" + }, + "icon": "https://www.msn.cn/favicon.ico" + }, + "MSN静态资源": { + "name": "微软MSN静态资源", + "categoryId": 2, + "url": { + "1": "assets.msn.cn", + "2": "img-s.msn.cn", + "icon": "https://www.msn.cn/favicon.ico" + }, + "company": "上海美思恩网络通信技术有限公司 (MSN 中国)" + } }, - "Edge CDN":{ + "Edge CDN": { "name": "微软 Edge 浏览器的CDN 与边缘服务根域名", "categoryId": 2, "url": "a-msedge.net", "icon": "https://www.microsoft.com/favicon.ico" }, - "微软 WNS(Windows 推送通知服务)的客户端通信":{ + "微软 WNS(Windows 推送通知服务)的客户端通信": { "name": "微软 WNS(Windows 推送通知服务)的客户端通信域名", "categoryId": 2, "url": "client.wns.windows.com", @@ -725,11 +960,19 @@ "company": "微软 Microsoft" }, "字节跳动": { - "抖音": { - "name": "抖音", - "categoryId": 0, - "url": "https://www.douyin.com/", - "icon": "https://www.douyin.com/favicon.ico" + "抖音相关": { + "抖音": { + "name": "抖音", + "categoryId": 0, + "url": "https://www.douyin.com/", + "icon": "https://www.douyin.com/favicon.ico" + }, + "吉云互动HTTPDNS解析服务": { + "name": "吉云互动HTTPDNS解析服务/移动端防劫持精准域名解析", + "categoryId": 7, + "url": "dig.bdurl.net", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + } }, "字节跳动官网": { "name": "字节跳动官网", @@ -737,6 +980,248 @@ "url": "https://www.bytedance.com/", "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" }, + "Trae": { + "Trae中国版主站": { + "name": "Trae中国版官方主站/AI IDE下载入口", + "categoryId": 10, + "url": "www.trae.cn", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae中国版核心API接口": { + "name": "Trae中国版核心API服务/模型调用与功能扩展接口", + "categoryId": 11, + "url": "api.trae.com.cn", + "icon": "https://www.trae.com.cn/favicon.ico" + }, + "Trae国际版Normal模式核心服务": { + "name": "Trae国际版Normal模式/基础AI编程功能接口", + "categoryId": 11, + "url": "grow-normal.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae中国版备用官网": { + "name": "Trae中国版备用官网/国内服务入口", + "categoryId": 10, + "url": "www.trae.com.cn", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版主站": { + "name": "Trae国际版官方主站/全球AI编程服务入口", + "categoryId": 10, + "url": "www.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae官方文档中心": { + "name": "Trae中国版官方文档中心/使用指南与API参考", + "categoryId": 15, + "url": "docs.trae.com.cn", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae产品资讯网站": { + "name": "Trae产品介绍与资讯平台", + "categoryId": 10, + "url": "traeai.com", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae中国版资讯百科": { + "name": "Trae中国版相关资讯与百科网站", + "categoryId": 10, + "url": "traecn.com", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae中国版CDN节点": { + "name": "Trae中国版静态资源CDN/官网图片JS安装包分发加速", + "categoryId": 2, + "url": "lf-cdn.trae.com.cn", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版CDN节点": { + "name": "Trae国际版静态资源CDN/国际版官网资源分发加速", + "categoryId": 2, + "url": "lf-cdn.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版Agent模式核心服务": { + "name": "Trae国际版Agent模式/智能体编程与复杂任务处理接口", + "categoryId": 11, + "url": "grow-agent.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版Builder模式核心服务": { + "name": "Trae国际版Builder模式/全栈项目构建与快速原型开发接口", + "categoryId": 11, + "url": "grow-builder.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版SOLO模式核心服务": { + "name": "Trae国际版SOLO模式/高级智能体与复杂任务自主执行接口", + "categoryId": 11, + "url": "grow-solo.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版高级功能服务": { + "name": "Trae国际版高级功能/代码重构与性能优化专业接口", + "categoryId": 11, + "url": "grow-advanced.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版主API接口": { + "name": "Trae国际版主API/用户认证与模型管理基础接口", + "categoryId": 11, + "url": "api.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版官方文档中心": { + "name": "Trae国际版文档中心/多语言使用指南与API参考", + "categoryId": 15, + "url": "docs.trae.ai", + "icon": "https://docs.trae.ai/favicon.ico" + }, + "Trae国际版静态资源CDN": { + "name": "Trae国际版静态资源CDN/IDE客户端与网页资源加速", + "categoryId": 2, + "url": "static.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版资产文件CDN": { + "name": "Trae国际版资产文件CDN/模型权重与工具包分发", + "categoryId": 2, + "url": "assets.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版英文官网": { + "name": "Trae国际版英文官网/全球英语用户服务平台", + "categoryId": 1, + "url": "en.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版日文官网": { + "name": "Trae国际版日文官网/日本市场本地化服务平台", + "categoryId": 1, + "url": "ja.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版韩文官网": { + "name": "Trae国际版韩文官网/韩国市场本地化服务平台", + "categoryId": 1, + "url": "ko.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版德文官网": { + "name": "Trae国际版德文官网/德语区市场本地化服务平台", + "categoryId": 1, + "url": "de.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版法文官网": { + "name": "Trae国际版法文官网/法语区市场本地化服务平台", + "categoryId": 1, + "url": "fr.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版插件市场": { + "name": "Trae国际版插件市场/第三方工具与主题集成中心", + "categoryId": 12, + "url": "marketplace.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版集成中心": { + "name": "Trae国际版集成中心/外部服务与代码仓库集成指南", + "categoryId": 13, + "url": "integrations.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版API网关": { + "name": "Trae国际版API网关/第三方集成安全与流量管理", + "categoryId": 11, + "url": "api-gateway.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版开发者SDK": { + "name": "Trae国际版开发者SDK/第三方应用集成工具包", + "categoryId": 14, + "url": "sdk.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版全局CDN": { + "name": "Trae国际版全局CDN/跨区域静态资源加速分发", + "categoryId": 2, + "url": "cdn.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版图片CDN": { + "name": "Trae国际版图片CDN/媒体资源加速与存储", + "categoryId": 2, + "url": "img.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版全局管理后台": { + "name": "Trae国际版全局管理后台/用户与权限核心管理平台", + "categoryId": 16, + "url": "admin.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版开发者控制台": { + "name": "Trae国际版开发者控制台/API与资源管理中心", + "categoryId": 16, + "url": "console.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版团队管理平台": { + "name": "Trae国际版团队管理平台/协作与权限控制中心", + "categoryId": 16, + "url": "team.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版计费系统": { + "name": "Trae国际版计费系统/订阅与支付管理平台", + "categoryId": 17, + "url": "billing.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版服务监控平台": { + "name": "Trae国际版服务监控平台/系统状态与告警中心", + "categoryId": 18, + "url": "monitor.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版日志中心": { + "name": "Trae国际版日志中心/操作与请求日志管理平台", + "categoryId": 19, + "url": "logs.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版遥测服务": { + "name": "Trae国际版遥测服务/客户端数据收集与分析平台", + "categoryId": 20, + "url": "telemetry.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版服务状态页": { + "name": "Trae国际版服务状态页/实时可用性与故障公告", + "categoryId": 21, + "url": "status.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版统一认证服务": { + "name": "Trae国际版统一认证服务/SSO与身份管理中心", + "categoryId": 22, + "url": "auth.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版审计日志平台": { + "name": "Trae国际版审计日志平台/合规追踪与报告生成", + "categoryId": 23, + "url": "audit.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + }, + "Trae国际版运维管理平台": { + "name": "Trae国际版运维管理平台/服务部署与故障处理中心", + "categoryId": 24, + "url": "ops.trae.ai", + "icon": "https://lf-cdn.trae.com.cn/obj/trae-com-cn/trae_website_prod_cn/favicon.png" + } + }, "今日头条": { "name": "今日头条", "categoryId": 0, @@ -750,42 +1235,62 @@ "icon": "https://v.douyin.com/favicon.ico" }, "字节跳动API服务": { - "name": "字节API服务", + "name": "字节跳动API服务", "categoryId": 22, "url": "zijieapi.com", "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" - }, - "今日头条API服务":{ + }, + "今日头条API服务": { "name": "今日头条API 服务", "categoryId": 22, "url": "toutiaoapi.com", "icon": "https://www.toutiao.com/favicon.ico" + }, + "snssdk": { + "日志 / 数据上报核心服务": { + "name": "日志 / 数据上报核心服务", + "categoryId": 2, + "url": "log.snssdk.com", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" }, - "豆包": { - "name": "豆包", - "categoryId": 0, - "url": "doubao.com", - "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png" + "应用 API": { + "name": "应用 API", + "categoryId": 22, + "url": "open.feishu.cn", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" }, + "账户 / 个人信息管理": { + "name": "账户 / 个人信息管理", + "categoryId": 22, + "url": "profile.snssdk.com", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + }, + "内容索引 / 推荐数据下发": { + "name": "内容索引 / 推荐数据下发", + "categoryId": 22, + "url": "is.snssdk.com", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + } + }, "飞书": { - "飞书":{ + "飞书": { "name": "飞书", "categoryId": 7, "url": "https://www.feishu.cn/", "icon": "https://www.feishu.cn/favicon.ico" } }, - "飞书服务":{ + "飞书服务": { "name": "飞书CalDAV 协议服务器", "categoryId": 22, "url": "caldav.feishu.cn", "icon": "https://www.feishu.cn/favicon.ico" }, "CDN": { - "字节静态资源":{ - "name": "字节跳动产品的官方文档、帮助中心、API 参考、静态资源", - "categoryId": 2, - "url": { + "字节静态资源": { + "name": "字节跳动产品的官方文档、帮助中心、API 参考、静态资源", + "categoryId": 2, + "url": { "1": "bytednsdoc.com", "2": "bytecdntp.com", "3": "bytescm.com", @@ -795,100 +1300,134 @@ "7": "statp.com", "8": "feishu.cn" }, - "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" }, - "今日头条静态资源":{ - "name": "今日头条静态资源", - "categoryId": 2, - "url": { + "字节跳动火山引擎权威 DNS 与智能调度服务": { + "name": "字节跳动火山引擎权威 DNS 与智能调度服务", + "categoryId": 21, + "url": "bytedns3.com", + "icon": "https://lf3-appstore-sign.bytedance.net/obj/volcengine-public/volcengine/favicon.ico" + }, + "今日头条静态资源": { + "name": "今日头条静态资源", + "categoryId": 2, + "url": { "1": "toutiaoimg.com", "2": "toutiaostatic.com", "3": "toutiaovod.com", "4": "toutiaocdn.com", "5": "pglstatp-toutiao.com" }, - "icon": "https://www.toutiao.com/favicon.ico" + "icon": "https://www.toutiao.com/favicon.ico" }, - "飞书CDN":{ - "name": "飞书CDN", - "categoryId": 2, - "url": { + "飞书CDN": { + "name": "飞书CDN", + "categoryId": 2, + "url": { "1": "feishucdn.com" }, - "icon": "https://www.feishu.cn/favicon.ico" + "icon": "https://www.feishu.cn/favicon.ico" } }, - "API相关":{ + "API相关": { "name": "字节跳动API相关", "categoryId": 2, "url": { - "1": "zijieapi.com" + "1": "zijieapi.com" }, "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" - }, - "穿山甲广告SDK":{ + }, + "穿山甲广告SDK": { "name": "穿山甲广告 SDK(com.bytedance.msdk)", "categoryId": 24, "url": { - "1":"mssdk.bytedance.com", - "2":"mssdk-bu.bytedance.com", - "3":"mssdk-tos.bytedance.com", - "4":"mssdk.pangle.cn", - "5":"mssdk.pangleglobal.com" - }, + "1": "mssdk.bytedance.com", + "2": "mssdk-bu.bytedance.com", + "3": "mssdk-tos.bytedance.com", + "4": "mssdk.pangle.cn", + "5": "mssdk.pangleglobal.com", + "6": "pangolin.snssdk.com" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "北京巨量引擎网络技术有限公司" }, - "穿山甲广告 API 接口":{ + "穿山甲广告 API 接口": { "name": "穿山甲广告 API 接口", "categoryId": 24, "url": { - "1":"api.pangle.cn" - }, + "1": "api.pangle.cn" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "北京巨量引擎网络技术有限公司" }, - "穿山甲广告日志上报":{ + "穿山甲广告日志上报": { "name": "穿山甲广告日志上报", "categoryId": 24, "url": { - "1":"log.pangle.cn" - }, + "1": "log.pangle.cn" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "北京巨量引擎网络技术有限公司" }, - "穿山甲广告素材 CDN 分发":{ + "穿山甲广告素材 CDN 分发": { "name": "穿山甲广告素材 CDN 分发", "categoryId": 24, "url": { - "1":"cdn.pangle.cn" - }, + "1": "cdn.pangle.cn" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "北京巨量引擎网络技术有限公司" }, - "穿山甲海外版 SDK 服务":{ + "穿山甲海外版 SDK 服务": { "name": "穿山甲海外版 SDK 服务", "categoryId": 24, "url": { - "1":"mssdk.pangleglobal.com" - }, + "1": "mssdk.pangleglobal.com" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "字节跳动有限公司" }, - "穿山甲":{ + "穿山甲": { "name": "穿山甲", "categoryId": 0, "url": { - "1":"https://www.csjplatform.com/", - "2":"https://csjplatform.com/" - }, + "1": "https://www.csjplatform.com/", + "2": "https://csjplatform.com/" + }, "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", - "company":"北京巨量引擎网络技术有限公司" + "company": "北京巨量引擎网络技术有限公司" }, - "豆包":{ - "CDN":{ - "豆包CDN":{ - "name": "豆包 CDN", + "字节跳动应用商店签名校验服务": { + "name": "字节跳动应用商店签名校验服务", + "categoryId": 21, + "url": "lf3-appstore-sign.bytedance.net", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + }, + "字节跳动企业核心域名": { + "name": "字节跳动企业核心域名", + "categoryId": 2, + "url": "bytedance.net", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + }, + "字节跳动 A/B 测试虚拟机调度服务": { + "name": "字节跳动 A/B 测试虚拟机调度服务", + "categoryId": 18, + "url": "abtestvm.bytedance.com", + "icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC" + }, + "豆包": { + "CDN": { + "豆包官网": { + "name": "豆包官网", + "categoryId": 0, + "url": { + "1": "doubao.com", + "2": "www.doubao.com" + }, + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png" + }, + "豆包CDN": { + "name": "豆包CDN", "categoryId": 2, "url": { "1": "cdnbuild.net", @@ -897,88 +1436,108 @@ "icon": "https://p3-pangle-empower.byteimg.com/obj/tos-cn-i-742ihxn9bs/ad/pangle/homepage/assets/favicon.ico", "company": "抖音视界有限公司" }, - "内部服务负载均衡":{ + "豆包AI优化服务": { + "name": "豆包AI优化服务", + "categoryId": 24, + "url": "opt.doubao.com", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png" + }, + "豆包多端云同步核心服务": { + "name": "豆包多端云同步核心服务", + "categoryId": 22, + "url": { + "1": "mcs.doubao.com" + }, + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png" + }, + "内部服务负载均衡": { "name": "内部服务负载均衡", "categoryId": 2, "url": { "1": "bytelb.net" }, - "icon": "www.doubao.com/favicon.ico", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png", "company": "抖音视界有限公司" }, - "WebSocket 长连接,实时交互":{ + "WebSocket 长连接,实时交互": { "name": "WebSocket 长连接,实时交互", "categoryId": 2, "url": { "1": "wss100-normal.doubao.com" }, - "icon": "www.doubao.com/favicon.ico", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png", "company": "抖音视界有限公司" }, - "前端 JS/CSS/ 图片 / 安装包分发":{ + "前端 JS/CSS/ 图片 / 安装包分发": { "name": "前端 JS/CSS/ 图片 / 安装包分发", "categoryId": 2, "url": { "1": "lf-flow-web-cdn.doubao.com" }, - "icon": "www.doubao.com/favicon.ico", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png", "company": "北京春田知韵科技有限公司" } - }, - "豆包":{ + }, + "豆包": { "name": "豆包", "categoryId": 0, "url": { - "1":"www.doubao.com" + "1": "www.doubao.com" }, - "icon": "www.doubao.com/favicon.ico", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png", "company": "北京春田知韵科技有限公司" + }, + "豆包日志采集与格式化服务": { + "name": "豆包日志采集与格式化服务", + "categoryId": 22, + "url": "logifier.doubao.com", + "icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png" } }, - "company": "字节跳动有限公司" + "company": "字节跳动有限公司" }, "金山办公": { "金山文档": { "name": "金山文档", "categoryId": 0, "url": { - "1":"kdocs.cn", - "2":"www.kdocs.cn", - "3":"365.kdocs.cn", - "4":"account.kdocs.cn" - }, + "1": "kdocs.cn", + "2": "www.kdocs.cn", + "3": "365.kdocs.cn", + "4": "account.kdocs.cn" + }, "icon": "https://qn.cache.wpscdn.cn/kdocs/mobile/touch/apple-180.png" }, "WPS Office": { "name": "WPS Office", "categoryId": 6, "url": { - "1":"wps.cn", - "2":"www.wps.cn" - }, + "1": "wps.cn", + "2": "www.wps.cn" + }, "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ2SURBVHgB7Va9biRFEK7qWa9XAuSByMEhr0FIzvARkEBgEUHEPQG79wKcTyL3Li/gnxdgl5TgTAQSQmeJxEJCIoIjOc/JyemSW4tbe70z28XX1T0z+zO2U6RzWe3unq6un6+/rl6iW7mV111Y7tzZImMekVAcvkm+NqUnxZy1S0jsQz49PdTF3Q82ych3WNzM1dM/Msp+S4UviQ32RHDlBm5MIgJrz9Bvs6y9e4Ida3MOvRXGnxRTyb2HgM6I/13nZDCQb98/IcNNtQC19J8JXf46InWM5npjfM/qnJ1dtVGjSOLgKkQWnMz4y+clcmgx2TiWb955QENpFsBhT/r7SDhymZMwEnfKOjbiFcSSRuO+k7F7FFk4xsdIECZ6xtxg7HqeuHFYQ583lh59Dgvn0qZXmL/CviGy/3tM9gJOoYPmehcMcaR7vE03Vj9yyNJsxlQf4xgkLrNFUCnvEzUwGZUoRDjjiB7peCzr9Gm9hflOgRokfZHdnfw1HPi9+X/IMu8CpHsznIIN3SgbqzvoOiXAMgBgd/nJ84TmibGx+pgcCTeoS9HSY5hpFnwh6vEPp/cr9jidE3/M+Vfb4ycv7hsf5mifzOQlYPFwM87X2B2qlq62kWnRhW3SOaC8sExDHFUadSt3OFsecg+9csGorgbAfw4GVOMDPef8fCJpy0bcnLcFVI5oFYORbcMpaQBD6/o+HybJYvawAVs4Kgq2Hd96ObqmjHK8h8gGgYw+yuWoGoXzrEXnE2SfCQ0zN3dUqc7e2VAyB0LjdtBSWugWASgK7G7EFNONtGRzFgWdp7JNKYxlCBYd+h4fP0+wFi/oGmp7ZK1WI/jowleyEIAXCy7Yl8UxGGyIzCwKjvXGxj5AZxAR2ElXPorbVDPbs7qmExKRcLUTR75plZkAFIVIDvQY3CYlpW3nKBQZ6VqoGUb6oOwgBPa1bHkUVJcnXwUd9hwAUlPZVyAAGZPnQl6UXBDLsqtrDdvReV6UHFKRuxH2AeZwCGTc2MkymO9JnSfyDN768+4WAggo7IWKxeH8vpRP3mo5TpQVUYPrBSttrabujI1sy8dvfIixZ76x3gajRhzPZq/+qEIUxjR9igr3NlVKqChZtk6NxgC9e9A8AUWL0hn+r+QvDPQTytLP+HiU3IiAmj8KXCh5IEWR8mN9C5xB1a3RfnFz/NGtTL0d7v39vsr5lQH4lTq4YM+U6aVxCuQD3JOpe19zuoNQwALp8qtsE6plvSvdXLWgmRnZL1isJXoSXjLu81GZUaGrL14otVrQ0Eg607oLfugakS2c61L9qf5a8i+e6O+CzLw3b1R1a3XHhZWp1zHhXy7Xr/NhrlvkI73fB2Vh0vvcr8pIdR0Xpisp49reILWbFPjncUe+qK0g83vK5tGbD12xqJQR3pPG0hrq8xZQ+JF/yvp0K7fyf5f/AKzIO6StcaNfAAAAAElFTkSuQmCC" }, "WPS CDN": { "name": "WPS CDN", "categoryId": 2, "url": { - "1":"wpsdns.com", - "2":"kdocs-om.wpscdn.cn", - "3":"qn.cache.wpscdn.cn", - "4":"fe-static.wpscdn.cn", - "5":"docer-ks3.wpscdn.cn", - "6":"cloudcdn.wpscdn.cn", - "7":"vasvip-pub.wpscdn.cn", - "8":"ks3.wpsplus.wpscdn.cn", - "9":"op-kdocs.wpscdn.cn", - "10":"volcengine-cache-weboffice.wpscdn.cn", - "11":"ac.wpscdn.cn", - "12":"honeycomb-emergency.wpscdn.cn", - "13":"res-honeycomb.wpscdn.cn", - "14":"kflow.wpscdn.cn", - "15":"personal-act.wpscdn.cn", - "16":"official-package.wpscdn.cn" - }, + "1": "wpsdns.com", + "2": "kdocs-om.wpscdn.cn", + "3": "qn.cache.wpscdn.cn", + "4": "fe-static.wpscdn.cn", + "5": "docer-ks3.wpscdn.cn", + "6": "cloudcdn.wpscdn.cn", + "7": "vasvip-pub.wpscdn.cn", + "8": "ks3.wpsplus.wpscdn.cn", + "9": "op-kdocs.wpscdn.cn", + "10": "volcengine-cache-weboffice.wpscdn.cn", + "11": "ac.wpscdn.cn", + "12": "honeycomb-emergency.wpscdn.cn", + "13": "res-honeycomb.wpscdn.cn", + "14": "kflow.wpscdn.cn", + "15": "personal-act.wpscdn.cn", + "16": "official-package.wpscdn.cn" + }, "icon": "https://wps.cn/favicon.ico" }, "company": "珠海金山办公科技有限公司" @@ -1006,7 +1565,7 @@ "icon": "https://sd.360.cn/favicon.ico" }, "360统计": { - "360数据统计类(山东)":{ + "360数据统计类(山东)": { "name": "360数据统计类(山东)", "categoryId": 0, "url": "stat.sd.360.cn", @@ -1072,10 +1631,10 @@ "name": "360安全云", "categoryId": 1, "url": { - "1": "https://saas.360.cn/", - "2": "safe.online.360.cn", - "3": "admin.online.360.cn", - "4": "client.saas.360.cn" + "1": "https://saas.360.cn/", + "2": "safe.online.360.cn", + "3": "admin.online.360.cn", + "4": "client.saas.360.cn" }, "icon": "https://saas.360.cn/favicon.ico" }, @@ -1093,13 +1652,13 @@ "company": "360数字安全集团/三六零数字安全科技集团有限公司" }, "360软件宝库": { - "360软件宝库":{ + "360软件宝库": { "name": "360软件宝库", "categoryId": 1, "url": "https://soft.360.cn/", "icon": "https://soft.360.cn/favicon.ico" }, - "360软件宝库开放平台":{ + "360软件宝库开放平台": { "name": "360软件宝库开放平台", "categoryId": 1, "url": "https://open.soft.360.cn/", @@ -1107,37 +1666,37 @@ } }, "360CDN": { - "安全产品专属高速下载":{ + "安全产品专属高速下载": { "name": "安全产品专属高速下载", "categoryId": 2, "url": "sfdl.360safe.com", "icon": "https://www.360.cn/favicon.ico" }, - "安全产品通用下载":{ + "安全产品通用下载": { "name": "安全产品通用下载", "categoryId": 2, "url": "dl.360safe.com", "icon": "https://www.360.cn/favicon.ico" }, - "360 全产品线下载":{ + "360 全产品线下载": { "name": "360 全产品线下载", "categoryId": 2, "url": "sfdl.360.cn", "icon": "https://www.360.cn/favicon.ico" }, - "360 官网统一下载入口":{ + "360 官网统一下载入口": { "name": "360 官网统一下载入口", "categoryId": 2, "url": "download.360.cn", "icon": "https://www.360.cn/favicon.ico" }, - "360静态资源CDN":{ + "360静态资源CDN": { "name": "360静态资源CDN", "categoryId": 2, "url": "qhres2.com", "icon": "https://www.360.cn/favicon.ico" }, - "安全产品下载加速域名":{ + "安全产品下载加速域名": { "name": "安全产品下载加速", "categoryId": 2, "url": "wsdl.360safe.com", @@ -1148,165 +1707,164 @@ "name": "360搜索", "categoryId": 1, "url": { - "1":"https://www.so.com/", - "2":"https://so.com/" - }, + "1": "https://www.so.com/", + "2": "https://so.com/" + }, "icon": "https://www.so.com/favicon.ico", "company": "天津三六零快看科技有限公司" }, - "聚越信息":{ - "聚越信息官网":{ + "聚越信息": { + "聚越信息官网": { "name": "聚越信息", "categoryId": 0, "url": { - "1": "www.mediav.com", - "2": "mediav.com", - "3": "mediav.cn" - }, + "1": "www.mediav.com", + "2": "mediav.com", + "3": "mediav.cn" + }, "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告展示与投放服务":{ + "广告展示与投放服务": { "name": "聚胜万合广告展示与投放服务", "categoryId": 22, "url": { - "1": "g.mediav.com", - "2": "show.g.mediav.com" - }, + "1": "g.mediav.com", + "2": "show.g.mediav.com" + }, "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "数据监测与分析服务":{ + "数据监测与分析服务": { "name": "聚胜万合数据监测与分析服务", "categoryId": 22, "url": "d.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "开放 API 接口":{ + "开放 API 接口": { "name": "聚胜万合开放 API 接口", "categoryId": 24, "url": "api.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "平台管理后台":{ + "平台管理后台": { "name": "聚胜万合平台管理后台", "categoryId": 24, "url": "admin.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "聚越信息CDN":{ - "聚越信息CDN":{ + "聚越信息CDN": { + "聚越信息CDN": { "name": "聚越信息CDN", "categoryId": 2, "url": "mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "加密静态资源 CDN":{ + "加密静态资源 CDN": { "name": "加密静态资源 CDN", "categoryId": 2, "url": "static-ssl.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "静态资源CDN":{ + "静态资源CDN": { "name": "聚越信息静态资源CDN", "categoryId": 2, "url": "static.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "数据监测CDN":{ + "数据监测CDN": { "name": "聚越信息数据监测CDN", "categoryId": 2, "url": "d.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "开放 API 接口CDN":{ + "开放 API 接口CDN": { "name": "聚越信息开放 API 接口CDN", "categoryId": 2, "url": "api.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "聚效平台CDN":{ + "聚效平台CDN": { "name": "聚效平台CDN", "categoryId": 2, "url": "mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "移动端适配CDN":{ + "移动端适配CDN": { "name": "移动端适配CDN", "categoryId": 2, "url": "m.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "图片素材CDN":{ + "图片素材CDN": { "name": "图片素材CDN", "categoryId": 2, "url": "img.mediav.com", - "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司" + "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司" }, - "聚越信息静态资源":{ + "聚越信息静态资源": { "name": "聚越信息静态资源", "categoryId": 2, "url": "static.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告展示备用CDN":{ + "广告展示备用CDN": { "name": "广告展示备用CDN", "categoryId": 2, "url": "show-3.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告展示扩展CDN":{ + "广告展示扩展CDN": { "name": "广告展示扩展CDN", "categoryId": 2, "url": { - "1": "show-2.mediav.com", - "2": "show-1.mediav.com" - }, + "1": "show-2.mediav.com", + "2": "show-1.mediav.com" + }, "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告展示基础CDN":{ + "广告展示基础CDN": { "name": "广告展示基础CDN", "categoryId": 2, "url": "show.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告投放综合CDN":{ + "广告投放综合CDN": { "name": "广告投放综合CDN", "categoryId": 2, "url": "g.mediav.com", "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" }, - "广告展示主CDN":{ + "广告展示主CDN": { "name": "广告展示主CDN", "categoryId": 2, "url": { - "1": "show-g.mediav.com", - "2": "show.g.mediav.com" + "1": "show-g.mediav.com", + "2": "show.g.mediav.com" }, "company": "上海聚胜万合广告有限公司/上海漫酷广告有限公司", "icon": "https://www.mediav.com/favicon.ico" } } } - }, "picsum-photos": { - "picsum-photos":{ + "picsum-photos": { "name": "面向开发者的开源占位图 API 项目", "categoryId": 0, "url": "picsum.photos", @@ -1319,61 +1877,76 @@ "name": "绮梦之家", "categoryId": 0, "url": { - "1": "https://www.amazehome.xyz/", - "2": "amazehome.xyz", - "3": "https://www.amazehome.cn/", - "4": "amazehome.cn" - }, + "1": "https://www.amazehome.xyz/", + "2": "amazehome.xyz", + "3": "https://www.amazehome.cn/", + "4": "amazehome.cn" + }, "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, - "绮梦之家认证中心":{ + "绮梦之家认证中心": { "name": "绮梦之家认证中心", "categoryId": 0, "url": "https://cas.amazehome.cn/", "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, - "绮梦之家云同步服务":{ + "绮梦之家云同步服务": { "name": "绮梦之家云同步服务", "categoryId": 0, "url": "https://cloud.amazehome.cn/", "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, - "绮梦之家Git服务":{ + "绮梦之家Git服务": { "name": "绮梦之家Git服务", "categoryId": 0, "url": "https://gitea.amazehome.xyz/", - "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" + "icon": "https://gitea.amazehome.xyz/assets/img/favicon.svg" }, - "绮梦之家导航":{ + "绮梦之家导航": { "name": "绮梦之家导航", "categoryId": 0, "url": "https://nav.amazehome.cn/", - "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" + "icon": "https://nav.amazehome.cn/icons/logo.png" }, - "绮梦之家VMWare虚拟化平台":{ + "绮梦之家VMWare虚拟化平台": { "name": "绮梦之家VMWare虚拟化平台", "categoryId": 0, "url": "https://vcenter.amazehome.xyz/", "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, - "绮梦之家Office协作服务":{ + "绮梦之家Office协作服务": { "name": "绮梦之家Office协作服务", "categoryId": 0, "url": "https://view.amazehome.cn/", - "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" + "icon": "https://view.amazehome.cn/welcome/css/favicon.ico" }, - "绮梦之家数据中心":{ + "绮梦之家数据中心": { "name": "绮梦之家数据中心", "categoryId": 0, "url": "https://data.amazehome.xyz/", "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, - "绮梦之家云服务":{ + "绮梦之家云服务": { "name": "绮梦之家云服务", "categoryId": 0, "url": "https://yun.amazehome.xyz/", "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" }, + "绮梦之家域控制器": { + "name": "绮梦之家AD域控制器", + "categoryId": 0, + "url": "dc.amazehome.xyz", + "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" + }, + "绮梦之家VMWare虚拟化桌面平台": { + "name": "绮梦之家VMWare虚拟化桌面平台", + "categoryId": 0, + "url": { + "1": "https://horizon.amazehome.xyz/", + "2": "https://horizon1.amazehome.xyz/" + }, + "icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png" + }, "company": "绮梦之家" }, "微步在线": { @@ -1403,12 +1976,12 @@ "name": "飞致云", "categoryId": 0, "url": { - "1":"https://fit2cloud.com/", - "2":"https://www.fit2cloud.com/" - }, + "1": "https://fit2cloud.com/", + "2": "https://www.fit2cloud.com/" + }, "icon": "https://fit2cloud.com/images/logo/favicon.ico" }, - "1Panel":{ + "1Panel": { "name": "1Panel", "categoryId": 0, "url": "https://1panel.cn/", @@ -1421,9 +1994,9 @@ "name": "Tailwind CSS", "categoryId": 0, "url": { - "1":"https://tailwindcss.com/", - "2":"https://cdn.tailwindcss.com/" - }, + "1": "https://tailwindcss.com/", + "2": "https://cdn.tailwindcss.com/" + }, "icon": "https://tailwindcss.com/favicons/favicon-32x32.png?v=4" }, "company": "Tailwind Labs" @@ -1475,9 +2048,9 @@ "name": "小米MIUI", "categoryId": 0, "url": { - "1":"https://www.miui.com/", - "2":"https://home.miui.com/" - }, + "1": "https://www.miui.com/", + "2": "https://home.miui.com/" + }, "icon": "https://www.miui.com/favicon.ico" }, "小米隐私": { @@ -1529,8 +2102,8 @@ "name": "Staticfile CDN", "categoryId": 2, "url": { - "1":"cdn.staticfile.org", - "2":"cdn.staticfile.net" + "1": "cdn.staticfile.org", + "2": "cdn.staticfile.net" }, "icon": "https://www.qiniu.com/favicon.ico" }, @@ -1546,16 +2119,19 @@ "Akamai CDN": { "name": "Akamai CDN", "categoryId": 2, - "url": "lf-rc1.yhgfb-cn-static.com", + "url": { + "1": "akamaized.net", + "2": "lf-rc2.yhgfb-cn-static.com" + }, "icon": "https://www.akamai.com/site/favicon/favicon.ico" }, "核心边缘 DNS 与 CDN 调度根域名": { "name": "核心边缘 DNS 与 CDN 调度根域名", "categoryId": 2, "url": { - "1":"akamaihd.net", - "2":"akamaiedge.net", - "3":"edgesuite.net" + "1": "akamaihd.net", + "2": "akamaiedge.net", + "3": "edgesuite.net" }, "icon": "https://www.akamai.com/site/favicon/favicon.ico" }, @@ -1575,13 +2151,13 @@ "icon": "https://passport.weibo.com/favicon.ico" }, "新浪微博开放平台": { - "新浪微博开放平台":{ + "新浪微博开放平台": { "name": "新浪微博开放平台", "categoryId": 22, "url": "https://open.weibo.com/", "icon": "https://open.weibo.com/favicon.ico" }, - "新浪微博开放平台API":{ + "新浪微博开放平台API": { "name": "新浪微博开放平台API", "categoryId": 22, "url": "https://api.weibo.com/", @@ -1592,7 +2168,7 @@ "name": "新浪微博CDN", "categoryId": 2, "url": { - "1":"sinaimg.cn" + "1": "sinaimg.cn" }, "icon": "https://weibo.com/favicon.ico" }, @@ -1603,156 +2179,156 @@ "name": "南京市中医院", "categoryId": 0, "url": { - "1":"https://www.njszyy.cn/", - "2":"https://njszyy.cn/" - }, - "icon": "#" + "1": "https://www.njszyy.cn/", + "2": "https://njszyy.cn/" + }, + "icon": "#" }, "company": "南京市中医院" }, "软件源相关": { - "腾讯镜像源":{ + "腾讯镜像源": { "name": "腾讯软件源", "categoryId": 0, "url": { "1": "https://mirrors.cloud.tencent.com/" - }, + }, "icon": "https://www.tencent.com/favicon.ico", "company": "深圳腾讯计算机系统有限公司" }, - "南京大学镜像源":{ + "南京大学镜像源": { "name": "南京大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.nju.edu.cn/" - }, + }, "icon": "https://www.nju.edu.cn/favicon.ico", "company": "南京大学" }, - "高校镜像源联盟":{ + "高校镜像源联盟": { "name": "高校镜像源联盟MirrorZ", "categoryId": 0, "url": { "1": "https://mirrorz.org/", - "2":"https://help.mirrorz.org/" - }, + "2": "https://help.mirrorz.org/" + }, "icon": "https://mirrorz.org/static/img/mirrorz.svg", "company": "MirrorZ Project" }, - "清华大学镜像源":{ + "清华大学镜像源": { "name": "清华大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.tuna.tsinghua.edu.cn/" - }, + }, "icon": "https://mirrors.tuna.tsinghua.edu.cn/static/img/favicon.png", "company": "清华大学" }, - "中国科学技术大学镜像源":{ + "中国科学技术大学镜像源": { "name": "中国科学技术大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.ustc.edu.cn/" - }, + }, "icon": "https://mirrors.ustc.edu.cn/static/img/favicon.png", "company": "中国科学技术大学" }, - "中国科学院软件研究所镜像源":{ + "中国科学院软件研究所镜像源": { "name": "中国科学院软件研究所开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.iscas.ac.cn/" - }, + }, "icon": "https://api.cas.cn/favicon/is.ico", "company": "中国科学院软件研究所" }, - "北京外国语大学镜像源":{ + "北京外国语大学镜像源": { "name": "北京外国语大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.bfsu.edu.cn/" - }, + }, "icon": "https://mirrors.bfsu.edu.cn/static/img/favicon.png", "company": "北京外国语大学" }, - "哈尔滨工业大学镜像源":{ + "哈尔滨工业大学镜像源": { "name": "哈尔滨工业大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.hit.edu.cn/" - }, + }, "icon": "https://mirrors.hit.edu.cn/favicon.svg", "company": "哈尔滨工业大学" }, - "北京大学镜像源":{ + "北京大学镜像源": { "name": "北京大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.pku.edu.cn/" - }, + }, "icon": "https://mirrors.pku.edu.cn/static/favicon.ico", "company": "北京大学" }, - "南京工业大学镜像源":{ + "南京工业大学镜像源": { "name": "南京工业大学开源软件镜像站", "categoryId": 0, "url": { "1": "https://mirrors.njtech.edu.cn/" - }, + }, "icon": "https://www.njtech.edu.cn/favicon.ico", "company": "南京工业大学" }, - "南阳理工学院镜像源":{ + "南阳理工学院镜像源": { "name": "南阳理工学院开源软件镜像站", "categoryId": 0, "url": { "1": "https://mirror.nyist.edu.cn/" - }, + }, "icon": "https://cernet.mirror.nyist.edu.cn/static/img/favicon.png", "company": "南阳理工学院" }, - "思源镜像站":{ + "思源镜像站": { "name": "思源镜像站", "categoryId": 0, "url": { "1": "https://mirror.sjtu.edu.cn/" - }, + }, "icon": "https://mirror.sjtu.edu.cn/favicon.ico", "company": "上海交通大学" }, - "南方科技大学镜像源":{ + "南方科技大学镜像源": { "name": "南方科技大学开源软件镜像站", "categoryId": 0, "url": { "1": "https://mirrors.sustech.edu.cn/" - }, + }, "icon": "https://mirrors.sustech.edu.cn/favicon.ico", "company": "南方科技大学" }, - "浙江大学开源软件镜像站":{ + "浙江大学开源软件镜像站": { "name": "浙江大学开源软件镜像站", "categoryId": 0, "url": { "1": "https://mirrors.zju.edu.cn/" - }, + }, "icon": "https://mirrors.zju.edu.cn/index/favicon.svg?v=f28569028f33e6ac676201f84c9d61bf", "company": "浙江大学" }, - "兰州大学开源社区镜像站":{ + "兰州大学开源社区镜像站": { "name": "兰州大学开源社区镜像站", "categoryId": 0, "url": { "1": "https://mirrors.lzu.edu.cn/" - }, + }, "icon": "https://mirrors.lzu.edu.cn/static/img/favicon.png", "company": "兰州大学" }, - "重庆邮电大学开源镜像站":{ + "重庆邮电大学开源镜像站": { "name": "重庆邮电大学开源镜像站", "categoryId": 0, "url": { "1": "https://mirrors.cqupt.edu.cn/" - }, + }, "icon": "https://mirrors.cqupt.edu.cn/favicon.ico", "company": "重庆邮电大学" } @@ -1766,7 +2342,7 @@ }, "icon": "https://www.bilibili.com/favicon.ico" }, - "哔哩哔哩游戏":{ + "哔哩哔哩游戏": { "name": "哔哩哔哩游戏", "categoryId": 0, "url": { @@ -1788,7 +2364,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "B 站直播的官方 Web 前端域名":{ + "B 站直播的官方 Web 前端域名": { "name": "哔哩哔哩直播官方 Web 前端域名", "categoryId": 24, "url": { @@ -1797,8 +2373,8 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "哔哩哔哩API":{ - "开放数据接口":{ + "哔哩哔哩API": { + "开放数据接口": { "name": "哔哩哔哩开放数据接口(API)", "categoryId": 24, "url": { @@ -1807,7 +2383,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "创作者数据中心":{ + "创作者数据中心": { "name": "哔哩哔哩创作者数据中心", "categoryId": 24, "url": { @@ -1816,7 +2392,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "个性化推荐数据接口":{ + "个性化推荐数据接口": { "name": "哔哩哔哩个性化推荐数据接口", "categoryId": 24, "url": { @@ -1825,7 +2401,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "动态内容核心API":{ + "动态内容核心API": { "name": "哔哩哔哩动态内容核心API", "categoryId": 24, "url": { @@ -1834,7 +2410,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "全局消息推送核心API":{ + "全局消息推送核心API": { "name": "哔哩哔哩全局消息推送核心API", "categoryId": 24, "url": { @@ -1843,7 +2419,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "消息中心核心域名":{ + "消息中心核心域名": { "name": "哔哩哔哩消息中心核心", "categoryId": 24, "url": { @@ -1852,7 +2428,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "全局广播与长连接核心域名":{ + "全局广播与长连接核心域名": { "name": "哔哩哔哩全局广播与长连接核心", "categoryId": 24, "url": { @@ -1861,7 +2437,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "核心业务API域名":{ + "核心业务API域名": { "name": "哔哩哔哩核心业务API", "categoryId": 24, "url": { @@ -1870,7 +2446,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "账号认证与登录核心域名":{ + "账号认证与登录核心域名": { "name": "哔哩哔哩账号认证与登录核心", "categoryId": 24, "url": { @@ -1879,7 +2455,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "核心数据服务":{ + "核心数据服务": { "name": "核心数据服务", "categoryId": 24, "url": { @@ -1888,7 +2464,7 @@ "icon": "https://www.bilibili.com/favicon.ico", "company": "杭州幻电科技有限公司" }, - "哔哩哔哩游戏用户行为与数据采集核心":{ + "哔哩哔哩游戏用户行为与数据采集核心": { "name": "哔哩哔哩游戏用户行为与数据采集核心", "categoryId": 24, "url": { @@ -1901,718 +2477,718 @@ }, "company": "上海宽娱数码科技有限公司" }, - "政府网站":{ - "国务院":{ - "中华人民共和国中央人民政府":{ + "政府网站": { + "国务院": { + "中华人民共和国中央人民政府": { "name": "中华人民共和国中央人民政府", "categoryId": 0, "url": "https://www.gov.cn/", "icon": "https://www.gov.cn/images/trs_favicon.ico" }, - "外交部":{ + "外交部": { "name": "外交部", "categoryId": 14, "url": "https://www.mfa.gov.cn/", "icon": "https://www.mfa.gov.cn/favicon.ico" }, - "国防部":{ + "国防部": { "name": "国防部", "categoryId": 14, "url": "https://www.mod.gov.cn/", "icon": "https://www.mod.gov.cn/favicon.ico" }, - "国家发展和改革委员会":{ + "国家发展和改革委员会": { "name": "国家发展和改革委员会", "categoryId": 14, "url": "https://www.ndrc.gov.cn/", "icon": "https://www.ndrc.gov.cn/favicon.ico" }, - "教育部":{ + "教育部": { "name": "教育部", "categoryId": 14, "url": "https://www.moe.gov.cn/", "icon": "https://www.moe.gov.cn/favicon.ico" }, - "科学技术部":{ + "科学技术部": { "name": "科学技术部", "categoryId": 14, "url": "https://www.most.gov.cn/", "icon": "https://www.most.gov.cn/favicon.ico" }, - "工业和信息化部":{ + "工业和信息化部": { "name": "工业和信息化部", "categoryId": 14, "url": "https://www.miit.gov.cn/", "icon": "https://www.miit.gov.cn/favicon.ico" }, - "国家民族事务委员会":{ + "国家民族事务委员会": { "name": "国家民族事务委员会", "categoryId": 14, "url": "https://www.seac.gov.cn/", "icon": "https://www.seac.gov.cn/favicon.ico" }, - "公安部":{ + "公安部": { "name": "公安部", "categoryId": 14, "url": "https://www.mps.gov.cn/", "icon": "https://www.mps.gov.cn/favicon.ico" }, - "民政部":{ + "民政部": { "name": "民政部", "categoryId": 14, "url": "https://www.mca.gov.cn/", "icon": "https://www.mca.gov.cn/favicon.ico" }, - "司法部":{ + "司法部": { "name": "司法部", "categoryId": 14, "url": "https://www.moj.gov.cn/", "icon": "https://www.moj.gov.cn/favicon.ico" }, - "财政部":{ + "财政部": { "name": "财政部", "categoryId": 14, "url": "https://www.mof.gov.cn/", "icon": "https://www.mof.gov.cn/favicon.ico" }, - "人力资源和社会保障部":{ + "人力资源和社会保障部": { "name": "人力资源和社会保障部", "categoryId": 14, "url": "https://www.mohrss.gov.cn/", "icon": "https://www.mohrss.gov.cn/favicon.ico" }, - "自然资源部":{ + "自然资源部": { "name": "自然资源部", "categoryId": 14, "url": "https://www.mnr.gov.cn/", "icon": "https://www.mnr.gov.cn/favicon.ico" }, - "生态环境部":{ + "生态环境部": { "name": "生态环境部", "categoryId": 14, "url": "https://www.mee.gov.cn/", "icon": "https://www.mee.gov.cn/favicon.ico" }, - "住房和城乡建设部":{ + "住房和城乡建设部": { "name": "住房和城乡建设部", "categoryId": 14, "url": "https://www.mohurd.gov.cn/", "icon": "https://www.mohurd.gov.cn/favicon.ico" }, - "交通运输部":{ + "交通运输部": { "name": "交通运输部", "categoryId": 14, "url": "https://www.mot.gov.cn/", "icon": "https://www.mot.gov.cn/favicon.ico" }, - "水利部":{ + "水利部": { "name": "水利部", "categoryId": 14, "url": "https://www.mwr.gov.cn/", "icon": "https://www.mwr.gov.cn/favicon.ico" }, - "农业农村部":{ + "农业农村部": { "name": "农业农村部", "categoryId": 14, "url": "https://www.moa.gov.cn/", "icon": "https://www.moa.gov.cn/favicon.ico" }, - "商务部":{ + "商务部": { "name": "商务部", "categoryId": 14, "url": "https://www.mofcom.gov.cn/", "icon": "https://www.mofcom.gov.cn/favicon.ico" }, - "文化和旅游部":{ + "文化和旅游部": { "name": "文化和旅游部", "categoryId": 14, "url": "https://www.mct.gov.cn/", "icon": "https://www.mct.gov.cn/favicon.ico" }, - "国家卫生健康委员会":{ + "国家卫生健康委员会": { "name": "国家卫生健康委员会", "categoryId": 14, "url": "https://www.nhc.gov.cn/", "icon": "https://www.nhc.gov.cn/favicon.ico" }, - "退役军人事务部":{ + "退役军人事务部": { "name": "退役军人事务部", "categoryId": 14, "url": "https://www.mva.gov.cn/", "icon": "https://www.mva.gov.cn/favicon.ico" }, - "应急管理部":{ + "应急管理部": { "name": "应急管理部", "categoryId": 14, "url": "https://www.mem.gov.cn/", "icon": "https://www.mem.gov.cn/favicon.ico" }, - "中国人民银行":{ + "中国人民银行": { "name": "中国人民银行", "categoryId": 14, "url": "https://www.pbc.gov.cn/", "icon": "https://www.pbc.gov.cn/favicon.ico" }, - "审计署":{ + "审计署": { "name": "审计署", "categoryId": 14, "url": "https://www.audit.gov.cn/", "icon": "https://www.audit.gov.cn/favicon.ico" }, - "国家航天局":{ + "国家航天局": { "name": "国家航天局", "categoryId": 14, "url": "https://www.cnsa.gov.cn/", "icon": "https://www.cnsa.gov.cn/favicon.ico" }, - "国家原子能机构":{ + "国家原子能机构": { "name": "国家原子能机构", "categoryId": 14, "url": "https://www.caea.gov.cn/", "icon": "https://www.caea.gov.cn/favicon.ico" }, - "国家乡村振兴局":{ + "国家乡村振兴局": { "name": "国家乡村振兴局", "categoryId": 14, "url": "https://www.nrra.gov.cn/", "icon": "https://www.nrra.gov.cn/favicon.ico" }, - "国务院国有资产监督管理委员会":{ + "国务院国有资产监督管理委员会": { "name": "国务院国有资产监督管理委员会", "categoryId": 14, "url": "https://www.sasac.gov.cn/", "icon": "https://www.sasac.gov.cn/favicon.ico" }, - "海关总署":{ + "海关总署": { "name": "海关总署", "categoryId": 14, "url": "https://www.customs.gov.cn/", "icon": "https://www.customs.gov.cn/favicon.ico" }, - "国家税务总局":{ + "国家税务总局": { "name": "国家税务总局", "categoryId": 14, "url": "https://www.chinatax.gov.cn/", "icon": "https://www.chinatax.gov.cn/favicon.ico" }, - "国家市场监督管理总局":{ + "国家市场监督管理总局": { "name": "国家市场监督管理总局", "categoryId": 14, "url": "https://www.samr.gov.cn/", "icon": "https://www.samr.gov.cn/favicon.ico" }, - "国家金融监督管理总局":{ + "国家金融监督管理总局": { "name": "国家金融监督管理总局", "categoryId": 14, "url": "https://www.cbirc.gov.cn/", "icon": "https://www.cbirc.gov.cn/favicon.ico" }, - "中国证券监督管理委员会":{ + "中国证券监督管理委员会": { "name": "中国证券监督管理委员会", "categoryId": 14, "url": "https://www.csrc.gov.cn/", "icon": "https://www.csrc.gov.cn/favicon.ico" }, - "国家广播电视总局":{ + "国家广播电视总局": { "name": "国家广播电视总局", "categoryId": 14, "url": "https://www.nrta.gov.cn/", "icon": "https://www.nrta.gov.cn/favicon.ico" }, - "国家体育总局":{ + "国家体育总局": { "name": "国家体育总局", "categoryId": 14, "url": "https://www.sport.gov.cn/", "icon": "https://www.sport.gov.cn/favicon.ico" }, - "国家信访局":{ + "国家信访局": { "name": "国家信访局", "categoryId": 14, "url": "https://www.gjxfj.gov.cn/", "icon": "https://www.gjxfj.gov.cn/favicon.ico" }, - "国家统计局":{ + "国家统计局": { "name": "国家统计局", "categoryId": 14, "url": "https://www.stats.gov.cn/", "icon": "https://www.stats.gov.cn/favicon.ico" }, - "国家知识产权局":{ + "国家知识产权局": { "name": "国家知识产权局", "categoryId": 14, "url": "https://www.cnipa.gov.cn/", "icon": "https://www.cnipa.gov.cn/favicon.ico" }, - "国家国际发展合作署":{ + "国家国际发展合作署": { "name": "国家国际发展合作署", "categoryId": 14, "url": "https://www.cidca.gov.cn/", "icon": "https://www.cidca.gov.cn/favicon.ico" }, - "国家医疗保障局":{ + "国家医疗保障局": { "name": "国家医疗保障局", "categoryId": 14, "url": "https://www.nhsa.gov.cn/", "icon": "https://www.nhsa.gov.cn/favicon.ico" }, - "国务院参事室":{ + "国务院参事室": { "name": "国务院参事室", "categoryId": 14, "url": "https://www.counsellor.gov.cn/", "icon": "https://www.counsellor.gov.cn/favicon.ico" }, - "国家机关事务管理局":{ + "国家机关事务管理局": { "name": "国家机关事务管理局", "categoryId": 14, "url": "https://www.ggj.gov.cn/", "icon": "https://www.ggj.gov.cn/favicon.ico" }, - "国家认证认可监督管理委员会":{ + "国家认证认可监督管理委员会": { "name": "国家认证认可监督管理委员会", "categoryId": 14, "url": "https://www.cnca.gov.cn/", "icon": "https://www.cnca.gov.cn/favicon.ico" }, - "国家标准化管理委员会":{ + "国家标准化管理委员会": { "name": "国家标准化管理委员会", "categoryId": 14, "url": "https://www.sac.gov.cn/", "icon": "https://www.sac.gov.cn/favicon.ico" }, - "国家新闻出版署":{ + "国家新闻出版署": { "name": "国家新闻出版署(国家版权局)", "categoryId": 14, "url": "https://www.nppa.gov.cn/", "icon": "https://www.nppa.gov.cn/favicon.ico" }, - "国家宗教事务局":{ + "国家宗教事务局": { "name": "国家宗教事务局", "categoryId": 14, "url": "https://www.sara.gov.cn/", "icon": "https://www.sara.gov.cn/favicon.ico" }, - "国务院侨务办公室":{ + "国务院侨务办公室": { "name": "国务院侨务办公室", "categoryId": 14, "url": "https://www.gqb.gov.cn/", "icon": "https://www.gqb.gov.cn/favicon.ico" }, - "国务院港澳事务办公室":{ + "国务院港澳事务办公室": { "name": "国务院港澳事务办公室", "categoryId": 14, "url": "https://www.hmo.gov.cn/", "icon": "https://www.hmo.gov.cn/favicon.ico" }, - "国务院台湾事务办公室":{ + "国务院台湾事务办公室": { "name": "国务院台湾事务办公室", "categoryId": 14, "url": "https://www.gat.gov.cn/", "icon": "https://www.gat.gov.cn/favicon.ico" }, - "国家互联网信息办公室":{ + "国家互联网信息办公室": { "name": "国家互联网信息办公室", "categoryId": 14, "url": "https://www.cac.gov.cn/", "icon": "https://www.cac.gov.cn/favicon.ico" }, - "国务院新闻办公室":{ + "国务院新闻办公室": { "name": "国务院新闻办公室", "categoryId": 14, "url": "https://www.scio.gov.cn/", "icon": "https://www.scio.gov.cn/favicon.ico" }, - "新华通讯社":{ + "新华通讯社": { "name": "新华通讯社", "categoryId": 14, "url": "https://www.xinhuanet.com/", "icon": "https://www.xinhuanet.com/favicon.ico" }, - "中国科学院":{ + "中国科学院": { "name": "中国科学院", "categoryId": 14, "url": "https://www.cas.cn/", "icon": "https://www.cas.cn/favicon.ico" }, - "中国社会科学院":{ + "中国社会科学院": { "name": "中国社会科学院", "categoryId": 14, "url": "https://www.cssn.cn/", "icon": "https://www.cssn.cn/favicon.ico" }, - "中国工程院":{ + "中国工程院": { "name": "中国工程院", "categoryId": 14, "url": "https://www.cae.cn/", "icon": "https://www.cae.cn/favicon.ico" }, - "国务院发展研究中心":{ + "国务院发展研究中心": { "name": "国务院发展研究中心", "categoryId": 14, "url": "https://www.drc.gov.cn/", "icon": "https://www.drc.gov.cn/favicon.ico" }, - "中央广播电视总台":{ + "中央广播电视总台": { "name": "中央广播电视总台", "categoryId": 14, "url": "https://www.cctv.com/", "icon": "https://www.cctv.com/favicon.ico" }, - "中国气象局":{ + "中国气象局": { "name": "中国气象局", "categoryId": 14, "url": "https://www.cma.gov.cn/", "icon": "https://www.cma.gov.cn/favicon.ico" }, - "国家行政学院":{ + "国家行政学院": { "name": "国家行政学院", "categoryId": 14, "url": "https://www.nsa.gov.cn/", "icon": "https://www.nsa.gov.cn/favicon.ico" }, - "国家粮食和物资储备局":{ + "国家粮食和物资储备局": { "name": "国家粮食和物资储备局", "categoryId": 14, "url": "https://www.lswz.gov.cn/", "icon": "https://www.lswz.gov.cn/favicon.ico" }, - "国家能源局":{ + "国家能源局": { "name": "国家能源局", "categoryId": 14, "url": "https://www.nea.gov.cn/", "icon": "https://www.nea.gov.cn/favicon.ico" }, - "国家国防科技工业局":{ + "国家国防科技工业局": { "name": "国家国防科技工业局", "categoryId": 14, "url": "https://www.sastind.gov.cn/", "icon": "https://www.sastind.gov.cn/favicon.ico" }, - "国家烟草专卖局":{ + "国家烟草专卖局": { "name": "国家烟草专卖局", "categoryId": 14, "url": "https://www.tobacco.gov.cn/", "icon": "https://www.tobacco.gov.cn/favicon.ico" }, - "国家移民管理局":{ + "国家移民管理局": { "name": "国家移民管理局", "categoryId": 14, "url": "https://www.nia.gov.cn/", "icon": "https://www.nia.gov.cn/favicon.ico" }, - "国家林业和草原局":{ + "国家林业和草原局": { "name": "国家林业和草原局", "categoryId": 14, "url": "https://www.forestry.gov.cn/", "icon": "https://www.forestry.gov.cn/favicon.ico" }, - "国家铁路局":{ + "国家铁路局": { "name": "国家铁路局", "categoryId": 14, "url": "https://www.nra.gov.cn/", "icon": "https://www.nra.gov.cn/favicon.ico" }, - "中国民用航空局":{ + "中国民用航空局": { "name": "中国民用航空局", "categoryId": 14, "url": "https://www.caac.gov.cn/", "icon": "https://www.caac.gov.cn/favicon.ico" }, - "国家邮政局":{ + "国家邮政局": { "name": "国家邮政局", "categoryId": 14, "url": "https://www.spb.gov.cn/", "icon": "https://www.spb.gov.cn/favicon.ico" }, - "国家文物局":{ + "国家文物局": { "name": "国家文物局", "categoryId": 14, "url": "https://www.ncha.gov.cn/", "icon": "https://www.ncha.gov.cn/favicon.ico" }, - "国家中医药管理局":{ + "国家中医药管理局": { "name": "国家中医药管理局", "categoryId": 14, "url": "https://www.natcm.gov.cn/", "icon": "https://www.natcm.gov.cn/favicon.ico" }, - "国家外汇管理局":{ + "国家外汇管理局": { "name": "国家外汇管理局", "categoryId": 14, "url": "https://www.safe.gov.cn/", "icon": "https://www.safe.gov.cn/favicon.ico" }, - "国家药品监督管理局":{ + "国家药品监督管理局": { "name": "国家药品监督管理局", "categoryId": 14, "url": "https://www.nmpa.gov.cn/", "icon": "https://www.nmpa.gov.cn/favicon.ico" }, - "国家政务服务平台":{ + "国家政务服务平台": { "name": "国家政务服务平台", "categoryId": 14, "url": "https://gjzwfw.www.gov.cn/", "icon": "https://gjzwfw.www.gov.cn/favicon.ico" }, - "北京政务服务网":{ + "北京政务服务网": { "name": "北京政务服务网", "categoryId": 14, "url": "http://banshi.beijing.gov.cn/", "icon": "http://banshi.beijing.gov.cn/favicon.ico" }, - "上海政务服务网":{ + "上海政务服务网": { "name": "上海政务服务网", "categoryId": 14, "url": "http://zwdt.sh.gov.cn/govPortals/index.do", "icon": "http://zwdt.sh.gov.cn/favicon.ico" }, - "广东政务服务网":{ + "广东政务服务网": { "name": "广东政务服务网", "categoryId": 14, "url": "http://www.gdzwfw.gov.cn", "icon": "http://www.gdzwfw.gov.cn/favicon.ico" }, - "深圳政务服务网":{ + "深圳政务服务网": { "name": "深圳政务服务网", "categoryId": 14, "url": "https://zwfw.sz.gov.cn/", "icon": "https://zwfw.sz.gov.cn/favicon.ico" }, - "浙江政务服务网":{ + "浙江政务服务网": { "name": "浙江政务服务网", "categoryId": 14, "url": "http://www.zjzwfw.gov.cn", "icon": "http://www.zjzwfw.gov.cn/favicon.ico" }, - "江苏政务服务网":{ + "江苏政务服务网": { "name": "江苏政务服务网", "categoryId": 14, "url": "http://www.jszwfw.gov.cn", "icon": "http://www.jszwfw.gov.cn/favicon.ico" }, - "四川政务服务网":{ + "四川政务服务网": { "name": "四川政务服务网", "categoryId": 14, "url": "http://www.sczwfw.gov.cn", "icon": "http://www.sczwfw.gov.cn/favicon.ico" }, - "湖北政务服务网":{ + "湖北政务服务网": { "name": "湖北政务服务网", "categoryId": 14, "url": "http://zwfw.hubei.gov.cn", "icon": "http://zwfw.hubei.gov.cn/favicon.ico" }, - "湖南政务服务网":{ + "湖南政务服务网": { "name": "湖南政务服务网", "categoryId": 14, "url": "http://zwfw-new.hunan.gov.cn/", "icon": "http://zwfw-new.hunan.gov.cn/favicon.ico" }, - "山东政务服务网":{ + "山东政务服务网": { "name": "山东政务服务网", "categoryId": 14, "url": "http://www.shandong.gov.cn/", "icon": "http://www.shandong.gov.cn/favicon.ico" }, - "河南政务服务网":{ + "河南政务服务网": { "name": "河南政务服务网", "categoryId": 14, "url": "http://www.hnzwfw.gov.cn", "icon": "http://www.hnzwfw.gov.cn/favicon.ico" }, - "河北政务服务网":{ + "河北政务服务网": { "name": "河北政务服务网", "categoryId": 14, "url": "http://www.hbzwfw.gov.cn/", "icon": "http://www.hbzwfw.gov.cn/favicon.ico" }, - "福建政务服务网":{ + "福建政务服务网": { "name": "福建政务服务网", "categoryId": 14, "url": "http://zwfw.fujian.gov.cn", "icon": "http://zwfw.fujian.gov.cn/favicon.ico" }, - "安徽政务服务网":{ + "安徽政务服务网": { "name": "安徽政务服务网", "categoryId": 14, "url": "https://www.ahzwfw.gov.cn", "icon": "https://www.ahzwfw.gov.cn/favicon.ico" }, - "陕西政务服务网":{ + "陕西政务服务网": { "name": "陕西政务服务网", "categoryId": 14, "url": "https://zwfw.shaanxi.gov.cn/sx/public/index", "icon": "https://zwfw.shaanxi.gov.cn/favicon.ico" }, - "重庆政务服务网":{ + "重庆政务服务网": { "name": "重庆政务服务网", "categoryId": 14, "url": "http://zwykb.cq.gov.cn/", "icon": "http://zwykb.cq.gov.cn/favicon.ico" }, - "天津政务服务网":{ + "天津政务服务网": { "name": "天津政务服务网", "categoryId": 14, "url": "https://zwfw.tj.gov.cn/", "icon": "https://zwfw.tj.gov.cn/favicon.ico" }, - "辽宁政务服务网":{ + "辽宁政务服务网": { "name": "辽宁政务服务网", "categoryId": 14, "url": "http://www.lnzwfw.gov.cn", "icon": "http://www.lnzwfw.gov.cn/favicon.ico" }, - "吉林政务服务网":{ + "吉林政务服务网": { "name": "吉林政务服务网", "categoryId": 14, "url": "http://zwfw.jl.gov.cn/jlszwfw/", "icon": "http://zwfw.jl.gov.cn/favicon.ico" }, - "黑龙江政务服务网":{ + "黑龙江政务服务网": { "name": "黑龙江政务服务网", "categoryId": 14, "url": "http://zwfw.hlj.gov.cn/", "icon": "http://zwfw.hlj.gov.cn/favicon.ico" }, - "江西政务服务网":{ + "江西政务服务网": { "name": "江西政务服务网", "categoryId": 14, "url": "http://www.jxzwfww.gov.cn/", "icon": "http://www.jxzwfww.gov.cn/favicon.ico" }, - "广西政务服务网":{ + "广西政务服务网": { "name": "广西政务服务网", "categoryId": 14, "url": "http://zwfw.gxzf.gov.cn", "icon": "http://zwfw.gxzf.gov.cn/favicon.ico" }, - "海南政务服务网":{ + "海南政务服务网": { "name": "海南政务服务网", "categoryId": 14, "url": "https://wssp.hainan.gov.cn/", "icon": "https://wssp.hainan.gov.cn/favicon.ico" }, - "贵州政务服务网":{ + "贵州政务服务网": { "name": "贵州政务服务网", "categoryId": 14, "url": "https://zwfw.guizhou.gov.cn/index.html", "icon": "https://zwfw.guizhou.gov.cn/favicon.ico" }, - "云南政务服务网":{ + "云南政务服务网": { "name": "云南政务服务网", "categoryId": 14, "url": "https://zwfw.yn.gov.cn/portal/", "icon": "https://zwfw.yn.gov.cn/favicon.ico" }, - "西藏政务服务网":{ + "西藏政务服务网": { "name": "西藏政务服务网", "categoryId": 14, "url": "http://www.xzzwfw.gov.cn", "icon": "http://www.xzzwfw.gov.cn/favicon.ico" }, - "甘肃政务服务网":{ + "甘肃政务服务网": { "name": "甘肃政务服务网", "categoryId": 14, "url": "https://zwfw.gansu.gov.cn/", "icon": "https://zwfw.gansu.gov.cn/favicon.ico" }, - "青海政务服务网":{ + "青海政务服务网": { "name": "青海政务服务网", "categoryId": 14, "url": "https://www.qhzwfw.gov.cn/", "icon": "https://www.qhzwfw.gov.cn/favicon.ico" }, - "宁夏政务服务网":{ + "宁夏政务服务网": { "name": "宁夏政务服务网", "categoryId": 14, "url": "http://zwfw.nx.gov.cn", "icon": "http://zwfw.nx.gov.cn/favicon.ico" }, - "新疆政务服务网":{ + "新疆政务服务网": { "name": "新疆政务服务网", "categoryId": 14, "url": "https://zwfw.xinjiang.gov.cn/", "icon": "https://zwfw.xinjiang.gov.cn/favicon.ico" }, - "内蒙古政务服务网":{ + "内蒙古政务服务网": { "name": "内蒙古政务服务网", "categoryId": 14, "url": "http://zwfw.nmg.gov.cn", "icon": "http://zwfw.nmg.gov.cn/favicon.ico" }, - "山西政务服务网":{ + "山西政务服务网": { "name": "山西政务服务网", "categoryId": 14, "url": "https://www.sxzwfw.gov.cn/", "icon": "https://www.sxzwfw.gov.cn/favicon.ico" }, - "新疆生产建设兵团政务服务网":{ + "新疆生产建设兵团政务服务网": { "name": "兵团政务服务网", "categoryId": 14, "url": "https://zwfw.xjbt.gov.cn/", "icon": "https://zwfw.xjbt.gov.cn/favicon.ico" }, - "香港特区政府一站通":{ + "香港特区政府一站通": { "name": "香港特区政府一站通", "categoryId": 14, "url": "https://www.gov.hk/", "icon": "https://www.gov.hk/favicon.ico" }, - "澳门特区政府":{ + "澳门特区政府": { "name": "澳门特区政府", "categoryId": 14, "url": "https://www.gov.mo/", "icon": "https://www.gov.mo/favicon.ico" }, - "台湾地区政府网站":{ + "台湾地区政府网站": { "name": "台湾地区政府网站", "categoryId": 14, "url": "https://www.ey.gov.tw/", "icon": "https://www.ey.gov.tw/favicon.ico" }, - "国家政务服务平台网站":{ + "国家政务服务平台网站": { "name": "国家政务服务平台", "categoryId": 14, "url": "https://gjzwfw.www.gov.cn/", "icon": "https://gjzwfw.www.gov.cn/favicon.ico" } }, - "company": "国务院办公厅" + "company": "国务院办公厅" }, - "优酷":{ - "优酷":{ - "name": "优酷视频", - "categoryId": 8, - "url": "https://www.youku.com/", - "icon": "https://www.youku.com/favicon.ico" + "优酷": { + "优酷": { + "name": "优酷视频", + "categoryId": 8, + "url": "https://www.youku.com/", + "icon": "https://www.youku.com/favicon.ico" }, - "统一数据采集域名":{ - "name": "优酷客户端统一数据采集", - "categoryId": 8, - "url": { - "1": "pcapp-data.youku.com", - "2": "data.youku.com", - "3": "pcapp-data-collect.youku.com" - }, - "icon": "https://www.youku.com/favicon.ico" + "统一数据采集域名": { + "name": "优酷客户端统一数据采集", + "categoryId": 8, + "url": { + "1": "pcapp-data.youku.com", + "2": "data.youku.com", + "3": "pcapp-data-collect.youku.com" + }, + "icon": "https://www.youku.com/favicon.ico" }, - "company": "优酷信息技术(北京)有限公司" + "company": "优酷信息技术(北京)有限公司" }, - "Steam":{ + "Steam": { "Steam商店官网": { "name": "Steam商店官网", - "categoryId": 0, + "categoryId": 26, "url": "store.steampowered.com", "icon": "https://store.steampowered.com/favicon.ico" }, "Steam全球官网": { "name": "Steam全球官网", - "categoryId": 0, + "categoryId": 26, "url": "www.steampowered.com", "icon": "https://www.steampowered.com/favicon.ico" }, "Steam中国官网": { "name": "Steam中国官网", - "categoryId": 0, + "categoryId": 26, "url": "store.steamchina.com", "icon": "https://store.steamchina.com/favicon.ico" }, @@ -2646,6 +3222,12 @@ "url": "steamgames.com", "icon": "https://store.steampowered.com/favicon.ico" }, + "Steam P2P节点发现服务器": { + "name": "Steam P2P节点发现服务器/游戏联机P2P连接路由服务", + "categoryId": 2, + "url": "discovery.steamserver.net", + "icon": "https://store.steampowered.com/favicon.ico" + }, "Steam客服帮助中心": { "name": "Steam客服帮助中心", "categoryId": 6, @@ -2689,6 +3271,750 @@ "icon": "https://store.steampowered.com/favicon.ico" }, "company": "Valve Corporation" + }, + "搜狗": { + "搜狗搜索": { + "name": "搜狗搜索", + "categoryId": 0, + "url": "www.sogou.com", + "icon": "https://www.sogou.com/favicon.ico" + }, + "云端配置同步与策略下发服务": { + "name": "云端配置同步与策略下发服务", + "categoryId": 22, + "url": "config.pinyin.sogou.com", + "icon": "https://www.sogou.com/favicon.ico" + }, + "信息发布与版本 / 词库更新服务": { + "name": "信息发布与版本 / 词库更新服务", + "categoryId": 22, + "url": "info.pinyin.sogou.com", + "icon": "https://www.sogou.com/favicon.ico" + }, + "网络连通性检测服务": { + "name": "网络连通性检测服务", + "categoryId": 22, + "url": "ping.pinyin.sogou.com", + "icon": "https://pinyin.sogou.com/favicon.ico" + }, + "账号个人信息与用户画像管理服务": { + "name": "账号个人信息与用户画像管理服务", + "categoryId": 22, + "url": { + "1": "profile.pinyin.sogou.com", + "2": "pc.profile.pinyin.sogou.com" + }, + "icon": "https://pinyin.sogou.com/favicon.ico" + }, + "company": "搜狗科技有限公司" + }, + "天星金融": { + "天星金融风控传感器服务": { + "name": "天星金融风控传感器服务", + "categoryId": 23, + "url": "sensor.jr.airstarfinance.net", + "icon": "https://lf3-appstore-sign.bytedance.net/obj/rc-client-assets/favicon.ico" + }, + "company": "天星金融" + }, + "InfoQ": { + "InfoQ 全球站": { + "name": "InfoQ 全球站", + "categoryId": 3, + "url": "https://www.infoq.com", + "icon": "https://static001.infoq.cn/static/infoq/www/img/share-default-5tgbiuhgfefgujjhg.png" + }, + "InfoQ 中文站": { + "name": "InfoQ 中文站", + "categoryId": 3, + "url": "https://www.infoq.cn", + "icon": "https://static001.infoq.cn/static/infoq/www/img/share-default-5tgbiuhgfefgujjhg.png" + }, + "InfoQ 中文站备用域名": { + "name": "InfoQ 中文站备用域名", + "categoryId": 3, + "url": "https://www.infoq.com.cn", + "icon": "https://static001.infoq.cn/static/infoq/www/img/share-default-5tgbiuhgfefgujjhg.png" + }, + "InfoQ 中文站静态资源 CDN 服务": { + "name": "InfoQ 中文站静态资源 CDN 服务", + "categoryId": 12, + "url": "https://static001.infoq.cn", + "icon": "https://static001.infoq.cn/static/infoq/www/img/share-default-5tgbiuhgfefgujjhg.png" + }, + "company": "极客邦控股(北京)有限公司" + }, + "51CTO": { + "51CTO 中文站": { + "name": "51CTO 中文站", + "categoryId": 3, + "url": "https://www.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 全站搜索服务": { + "name": "51CTO 全站搜索服务", + "categoryId": 13, + "url": "https://sc.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 云计算频道": { + "name": "51CTO 云计算频道", + "categoryId": 10, + "url": "https://cloud.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 数据库频道": { + "name": "51CTO 数据库频道", + "categoryId": 11, + "url": "https://database.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 认证考试": { + "name": "51CTO 认证考试", + "categoryId": 22, + "url": "https://exam.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 学院": { + "name": "51CTO 学院", + "categoryId": 22, + "url": "https://edu.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 专题活动": { + "name": "51CTO 专题活动", + "categoryId": 3, + "url": "https://special.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 专家博客": { + "name": "51CTO 专家博客", + "categoryId": 3, + "url": "https://blog.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 服务器频道": { + "name": "51CTO 服务器频道", + "categoryId": 10, + "url": "https://server.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 其他业务": { + "name": "51CTO 其他业务", + "categoryId": 3, + "url": "https://other.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO 精品班": { + "name": "51CTO 精品班", + "categoryId": 23, + "url": "https://e.51cto.com/", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO API 服务": { + "name": "51CTO API 服务", + "categoryId": 17, + "url": "https://apie.51cto.com", + "icon": "https://www.51cto.com/favicon.ico" + }, + "51CTO CDN": { + "name": "51CTO静态资源CDN", + "categoryId": 2, + "url": { + "1": "51ctocdn.cn", + "2": "51cto.com" + }, + "icon": "https://www.51cto.com/favicon.ico" + }, + "company": "北京无忧创想信息技术有限公司" + }, + "知乎": { + "知乎主站": { + "name": "知乎主站", + "categoryId": 1, + "url": "https://www.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎移动端H5": { + "name": "知乎移动端H5", + "categoryId": 1, + "url": "https://m.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎学术版": { + "name": "知乎学术版", + "categoryId": 1, + "url": "https://www.zhihu.com/academic", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎公共API": { + "name": "知乎公共API", + "categoryId": 1, + "url": "https://api.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎日报API": { + "name": "知乎日报API", + "categoryId": 11, + "url": "https://news-at.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎内容流API": { + "name": "知乎内容流API", + "categoryId": 11, + "url": "https://api.zhihu.com/feed", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎数据中心API": { + "name": "知乎数据中心API", + "categoryId": 11, + "url": "https://datahub.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎搜索API": { + "name": "知乎搜索API", + "categoryId": 11, + "url": "https://api.zhihu.com/search", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎广告API": { + "name": "知乎广告API", + "categoryId": 11, + "url": "https://api.ad.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎静态资源CDN": { + "name": "知乎静态资源CDN", + "categoryId": 12, + "url": "https://static.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎图片CDN": { + "name": "知乎图片CDN", + "categoryId": 2, + "url": "https://zhimg.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎高清图片CDN": { + "name": "知乎高清图片CDN", + "categoryId": 12, + "url": "https://pica.zhimg.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎npm包CDN": { + "name": "知乎npm包CDN", + "categoryId": 12, + "url": "https://unpkg.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎专栏": { + "name": "知乎专栏", + "categoryId": 3, + "url": "https://zhuanlan.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎创作者平台": { + "name": "知乎创作者平台", + "categoryId": 3, + "url": "https://creator.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎学堂": { + "name": "知乎学堂", + "categoryId": 9, + "url": "https://zhixuetang.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎账号认证": { + "name": "知乎账号认证", + "categoryId": 23, + "url": "https://passport.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎Live直播": { + "name": "知乎Live直播", + "categoryId": 5, + "url": "https://live.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎好物电商": { + "name": "知乎好物电商", + "categoryId": 10, + "url": "https://market.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎广告平台": { + "name": "知乎广告平台", + "categoryId": 11, + "url": "https://ad.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎用户品牌保护": { + "name": "知乎用户品牌保护", + "categoryId": 25, + "url": "https://zhihuer.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎创作者品牌保护": { + "name": "知乎创作者品牌保护", + "categoryId": 25, + "url": "https://zhihuzhe.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎Web端MQTT实时消息网关": { + "name": "知乎Web端MQTT实时消息网关", + "categoryId": 18, + "url": "mqtt-web.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎网页端数据分析服务": { + "name": "知乎网页端数据分析服务", + "categoryId": 23, + "url": "zhihu-web-analytics.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎静态资源备用服务": { + "name": "知乎静态资源备用服务", + "categoryId": 25, + "url": "sugar.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎应用崩溃日志收集服务": { + "name": "知乎应用崩溃日志收集服务", + "categoryId": 24, + "url": "crash2.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎验证码服务": { + "name": "知乎验证码服务", + "categoryId": 22, + "url": "captcha.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "知乎应用性能管理服务": { + "name": "知乎应用性能管理服务", + "categoryId": 26, + "url": "apm.zhihu.com", + "icon": "https://static.zhihu.com/heifetz/favicon.ico" + }, + "company": "北京春田知韵科技有限公司" + }, + "epicgames": { + "游戏商店": { + "name": "Epic游戏商店", + "categoryId": 26, + "url": "store.epicgames.com", + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "EpicGames": { + "name": "Epic Games官网", + "categoryId": 26, + "url": "www.epicgames.com", + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "Epic Games用户行为跟踪服务": { + "name": "Epic Games用户行为跟踪服务", + "categoryId": 25, + "url": "tracker.epicgames.com", + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "Epic CDN": { + "name": "Epic CDN", + "categoryId": 2, + "url": { + "1": "cdn1.epicgames.com", + "2": "cdn2.epicgames.com", + "3": "cdn.epicgames.com" + }, + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "Epic商店静态后端IPv4 API": { + "name": "Epic商店静态后端IPv4 API", + "categoryId": 27, + "url": "store-site-backend-static-ipv4.ak.epicgames.com", + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "Epic Games生产环境静态资源服务器": { + "name": "Epic Games生产环境静态资源服务器", + "categoryId": 27, + "url": "static-assets-prod.epicgames.com", + "icon": "https://static-assets-prod.epicgames.com/epic-store/static/favicon.ico" + }, + "Epic ECOSEC Nelly安全服务(Cloudflare防护)": { + "name": "Epic ECOSEC Nelly安全服务(Cloudflare防护)", + "categoryId": 21, + "url": "nelly-service-prod-cloudflare.ecosec.on.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器公共服务(Cloudflare加速)": { + "name": "Epic启动器公共服务(Cloudflare加速)", + "categoryId": 19, + "url": "launcher-public-service-prod-cloudflare.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic CDN Cloudflare专属节点": { + "name": "Epic CDN Cloudflare专属节点", + "categoryId": 18, + "url": "cloudflare.epicgamescdn.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic账号服务接口": { + "name": "Epic账号服务接口/账号验证与登录核心API", + "categoryId": 24, + "url": "account-public-service-prod03.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器排队服务接口": { + "name": "Epic启动器排队服务接口/启动器排队限流API", + "categoryId": 24, + "url": "launcherwaitingroom-public-service-prod06.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器核心服务接口": { + "name": "Epic启动器核心服务接口/启动器运行相关API", + "categoryId": 24, + "url": "launcher-public-service-prod06.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "EpicGames官方网站": { + "name": "EpicGames官方网站/Epic主站及品牌展示", + "categoryId": 26, + "url": "www.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器官网服务": { + "name": "Epic启动器官网服务/启动器网页端入口", + "categoryId": 26, + "url": "launcher-website-prod07.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic用户行为跟踪": { + "name": "Epic用户行为跟踪/用户操作数据统计分析", + "categoryId": 27, + "url": "tracking.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器账号登录页": { + "name": "Epic启动器账号登录页/启动器端账号验证入口", + "categoryId": 26, + "url": "accounts.launcher-website-prod07.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic账号中心": { + "name": "Epic账号中心/账号信息管理与安全验证", + "categoryId": 26, + "url": "accounts.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "虚幻引擎CDN节点1": { + "name": "虚幻引擎CDN节点1/虚幻引擎资源分发加速", + "categoryId": 2, + "url": "cdn1.unrealengine.com", + "icon": "https://www.unrealengine.com/favicon.ico" + }, + "虚幻引擎CDN节点2": { + "name": "虚幻引擎CDN节点2/虚幻引擎资源分发加速", + "categoryId": 2, + "url": "cdn2.unrealengine.com", + "icon": "https://www.unrealengine.com/favicon.ico" + }, + "Epic数据路由分发": { + "name": "Epic数据路由分发/业务数据中转与统计上报", + "categoryId": 27, + "url": "datarouter.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic授权服务接口": { + "name": "Epic授权服务接口/游戏权限与授权验证API", + "categoryId": 24, + "url": "entitlement-public-service-prod08.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic订单处理接口": { + "name": "Epic订单处理接口/商城订单支付结算API", + "categoryId": 24, + "url": "orderprocessor-public-service-ecomprod01.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏库服务接口": { + "name": "Epic游戏库服务接口/游戏商城目录查询API", + "categoryId": 24, + "url": "catalog-public-service-prod06.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic好友服务接口": { + "name": "Epic好友服务接口/好友列表及联机状态API", + "categoryId": 24, + "url": "friends-public-service-prod06.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic服务器状态接口": { + "name": "Epic服务器状态接口/游戏及服务运行状态API", + "categoryId": 24, + "url": "lightswitch-public-service-prod06.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic账号门户": { + "name": "Epic账号门户/账号相关服务网页入口", + "categoryId": 26, + "url": "accountportal-website-prod07.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic虚幻竞技场服务接口": { + "name": "Epic虚幻竞技场服务接口/UT游戏专属API", + "categoryId": 24, + "url": "ut-public-service-prod10.ol.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏下载节点1": { + "name": "Epic游戏下载节点1/游戏安装包主下载地址", + "categoryId": 26, + "url": "download.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏下载节点2": { + "name": "Epic游戏下载节点2/游戏安装包备用下载地址", + "categoryId": 26, + "url": "download2.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏下载节点3": { + "name": "Epic游戏下载节点3/游戏安装包备用下载地址", + "categoryId": 26, + "url": "download3.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏下载节点4": { + "name": "Epic游戏下载节点4/游戏安装包备用下载地址", + "categoryId": 26, + "url": "download4.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic静态资源存储": { + "name": "Epic静态资源存储/网页及游戏静态文件分发", + "categoryId": 2, + "url": "static-assets-prod.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic商城后端静态资源": { + "name": "Epic商城后端静态资源/商城页面静态文件加速", + "categoryId": 2, + "url": "store-site-backend-static.ak.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic商城内容分发": { + "name": "Epic商城内容分发/商城商品图片及素材加速", + "categoryId": 2, + "url": "store-content.ak.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏库存储服务": { + "name": "Epic游戏库存储服务/用户游戏库数据云端存储", + "categoryId": 21, + "url": "library-service.live.use1a.on.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic数据存储服务": { + "name": "Epic数据存储服务/业务数据及用户文件云端存储", + "categoryId": 21, + "url": "datastorage-public-service-liveegs.live.use1a.on.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic Fastly下载加速": { + "name": "Epic Fastly下载加速/FastlyCDN游戏下载节点", + "categoryId": 2, + "url": "fastly-download.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic游戏商城": { + "name": "Epic游戏商城/游戏购买及领取主页面", + "categoryId": 26, + "url": "store.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "Epic启动器商城入口": { + "name": "Epic启动器商城入口/启动器端商城访问地址", + "categoryId": 26, + "url": "launcher.store.epicgames.com", + "icon": "https://www.epicgames.com/favicon.ico" + }, + "company": "Epic Games, Inc" + }, + "Cloudflare": { + "Cloudflare官网": { + "name": "Cloudflare", + "categoryId": 25, + "url": { + "1": "www.cloudflare.net", + "2": "www.cloudflare.com", + "3": "cloudflare.com", + "4": "cloudflare.net" + }, + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare DNS": { + "name": "Cloudflare DNS", + "categoryId": 25, + "url": { + "1": "dns1.cloudflare.com", + "2": "dns2.cloudflare.com" + }, + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare权威DNS服务器": { + "name": "Cloudflare权威DNS服务器", + "categoryId": 2, + "url": "ns.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare Workers平台": { + "name": "Cloudflare Workers平台", + "categoryId": 22, + "url": "workers.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare Pages托管服务": { + "name": "Cloudflare Pages托管服务", + "categoryId": 18, + "url": "pages.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare管理API": { + "name": "Cloudflare管理API", + "categoryId": 22, + "url": "api.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare One零信任平台": { + "name": "Cloudflare One零信任平台", + "categoryId": 21, + "url": "one.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare for Teams服务": { + "name": "Cloudflare for Teams服务", + "categoryId": 21, + "url": "teams.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare网络雷达": { + "name": "Cloudflare网络雷达", + "categoryId": 25, + "url": "radar.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare辅助DNS服务": { + "name": "Cloudflare辅助DNS服务", + "categoryId": 17, + "url": "secondary.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare Workers预览域": { + "name": "Cloudflare Workers预览域", + "categoryId": 22, + "url": "workers.dev", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare官网运营技术支持服务": { + "name": "Cloudflare官网运营技术支持服务", + "categoryId": 23, + "url": "ot.www.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "Cloudflare安全挑战服务": { + "name": "Cloudflare安全挑战服务", + "categoryId": 21, + "url": "challenges.cloudflare.com", + "icon": "https://www.cloudflare.com/favicon.ico" + }, + "company": "Cloudflare, Inc" + }, + "hcaptcha": { + "hCaptcha主站与管理平台": { + "name": "hCaptcha主站与管理平台", + "categoryId": 21, + "url": "hcaptcha.com", + "icon": "https://www.hcaptcha.com/images/64da82f6bf67de1b1278925f_hcaptcha-logo-hand.png" + }, + "hCaptcha核心验证API服务": { + "name": "hCaptcha核心验证API服务", + "categoryId": 21, + "url": "api.hcaptcha.com", + "icon": "https://www.hcaptcha.com/images/64da82f6bf67de1b1278925f_hcaptcha-logo-hand.png" + }, + "hCaptcha JavaScript API服务": { + "name": "hCaptcha JavaScript API服务", + "categoryId": 21, + "url": "js.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "hCaptcha静态资源CDN": { + "name": "hCaptcha静态资源CDN", + "categoryId": 18, + "url": "newassets.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "hCaptcha挑战页面服务": { + "name": "hCaptcha挑战页面服务", + "categoryId": 21, + "url": "challenges.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "hCaptcha验证图片资源库": { + "name": "hCaptcha验证图片资源库", + "categoryId": 18, + "url": "imgs.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "hCaptcha管理控制台": { + "name": "hCaptcha管理控制台", + "categoryId": 21, + "url": "dashboard.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "hCaptcha开发者文档中心": { + "name": "hCaptcha开发者文档中心", + "categoryId": 21, + "url": "docs.hcaptcha.com", + "icon": "https://www.hcaptcha.com/favicon.ico" + }, + "company": "Intuition Machines, Inc." + }, + "unpkg": { + "Unpkg前端公共CDN": { + "name": "Unpkg前端公共CDN/全球NPM包静态资源极速分发加速", + "categoryId": 2, + "url": "unpkg.com", + "icon": "https://unpkg.com/favicon.jpg" + }, + "Unpkg北美节点CDN": { + "name": "Unpkg北美节点CDN/海外NPM包资源分发加速", + "categoryId": 2, + "url": "unpkg-cdn.com", + "icon": "https://unpkg.com/favicon.jpg" + }, + "Unpkg国内加速节点": { + "name": "Unpkg国内加速节点/大陆地区NPM包资源极速访问", + "categoryId": 2, + "url": "unpkg.zhimg.com", + "icon": "https://unpkg.com/favicon.jpg" + }, + "Unpkg官方子域CDN": { + "name": "Unpkg官方子域CDN/静态资源分流加速节点", + "categoryId": 2, + "url": "cdn.unpkg.com", + "icon": "https://unpkg.com/favicon.ico" + } + }, + "UserCSS前端资源分享平台": { + "name": "UserCSS前端资源分享平台/免费纯CSS UI组件与特效模板库", + "categoryId": 1, + "url": { + "1": "www.userscss.top", + "2": "userscss.top", + "icon": "#" + }, + "company": "未知/个人" } } } \ No newline at end of file diff --git a/static/index.html b/static/index.html index 73e9509..4626132 100644 --- a/static/index.html +++ b/static/index.html @@ -7,7 +7,7 @@ - + @@ -1086,20 +1086,36 @@

屏蔽配置

-
- - -
-
- - -
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

GFWList配置

+
+ + +
+
+ +
diff --git a/static/js/config.js b/static/js/config.js index 020da0f..8354baa 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -74,6 +74,10 @@ function populateConfigForm(config) { //setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt')); // 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN')); + setElementValue('shield-custom-block-ip', getSafeValue(shieldConfig.CustomBlockIP, '')); + // GFWList配置 + setElementValue('shield-gfwlist-ip', getSafeValue(shieldConfig.GFWListIP, '')); + setElementValue('shield-gfwlist-content', getSafeValue(shieldConfig.GFWListContent, '')); } // 工具函数:安全设置元素值 @@ -197,7 +201,10 @@ function collectFormData() { }, shield: { updateInterval: updateInterval, - blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN' + blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN', + customBlockIP: getElementValue('shield-custom-block-ip'), + gfwListIP: getElementValue('shield-gfwlist-ip'), + gfwListContent: getElementValue('shield-gfwlist-content') } }; } @@ -205,9 +212,14 @@ function collectFormData() { // 工具函数:安全获取元素值 function getElementValue(elementId) { const element = document.getElementById(elementId); - if (element && element.tagName === 'INPUT') { - if (element.type === 'checkbox') { - return element.checked; + if (element) { + if (element.tagName === 'INPUT') { + if (element.type === 'checkbox') { + return element.checked; + } + return element.value; + } else if (element.tagName === 'TEXTAREA') { + return element.value; } return element.value; } diff --git a/static/js/dashboard.js b/static/js/dashboard.js index b432370..3415346 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -162,7 +162,19 @@ function processRealTimeData(stats) { // 计算响应时间趋势 let responsePercent = '---'; let trendClass = 'text-gray-400'; - let trendIcon = '---'; + let trendIcon = '•'; + + // 查找箭头元素 + const responseTimePercentElem = document.getElementById('response-time-percent'); + let parent = null; + let arrowIcon = null; + + if (responseTimePercentElem) { + parent = responseTimePercentElem.parentElement; + if (parent) { + arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle'); + } + } if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) { // 首次加载时初始化历史数据,不计算趋势 @@ -171,6 +183,10 @@ function processRealTimeData(stats) { responsePercent = '0.0%'; trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } else { const prevResponseTime = window.dashboardHistoryData.prevResponseTime; @@ -178,16 +194,36 @@ function processRealTimeData(stats) { const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100; responsePercent = Math.abs(changePercent).toFixed(1) + '%'; - // 设置趋势图标和颜色 + // 处理-0.0%的情况 + if (responsePercent === '-0.0%') { + responsePercent = '0.0%'; + } + + // 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头 if (changePercent > 0) { - trendIcon = '↓'; - trendClass = 'text-danger'; - } else if (changePercent < 0) { + // 响应时间增加,数值上升 trendIcon = '↑'; trendClass = 'text-success'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-up mr-1'; + parent.className = 'text-success text-sm flex items-center'; + } + } else if (changePercent < 0) { + // 响应时间减少,数值下降 + trendIcon = '↓'; + trendClass = 'text-danger'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-down mr-1'; + parent.className = 'text-danger text-sm flex items-center'; + } } else { + // 趋势为0时,显示圆点图标 trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } // 更新历史数据 @@ -196,7 +232,6 @@ function processRealTimeData(stats) { } document.getElementById('avg-response-time').textContent = responseTime; - const responseTimePercentElem = document.getElementById('response-time-percent'); if (responseTimePercentElem) { responseTimePercentElem.textContent = trendIcon + ' ' + responsePercent; responseTimePercentElem.className = `text-sm flex items-center ${trendClass}`; @@ -227,7 +262,19 @@ function processRealTimeData(stats) { // 计算活跃IP趋势 let ipsPercent = '---'; let trendClass = 'text-gray-400'; - let trendIcon = '---'; + let trendIcon = '•'; + + // 查找箭头元素 + const activeIpsPercentElem = document.getElementById('active-ips-percent'); + let parent = null; + let arrowIcon = null; + + if (activeIpsPercentElem) { + parent = activeIpsPercentElem.parentElement; + if (parent) { + arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle'); + } + } if (stats.activeIPs !== undefined) { const prevActiveIPs = window.dashboardHistoryData.prevActiveIPs; @@ -238,20 +285,43 @@ function processRealTimeData(stats) { ipsPercent = '0.0%'; trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } else { if (prevActiveIPs > 0) { const changePercent = ((stats.activeIPs - prevActiveIPs) / prevActiveIPs) * 100; ipsPercent = Math.abs(changePercent).toFixed(1) + '%'; + // 处理-0.0%的情况 + if (ipsPercent === '-0.0%') { + ipsPercent = '0.0%'; + } + + // 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头 if (changePercent > 0) { trendIcon = '↑'; - trendClass = 'text-primary'; + trendClass = 'text-success'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-up mr-1'; + parent.className = 'text-success text-sm flex items-center'; + } } else if (changePercent < 0) { trendIcon = '↓'; - trendClass = 'text-secondary'; + trendClass = 'text-danger'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-down mr-1'; + parent.className = 'text-danger text-sm flex items-center'; + } } else { + // 趋势为0时,显示圆点图标 trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } } @@ -261,7 +331,6 @@ function processRealTimeData(stats) { } document.getElementById('active-ips').textContent = activeIPs; - const activeIpsPercentElem = document.getElementById('active-ips-percentage'); if (activeIpsPercentElem) { activeIpsPercentElem.textContent = trendIcon + ' ' + ipsPercent; activeIpsPercentElem.className = `text-sm flex items-center ${trendClass}`; @@ -627,7 +696,19 @@ async function loadDashboardData() { // 计算响应时间趋势 let responsePercent = '---'; let trendClass = 'text-gray-400'; - let trendIcon = '---'; + let trendIcon = '•'; + + // 查找箭头元素 + const responseTimePercentElem = document.getElementById('response-time-percent'); + let parent = null; + let arrowIcon = null; + + if (responseTimePercentElem) { + parent = responseTimePercentElem.parentElement; + if (parent) { + arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle'); + } + } if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) { // 首次加载时初始化历史数据,不计算趋势 @@ -636,6 +717,10 @@ async function loadDashboardData() { responsePercent = '0.0%'; trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } else { const prevResponseTime = window.dashboardHistoryData.prevResponseTime; @@ -644,16 +729,38 @@ async function loadDashboardData() { const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100; responsePercent = Math.abs(changePercent).toFixed(1) + '%'; - // 设置趋势图标和颜色(响应时间增加是负面的,减少是正面的) + // 处理-0.0%的情况 + if (responsePercent === '-0.0%') { + responsePercent = '0.0%'; + } + + // 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头 + // 对于响应时间,数值增加表示性能下降,数值减少表示性能提升 + // 但根据用户要求,我们只根据数值变化方向来设置颜色 if (changePercent > 0) { - trendIcon = '↓'; - trendClass = 'text-danger'; - } else if (changePercent < 0) { + // 响应时间增加(性能下降),数值上升 trendIcon = '↑'; trendClass = 'text-success'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-up mr-1'; + parent.className = 'text-success text-sm flex items-center'; + } + } else if (changePercent < 0) { + // 响应时间减少(性能提升),数值下降 + trendIcon = '↓'; + trendClass = 'text-danger'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-down mr-1'; + parent.className = 'text-danger text-sm flex items-center'; + } } else { + // 趋势为0时,显示圆点图标 trendIcon = '•'; trendClass = 'text-gray-500'; + if (arrowIcon) { + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } } } @@ -663,7 +770,6 @@ async function loadDashboardData() { } document.getElementById('avg-response-time').textContent = responseTime; - const responseTimePercentElem = document.getElementById('response-time-percent'); if (responseTimePercentElem) { responseTimePercentElem.textContent = trendIcon + ' ' + responsePercent; responseTimePercentElem.className = `text-sm flex items-center ${trendClass}`; @@ -697,7 +803,19 @@ async function loadDashboardData() { // 计算活跃IP趋势 let ipsPercent = '---'; let trendClass = 'text-gray-400'; - let trendIcon = '---'; + let trendIcon = '•'; + + // 查找箭头元素 + const activeIpsPercentElem = document.getElementById('active-ips-percent'); + let parent = null; + let arrowIcon = null; + + if (activeIpsPercentElem) { + parent = activeIpsPercentElem.parentElement; + if (parent) { + arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down'); + } + } if (stats.activeIPs !== undefined && stats.activeIPs !== null) { // 存储当前值用于下次计算趋势 @@ -713,9 +831,21 @@ async function loadDashboardData() { if (changePercent > 0) { trendIcon = '↑'; trendClass = 'text-success'; + + // 更新箭头图标和颜色 + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-up mr-1'; + parent.className = 'text-success text-sm flex items-center'; + } } else if (changePercent < 0) { trendIcon = '↓'; trendClass = 'text-danger'; + + // 更新箭头图标和颜色 + if (arrowIcon) { + arrowIcon.className = 'fa fa-arrow-down mr-1'; + parent.className = 'text-danger text-sm flex items-center'; + } } else { trendIcon = '•'; trendClass = 'text-gray-500'; @@ -724,7 +854,6 @@ async function loadDashboardData() { } document.getElementById('active-ips').textContent = activeIPs; - const activeIpsPercentElem = document.getElementById('active-ips-percent'); if (activeIpsPercentElem) { activeIpsPercentElem.textContent = trendIcon + ' ' + ipsPercent; activeIpsPercentElem.className = `text-sm flex items-center ${trendClass}`; @@ -768,29 +897,51 @@ function updateStatsCards(stats) { console.log('更新统计卡片,收到数据:', stats); // 适配不同的数据结构 - let totalQueries = 0, blockedQueries = 0, allowedQueries = 0, errorQueries = 0; + // 保存当前显示的值,用于在数据缺失时保留 + let totalQueries, blockedQueries, allowedQueries, errorQueries; let topQueryType = 'A', queryTypePercentage = 0; - let activeIPs = 0, activeIPsPercentage = 0; + let activeIPs, activeIPsPercentage = 0; + + // 优先从DOM中获取当前显示的值,作为默认值 + const totalQueriesElem = document.getElementById('total-queries'); + const blockedQueriesElem = document.getElementById('blocked-queries'); + const allowedQueriesElem = document.getElementById('allowed-queries'); + const errorQueriesElem = document.getElementById('error-queries'); + const activeIPsElem = document.getElementById('active-ips'); + + // 解析当前显示的值,作为默认值 + const getCurrentValue = (elem) => { + if (!elem) return 0; + const text = elem.textContent.replace(/,/g, '').replace(/[^0-9]/g, ''); + return parseInt(text) || 0; + }; + + // 初始化默认值为当前显示的值 + totalQueries = getCurrentValue(totalQueriesElem); + blockedQueries = getCurrentValue(blockedQueriesElem); + allowedQueries = getCurrentValue(allowedQueriesElem); + errorQueries = getCurrentValue(errorQueriesElem); + activeIPs = getCurrentValue(activeIPsElem); // 检查数据结构,兼容可能的不同格式 if (stats) { - // 优先使用顶层字段 - totalQueries = stats.totalQueries || 0; - blockedQueries = stats.blockedQueries || 0; - allowedQueries = stats.allowedQueries || 0; - errorQueries = stats.errorQueries || 0; - topQueryType = stats.topQueryType || 'A'; - queryTypePercentage = stats.queryTypePercentage || 0; - activeIPs = stats.activeIPs || 0; - activeIPsPercentage = stats.activeIPsPercentage || 0; + // 优先使用顶层字段,只有当值存在时才更新 + if (stats.totalQueries !== undefined) totalQueries = stats.totalQueries; + if (stats.blockedQueries !== undefined) blockedQueries = stats.blockedQueries; + if (stats.allowedQueries !== undefined) allowedQueries = stats.allowedQueries; + if (stats.errorQueries !== undefined) errorQueries = stats.errorQueries; + if (stats.topQueryType !== undefined) topQueryType = stats.topQueryType; + if (stats.queryTypePercentage !== undefined) queryTypePercentage = stats.queryTypePercentage; + if (stats.activeIPs !== undefined) activeIPs = stats.activeIPs; + if (stats.activeIPsPercentage !== undefined) activeIPsPercentage = stats.activeIPsPercentage; // 如果dns对象存在,优先使用其中的数据 if (stats.dns) { - totalQueries = stats.dns.Queries || totalQueries; - blockedQueries = stats.dns.Blocked || blockedQueries; - allowedQueries = stats.dns.Allowed || allowedQueries; - errorQueries = stats.dns.Errors || errorQueries; + if (stats.dns.Queries !== undefined) totalQueries = stats.dns.Queries; + if (stats.dns.Blocked !== undefined) blockedQueries = stats.dns.Blocked; + if (stats.dns.Allowed !== undefined) allowedQueries = stats.dns.Allowed; + if (stats.dns.Errors !== undefined) errorQueries = stats.dns.Errors; // 计算最常用查询类型的百分比 if (stats.dns.QueryTypes && stats.dns.Queries > 0) { @@ -805,14 +956,14 @@ function updateStatsCards(stats) { } } else if (Array.isArray(stats) && stats.length > 0) { // 可能的数据结构3: 数组形式 - totalQueries = stats[0].total || 0; - blockedQueries = stats[0].blocked || 0; - allowedQueries = stats[0].allowed || 0; - errorQueries = stats[0].error || 0; - topQueryType = stats[0].topQueryType || 'A'; - queryTypePercentage = stats[0].queryTypePercentage || 0; - activeIPs = stats[0].activeIPs || 0; - activeIPsPercentage = stats[0].activeIPsPercentage || 0; + if (stats[0].total !== undefined) totalQueries = stats[0].total; + if (stats[0].blocked !== undefined) blockedQueries = stats[0].blocked; + if (stats[0].allowed !== undefined) allowedQueries = stats[0].allowed; + if (stats[0].error !== undefined) errorQueries = stats[0].error; + if (stats[0].topQueryType !== undefined) topQueryType = stats[0].topQueryType; + if (stats[0].queryTypePercentage !== undefined) queryTypePercentage = stats[0].queryTypePercentage; + if (stats[0].activeIPs !== undefined) activeIPs = stats[0].activeIPs; + if (stats[0].activeIPsPercentage !== undefined) activeIPsPercentage = stats[0].activeIPsPercentage; } // 存储正在进行的动画状态,避免动画重叠 @@ -1040,23 +1191,33 @@ function updateStatsCards(stats) { animateValue('active-ips', activeIPs); // DNSSEC相关数据 - let dnssecEnabled = false, dnssecQueries = 0, dnssecSuccess = 0, dnssecFailed = 0, dnssecUsage = 0; + // 优先从DOM中获取当前显示的值,作为默认值 + const dnssecSuccessElem = document.getElementById('dnssec-success'); + const dnssecFailedElem = document.getElementById('dnssec-failed'); + const dnssecQueriesElem = document.getElementById('dnssec-queries'); + + // 从当前显示值初始化,确保数据刷新前保留前一次结果 + let dnssecEnabled = false; + let dnssecQueries = getCurrentValue(dnssecQueriesElem); + let dnssecSuccess = getCurrentValue(dnssecSuccessElem); + let dnssecFailed = getCurrentValue(dnssecFailedElem); + let dnssecUsage = 0; // 检查DNSSEC数据 if (stats) { - // 优先使用顶层字段 - dnssecEnabled = stats.dnssecEnabled || false; - dnssecQueries = stats.dnssecQueries || 0; - dnssecSuccess = stats.dnssecSuccess || 0; - dnssecFailed = stats.dnssecFailed || 0; - dnssecUsage = stats.dnssecUsage || 0; + // 优先使用顶层字段,只有当值存在时才更新 + if (stats.dnssecEnabled !== undefined) dnssecEnabled = stats.dnssecEnabled; + if (stats.dnssecQueries !== undefined) dnssecQueries = stats.dnssecQueries; + if (stats.dnssecSuccess !== undefined) dnssecSuccess = stats.dnssecSuccess; + if (stats.dnssecFailed !== undefined) dnssecFailed = stats.dnssecFailed; + if (stats.dnssecUsage !== undefined) dnssecUsage = stats.dnssecUsage; // 如果dns对象存在,优先使用其中的数据 if (stats.dns) { - dnssecEnabled = stats.dns.DNSSECEnabled || dnssecEnabled; - dnssecQueries = stats.dns.DNSSECQueries || dnssecQueries; - dnssecSuccess = stats.dns.DNSSECSuccess || dnssecSuccess; - dnssecFailed = stats.dns.DNSSECFailed || dnssecFailed; + if (stats.dns.DNSSECEnabled !== undefined) dnssecEnabled = stats.dns.DNSSECEnabled; + if (stats.dns.DNSSECQueries !== undefined) dnssecQueries = stats.dns.DNSSECQueries; + if (stats.dns.DNSSECSuccess !== undefined) dnssecSuccess = stats.dns.DNSSECSuccess; + if (stats.dns.DNSSECFailed !== undefined) dnssecFailed = stats.dns.DNSSECFailed; } // 如果没有直接提供使用率,计算使用率 @@ -1102,12 +1263,66 @@ function updateStatsCards(stats) { if (queryTypePercentageElement) queryTypePercentageElement.textContent = `${Math.round(queryTypePercentage)}%`; if (activeIpsPercentElement) activeIpsPercentElement.textContent = `${Math.round(activeIPsPercentage)}%`; - // 计算并平滑更新百分比 + // 计算并平滑更新百分比,同时更新箭头颜色和方向 + function updatePercentWithArrow(elementId, percentage, prevValue, currentValue) { + const element = document.getElementById(elementId); + if (!element) return; + + // 更新百分比数值 + updatePercentage(elementId, percentage); + + // 查找父元素,获取箭头图标 + const parent = element.parentElement; + if (!parent) return; + + let arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle'); + if (!arrowIcon) return; + + // 计算变化趋势 + let isIncrease = currentValue > prevValue; + let isDecrease = currentValue < prevValue; + let isNoChange = currentValue === prevValue; + + // 处理百分比显示,避免-0.0%的情况 + let formattedPercentage = percentage; + if (percentage === '-0.0%') { + formattedPercentage = '0.0%'; + updatePercentage(elementId, formattedPercentage); + } + + // 更新箭头图标和颜色 + if (isIncrease) { + arrowIcon.className = 'fa fa-arrow-up mr-1'; + parent.className = 'text-success text-sm flex items-center'; + } else if (isDecrease) { + arrowIcon.className = 'fa fa-arrow-down mr-1'; + parent.className = 'text-danger text-sm flex items-center'; + } else if (isNoChange) { + // 趋势为0时,显示圆点图标 + arrowIcon.className = 'fa fa-circle mr-1 text-xs'; + parent.className = 'text-gray-500 text-sm flex items-center'; + } + } + + // 保存历史数据,用于计算趋势 + window.dashboardHistoryData = window.dashboardHistoryData || { + totalQueries: 0, + blockedQueries: 0, + allowedQueries: 0, + errorQueries: 0 + }; + + // 计算百分比并更新箭头 if (totalQueries > 0) { - updatePercentage('blocked-percent', `${Math.round((blockedQueries / totalQueries) * 100)}%`); - updatePercentage('allowed-percent', `${Math.round((allowedQueries / totalQueries) * 100)}%`); - updatePercentage('error-percent', `${Math.round((errorQueries / totalQueries) * 100)}%`); - updatePercentage('queries-percent', '100%'); + const queriesPercent = '100%'; + const blockedPercent = `${Math.round((blockedQueries / totalQueries) * 100)}%`; + const allowedPercent = `${Math.round((allowedQueries / totalQueries) * 100)}%`; + const errorPercent = `${Math.round((errorQueries / totalQueries) * 100)}%`; + + updatePercentWithArrow('queries-percent', queriesPercent, window.dashboardHistoryData.totalQueries, totalQueries); + updatePercentWithArrow('blocked-percent', blockedPercent, window.dashboardHistoryData.blockedQueries, blockedQueries); + updatePercentWithArrow('allowed-percent', allowedPercent, window.dashboardHistoryData.allowedQueries, allowedQueries); + updatePercentWithArrow('error-percent', errorPercent, window.dashboardHistoryData.errorQueries, errorQueries); } else { updatePercentage('queries-percent', '---'); updatePercentage('blocked-percent', '---'); @@ -1115,6 +1330,12 @@ function updateStatsCards(stats) { updatePercentage('error-percent', '---'); } + // 更新历史数据 + window.dashboardHistoryData.totalQueries = totalQueries; + window.dashboardHistoryData.blockedQueries = blockedQueries; + window.dashboardHistoryData.allowedQueries = allowedQueries; + window.dashboardHistoryData.errorQueries = errorQueries; + } diff --git a/static/js/logs.js b/static/js/logs.js index bfef799..40b3dca 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -280,45 +280,45 @@ async function getDomainInfo(domain) { // 如果有URL属性,直接检查域名 if (website.url) { // 处理字符串类型的URL - if (typeof website.url === 'string') { - console.log(' 检查字符串URL:', website.url); - if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) { - console.log(' 匹配成功,返回网站信息'); - return { - name: website.name, - icon: website.icon, - categoryId: website.categoryId, - categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', - company: website.company || companyName - }; - } + if (typeof website.url === 'string') { + console.log(' 检查字符串URL:', website.url); + if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: website.name, + icon: website.icon, + categoryId: website.categoryId, + categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', + company: website.company || companyName + }; } - // 处理对象类型的URL - else if (typeof website.url === 'object') { - console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL'); - for (const urlKey in website.url) { - if (website.url.hasOwnProperty(urlKey)) { - const urlValue = website.url[urlKey]; - console.log(' 检查URL', urlKey, ':', urlValue); - if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) { - console.log(' 匹配成功,返回网站信息'); - return { - name: website.name, - icon: website.icon, - categoryId: website.categoryId, - categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', - company: website.company || companyName - }; - } + } + // 处理对象类型的URL + else if (typeof website.url === 'object') { + console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL'); + for (const urlKey in website.url) { + if (website.url.hasOwnProperty(urlKey)) { + const urlValue = website.url[urlKey]; + console.log(' 检查URL', urlKey, ':', urlValue); + if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: website.name, + icon: website.icon, + categoryId: website.categoryId, + categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', + company: website.company || companyName + }; } } } + } } else if (typeof website === 'object' && website !== null) { // 没有URL属性,可能是嵌套的类别 console.log(' 发现嵌套类别,进一步检查'); for (const nestedWebsiteKey in website) { if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') { - console.log(' 检查嵌套网站:', nestedWebsiteKey); + console.log(' 检查嵌套网站/类别:', nestedWebsiteKey); const nestedWebsite = website[nestedWebsiteKey]; if (nestedWebsite.url) { @@ -356,8 +356,54 @@ async function getDomainInfo(domain) { } } } + } else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) { + // 嵌套类别中的嵌套类别,递归检查 + console.log(' 发现二级嵌套类别,进一步检查'); + for (const secondNestedWebsiteKey in nestedWebsite) { + if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') { + console.log(' 检查二级嵌套网站:', secondNestedWebsiteKey); + const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey]; + + if (secondNestedWebsite.url) { + // 处理字符串类型的URL + if (typeof secondNestedWebsite.url === 'string') { + console.log(' 检查字符串URL:', secondNestedWebsite.url); + if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: secondNestedWebsite.name, + icon: secondNestedWebsite.icon, + categoryId: secondNestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', + company: secondNestedWebsite.company || companyName + }; + } + } + // 处理对象类型的URL + else if (typeof secondNestedWebsite.url === 'object') { + console.log(' 检查对象类型URL,包含', Object.keys(secondNestedWebsite.url).length, '个URL'); + for (const urlKey in secondNestedWebsite.url) { + if (secondNestedWebsite.url.hasOwnProperty(urlKey)) { + const urlValue = secondNestedWebsite.url[urlKey]; + console.log(' 检查URL', urlKey, ':', urlValue); + if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: secondNestedWebsite.name, + icon: secondNestedWebsite.icon, + categoryId: secondNestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', + company: secondNestedWebsite.company || companyName + }; + } + } + } + } + } + } + } } else { - console.log(' 嵌套网站没有URL属性'); + console.log(' 嵌套网站没有URL属性且不是对象类型'); } } } @@ -1891,7 +1937,7 @@ async function showLogDetailModal(log) { ${domainInfo.categoryName || '未知'}
- 所属单位: + 所属单位/公司: ${domainInfo.company || '未知'}
diff --git a/temp_config.json b/temp_config.json new file mode 100644 index 0000000..237b7c2 --- /dev/null +++ b/temp_config.json @@ -0,0 +1,52 @@ +{ + "dns": { + "port": 5353, + "upstreamDNS": [ + "223.5.5.5:53", + "223.6.6.6:53", + "117.50.10.10:53", + "10.35.10.200:53" + ], + "dnssecUpstreamDNS": [ + "117.50.10.10:53", + "101.226.4.6:53", + "218.30.118.6:53", + "208.67.220.220:53", + "208.67.222.222:53" + ], + "timeout": 5000, + "statsFile": "data/stats.json", + "saveInterval": 300, + "cacheTTL": 30, + "enableDNSSEC": true, + "queryMode": "parallel", + "domainSpecificDNS": { + "amazehome.xyz": ["10.35.10.200:53"] + } + }, + "http": { + "port": 8081, + "host": "0.0.0.0", + "enableAPI": true, + "username": "admin", + "password": "admin" + }, + "shield": { + "localRulesFile": "data/rules.txt", + "blacklists": [], + "updateInterval": 3600, + "hostsFile": "data/hosts.txt", + "blockMethod": "NXDOMAIN", + "customBlockIP": "", + "statsFile": "./data/shield_stats.json", + "statsSaveInterval": 60, + "remoteRulesCacheDir": "data/remote_rules" + }, + "log": { + "file": "logs/dns-server-5353.log", + "level": "debug", + "maxSize": 100, + "maxBackups": 10, + "maxAge": 30 + } +} \ No newline at end of file diff --git a/test-domain-info.js b/test-domain-info.js new file mode 100644 index 0000000..d53dc39 --- /dev/null +++ b/test-domain-info.js @@ -0,0 +1,261 @@ +// 测试脚本,用于调试 getDomainInfo 函数 +const fs = require('fs'); +const path = require('path'); + +// 模拟浏览器环境的 console.log +console.log = function() { + process.stdout.write(Array.from(arguments).join(' ') + '\n'); +}; + +// 读取域名信息数据库 +const domainInfoPath = path.join(__dirname, 'static/domain-info/domains/domain-info.json'); +const domainInfoDatabase = JSON.parse(fs.readFileSync(domainInfoPath, 'utf8')); + +// 模拟已加载的数据库 +let domainInfoLoaded = true; + +// 检查域名是否匹配 +function isDomainMatch(urlValue, targetDomain, categoryId) { + console.log(' 开始匹配URL:', urlValue, '目标域名:', targetDomain, '类别ID:', categoryId); + + // 规范化目标域名,去除末尾的点 + const normalizedTargetDomain = targetDomain.replace(/\.$/, '').toLowerCase(); + + try { + // 尝试将URL值解析为完整URL + console.log(' 尝试解析URL为完整URL'); + const url = new URL(urlValue); + let hostname = url.hostname.toLowerCase(); + // 规范化主机名,去除末尾的点 + hostname = hostname.replace(/\.$/, ''); + console.log(' 解析成功,主机名:', hostname, '规范化目标域名:', normalizedTargetDomain); + + // 根据类别ID选择匹配方式 + if (categoryId === 2) { + // CDN类别,使用域名后缀匹配 + if (normalizedTargetDomain.endsWith('.' + hostname) || normalizedTargetDomain === hostname) { + console.log(' CDN域名后缀匹配成功'); + return true; + } else { + console.log(' CDN域名后缀不匹配'); + return false; + } + } else { + // 其他类别,使用完整域名匹配 + if (hostname === normalizedTargetDomain) { + console.log(' 完整域名匹配成功'); + return true; + } else { + console.log(' 完整域名不匹配'); + return false; + } + } + } catch (e) { + console.log(' 解析URL失败,将其视为纯域名处理,错误信息:', e.message); + // 如果是纯域名而不是完整URL + let urlDomain = urlValue.toLowerCase(); + // 规范化纯域名,去除末尾的点 + urlDomain = urlDomain.replace(/\.$/, ''); + console.log(' 处理为纯域名:', urlDomain, '规范化目标域名:', normalizedTargetDomain); + + // 根据类别ID选择匹配方式 + if (categoryId === 2) { + // CDN类别,使用域名后缀匹配 + if (normalizedTargetDomain.endsWith('.' + urlDomain) || normalizedTargetDomain === urlDomain) { + console.log(' CDN域名后缀匹配成功'); + return true; + } else { + console.log(' CDN域名后缀不匹配'); + return false; + } + } else { + // 其他类别,使用完整域名匹配 + if (urlDomain === normalizedTargetDomain) { + console.log(' 完整域名匹配成功'); + return true; + } else { + console.log(' 完整域名不匹配'); + return false; + } + } + } +} + +// 根据域名查找对应的网站信息 +async function getDomainInfo(domain) { + console.log('开始查找域名信息,域名:', domain); + + if (!domainInfoDatabase || !domainInfoDatabase.domains) { + console.error('域名信息数据库无效或为空'); + return null; + } + + // 规范化域名,移除可能的端口号 + const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase(); + console.log('规范化后的域名:', normalizedDomain); + + // 遍历所有公司 + console.log('开始遍历公司,总公司数:', Object.keys(domainInfoDatabase.domains).length); + for (const companyKey in domainInfoDatabase.domains) { + if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) { + console.log('检查公司:', companyKey); + const companyData = domainInfoDatabase.domains[companyKey]; + const companyName = companyData.company || companyKey; + + // 遍历公司下的所有网站和类别 + for (const websiteKey in companyData) { + if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') { + console.log(' 检查网站/类别:', websiteKey); + const website = companyData[websiteKey]; + + // 如果有URL属性,直接检查域名 + if (website.url) { + // 处理字符串类型的URL + if (typeof website.url === 'string') { + console.log(' 检查字符串URL:', website.url); + if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: website.name, + icon: website.icon, + categoryId: website.categoryId, + categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', + company: website.company || companyName + }; + } + } + // 处理对象类型的URL + else if (typeof website.url === 'object') { + console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL'); + for (const urlKey in website.url) { + if (website.url.hasOwnProperty(urlKey)) { + const urlValue = website.url[urlKey]; + console.log(' 检查URL', urlKey, ':', urlValue); + if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: website.name, + icon: website.icon, + categoryId: website.categoryId, + categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', + company: website.company || companyName + }; + } + } + } + } + } else if (typeof website === 'object' && website !== null) { + // 没有URL属性,可能是嵌套的类别 + console.log(' 发现嵌套类别,进一步检查'); + for (const nestedWebsiteKey in website) { + if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') { + console.log(' 检查嵌套网站/类别:', nestedWebsiteKey); + const nestedWebsite = website[nestedWebsiteKey]; + + if (nestedWebsite.url) { + // 处理字符串类型的URL + if (typeof nestedWebsite.url === 'string') { + console.log(' 检查字符串URL:', nestedWebsite.url); + if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: nestedWebsite.name, + icon: nestedWebsite.icon, + categoryId: nestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', + company: nestedWebsite.company || companyName + }; + } + } + // 处理对象类型的URL + else if (typeof nestedWebsite.url === 'object') { + console.log(' 检查对象类型URL,包含', Object.keys(nestedWebsite.url).length, '个URL'); + for (const urlKey in nestedWebsite.url) { + if (nestedWebsite.url.hasOwnProperty(urlKey)) { + const urlValue = nestedWebsite.url[urlKey]; + console.log(' 检查URL', urlKey, ':', urlValue); + if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: nestedWebsite.name, + icon: nestedWebsite.icon, + categoryId: nestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', + company: nestedWebsite.company || companyName + }; + } + } + } + } + } else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) { + // 嵌套类别中的嵌套类别,递归检查 + console.log(' 发现二级嵌套类别,进一步检查'); + for (const secondNestedWebsiteKey in nestedWebsite) { + if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') { + console.log(' 检查二级嵌套网站:', secondNestedWebsiteKey); + const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey]; + + if (secondNestedWebsite.url) { + // 处理字符串类型的URL + if (typeof secondNestedWebsite.url === 'string') { + console.log(' 检查字符串URL:', secondNestedWebsite.url); + if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: secondNestedWebsite.name, + icon: secondNestedWebsite.icon, + categoryId: secondNestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', + company: secondNestedWebsite.company || companyName + }; + } + } + // 处理对象类型的URL + else if (typeof secondNestedWebsite.url === 'object') { + console.log(' 检查对象类型URL,包含', Object.keys(secondNestedWebsite.url).length, '个URL'); + for (const urlKey in secondNestedWebsite.url) { + if (secondNestedWebsite.url.hasOwnProperty(urlKey)) { + const urlValue = secondNestedWebsite.url[urlKey]; + console.log(' 检查URL', urlKey, ':', urlValue); + if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) { + console.log(' 匹配成功,返回网站信息'); + return { + name: secondNestedWebsite.name, + icon: secondNestedWebsite.icon, + categoryId: secondNestedWebsite.categoryId, + categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', + company: secondNestedWebsite.company || companyName + }; + } + } + } + } + } + } + } + } else { + console.log(' 嵌套网站没有URL属性且不是对象类型'); + } + } + } + } else { + console.log(' 网站没有URL属性'); + } + } + } + } + } + + console.log('未找到匹配的域名信息'); + return null; +} + +// 测试 mcs.doubao.com +getDomainInfo('mcs.doubao.com').then(result => { + console.log('\n=== 测试结果 ==='); + if (result) { + console.log('匹配成功:', JSON.stringify(result, null, 2)); + } else { + console.log('匹配失败'); + } +});