添加重启endpoint

This commit is contained in:
Alex Yang
2025-11-27 18:26:00 +08:00
parent 8ee1d94471
commit 82f17ad875
13 changed files with 203212 additions and 42 deletions

5
data/shield_stats.json Normal file
View File

@@ -0,0 +1,5 @@
{
"blockedDomainsCount": {},
"resolvedDomainsCount": {},
"lastSaved": "2025-11-27T18:23:48.562056138+08:00"
}

3129
data/stats.json Normal file

File diff suppressed because it is too large Load Diff

BIN
dns-server Executable file

Binary file not shown.

View File

@@ -65,16 +65,18 @@ type Server struct {
resolvedDomainsMutex sync.RWMutex
resolvedDomains map[string]*BlockedDomain // 用于记录解析的域名
clientStatsMutex sync.RWMutex
clientStats map[string]*ClientStats // 用于记录客户端统计
clientStats map[string]*ClientStats // 用于记录客户端统计
hourlyStatsMutex sync.RWMutex
hourlyStats map[string]int64 // 按小时统计屏蔽数量
hourlyStats map[string]int64 // 按小时统计屏蔽数量
dailyStatsMutex sync.RWMutex
dailyStats map[string]int64 // 按天统计屏蔽数量
dailyStats map[string]int64 // 按天统计屏蔽数量
monthlyStatsMutex sync.RWMutex
monthlyStats map[string]int64 // 按月统计屏蔽数量
monthlyStats map[string]int64 // 按月统计屏蔽数量
saveTicker *time.Ticker // 用于定时保存数据
startTime time.Time // 服务器启动时间
saveDone chan struct{} // 用于通知保存协程停止
stopped bool // 服务器是否已经停止
stoppedMutex sync.Mutex // 保护stopped标志的互斥锁
}
// Stats DNS服务器统计信息
@@ -102,9 +104,9 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
Net: "udp",
Timeout: time.Duration(config.Timeout) * time.Millisecond,
},
ctx: ctx,
cancel: cancel,
startTime: time.Now(), // 记录服务器启动时间
ctx: ctx,
cancel: cancel,
startTime: time.Now(), // 记录服务器启动时间
stats: &Stats{
Queries: 0,
Blocked: 0,
@@ -123,6 +125,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
dailyStats: make(map[string]int64),
monthlyStats: make(map[string]int64),
saveDone: make(chan struct{}),
stopped: false, // 初始化为未停止状态
}
// 加载已保存的统计数据

View File

@@ -71,6 +71,7 @@ func (s *Server) Start() error {
mux.HandleFunc("/api/query", s.handleQuery)
mux.HandleFunc("/api/status", s.handleStatus)
mux.HandleFunc("/api/config", s.handleConfig)
mux.HandleFunc("/api/config/restart", s.handleRestart)
// 添加统计相关接口
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
mux.HandleFunc("/api/top-resolved", s.handleTopResolvedDomains)
@@ -1062,3 +1063,37 @@ func isValidIP(ip string) bool {
}
return true
}
// handleRestart 处理重启服务请求
func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
logger.Info("收到重启服务请求")
// 停止DNS服务器
s.dnsServer.Stop()
// 重新加载屏蔽规则
if err := s.shieldManager.LoadRules(); err != nil {
logger.Error("重新加载屏蔽规则失败", "error", err)
}
// 重新启动DNS服务器
go func() {
if err := s.dnsServer.Start(); err != nil {
logger.Error("DNS服务器重启失败", "error", err)
}
}()
// 重新启动定时更新任务
s.shieldManager.StopAutoUpdate()
s.shieldManager.StartAutoUpdate()
// 返回成功响应
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "服务已重启"})
logger.Info("服务重启成功")
}

52202
logs/dns-server.log Normal file

File diff suppressed because it is too large Load Diff

BIN
output/dns-server Executable file

Binary file not shown.

View File

@@ -638,9 +638,9 @@
<div id="shield-content" class="hidden">
<!-- 屏蔽管理页面内容 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<h3 class="text-lg font-semibold mb-6">屏蔽规则管理</h3>
<h3 class="text-lg font-semibold mb-6">远程规则管理</h3>
<!-- 这里将添加屏蔽规则管理相关内容 -->
<p>屏蔽管理页面内容待实现</p>
<p>远程规则管理页面内容待实现</p>
</div>
</div>

View File

@@ -1219,7 +1219,21 @@ function initTimeRangeToggle() {
button.classList.add(...styleConfig.activeHover); // 添加选中时的浅色悬停
// 获取并更新当前时间范围
const rangeValue = button.dataset.range || button.textContent.trim().replace(/[^0-9a-zA-Z]/g, '');
let rangeValue;
if (button.dataset.range) {
rangeValue = button.dataset.range;
} else {
const btnText = button.textContent.trim();
if (btnText.includes('24')) {
rangeValue = '24h';
} else if (btnText.includes('7')) {
rangeValue = '7d';
} else if (btnText.includes('30')) {
rangeValue = '30d';
} else {
rangeValue = btnText.replace(/[^0-9a-zA-Z]/g, '');
}
}
currentTimeRange = rangeValue;
console.log('更新时间范围为:', currentTimeRange);
}
@@ -1233,7 +1247,7 @@ function initTimeRangeToggle() {
// 移除自定义鼠标悬停提示效果
});
// 确保默认选中第一个按钮
// 确保默认选中第一个按钮并显示混合内容
if (timeRangeButtons.length > 0) {
const firstButton = timeRangeButtons[0];
const firstStyle = buttonStyles[0];
@@ -1241,18 +1255,24 @@ function initTimeRangeToggle() {
// 先重置所有按钮
timeRangeButtons.forEach((btn, index) => {
const btnStyle = buttonStyles[index % buttonStyles.length];
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500');
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500', 'bg-gray-500', 'mixed-view-active');
btn.classList.remove(...btnStyle.active);
btn.classList.remove(...btnStyle.activeHover);
btn.classList.add(...btnStyle.normal);
btn.classList.add(...btnStyle.hover);
});
// 然后设置第一个按钮为激活状态
// 然后设置第一个按钮为激活状态,并标记为混合视图
firstButton.classList.remove(...firstStyle.normal);
firstButton.classList.remove(...firstStyle.hover);
firstButton.classList.add('active');
firstButton.classList.add('active', 'mixed-view-active');
firstButton.classList.add(...firstStyle.active);
console.log('默认选中第一个按钮:', firstButton.textContent.trim());
firstButton.classList.add(...firstStyle.activeHover);
console.log('默认选中第一个按钮并显示混合内容:', firstButton.textContent.trim());
// 设置默认显示混合内容
isMixedView = true;
currentTimeRange = 'mixed';
}
}
@@ -1479,28 +1499,126 @@ function initDetailedTimeRangeToggle() {
console.log('初始化详细图表时间范围切换,找到按钮数量:', detailedTimeRangeButtons.length);
detailedTimeRangeButtons.forEach((button) => {
// 初始化详细图表的默认状态,与主图表保持一致
detailedCurrentTimeRange = currentTimeRange;
detailedIsMixedView = isMixedView;
// 定义按钮样式配置,与主视图保持一致
const buttonStyles = [
{ // 24小时按钮
normal: ['bg-gray-100', 'text-gray-700'],
hover: ['hover:bg-blue-100'],
active: ['bg-blue-500', 'text-white'],
activeHover: ['hover:bg-blue-400']
},
{ // 7天按钮
normal: ['bg-gray-100', 'text-gray-700'],
hover: ['hover:bg-green-100'],
active: ['bg-green-500', 'text-white'],
activeHover: ['hover:bg-green-400']
},
{ // 30天按钮
normal: ['bg-gray-100', 'text-gray-700'],
hover: ['hover:bg-purple-100'],
active: ['bg-purple-500', 'text-white'],
activeHover: ['hover:bg-purple-400']
},
{ // 混合视图按钮
normal: ['bg-gray-100', 'text-gray-700'],
hover: ['hover:bg-gray-200'],
active: ['bg-gray-500', 'text-white'],
activeHover: ['hover:bg-gray-400']
}
];
// 设置初始按钮状态
detailedTimeRangeButtons.forEach((button, index) => {
const styleConfig = buttonStyles[index % buttonStyles.length];
// 移除所有初始样式
button.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-gray-200', 'text-gray-700',
'bg-green-500', 'bg-purple-500', 'bg-gray-100', 'mixed-view-active');
// 设置非选中状态样式
button.classList.add('transition-colors', 'duration-200');
button.classList.add(...styleConfig.normal);
button.classList.add(...styleConfig.hover);
// 如果是第一个按钮且当前是混合视图,设置为混合视图激活状态
if (index === 0 && detailedIsMixedView) {
button.classList.remove(...styleConfig.normal);
button.classList.remove(...styleConfig.hover);
button.classList.add('active', 'mixed-view-active');
button.classList.add(...styleConfig.active);
button.classList.add(...styleConfig.activeHover);
}
});
detailedTimeRangeButtons.forEach((button, index) => {
button.addEventListener('click', () => {
// 设置当前时间范围
const timeRange = button.dataset.range;
const isMixedMode = timeRange === 'mixed';
const styleConfig = buttonStyles[index % buttonStyles.length];
console.log('时间范围按钮被点击:', { timeRange, isMixedMode });
// 检查是否是再次点击已选中的按钮
const isActive = button.classList.contains('active');
// 更新按钮状态
detailedTimeRangeButtons.forEach((btn) => {
if (btn === button) {
btn.classList.add('bg-blue-500', 'text-white');
btn.classList.remove('bg-gray-200', 'text-gray-700');
} else {
btn.classList.remove('bg-blue-500', 'text-white');
btn.classList.add('bg-gray-200', 'text-gray-700');
}
// 重置所有按钮为非选中状态
detailedTimeRangeButtons.forEach((btn, btnIndex) => {
const btnStyle = buttonStyles[btnIndex % buttonStyles.length];
// 移除所有可能的激活状态类
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500', 'bg-gray-500', 'mixed-view-active');
btn.classList.remove(...btnStyle.active);
btn.classList.remove(...btnStyle.activeHover);
// 添加非选中状态类
btn.classList.add(...btnStyle.normal);
btn.classList.add(...btnStyle.hover);
});
// 更新详细图表专用变量
detailedCurrentTimeRange = timeRange;
detailedIsMixedView = isMixedMode;
if (isActive && index < 3) { // 再次点击已选中的时间范围按钮
// 切换到混合视图
detailedIsMixedView = true;
detailedCurrentTimeRange = 'mixed';
console.log('详细图表切换到混合视图');
// 设置当前按钮为特殊混合视图状态
button.classList.remove(...styleConfig.normal);
button.classList.remove(...styleConfig.hover);
button.classList.add('active', 'mixed-view-active');
button.classList.add(...styleConfig.active);
button.classList.add(...styleConfig.activeHover);
} else {
// 普通选中模式
detailedIsMixedView = false;
// 设置当前按钮为激活状态
button.classList.remove(...styleConfig.normal);
button.classList.remove(...styleConfig.hover);
button.classList.add('active');
button.classList.add(...styleConfig.active);
button.classList.add(...styleConfig.activeHover);
// 获取并更新当前时间范围
let rangeValue;
if (button.dataset.range) {
rangeValue = button.dataset.range;
} else {
const btnText = button.textContent.trim();
if (btnText.includes('24')) {
rangeValue = '24h';
} else if (btnText.includes('7')) {
rangeValue = '7d';
} else if (btnText.includes('30')) {
rangeValue = '30d';
} else {
rangeValue = btnText.replace(/[^0-9a-zA-Z]/g, '');
}
}
detailedCurrentTimeRange = rangeValue;
console.log('详细图表更新时间范围为:', detailedCurrentTimeRange);
}
// 重新绘制详细图表
drawDetailedDNSRequestsChart();
});
});
@@ -1610,17 +1728,26 @@ function drawDetailedDNSRequestsChart() {
});
} else {
// 普通视图
// 根据详细视图时间范围选择API函数
// 根据详细视图时间范围选择API函数和对应的颜色
let apiFunction;
let chartColor;
let chartFillColor;
switch (detailedCurrentTimeRange) {
case '7d':
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#22c55e'; // 绿色与混合视图中的7天数据颜色一致
chartFillColor = 'rgba(34, 197, 94, 0.1)';
break;
case '30d':
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#a855f7'; // 紫色与混合视图中的30天数据颜色一致
chartFillColor = 'rgba(168, 85, 247, 0.1)';
break;
default: // 24h
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#3b82f6'; // 蓝色与混合视图中的24小时数据颜色一致
chartFillColor = 'rgba(59, 130, 246, 0.1)';
}
// 获取统计数据
@@ -1631,8 +1758,8 @@ function drawDetailedDNSRequestsChart() {
detailedDnsRequestsChart.data.datasets = [{
label: 'DNS请求数量',
data: data.data,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderColor: chartColor,
backgroundColor: chartFillColor,
tension: 0.4,
fill: true
}];
@@ -1650,8 +1777,8 @@ function drawDetailedDNSRequestsChart() {
datasets: [{
label: 'DNS请求数量',
data: data.data,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderColor: chartColor,
backgroundColor: chartFillColor,
tension: 0.4,
fill: true
}]
@@ -1762,6 +1889,7 @@ function drawDNSRequestsChart() {
// 创建或更新图表
if (dnsRequestsChart) {
// 使用第一个数据集的标签,但确保每个数据集使用自己的数据
dnsRequestsChart.data.labels = results[0].labels;
dnsRequestsChart.data.datasets = datasets;
dnsRequestsChart.options.plugins.legend.display = showLegend;
@@ -1815,17 +1943,26 @@ function drawDNSRequestsChart() {
});
} else {
// 普通视图
// 根据当前时间范围选择API函数
// 根据当前时间范围选择API函数和对应的颜色
let apiFunction;
let chartColor;
let chartFillColor;
switch (currentTimeRange) {
case '7d':
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#22c55e'; // 绿色与混合视图中的7天数据颜色一致
chartFillColor = 'rgba(34, 197, 94, 0.1)';
break;
case '30d':
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#a855f7'; // 紫色与混合视图中的30天数据颜色一致
chartFillColor = 'rgba(168, 85, 247, 0.1)';
break;
default: // 24h
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
chartColor = '#3b82f6'; // 蓝色与混合视图中的24小时数据颜色一致
chartFillColor = 'rgba(59, 130, 246, 0.1)';
}
// 获取统计数据
@@ -1836,8 +1973,8 @@ function drawDNSRequestsChart() {
dnsRequestsChart.data.datasets = [{
label: 'DNS请求数量',
data: data.data,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderColor: chartColor,
backgroundColor: chartFillColor,
tension: 0.4,
fill: true
}];
@@ -1855,8 +1992,8 @@ function drawDNSRequestsChart() {
datasets: [{
label: 'DNS请求数量',
data: data.data,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderColor: chartColor,
backgroundColor: chartFillColor,
tension: 0.4,
fill: true
}]