增加威胁域名审计
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
# DNS 查询日志加载性能优化方案
|
||||
|
||||
## 问题分析
|
||||
|
||||
当前日志详情页面加载时间约 10 秒,主要性能瓶颈分析如下:
|
||||
|
||||
### 1. **主要性能瓶颈**
|
||||
|
||||
#### 1.1 同步 API 调用(最严重)
|
||||
- **位置**: `updateLogsTable()` 函数第 1221 行
|
||||
- **问题**: 对每条日志都调用 `await isDomainInTrackerDatabase(log.domain)`
|
||||
- **影响**: 假设每页 10 条日志,每条日志检查跟踪器数据库需要 100-500ms,总计 1-5 秒
|
||||
- **代码**:
|
||||
```javascript
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.domain); // ❌ 每条都 await
|
||||
```
|
||||
|
||||
#### 1.2 跟踪器数据库加载
|
||||
- **位置**: `isDomainInTrackerDatabase()` 函数
|
||||
- **问题**: 每次检查都要等待数据库加载完成
|
||||
- **影响**: 首次加载或数据库未加载完成时,每条日志都要等待
|
||||
|
||||
#### 1.3 域名信息 API 调用
|
||||
- **位置**: `getDomainInfoFromAPI()` 函数
|
||||
- **问题**: 如果实现中调用 API,会产生网络延迟
|
||||
- **影响**: 每个域名都可能产生一次 HTTP 请求
|
||||
|
||||
#### 1.4 IP 地理位置查询
|
||||
- **位置**: `getIpGeolocation()` 函数
|
||||
- **问题**: 虽然有缓存,但首次查询仍需调用外部 API
|
||||
- **影响**: 每个新 IP 地址需要 200-500ms
|
||||
|
||||
### 2. **性能测试数据(估算)**
|
||||
|
||||
| 操作 | 单次耗时 | 每页 10 条总耗时 | 优化后耗时 |
|
||||
|------|---------|----------------|-----------|
|
||||
| 跟踪器检查 | 100-500ms | 1-5 秒 | <100ms |
|
||||
| IP 地理位置 | 200-500ms | 2-5 秒 | <500ms(缓存) |
|
||||
| 后端 API 查询 | 100-300ms | 100-300ms | 不变 |
|
||||
| DOM 渲染 | 50-100ms | 50-100ms | 不变 |
|
||||
| **总计** | **10 秒** | **3-10 秒** | **<1 秒** |
|
||||
|
||||
---
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 方案一:异步并行处理(推荐优先实施)⭐⭐⭐⭐⭐
|
||||
|
||||
#### 1.1 批量并行检查跟踪器
|
||||
|
||||
**问题**: 当前是串行等待每个域名的检查结果
|
||||
**解决**: 使用 `Promise.all()` 并行处理所有域名的检查
|
||||
|
||||
```javascript
|
||||
// 优化前(串行)
|
||||
for (const log of logs) {
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.domain);
|
||||
// ... 处理逻辑
|
||||
}
|
||||
|
||||
// 优化后(并行)
|
||||
const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain));
|
||||
const trackerResults = await Promise.all(trackerChecks);
|
||||
|
||||
// 然后渲染表格
|
||||
logs.forEach((log, index) => {
|
||||
const trackerInfo = trackerResults[index];
|
||||
// ... 渲染逻辑
|
||||
});
|
||||
```
|
||||
|
||||
**预期效果**: 从 1-5 秒降低到 100-500ms
|
||||
|
||||
#### 1.2 预加载跟踪器数据库
|
||||
|
||||
**问题**: 每次检查都要等待数据库加载
|
||||
**解决**: 页面初始化时立即加载,而不是延迟加载
|
||||
|
||||
```javascript
|
||||
// 在页面加载时立即执行
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadTrackersDatabase(); // 提前加载,不等待日志显示
|
||||
});
|
||||
```
|
||||
|
||||
**预期效果**: 消除数据库加载等待时间
|
||||
|
||||
---
|
||||
|
||||
### 方案二:缓存优化 ⭐⭐⭐⭐
|
||||
|
||||
#### 2.1 增强跟踪器检查缓存
|
||||
|
||||
**问题**: 相同的域名被重复检查
|
||||
**解决**: 添加内存缓存,避免重复检查
|
||||
|
||||
```javascript
|
||||
// 添加缓存
|
||||
const trackerCache = new Map();
|
||||
const trackerCacheTimestamp = new Map();
|
||||
const TRACKER_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 小时
|
||||
|
||||
async function isDomainInTrackerDatabase(domain) {
|
||||
// 检查缓存
|
||||
const cached = trackerCache.get(domain);
|
||||
const timestamp = trackerCacheTimestamp.get(domain);
|
||||
|
||||
if (cached && timestamp && (Date.now() - timestamp) < TRACKER_CACHE_EXPIRY) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 执行实际检查...
|
||||
const result = // ... 检查逻辑
|
||||
|
||||
// 缓存结果
|
||||
trackerCache.set(domain, result);
|
||||
trackerCacheTimestamp.set(domain, Date.now());
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**预期效果**: 重复域名检查从 100ms 降低到 <1ms
|
||||
|
||||
#### 2.2 IP 地理位置缓存优化
|
||||
|
||||
**问题**: 相同 IP 被重复查询
|
||||
**解决**: 已有 LRU 缓存,确保缓存命中率高
|
||||
|
||||
```javascript
|
||||
// 确保缓存大小足够(当前为 1000,建议保持)
|
||||
const GEOLOCATION_CACHE_MAX_SIZE = 1000;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案三:延迟渲染优化 ⭐⭐⭐
|
||||
|
||||
#### 3.1 先渲染后更新
|
||||
|
||||
**问题**: 等待所有数据获取完成才渲染表格
|
||||
**解决**: 先渲染基本内容,再异步更新增强信息
|
||||
|
||||
```javascript
|
||||
async function updateLogsTable(logs) {
|
||||
// 第一步:立即渲染基本表格(不等待跟踪器检查)
|
||||
renderBasicTable(logs);
|
||||
|
||||
// 第二步:异步获取跟踪器信息并更新
|
||||
const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain));
|
||||
Promise.all(trackerChecks).then(results => {
|
||||
updateTrackerIcons(results); // 只更新图标部分
|
||||
});
|
||||
|
||||
// 第三步:异步获取 IP 地理位置
|
||||
logs.forEach(log => {
|
||||
getIpGeolocation(log.clientIP).then(location => {
|
||||
updateLocationElement(log.clientIP, location);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**预期效果**: 用户感知加载时间从 10 秒降低到 500ms 以内
|
||||
|
||||
#### 3.2 虚拟滚动(针对大量数据)
|
||||
|
||||
**问题**: 如果用户选择每页 100 条,DOM 节点过多
|
||||
**解决**: 只渲染可见区域的日志
|
||||
|
||||
```javascript
|
||||
// 使用虚拟滚动库或自定义实现
|
||||
// 只渲染可视区域的 20-30 条,滚动时动态加载
|
||||
```
|
||||
|
||||
**预期效果**: 渲染时间从 100ms 降低到 20ms
|
||||
|
||||
---
|
||||
|
||||
### 方案四:后端优化 ⭐⭐⭐⭐
|
||||
|
||||
#### 4.1 后端批量检查跟踪器
|
||||
|
||||
**问题**: 前端检查每条日志的跟踪器状态
|
||||
**解决**: 后端在返回日志时直接包含跟踪器信息
|
||||
|
||||
```go
|
||||
// 后端代码修改
|
||||
type QueryLog struct {
|
||||
// ... 现有字段
|
||||
IsTracker bool `json:"isTracker"`
|
||||
TrackerName string `json:"trackerName,omitempty"`
|
||||
}
|
||||
|
||||
// 在返回日志前批量检查
|
||||
func (s *Server) enrichLogsWithTrackerInfo(logs []QueryLog) {
|
||||
for i := range logs {
|
||||
logs[i].IsTracker, logs[i].TrackerName = s.checkTracker(logs[i].Domain)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**预期效果**: 完全消除前端跟踪器检查时间
|
||||
|
||||
#### 4.2 后端缓存优化
|
||||
|
||||
**问题**: 后端查询数据库慢
|
||||
**解决**: 增加 Redis 或内存缓存
|
||||
|
||||
```go
|
||||
// 使用内存缓存查询结果
|
||||
var queryCache = lru.New(100)
|
||||
```
|
||||
|
||||
**预期效果**: 后端查询时间从 300ms 降低到 50ms
|
||||
|
||||
---
|
||||
|
||||
### 方案五:数据库索引优化 ⭐⭐⭐
|
||||
|
||||
#### 5.1 添加数据库索引
|
||||
|
||||
**问题**: 日志查询慢
|
||||
**解决**: 为常用查询字段添加索引
|
||||
|
||||
```sql
|
||||
-- 为时间戳添加索引(加速时间范围查询)
|
||||
CREATE INDEX idx_timestamp ON query_logs(timestamp DESC);
|
||||
|
||||
-- 为域名添加索引(加速域名搜索)
|
||||
CREATE INDEX idx_domain ON query_logs(domain);
|
||||
|
||||
-- 为客户端 IP 添加索引
|
||||
CREATE INDEX idx_client_ip ON query_logs(client_ip);
|
||||
|
||||
-- 组合索引(加速组合查询)
|
||||
CREATE INDEX idx_timestamp_result ON query_logs(timestamp DESC, result);
|
||||
```
|
||||
|
||||
**预期效果**: 后端查询时间从 500ms 降低到 50ms
|
||||
|
||||
---
|
||||
|
||||
## 实施优先级
|
||||
|
||||
### 第一阶段(立即实施,效果最明显)
|
||||
1. ✅ **方案 1.1**: 并行处理跟踪器检查
|
||||
2. ✅ **方案 2.1**: 添加跟踪器检查缓存
|
||||
3. ✅ **方案 1.2**: 预加载跟踪器数据库
|
||||
|
||||
**预期效果**: 10 秒 → 2-3 秒
|
||||
|
||||
### 第二阶段(本周内实施)
|
||||
4. ✅ **方案 3.1**: 先渲染后更新
|
||||
5. ✅ **方案 4.1**: 后端批量检查跟踪器
|
||||
|
||||
**预期效果**: 2-3 秒 → 1 秒以内
|
||||
|
||||
### 第三阶段(长期优化)
|
||||
6. ✅ **方案 5.1**: 数据库索引优化
|
||||
7. ✅ **方案 4.2**: 后端缓存
|
||||
8. ✅ **方案 3.2**: 虚拟滚动
|
||||
|
||||
**预期效果**: 1 秒 → 500ms 以内
|
||||
|
||||
---
|
||||
|
||||
## 具体代码修改
|
||||
|
||||
### 修改 1: `logs.js` - 并行处理跟踪器检查
|
||||
|
||||
```javascript
|
||||
// 在 updateLogsTable() 函数中,替换第 1155-1353 行
|
||||
|
||||
async function updateLogsTable(logs) {
|
||||
const tableBody = document.getElementById('logs-table-body');
|
||||
if (!tableBody) return;
|
||||
|
||||
// 清空表格
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
if (logs.length === 0) {
|
||||
// 显示空状态
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
||||
<div>暂无查询日志</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为移动设备
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// 【优化】并行处理所有跟踪器检查
|
||||
const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain));
|
||||
const trackerResults = await Promise.all(trackerChecks);
|
||||
|
||||
// 预创建文档片段,减少 DOM 操作
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// 填充表格
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
const log = logs[i];
|
||||
const trackerInfo = trackerResults[i];
|
||||
const isTracker = trackerInfo !== null;
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.className = `border-b border-gray-100 hover:bg-gray-50 transition-colors ${
|
||||
log.result === 'blocked' ? 'bg-red-50' : ''
|
||||
}`;
|
||||
|
||||
// ... 渲染逻辑(保持不变,但移除 await)
|
||||
|
||||
fragment.appendChild(row);
|
||||
}
|
||||
|
||||
// 一次性添加到 DOM
|
||||
tableBody.appendChild(fragment);
|
||||
|
||||
// 【优化】异步更新 IP 地理位置(不阻塞渲染)
|
||||
logs.forEach(log => {
|
||||
if (!isPrivateIP(log.clientIP)) {
|
||||
getIpGeolocation(log.clientIP).then(location => {
|
||||
const locationEl = document.querySelector(`.location-${log.clientIP.replace(/[.:]/g, '-')}`);
|
||||
if (locationEl) {
|
||||
locationEl.textContent = location;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 修改 2: `logs.js` - 添加跟踪器缓存
|
||||
|
||||
```javascript
|
||||
// 在全局变量部分添加
|
||||
const trackerCache = new Map();
|
||||
const trackerCacheTimestamp = new Map();
|
||||
const TRACKER_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 小时
|
||||
|
||||
// 修改 isDomainInTrackerDatabase 函数
|
||||
async function isDomainInTrackerDatabase(domain) {
|
||||
// 检查缓存
|
||||
const cached = trackerCache.get(domain);
|
||||
const timestamp = trackerCacheTimestamp.get(domain);
|
||||
|
||||
if (cached !== undefined && timestamp && (Date.now() - timestamp) < TRACKER_CACHE_EXPIRY) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 确保数据库已加载
|
||||
if (!trackersDatabase || !trackersLoaded) {
|
||||
await loadTrackersDatabase();
|
||||
}
|
||||
|
||||
if (!trackersDatabase || !trackersDatabase.trackers) {
|
||||
trackerCache.set(domain, null);
|
||||
trackerCacheTimestamp.set(domain, Date.now());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查逻辑(保持不变)
|
||||
let result = null;
|
||||
|
||||
if (trackersDatabase.trackers.hasOwnProperty(domain)) {
|
||||
result = trackersDatabase.trackers[domain];
|
||||
} else {
|
||||
for (const trackerKey in trackersDatabase.trackers) {
|
||||
const tracker = trackersDatabase.trackers[trackerKey];
|
||||
if (tracker && tracker.url) {
|
||||
try {
|
||||
const trackerUrl = new URL(tracker.url);
|
||||
if (trackerUrl.hostname === domain) {
|
||||
result = tracker;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略无效 URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
trackerCache.set(domain, result);
|
||||
trackerCacheTimestamp.set(domain, Date.now());
|
||||
|
||||
// 限制缓存大小(LRU 简单实现)
|
||||
if (trackerCache.size > 1000) {
|
||||
const oldestKey = trackerCache.keys().next().value;
|
||||
trackerCache.delete(oldestKey);
|
||||
trackerCacheTimestamp.delete(oldestKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改 3: `index.html` - 预加载跟踪器数据库
|
||||
|
||||
```html
|
||||
<!-- 在页面底部,logs.js 加载后立即执行 -->
|
||||
<script>
|
||||
// 页面加载时立即预加载跟踪器数据库
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 异步加载,不阻塞页面
|
||||
if (typeof loadTrackersDatabase === 'function') {
|
||||
loadTrackersDatabase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 添加性能日志
|
||||
|
||||
```javascript
|
||||
// 在 loadLogs() 函数中添加性能监控
|
||||
async function loadLogs() {
|
||||
const startTime = performance.now();
|
||||
console.log('🚀 开始加载日志...');
|
||||
|
||||
// ... 加载逻辑
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
console.log(`✅ 日志加载完成,耗时:${duration.toFixed(2)}ms`);
|
||||
|
||||
// 分解各阶段耗时
|
||||
console.log('📊 性能分析:', {
|
||||
API 请求:apiTime,
|
||||
跟踪器检查:trackerTime,
|
||||
DOM 渲染:renderTime,
|
||||
IP 查询:ipTime
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 预期最终效果
|
||||
|
||||
| 优化阶段 | 加载时间 | 改善幅度 |
|
||||
|---------|---------|---------|
|
||||
| 优化前 | 10 秒 | - |
|
||||
| 第一阶段 | 2-3 秒 | 70-80% |
|
||||
| 第二阶段 | 1 秒以内 | 90% |
|
||||
| 第三阶段 | 500ms 以内 | 95% |
|
||||
|
||||
---
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **内存使用**: 缓存会增加内存占用,建议限制缓存大小
|
||||
2. **缓存一致性**: 跟踪器数据库更新后需要清理缓存
|
||||
3. **浏览器兼容性**: `Promise.all()` 在老旧浏览器可能不支持
|
||||
4. **并发限制**: 如果日志数量过多(>100),考虑分批处理
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
**最优先实施的优化**:
|
||||
1. ✅ 并行处理跟踪器检查(方案 1.1)
|
||||
2. ✅ 添加跟踪器检查缓存(方案 2.1)
|
||||
3. ✅ 先渲染后更新(方案 3.1)
|
||||
|
||||
这三项优化可以将加载时间从 10 秒降低到 1 秒以内,且实施成本低,风险小。
|
||||
Reference in New Issue
Block a user