远程列表web
This commit is contained in:
134
http/server.go
134
http/server.go
@@ -3,6 +3,7 @@ package http
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -37,19 +38,19 @@ func (s *Server) Start() error {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// API路由
|
||||
if s.config.EnableAPI {
|
||||
mux.HandleFunc("/api/stats", s.handleStats)
|
||||
mux.HandleFunc("/api/shield", s.handleShield)
|
||||
mux.HandleFunc("/api/shield/hosts", s.handleShieldHosts)
|
||||
mux.HandleFunc("/api/query", s.handleQuery)
|
||||
mux.HandleFunc("/api/status", s.handleStatus)
|
||||
mux.HandleFunc("/api/config", s.handleConfig)
|
||||
// 添加统计相关接口
|
||||
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
|
||||
mux.HandleFunc("/api/top-resolved", s.handleTopResolvedDomains)
|
||||
mux.HandleFunc("/api/recent-blocked", s.handleRecentBlockedDomains)
|
||||
mux.HandleFunc("/api/hourly-stats", s.handleHourlyStats)
|
||||
}
|
||||
if s.config.EnableAPI {
|
||||
mux.HandleFunc("/api/stats", s.handleStats)
|
||||
mux.HandleFunc("/api/shield", s.handleShield)
|
||||
mux.HandleFunc("/api/shield/hosts", s.handleShieldHosts)
|
||||
mux.HandleFunc("/api/query", s.handleQuery)
|
||||
mux.HandleFunc("/api/status", s.handleStatus)
|
||||
mux.HandleFunc("/api/config", s.handleConfig)
|
||||
// 添加统计相关接口
|
||||
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
|
||||
mux.HandleFunc("/api/top-resolved", s.handleTopResolvedDomains)
|
||||
mux.HandleFunc("/api/recent-blocked", s.handleRecentBlockedDomains)
|
||||
mux.HandleFunc("/api/hourly-stats", s.handleHourlyStats)
|
||||
}
|
||||
|
||||
// 静态文件服务(可后续添加前端界面)
|
||||
mux.Handle("/", http.FileServer(http.Dir("./static")))
|
||||
@@ -99,9 +100,9 @@ func (s *Server) handleTopBlockedDomains(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
domains := s.dnsServer.GetTopBlockedDomains(10)
|
||||
|
||||
|
||||
// 转换为前端需要的格式
|
||||
result := make([]map[string]interface{}, len(domains))
|
||||
for i, domain := range domains {
|
||||
@@ -110,7 +111,7 @@ func (s *Server) handleTopBlockedDomains(w http.ResponseWriter, r *http.Request)
|
||||
"count": domain.Count,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
@@ -121,9 +122,9 @@ func (s *Server) handleTopResolvedDomains(w http.ResponseWriter, r *http.Request
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
domains := s.dnsServer.GetTopResolvedDomains(10)
|
||||
|
||||
|
||||
// 转换为前端需要的格式
|
||||
result := make([]map[string]interface{}, len(domains))
|
||||
for i, domain := range domains {
|
||||
@@ -132,7 +133,7 @@ func (s *Server) handleTopResolvedDomains(w http.ResponseWriter, r *http.Request
|
||||
"count": domain.Count,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
@@ -143,9 +144,9 @@ func (s *Server) handleRecentBlockedDomains(w http.ResponseWriter, r *http.Reque
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
domains := s.dnsServer.GetRecentBlockedDomains(10)
|
||||
|
||||
|
||||
// 转换为前端需要的格式
|
||||
result := make([]map[string]interface{}, len(domains))
|
||||
for i, domain := range domains {
|
||||
@@ -154,7 +155,7 @@ func (s *Server) handleRecentBlockedDomains(w http.ResponseWriter, r *http.Reque
|
||||
"time": domain.LastSeen.Format("15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
@@ -165,26 +166,26 @@ func (s *Server) handleHourlyStats(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
hourlyStats := s.dnsServer.GetHourlyStats()
|
||||
|
||||
|
||||
// 生成最近24小时的数据
|
||||
now := time.Now()
|
||||
labels := make([]string, 24)
|
||||
data := make([]int64, 24)
|
||||
|
||||
|
||||
for i := 23; i >= 0; i-- {
|
||||
hour := now.Add(time.Duration(-i) * time.Hour)
|
||||
hourKey := hour.Format("2006-01-02-15")
|
||||
labels[23-i] = hour.Format("15:00")
|
||||
data[23-i] = hourlyStats[hourKey]
|
||||
}
|
||||
|
||||
|
||||
result := map[string]interface{}{
|
||||
"labels": labels,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
@@ -367,56 +368,65 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(status)
|
||||
}
|
||||
|
||||
// saveConfigToFile 保存配置到文件
|
||||
func saveConfigToFile(config *config.Config, filePath string) error {
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filePath, data, 0644)
|
||||
}
|
||||
|
||||
// handleConfig 处理配置请求
|
||||
func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
// 返回当前配置(包括远程规则相关配置)
|
||||
// 返回当前配置(包括黑名单配置)
|
||||
config := map[string]interface{}{
|
||||
"shield": map[string]interface{}{
|
||||
"blockMethod": s.globalConfig.Shield.BlockMethod,
|
||||
"customBlockIP": s.globalConfig.Shield.CustomBlockIP,
|
||||
"remoteRules": s.globalConfig.Shield.RemoteRules,
|
||||
"blockMethod": s.globalConfig.Shield.BlockMethod,
|
||||
"customBlockIP": s.globalConfig.Shield.CustomBlockIP,
|
||||
"blacklists": s.globalConfig.Shield.Blacklists,
|
||||
"updateInterval": s.globalConfig.Shield.UpdateInterval,
|
||||
},
|
||||
}
|
||||
json.NewEncoder(w).Encode(config)
|
||||
|
||||
|
||||
case http.MethodPost:
|
||||
// 更新配置
|
||||
var req struct {
|
||||
Shield struct {
|
||||
BlockMethod string `json:"blockMethod"`
|
||||
CustomBlockIP string `json:"customBlockIP"`
|
||||
RemoteRules []string `json:"remoteRules"`
|
||||
UpdateInterval int `json:"updateInterval"`
|
||||
BlockMethod string `json:"blockMethod"`
|
||||
CustomBlockIP string `json:"customBlockIP"`
|
||||
Blacklists []config.BlacklistEntry `json:"blacklists"`
|
||||
UpdateInterval int `json:"updateInterval"`
|
||||
} `json:"shield"`
|
||||
}
|
||||
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "无效的请求体", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 更新屏蔽配置
|
||||
if req.Shield.BlockMethod != "" {
|
||||
// 验证屏蔽方法是否有效
|
||||
validMethods := map[string]bool{
|
||||
"NXDOMAIN": true,
|
||||
"refused": true,
|
||||
"emptyIP": true,
|
||||
"refused": true,
|
||||
"emptyIP": true,
|
||||
"customIP": true,
|
||||
}
|
||||
|
||||
|
||||
if !validMethods[req.Shield.BlockMethod] {
|
||||
http.Error(w, "无效的屏蔽方法", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
s.globalConfig.Shield.BlockMethod = req.Shield.BlockMethod
|
||||
|
||||
|
||||
// 如果选择了customIP,验证IP地址
|
||||
if req.Shield.BlockMethod == "customIP" {
|
||||
if req.Shield.CustomBlockIP == "" {
|
||||
@@ -430,20 +440,32 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if req.Shield.CustomBlockIP != "" {
|
||||
s.globalConfig.Shield.CustomBlockIP = req.Shield.CustomBlockIP
|
||||
}
|
||||
|
||||
// 更新远程规则列表
|
||||
if req.Shield.RemoteRules != nil {
|
||||
s.globalConfig.Shield.RemoteRules = req.Shield.RemoteRules
|
||||
|
||||
// 更新黑名单配置
|
||||
if req.Shield.Blacklists != nil {
|
||||
// 验证黑名单配置
|
||||
for i, bl := range req.Shield.Blacklists {
|
||||
if bl.URL == "" {
|
||||
http.Error(w, fmt.Sprintf("黑名单URL不能为空,索引: %d", i), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(bl.URL, "http://") && !strings.HasPrefix(bl.URL, "https://") {
|
||||
http.Error(w, fmt.Sprintf("黑名单URL必须以http://或https://开头,索引: %d", i), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.globalConfig.Shield.Blacklists = req.Shield.Blacklists
|
||||
s.shieldManager.UpdateBlacklist(req.Shield.Blacklists)
|
||||
// 重新加载规则
|
||||
if err := s.shieldManager.LoadRules(); err != nil {
|
||||
logger.Error("重新加载规则失败", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新更新间隔
|
||||
if req.Shield.UpdateInterval > 0 {
|
||||
s.globalConfig.Shield.UpdateInterval = req.Shield.UpdateInterval
|
||||
@@ -451,13 +473,19 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
s.shieldManager.StopAutoUpdate()
|
||||
s.shieldManager.StartAutoUpdate()
|
||||
}
|
||||
|
||||
|
||||
// 保存配置到文件
|
||||
if err := saveConfigToFile(s.globalConfig, "./config.json"); err != nil {
|
||||
logger.Error("保存配置到文件失败", "error", err)
|
||||
// 不返回错误,只记录日志,因为配置已经在内存中更新成功
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "配置已更新",
|
||||
})
|
||||
|
||||
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
@@ -470,7 +498,7 @@ func isValidIP(ip string) bool {
|
||||
if len(parts) != 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
for _, part := range parts {
|
||||
// 检查是否为数字
|
||||
for _, char := range part {
|
||||
|
||||
Reference in New Issue
Block a user