使用 pg 替换 sql

This commit is contained in:
yml2213
2026-07-22 13:34:55 +08:00
parent 0c60f76af0
commit f9dc80e563
8 changed files with 101 additions and 24 deletions
+5 -1
View File
@@ -1,6 +1,9 @@
PORT=8080 PORT=8080
JWT_SECRET=your_very_secure_jwt_secret_here_change_me_in_production 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 GIN_MODE=release
OPEN_API_KEY=sk_source_dev_key_change_me OPEN_API_KEY=sk_source_dev_key_change_me
OPEN_API_SECRET=sk_source_dev_secret_change_me OPEN_API_SECRET=sk_source_dev_secret_change_me
@@ -9,6 +12,7 @@ OPEN_API_DEBUG=false
LOG_FILE=logs/app.log LOG_FILE=logs/app.log
# Docker 构建基础镜像;如镜像源不可用,可改为官方镜像或你的私有镜像源 # Docker 构建基础镜像;如镜像源不可用,可改为官方镜像或你的私有镜像源
POSTGRES_IMAGE=docker.m.daocloud.io/library/postgres:16-alpine
GO_BUILDER_IMAGE=docker.m.daocloud.io/library/golang:1.26-alpine GO_BUILDER_IMAGE=docker.m.daocloud.io/library/golang:1.26-alpine
BACKEND_RUNTIME_IMAGE=docker.m.daocloud.io/library/alpine:3.22 BACKEND_RUNTIME_IMAGE=docker.m.daocloud.io/library/alpine:3.22
NODE_BUILDER_IMAGE=docker.m.daocloud.io/library/node:22-alpine NODE_BUILDER_IMAGE=docker.m.daocloud.io/library/node:22-alpine
+1 -6
View File
@@ -8,16 +8,11 @@ FROM ${GO_BUILDER_IMAGE} AS builder
ARG GOPROXY ARG GOPROXY
ARG GOSUMDB ARG GOSUMDB
ARG ALPINE_MIRROR
ENV GOPROXY=${GOPROXY} ENV GOPROXY=${GOPROXY}
ENV GOSUMDB=${GOSUMDB} ENV GOSUMDB=${GOSUMDB}
WORKDIR /app 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 # 复制 Go 依赖文件 / Copy Go dependency files
COPY backend/go.mod backend/go.sum ./ COPY backend/go.mod backend/go.sum ./
RUN go mod download RUN go mod download
@@ -26,7 +21,7 @@ COPY backend ./backend
WORKDIR /app/backend WORKDIR /app/backend
# 构建 Go 二进制文件 / Build Go binary # 构建 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 # 最终运行镜像 / Runtime image
FROM ${BACKEND_RUNTIME_IMAGE} FROM ${BACKEND_RUNTIME_IMAGE}
+3 -8
View File
@@ -2,8 +2,6 @@ package main
import ( import (
"log" "log"
"os"
"path/filepath"
"affiliate_dash/internal/config" "affiliate_dash/internal/config"
"affiliate_dash/internal/handler" "affiliate_dash/internal/handler"
@@ -15,7 +13,7 @@ import (
"affiliate_dash/internal/service" "affiliate_dash/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/driver/sqlite" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -33,11 +31,7 @@ func main() {
gin.SetMode(cfg.Mode) gin.SetMode(cfg.Mode)
if err := os.MkdirAll(filepath.Dir(cfg.DBPath), 0o755); err != nil { db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{
log.Fatalf("create data dir: %v", err)
}
db, err := gorm.Open(sqlite.Open(cfg.DBPath), &gorm.Config{
Logger: applog.NewGormLogger(cfg.Mode), Logger: applog.NewGormLogger(cfg.Mode),
}) })
if err != nil { if err != nil {
@@ -79,6 +73,7 @@ func main() {
r := router.Setup(h) r := router.Setup(h)
addr := ":" + cfg.Port addr := ":" + cfg.Port
log.Printf("游戏皮肤分销系统 API 启动: http://localhost%s", addr) log.Printf("游戏皮肤分销系统 API 启动: http://localhost%s", addr)
log.Printf("数据库: PostgreSQL")
log.Printf("默认管理员: admin / admin123") log.Printf("默认管理员: admin / admin123")
log.Printf("开放接口鉴权: X-Api-Key + X-Timestamp + X-Nonce + X-Sign (HMAC-SHA256)") log.Printf("开放接口鉴权: X-Api-Key + X-Timestamp + X-Nonce + X-Sign (HMAC-SHA256)")
if cfg.OpenAPIDebug { if cfg.OpenAPIDebug {
+7 -3
View File
@@ -6,9 +6,10 @@ require (
github.com/gin-contrib/cors v1.7.7 github.com/gin-contrib/cors v1.7.7
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.54.0 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 gorm.io/gorm v1.31.2
) )
@@ -24,14 +25,16 @@ require (
github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // 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 go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.23.0 // indirect golang.org/x/arch v0.23.0 // indirect
golang.org/x/net v0.56.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/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect golang.org/x/text v0.40.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.10 // indirect
+13
View File
@@ -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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.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.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/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 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
+5 -5
View File
@@ -10,10 +10,10 @@ import (
) )
type Config struct { type Config struct {
Port string Port string
JWTSecret string JWTSecret string
DBPath string DatabaseURL string
Mode string // debug / release Mode string // debug / release
// OpenAPIKey 皮肤源头开放接口标识(Header: X-Api-Key,可公开给对接方) // OpenAPIKey 皮肤源头开放接口标识(Header: X-Api-Key,可公开给对接方)
OpenAPIKey string OpenAPIKey string
// OpenAPISecret 签名密钥(仅用于 HMAC,不走 Header // OpenAPISecret 签名密钥(仅用于 HMAC,不走 Header
@@ -35,7 +35,7 @@ func Load() *Config {
return &Config{ return &Config{
Port: getEnv("PORT", "8080"), Port: getEnv("PORT", "8080"),
JWTSecret: getEnv("JWT_SECRET", "affiliate-dash-dev-secret-change-me"), 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, Mode: mode,
OpenAPIKey: getEnv("OPEN_API_KEY", "sk_source_dev_key_change_me"), OpenAPIKey: getEnv("OPEN_API_KEY", "sk_source_dev_key_change_me"),
OpenAPISecret: getEnv("OPEN_API_SECRET", "sk_source_dev_secret_change_me"), OpenAPISecret: getEnv("OPEN_API_SECRET", "sk_source_dev_secret_change_me"),
+20 -1
View File
@@ -1,4 +1,19 @@
services: 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: backend:
build: build:
context: . context: .
@@ -15,7 +30,7 @@ services:
environment: environment:
- PORT=8080 - PORT=8080
- JWT_SECRET=${JWT_SECRET} - 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} - GIN_MODE=${GIN_MODE:-release}
- OPEN_API_KEY=${OPEN_API_KEY} - OPEN_API_KEY=${OPEN_API_KEY}
- OPEN_API_SECRET=${OPEN_API_SECRET} - OPEN_API_SECRET=${OPEN_API_SECRET}
@@ -30,6 +45,9 @@ services:
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
depends_on:
postgres:
condition: service_healthy
caddy: caddy:
build: build:
@@ -52,6 +70,7 @@ services:
restart: unless-stopped restart: unless-stopped
volumes: volumes:
postgres-data:
backend-data: backend-data:
caddy-data: caddy-data:
caddy-config: caddy-config:
+47
View File
@@ -20,6 +20,14 @@ fi
BACKEND_PORT="${PORT:-8080}" BACKEND_PORT="${PORT:-8080}"
FRONTEND_PORT="${FRONTEND_PORT:-5173}" 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() { free_port() {
@@ -39,6 +47,44 @@ free_port() {
fi 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() { cleanup() {
trap - INT TERM EXIT trap - INT TERM EXIT
echo "" echo ""
@@ -77,6 +123,7 @@ fi
free_port "$BACKEND_PORT" free_port "$BACKEND_PORT"
free_port "$FRONTEND_PORT" free_port "$FRONTEND_PORT"
ensure_postgres
log "启动后端 http://localhost:${BACKEND_PORT}" log "启动后端 http://localhost:${BACKEND_PORT}"
( (