Skip to content

Commit

Permalink
feat: use bbolt replace sqlite3 so we can cross compile anywhere
Browse files Browse the repository at this point in the history
feat: use flag to start replace config.yaml
  • Loading branch information
zaigie committed Jan 26, 2024
1 parent 9173bcd commit 9af1e07
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 134 deletions.
18 changes: 10 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''
Expand Down
59 changes: 24 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand All @@ -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 即可打开可视化界面**

Expand All @@ -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
```
### 玩家
#### 在线玩家列表
Expand Down
178 changes: 90 additions & 88 deletions cmd/pst-server/main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package main

import (
"database/sql"
"embed"
"encoding/json"
"flag"
"fmt"
"log"
Expand All @@ -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()
Expand All @@ -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)

Expand All @@ -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"] == "<null/err>" {
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"] == "<null/err>" {
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 == "<null/err>" || strings.Contains(player.SteamID, "000000") {
player.SteamID = playerData["steamid"]
}
if player.PlayerUID == "<null/err>" || 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 == "<null/err>" || strings.Contains(dbSteamID, "000000") {
updateSteamID = player["steamid"]
serializedPlayer, err := json.Marshal(player)
if err != nil {
return err
}
if dbPlayerUID == "<null/err>" || 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()

Expand Down Expand Up @@ -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)
})
}

// 标记当前在线的玩家
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading

0 comments on commit 9af1e07

Please sign in to comment.