From f9dc80e5638b2e53ff1f8638e333edda36e6f2f5 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 22 Jul 2026 13:34:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20pg=20=E6=9B=BF=E6=8D=A2=20?= =?UTF-8?q?sql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 6 +++- Dockerfile.backend | 7 +---- backend/cmd/server/main.go | 11 ++------ backend/go.mod | 10 +++++-- backend/go.sum | 13 +++++++++ backend/internal/config/config.go | 10 +++---- docker-compose.yml | 21 +++++++++++++- start.sh | 47 +++++++++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index 3b88a8f..ddf6d1b 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ PORT=8080 JWT_SECRET=your_very_secure_jwt_secret_here_change_me_in_production -DB_PATH=data/app.db +POSTGRES_USER=affiliate +POSTGRES_PASSWORD=affiliate_dev_password +POSTGRES_DB=affiliate_dash +DATABASE_URL=postgres://affiliate:affiliate_dev_password@127.0.0.1:5432/affiliate_dash?sslmode=disable GIN_MODE=release OPEN_API_KEY=sk_source_dev_key_change_me OPEN_API_SECRET=sk_source_dev_secret_change_me @@ -9,6 +12,7 @@ OPEN_API_DEBUG=false LOG_FILE=logs/app.log # Docker 构建基础镜像;如镜像源不可用,可改为官方镜像或你的私有镜像源 +POSTGRES_IMAGE=docker.m.daocloud.io/library/postgres:16-alpine GO_BUILDER_IMAGE=docker.m.daocloud.io/library/golang:1.26-alpine BACKEND_RUNTIME_IMAGE=docker.m.daocloud.io/library/alpine:3.22 NODE_BUILDER_IMAGE=docker.m.daocloud.io/library/node:22-alpine diff --git a/Dockerfile.backend b/Dockerfile.backend index 08714dd..1f0bfd5 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -8,16 +8,11 @@ FROM ${GO_BUILDER_IMAGE} AS builder ARG GOPROXY ARG GOSUMDB -ARG ALPINE_MIRROR ENV GOPROXY=${GOPROXY} ENV GOSUMDB=${GOSUMDB} WORKDIR /app -# SQLite 驱动依赖 CGO,构建阶段需要 C 编译工具 / SQLite driver needs CGO and a C toolchain -RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g; s|http://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories \ - && apk --no-cache add build-base - # 复制 Go 依赖文件 / Copy Go dependency files COPY backend/go.mod backend/go.sum ./ RUN go mod download @@ -26,7 +21,7 @@ COPY backend ./backend WORKDIR /app/backend # 构建 Go 二进制文件 / Build Go binary -RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-linkmode external -extldflags '-static'" -o /server ./cmd/server +RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server # 最终运行镜像 / Runtime image FROM ${BACKEND_RUNTIME_IMAGE} diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 192e6aa..7b0a8ff 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -2,8 +2,6 @@ package main import ( "log" - "os" - "path/filepath" "affiliate_dash/internal/config" "affiliate_dash/internal/handler" @@ -15,7 +13,7 @@ import ( "affiliate_dash/internal/service" "github.com/gin-gonic/gin" - "gorm.io/driver/sqlite" + "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -33,11 +31,7 @@ func main() { gin.SetMode(cfg.Mode) - if err := os.MkdirAll(filepath.Dir(cfg.DBPath), 0o755); err != nil { - log.Fatalf("create data dir: %v", err) - } - - db, err := gorm.Open(sqlite.Open(cfg.DBPath), &gorm.Config{ + db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{ Logger: applog.NewGormLogger(cfg.Mode), }) if err != nil { @@ -79,6 +73,7 @@ func main() { r := router.Setup(h) addr := ":" + cfg.Port log.Printf("游戏皮肤分销系统 API 启动: http://localhost%s", addr) + log.Printf("数据库: PostgreSQL") log.Printf("默认管理员: admin / admin123") log.Printf("开放接口鉴权: X-Api-Key + X-Timestamp + X-Nonce + X-Sign (HMAC-SHA256)") if cfg.OpenAPIDebug { diff --git a/backend/go.mod b/backend/go.mod index 2bcd284..9ca7a15 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -6,9 +6,10 @@ require ( github.com/gin-contrib/cors v1.7.7 github.com/gin-gonic/gin v1.12.0 github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 golang.org/x/crypto v0.54.0 - gorm.io/driver/sqlite v1.6.0 + gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.2 ) @@ -24,14 +25,16 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -42,6 +45,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect golang.org/x/arch v0.23.0 // indirect golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.40.0 // indirect google.golang.org/protobuf v1.36.10 // indirect diff --git a/backend/go.sum b/backend/go.sum index 31dab42..e0e64f8 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -36,6 +36,14 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -70,6 +78,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -90,6 +99,8 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= @@ -101,6 +112,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 706b74d..bc90928 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -10,10 +10,10 @@ import ( ) type Config struct { - Port string - JWTSecret string - DBPath string - Mode string // debug / release + Port string + JWTSecret string + DatabaseURL string + Mode string // debug / release // OpenAPIKey 皮肤源头开放接口标识(Header: X-Api-Key,可公开给对接方) OpenAPIKey string // OpenAPISecret 签名密钥(仅用于 HMAC,不走 Header) @@ -35,7 +35,7 @@ func Load() *Config { return &Config{ Port: getEnv("PORT", "8080"), JWTSecret: getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me"), - DBPath: getEnv("DB_PATH", "data/app.db"), + DatabaseURL: getEnv("DATABASE_URL", "postgres://affiliate:affiliate_dev_password@127.0.0.1:5432/affiliate_dash?sslmode=disable"), Mode: mode, OpenAPIKey: getEnv("OPEN_API_KEY", "sk_source_dev_key_change_me"), OpenAPISecret: getEnv("OPEN_API_SECRET", "sk_source_dev_secret_change_me"), diff --git a/docker-compose.yml b/docker-compose.yml index c07a68a..4993feb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,19 @@ services: + postgres: + image: ${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine} + environment: + - POSTGRES_USER=${POSTGRES_USER:-affiliate} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-affiliate_dev_password} + - POSTGRES_DB=${POSTGRES_DB:-affiliate_dash} + volumes: + - postgres-data:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + backend: build: context: . @@ -15,7 +30,7 @@ services: environment: - PORT=8080 - JWT_SECRET=${JWT_SECRET} - - DB_PATH=/data/app.db + - DATABASE_URL=postgres://${POSTGRES_USER:-affiliate}:${POSTGRES_PASSWORD:-affiliate_dev_password}@postgres:5432/${POSTGRES_DB:-affiliate_dash}?sslmode=disable - GIN_MODE=${GIN_MODE:-release} - OPEN_API_KEY=${OPEN_API_KEY} - OPEN_API_SECRET=${OPEN_API_SECRET} @@ -30,6 +45,9 @@ services: interval: 30s timeout: 10s retries: 3 + depends_on: + postgres: + condition: service_healthy caddy: build: @@ -52,6 +70,7 @@ services: restart: unless-stopped volumes: + postgres-data: backend-data: caddy-data: caddy-config: diff --git a/start.sh b/start.sh index dc4cfe0..362ff58 100755 --- a/start.sh +++ b/start.sh @@ -20,6 +20,14 @@ fi BACKEND_PORT="${PORT:-8080}" FRONTEND_PORT="${FRONTEND_PORT:-5173}" +POSTGRES_USER="${POSTGRES_USER:-affiliate}" +POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-affiliate_dev_password}" +POSTGRES_DB="${POSTGRES_DB:-affiliate_dash}" +POSTGRES_PORT="${POSTGRES_PORT:-5432}" +POSTGRES_IMAGE="${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}" +DEV_DB_CONTAINER="${DEV_DB_CONTAINER:-affiliate_dash_postgres_dev}" +DATABASE_URL="${DATABASE_URL:-postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable}" +export DATABASE_URL # 结束指定端口上的进程(避免残留占用) free_port() { @@ -39,6 +47,44 @@ free_port() { fi } +ensure_postgres() { + if (echo >"/dev/tcp/127.0.0.1/${POSTGRES_PORT}") >/dev/null 2>&1; then + log "检测到本机 PostgreSQL 端口已可访问: ${POSTGRES_PORT}" + return 0 + fi + if ! command -v docker >/dev/null 2>&1; then + log "未找到 Docker,请确认本机 PostgreSQL 已可通过 DATABASE_URL 访问" + return 0 + fi + if docker ps --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER"; then + log "PostgreSQL 开发容器已运行: ${DEV_DB_CONTAINER}" + elif docker ps -a --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER"; then + log "启动 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}" + docker start "$DEV_DB_CONTAINER" >/dev/null + else + log "创建 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}" + docker run -d \ + --name "$DEV_DB_CONTAINER" \ + -p "${POSTGRES_PORT}:5432" \ + -e POSTGRES_USER="$POSTGRES_USER" \ + -e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ + -e POSTGRES_DB="$POSTGRES_DB" \ + -v "${DEV_DB_CONTAINER}-data:/var/lib/postgresql/data" \ + "$POSTGRES_IMAGE" >/dev/null + fi + + for i in $(seq 1 40); do + if docker exec "$DEV_DB_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then + log "PostgreSQL 已就绪" + return 0 + fi + sleep 0.5 + done + echo "错误: PostgreSQL 启动超时" + docker logs "$DEV_DB_CONTAINER" 2>/dev/null || true + exit 1 +} + cleanup() { trap - INT TERM EXIT echo "" @@ -77,6 +123,7 @@ fi free_port "$BACKEND_PORT" free_port "$FRONTEND_PORT" +ensure_postgres log "启动后端 http://localhost:${BACKEND_PORT}" (