diff --git a/Makefile b/Makefile index 7775aa1..0da3c87 100644 --- a/Makefile +++ b/Makefile @@ -18,15 +18,17 @@ build: # 为所有平台构建 build-all: rm -rf dist/ && mkdir -p dist/ - GOOS=windows GOARCH=386 go build -o ./dist/pst-cli_${GIT_TAG}_windows_x86.exe ./cmd/pst-cli/main.go - GOOS=linux GOARCH=amd64 go build -o ./dist/pst-cli_${GIT_TAG}_linux_amd64 ./cmd/pst-cli/main.go - GOOS=linux GOARCH=arm64 go build -o ./dist/pst-cli_${GIT_TAG}_linux_arm64 ./cmd/pst-cli/main.go - GOOS=darwin GOARCH=amd64 go build -o ./dist/pst-cli_${GIT_TAG}_macos_amd64 ./cmd/pst-cli/main.go - GOOS=darwin GOARCH=arm64 go build -o ./dist/pst-cli_${GIT_TAG}_macos_arm64 ./cmd/pst-cli/main.go + GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o ./dist/pst-cli_${GIT_TAG}_windows_x86.exe ./cmd/pst-cli/main.go + GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ./dist/pst-cli_${GIT_TAG}_linux_amd64 ./cmd/pst-cli/main.go + GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ./dist/pst-cli_${GIT_TAG}_linux_arm64 ./cmd/pst-cli/main.go + GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ./dist/pst-cli_${GIT_TAG}_macos_amd64 ./cmd/pst-cli/main.go + GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ./dist/pst-cli_${GIT_TAG}_macos_arm64 ./cmd/pst-cli/main.go # pst-server - CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS="-static" go build -a -o ./dist/pst-server_${GIT_TAG}_linux_amd64 ./cmd/pst-server/main.go - CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" go build -a -o ./dist/pst-server_${GIT_TAG}_linux_arm64 ./cmd/pst-server/main.go - + GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o ./dist/pst-server_${GIT_TAG}_windows_x86.exe ./cmd/pst-server/main.go + GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ./dist/pst-server_${GIT_TAG}_linux_amd64 ./cmd/pst-server/main.go + GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ./dist/pst-server_${GIT_TAG}_linux_arm64 ./cmd/pst-server/main.go + GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ./dist/pst-server_${GIT_TAG}_macos_amd64 ./cmd/pst-server/main.go + GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ./dist/pst-server_${GIT_TAG}_macos_arm64 ./cmd/pst-server/main.go # show help help: @echo '' diff --git a/README.md b/README.md index fc81407..d941e35 100644 --- a/README.md +++ b/README.md @@ -43,29 +43,20 @@ RCONEnabled=true,RCONPort=25575 mv pst-server_v0.2.0_linux_amd64 pst-server ``` -2. **首次运行**,生成 `config.yaml` 和 `players.db` 文件 +2. 运行 ```bash -./pst-server +# 和服务器部署在同一机器上 +./pst-server --port 8080 -p {你的 AdminPassword} +# 和服务器不在同一机器上 +./pst-server --port 8080 -a {服务器IP:RCON端口} -p {你的 AdminPassword} ``` -**然后 Ctrl + C 关闭程序** - -3. 修改 `config.yaml` 文件配置 - -> host 为服务器的 IP:RCON 端口 - -```yaml -host: 127.0.0.1:25575 -password: { 这里写你的AdminPassword } -timeout: 10 -``` - -4. 后台运行 +3. 后台运行 ```bash # 后台运行并将日志保存在 server.log -nohup ./pst-server --port 8080 > server.log 2>&1 & +nohup ./pst-server --port 8080 -a 127.0.0.1:25575 -p {你的 AdminPassword} > server.log 2>&1 & # 查看日志 tail -f server.log ``` @@ -87,29 +78,19 @@ kill $(ps aux | grep 'pst-server' | awk '{print $2}') | head -n 1 2. 按下 `Win + R`,输入 `powershell` 打开 Powershell,通过 `cd` 命令到下载的可执行文件目录 -3. **首次运行**,生成 `config.yaml` 和 `players.db` 文件 +3. 持续运行,不要关闭窗口 -```powershell -.\pst-server.exe -``` + - 和服务器部署在同一机器上 -**然后 Ctrl +C 关闭程序** + ```powershell + .\pst-server.exe --port 8080 -p {你的 AdminPassword} + ``` -4. 记事本或编辑工具打开 `config.yaml` 文件,进行配置 + - 和服务器不在同一机器上 -> host 为服务器的 IP:RCON 端口 - -```yaml -host: 127.0.0.1:25575 -password: { 这里写你的AdminPassword } -timeout: 10 -``` - -5. 持续运行 - -```powershell -.\pst-server.exe --port 8080 -``` + ```powershell + .\pst-server.exe --port 8080 -a {服务器IP:RCON端口} -p {你的 AdminPassword} + ``` **浏览器访问 http://<服务器 IP>:8080 即可打开可视化界面** @@ -127,6 +108,14 @@ timeout: 10 mv pst-cli_{version}_{platform}_{arch} pst-cli ``` +首次运行会自动生成 `config.yaml` ,修改文件 + +```yaml +host: 127.0.0.1:25575 +password: "你的 AdminPassword" +timeout: 10 +``` + ### 玩家 #### 在线玩家列表 diff --git a/cmd/pst-server/main.go b/cmd/pst-server/main.go index c6a1b1b..71c5b64 100644 --- a/cmd/pst-server/main.go +++ b/cmd/pst-server/main.go @@ -1,8 +1,8 @@ package main import ( - "database/sql" "embed" + "encoding/json" "flag" "fmt" "log" @@ -11,43 +11,47 @@ import ( "time" "github.com/gin-gonic/gin" - _ "github.com/mattn/go-sqlite3" "github.com/spf13/viper" "github.com/zaigie/palworld-server-tool/pkg/tool" + "go.etcd.io/bbolt" ) -var cfgFile string var port string -var db *sql.DB //go:embed web/* var embeddedFiles embed.FS -func initDB() *sql.DB { - var err error - db, err := sql.Open("sqlite3", "./players.db") +var db *bbolt.DB + +type Player struct { + Name string `json:"name"` + SteamID string `json:"steamid"` + PlayerUID string `json:"playeruid"` + LastOnline time.Time `json:"last_online"` +} + +func initDB() *bbolt.DB { + db, err := bbolt.Open("players.db", 0600, &bbolt.Options{Timeout: 1 * time.Second}) if err != nil { log.Fatal(err) } - createTableSQL := `CREATE TABLE IF NOT EXISTS players ( - "name" TEXT NOT NULL PRIMARY KEY, - "steamid" TEXT, - "playeruid" TEXT, - "last_online" DATETIME DEFAULT CURRENT_TIMESTAMP - );` + // 创建bucket + err = db.Update(func(tx *bbolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte("players")) + return err + }) - _, err = db.Exec(createTableSQL) if err != nil { log.Fatal(err) } - if err = db.Ping(); err != nil { - log.Fatalf("Error connecting to the database: %v", err) - } return db } +var rconAddr, rconPassword string +var rconTimeout int + func main() { db = initDB() @@ -56,11 +60,13 @@ func main() { } defer db.Close() - flag.StringVar(&cfgFile, "config", "", "config file") flag.StringVar(&port, "port", "8080", "port") + flag.StringVar(&rconAddr, "a", "127.0.0.1:25575", "rcon address") + flag.StringVar(&rconPassword, "p", "", "rcon password") + flag.IntVar(&rconTimeout, "t", 10, "rcon timeout") flag.Parse() - initConfig(cfgFile) + initConfig() go scheduleTask(db) @@ -77,68 +83,62 @@ func main() { setupApiRoutes(router) // 启动 HTTP 服务器 - router.Run(fmt.Sprintf(":%s", port)) // 监听在 8080 端口 + router.Run(fmt.Sprintf(":%s", port)) // 监听端口 } -func initConfig(cfg string) { - if cfg != "" { - viper.SetConfigFile(cfg) - viper.SetConfigType("yaml") - } else { - viper.AddConfigPath(".") - viper.SetConfigName("config") - viper.SetConfigType("yaml") - } - - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - viper.Set("host", "127.0.0.1:25575") - viper.Set("password", "") - viper.Set("timeout", 10) - viper.WriteConfigAs("config.yaml") - } else { - fmt.Println("config file was found but another error was produced") - } - } +func initConfig() { + viper.Set("host", rconAddr) + viper.Set("password", rconPassword) + viper.Set("timeout", rconTimeout) } -func updatePlayerData(db *sql.DB, players []map[string]string) { - for _, player := range players { - // 跳过特殊情况 - if player["name"] == "" { - continue - } +func updatePlayerData(db *bbolt.DB, playersData []map[string]string) { + err := db.Update(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("players")) + for _, playerData := range playersData { + if playerData["name"] == "" { + continue + } + existingPlayerData := b.Get([]byte(playerData["name"])) + var player Player + if existingPlayerData != nil { + if err := json.Unmarshal(existingPlayerData, &player); err != nil { + return err + } - var dbSteamID, dbPlayerUID string - err := db.QueryRow("SELECT steamid, playeruid FROM players WHERE name = ?", player["name"]).Scan(&dbSteamID, &dbPlayerUID) - if err != nil && err != sql.ErrNoRows { - log.Println("Error checking player:", err) - continue - } + if player.SteamID == "" || strings.Contains(player.SteamID, "000000") { + player.SteamID = playerData["steamid"] + } + if player.PlayerUID == "" || strings.Contains(player.PlayerUID, "000000") { + player.PlayerUID = playerData["playeruid"] + } + player.LastOnline = time.Now() + } else { + player = Player{ + Name: playerData["name"], + SteamID: playerData["steamid"], + PlayerUID: playerData["playeruid"], + LastOnline: time.Now(), + } + } - if err == sql.ErrNoRows { - // 新玩家,插入数据 - _, err = db.Exec("INSERT INTO players (name, steamid, playeruid) VALUES (?, ?, ?)", player["name"], player["steamid"], player["playeruid"]) - } else { - // 现有玩家,更新数据 - updateSteamID := dbSteamID - updatePlayerUID := dbPlayerUID - if dbSteamID == "" || strings.Contains(dbSteamID, "000000") { - updateSteamID = player["steamid"] + serializedPlayer, err := json.Marshal(player) + if err != nil { + return err } - if dbPlayerUID == "" || strings.Contains(dbPlayerUID, "000000") { - updatePlayerUID = player["playeruid"] + if err := b.Put([]byte(player.Name), serializedPlayer); err != nil { + return err } - _, err = db.Exec("UPDATE players SET steamid = ?, playeruid = ?, last_online = CURRENT_TIMESTAMP WHERE name = ?", updateSteamID, updatePlayerUID, player["name"]) } + return nil + }) - if err != nil { - log.Println("Error updating player:", err) - } + if err != nil { + log.Println("Error updating player:", err) } } -func scheduleTask(db *sql.DB) { +func scheduleTask(db *bbolt.DB) { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() @@ -184,38 +184,40 @@ func listPlayer(c *gin.Context) { updatePlayerData(db, getCurrentPlayers) currentPlayers = getCurrentPlayers } - rows, err := db.Query("SELECT name,steamid,playeruid,strftime('%Y-%m-%d %H:%M:%S', last_online, 'localtime') AS last_online FROM players ORDER BY last_online DESC") + var players []Player + err := db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("players")) + return b.ForEach(func(k, v []byte) error { + var player Player + if err := json.Unmarshal(v, &player); err != nil { + return err + } + players = append(players, player) + return nil + }) + }) if err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) return } - defer rows.Close() // 构建包含所有玩家信息的列表 allPlayers := make([]map[string]interface{}, 0) - currentLocalTime := time.Now().Local() - - for rows.Next() { - var name, steamID, playerUID, lastOnline string - if err := rows.Scan(&name, &steamID, &playerUID, &lastOnline); err != nil { - log.Println("Error reading player name:", err) - continue - } - lastOnlineTime, _ := time.ParseInLocation("2006-01-02 15:04:05", lastOnline, time.Local) - diff := currentLocalTime.Sub(lastOnlineTime) + currentLocalTime := time.Now() + for _, player := range players { + diff := currentLocalTime.Sub(player.LastOnline) online := false if diff < 5*time.Minute { online = true } - - playerData := map[string]interface{}{ - "name": name, - "steamid": steamID, - "playeruid": playerUID, - "last_online": lastOnline, + lastOnlineTimeStr := player.LastOnline.Format("2006-01-02 15:04:05") + allPlayers = append(allPlayers, map[string]interface{}{ + "name": player.Name, + "steamid": player.SteamID, + "playeruid": player.PlayerUID, + "last_online": lastOnlineTimeStr, "online": online, - } - allPlayers = append(allPlayers, playerData) + }) } // 标记当前在线的玩家 diff --git a/go.mod b/go.mod index e038ad4..18d9d68 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/gorcon/rcon v1.3.4 github.com/jedib0t/go-pretty/v6 v6.5.3 - github.com/mattn/go-sqlite3 v1.14.19 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 + go.etcd.io/bbolt v1.3.8 ) require ( diff --git a/go.sum b/go.sum index 05c0ca1..822ba71 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,6 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -122,6 +120,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=