go-mc-server-listener/example/main.go
2025-04-05 10:23:28 +08:00

302 lines
6.3 KiB
Go

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))
}