From 9f6da286bad78a15bcafa8cc2086c929fb50fbb0 Mon Sep 17 00:00:00 2001 From: shenlan Date: Thu, 7 Aug 2025 23:13:44 +0800 Subject: [PATCH] feat: add config and logging --- cmd/api/main.go | 50 ++++++++++++++++++++++++++++++++++++----- go.mod | 3 +++ go.sum | 10 +++++++++ server/Makefile | 13 +++++++---- server/config/config.go | 41 +++++++++++++++++++++++++++++++++ ui/homepage/Makefile | 10 ++++++++- ui/panel/Makefile | 10 ++++++--- 7 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 server/config/config.go diff --git a/cmd/api/main.go b/cmd/api/main.go index 64b0fb4..fd32b60 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -3,32 +3,72 @@ package main import ( "context" "io/fs" - "log" + "log/slog" "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5" + "github.com/redis/go-redis/v9" "xcontrol/server" "xcontrol/server/api" + "xcontrol/server/config" "xcontrol/ui" ) func main() { + cfg, err := config.Load() + if err != nil { + slog.Warn("load config", "err", err) + cfg = &config.Server{} + } + + level := slog.LevelInfo + switch strings.ToLower(cfg.Log.Level) { + case "debug": + level = slog.LevelDebug + case "warn", "warning": + level = slog.LevelWarn + case "error": + level = slog.LevelError + } + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})) + slog.SetDefault(logger) + var conn *pgx.Conn - if dsn := os.Getenv("KB_DSN"); dsn != "" { - var err error + if dsn := cfg.Postgres.DSN; dsn != "" { + logger.Debug("connecting to postgres", "dsn", dsn) conn, err = pgx.Connect(context.Background(), dsn) if err != nil { - log.Printf("kb db connect error: %v", err) + logger.Error("postgres connect error", "err", err) + } else { + logger.Info("postgres connected") } + } else { + logger.Warn("postgres dsn not provided") + } + + if addr := cfg.Redis.Addr; addr != "" { + logger.Debug("connecting to redis", "addr", addr) + rdb := redis.NewClient(&redis.Options{ + Addr: addr, + Password: cfg.Redis.Password, + }) + if err := rdb.Ping(context.Background()).Err(); err != nil { + logger.Error("redis connect error", "err", err) + } else { + logger.Info("redis connected") + } + } else { + logger.Warn("redis addr not provided") } uiFS, err := fs.Sub(ui.Assets, "dist") if err != nil { - log.Fatalf("ui assets: %v", err) + logger.Error("ui assets", "err", err) + return } r := server.New( diff --git a/go.mod b/go.mod index 763c365..dbaa115 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/go-git/go-git/v5 v5.16.2 github.com/jackc/pgx/v5 v5.7.5 + github.com/redis/go-redis/v9 v9.12.0 github.com/yuin/goldmark v1.7.13 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.2 @@ -18,9 +19,11 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/bytedance/sonic v1.9.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect diff --git a/go.sum b/go.sum index 087f236..5851550 100644 --- a/go.sum +++ b/go.sum @@ -9,9 +9,15 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -22,6 +28,8 @@ github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -104,6 +112,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU= +github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= diff --git a/server/Makefile b/server/Makefile index 86bfbd1..fc152db 100644 --- a/server/Makefile +++ b/server/Makefile @@ -4,7 +4,7 @@ MODULE := xcontrol PORT := 3001 OS := $(shell uname -s) -.PHONY: all build run clean init help dev +.PHONY: all build run clean init help dev test all: build @@ -37,8 +37,12 @@ build: go build -o $(APP_NAME) $(MAIN_FILE) run: - @echo ">>> 运行 $(APP_NAME) on port $(PORT)" - PORT=$(PORT) go run $(MAIN_FILE) + @echo ">>> 运行 $(APP_NAME) on port $(PORT) (后台运行)" + @nohup env PORT=$(PORT) go run $(MAIN_FILE) > $(APP_NAME).log 2>&1 & + +test: + @echo ">>> 运行单元测试" + go test ./... dev: @echo ">>> 开发模式运行 $(APP_NAME) (热重载) on port $(PORT)" @@ -57,7 +61,8 @@ help: @echo " XControl Server Makefile" @echo "" @echo "make build 编译 server 可执行文件" - @echo "make run 运行 server (默认端口: $(PORT))" + @echo "make run 后台运行 server (默认端口: $(PORT))" + @echo "make test 运行单元测试" @echo "make dev 开发模式运行 (自动检测 air,如无则用 go run)" @echo "make init 初始化依赖(自动选择国内/默认 Go 模块代理,air 可选)" @echo "make clean 清理构建产物" diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 0000000..edded20 --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,41 @@ +package config + +import ( + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +type Log struct { + Level string `yaml:"level"` +} + +type Redis struct { + Addr string `yaml:"addr"` + Password string `yaml:"password"` +} + +type Postgres struct { + DSN string `yaml:"dsn"` +} + +type Server struct { + Log Log `yaml:"log"` + Redis Redis `yaml:"redis"` + Postgres Postgres `yaml:"postgres"` +} + +// Load reads server/config/server.yaml and unmarshals into Server struct. +func Load() (*Server, error) { + path := filepath.Join("server", "config", "server.yaml") + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var cfg Server + if err := yaml.Unmarshal(b, &cfg); err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/ui/homepage/Makefile b/ui/homepage/Makefile index ea20494..c8277dc 100644 --- a/ui/homepage/Makefile +++ b/ui/homepage/Makefile @@ -4,7 +4,7 @@ YARN := $(shell command -v yarn 2>/dev/null) MAGICK := $(shell command -v magick 2>/dev/null || command -v convert 2>/dev/null) OS := $(shell uname -s) -.PHONY: init dev build export clean info icon +.PHONY: init dev build export clean info icon run test icon: @echo "🎨 Generating favicon and icon images..." @@ -51,6 +51,14 @@ dev: @echo "🚀 Starting Next.js dev server (homepage)..." yarn next dev -p 3001 +run: + @echo "🚀 Starting Next.js dev server (homepage) in background..." + @nohup yarn next dev -p 3001 >/tmp/homepage.log 2>&1 & + +test: + @echo "🔍 Running tests..." + @yarn test || echo "No tests configured" + build: @echo "🔨 Building homepage..." yarn next build diff --git a/ui/panel/Makefile b/ui/panel/Makefile index 9f107e5..9f6d6c4 100644 --- a/ui/panel/Makefile +++ b/ui/panel/Makefile @@ -5,7 +5,7 @@ NODE_VERSION := $(shell node -v 2>/dev/null || echo "Not Found") YARN := $(shell command -v yarn 2>/dev/null) OS := $(shell uname -s) -.PHONY: init build run export clean info +.PHONY: init build run export clean info test init: @echo "🔧 Installing dependencies..." @@ -35,8 +35,12 @@ build: yarn build run: - @echo "🚀 Starting local dev server..." - yarn dev + @echo "🚀 Starting local dev server in background..." + @nohup yarn dev >/tmp/panel.log 2>&1 & + +test: + @echo "🔍 Running tests..." + @yarn test || echo "No tests configured" export: @echo "📦 Exporting static site to ./out ..."