package main import ( "bytes" "encoding/binary" "encoding/csv" "encoding/json" "fmt" "io" "log" "net" "net/http" "os" "path/filepath" "strconv" "strings" "time" ) // 配置部分 var ( servers = []struct { Addr string Port int }{ {"192.168.1.214", 25565}, {"HY.HYPIXEL.COM.CN", 25565}, } dataExpiration = 180 * 24 * time.Hour checkInterval = 30 * time.Second webPort = "8080" ) // 历史记录结构 type HistoryEntry struct { Timestamp time.Time `json:"timestamp"` Online int `json:"online"` } // Minecraft协议处理函数 func writeVarInt(w io.Writer, value int) error { for { if (value & ^0x7F) == 0 { return binary.Write(w, binary.BigEndian, byte(value)) } binary.Write(w, binary.BigEndian, byte((value&0x7F)|0x80)) value = int(uint32(value) >> 7) } } func readVarInt(r io.Reader) (int, error) { var num int var shift uint for { b := make([]byte, 1) _, err := r.Read(b) if err != nil { return 0, err } num |= int(b[0]&0x7F) << shift shift += 7 if b[0]&0x80 == 0 { break } } return num, nil } func writeString(w io.Writer, s string) error { if err := writeVarInt(w, len(s)); err != nil { return err } _, err := w.Write([]byte(s)) return err } func readString(r io.Reader) (string, error) { length, err := readVarInt(r) if err != nil { return "", err } data := make([]byte, length) _, err = io.ReadFull(r, data) return string(data), err } func queryMinecraftServer(addr string, port int) (int, error) { conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", addr, port), 5*time.Second) if err != nil { return 0, err } defer conn.Close() // 握手包 handshake := new(bytes.Buffer) writeVarInt(handshake, 0) // 包ID writeVarInt(handshake, -1) // 协议版本 writeString(handshake, addr) // 服务器地址 binary.Write(handshake, binary.BigEndian, uint16(port)) writeVarInt(handshake, 1) // 下一个状态 (Status) // 发送握手包 packet := new(bytes.Buffer) writeVarInt(packet, handshake.Len()) packet.Write(handshake.Bytes()) if _, err := conn.Write(packet.Bytes()); err != nil { return 0, err } // 发送请求包 request := new(bytes.Buffer) writeVarInt(request, 0) // 请求包ID packet.Reset() writeVarInt(packet, request.Len()) packet.Write(request.Bytes()) if _, err := conn.Write(packet.Bytes()); err != nil { return 0, err } // 读取响应 length, err := readVarInt(conn) if err != nil { return 0, err } data := make([]byte, length) _, err = io.ReadFull(conn, data) if err != nil { return 0, err } // 解析JSON jsonStr, err := readString(bytes.NewReader(data[1:])) // 跳过包ID if err != nil { return 0, err } var response struct { Players struct { Online int `json:"online"` } `json:"players"` } if err := json.Unmarshal([]byte(jsonStr), &response); err != nil { return 0, err } return response.Players.Online, nil } // 数据存储 func getFilename(addr string, port int) string { safeAddr := strings.ReplaceAll(addr, ".", "_") return fmt.Sprintf("server_%s_%d.csv", safeAddr, port) } func saveData(addr string, port, online int) error { filename := getFilename(addr, port) file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer file.Close() writer := csv.NewWriter(file) defer writer.Flush() if stat, _ := file.Stat(); stat.Size() == 0 { writer.Write([]string{"timestamp", "online"}) } timestamp := time.Now().UTC().Format(time.RFC3339) return writer.Write([]string{timestamp, strconv.Itoa(online)}) } func cleanupData() { for range time.Tick(24 * time.Hour) { cutoff := time.Now().Add(-dataExpiration) files, _ := filepath.Glob("server_*.csv") for _, f := range files { processFile(f, cutoff) } } } func processFile(filename string, cutoff time.Time) { file, err := os.Open(filename) if err != nil { return } defer file.Close() reader := csv.NewReader(file) records, _ := reader.ReadAll() var filtered [][]string for i, r := range records { if i == 0 { filtered = append(filtered, r) continue } if ts, err := time.Parse(time.RFC3339, r[0]); err == nil && ts.After(cutoff) { filtered = append(filtered, r) } } file.Close() os.WriteFile(filename, []byte{}, 0644) file, _ = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) writer := csv.NewWriter(file) writer.WriteAll(filtered) writer.Flush() file.Close() } // Web界面 func webHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./index.html") } func dataHandler(w http.ResponseWriter, r *http.Request) { type serverData struct { Addr string `json:"addr"` Port int `json:"port"` Current int `json:"current"` Updated time.Time `json:"updated"` History []HistoryEntry `json:"history"` } var response []serverData for _, s := range servers { history := readHistory(s.Addr, s.Port) current := 0 updated := time.Time{} if len(history) > 0 { current = history[len(history)-1].Online updated = history[len(history)-1].Timestamp } response = append(response, serverData{ Addr: s.Addr, Port: s.Port, Current: current, Updated: updated, History: history, }) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } func readHistory(addr string, port int) []HistoryEntry { filename := getFilename(addr, port) file, err := os.Open(filename) if err != nil { return nil } defer file.Close() reader := csv.NewReader(file) records, err := reader.ReadAll() if err != nil { return nil } var history []HistoryEntry for i, r := range records { if i == 0 { continue } ts, _ := time.Parse(time.RFC3339, r[0]) online, _ := strconv.Atoi(r[1]) history = append(history, HistoryEntry{ts, online}) } return history } func main() { // 启动数据清理 go cleanupData() // 启动监控任务 go func() { ticker := time.NewTicker(checkInterval) for range ticker.C { for _, s := range servers { go func(addr string, port int) { if online, err := queryMinecraftServer(addr, port); err == nil { saveData(addr, port, online) } }(s.Addr, s.Port) } } }() // 启动Web服务器 http.HandleFunc("/", webHandler) http.HandleFunc("/data", dataHandler) log.Printf("Server running on :%s", webPort) log.Fatal(http.ListenAndServe(":"+webPort, nil)) }