You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang CRUD应用Postman请求报错Socket hang up及数据库连接拒绝问题

Golang CRUD应用数据库连接失败问题解决

问题现象

按照教程构建Golang CRUD应用后,通过PgAdmin成功连接PostgreSQL并创建了表,用Docker Compose启动所有服务后,Postman请求返回Error: socket hang up,应用服务器日志报错:dial tcp 127.0.0.1:5432: connect: connection refused。

核心文件

docker-compose.yml

version: '3'

services:
  dicane-service:
    build:
      context: ./../dicane-service
      dockerfile: ./../dicane-service/dicane-service.dockerfile
    restart: always
    ports:
      - "8000:8000"
    deploy:
      mode: replicated
      replicas: 1
    environment:
      DSN: "host=postgres port=5432 user=postgres password=password dbname=postgres sslmode=disable timezone=UTC connect_timeout=5"
    depends_on:
      - postgres
      
  postgres:
    image: 'postgres:14.0'
    ports:
      - "5432:5432"
    restart: always
    deploy:
      mode: replicated
      replicas: 1
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: postgres
    volumes:
      - ./db-data/postgres/:/var/lib/postgresql/data/
  
  pg-admin:
    image: dpage/pgadmin4:7
    environment:
      - PGADMIN_DEFAULT_EMAIL=igorlacourt@gmail.com
      - PGADMIN_DEFAULT_PASSWORD=password
      - PGADMIN_LISTEN_PORT=5050
    ports:
      - "5050:5050"

main.go

package main

import (
    "database/sql"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gorilla/mux"
    _ "github.com/lib/pq"
)

type Sale struct {
    Sale_id                    string    `json:"sale_id"`
    // 省略其他字段...
    Images                     []string  `json:"images"`
}

func main() {
    // connect to database
    db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    //create router
    router := mux.NewRouter()
    router.HandleFunc("/sales", getSales(db)).Methods("GET")
    router.HandleFunc("/sales/{id}", getSale(db)).Methods("GET")
    router.HandleFunc("/sales", createSale(db)).Methods("POST")
    router.HandleFunc("/sales/{id}", updateSale(db)).Methods("PUT")
    router.HandleFunc("/sales/{id}", deleteSale(db)).Methods("DELETE")

    // start server
    log.Fatal(http.ListenAndServe(":8000", jsonContentTypeMiddleware(router)))

}

func jsonContentTypeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        next.ServeHTTP(w, r)
    })
}

func getSales(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        rows, err := db.Query("SELECT * FROM sales")
        if err != nil {
            log.Fatal(err)
        }
        defer rows.Close()

        sales := []Sale{}
        for rows.Next() {
            var sale Sale
            if err := rows.Scan(
                &sale.Sale_id,
                // 省略其他字段扫描...
                &sale.Images,
            ); err != nil {
                log.Fatal(err)
            }
            sales = append(sales, sale)
        }
        if err := rows.Err(); err != nil {
            log.Fatal(err)
        }

        json.NewEncoder(w).Encode(sales)
    }
}

func getSale(db *sql.DB) http.HandlerFunc {
    print("getSale")
    return func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        id := vars["sale_id"]

        var sale Sale
        err := db.QueryRow("SELECT * FROM sales WHERE sale_id = $1", id).Scan(
            &sale.Sale_id,
            // 省略其他字段扫描...
            &sale.Images,
        )
        if err != nil {
            log.Fatal(err)
        }

        json.NewEncoder(w).Encode(sale)
    }
}

func createSale(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var sale Sale
        json.NewDecoder(r.Body).Decode(&sale)

        err := db.QueryRow("INSERT INTO sales ("+
            // 省略其他字段...
            " images)"+
            " VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $21, $22, $23)"+
            " RETURNING sale_id",
            // 省略其他参数...
            ).Scan(&sale.Sale_id)
        if err != nil {
            log.Fatal(err)
        }
        json.NewEncoder(w).Encode(sale)
    }
}

func updateSale(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var sale Sale
        json.NewDecoder(r.Body).Decode(&sale)

        vars := mux.Vars(r)
        saleId := vars["sale_id"]

        _, err := db.Exec("UPDATE sales SET"+
            // 省略其他字段设置...
            )+"WHERE id = $24",
            // 省略其他参数...
            saleId,
        )
        if err != nil {
            log.Fatal(err)
        }

        json.NewEncoder(w).Encode(sale)
    }
}

func deleteSale(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        saleId := vars["sale_id"]

        var sale Sale
        err := db.QueryRow("SELECT * FROM sales WHERE sale_id = $1", saleId).Scan(
            &sale.Sale_id,
            // 省略其他字段扫描...
            &sale.Images,
        )
        if err != nil {
            w.WriteHeader(http.StatusNotFound)
            return
        } else {
            _, err := db.Exec("DELETE FROM sales WHERE sale_id = $1", saleId)
            if err != nil {
                //todo : fix error handling
                w.WriteHeader(http.StatusNotFound)
                return
            }

            json.NewEncoder(w).Encode("User deleted")
        }
    }
}

问题修复步骤

1. 修正环境变量不匹配问题

docker-compose.yml中配置的环境变量是DSN,但main.go里读取的是DATABASE_URL,导致应用无法获取正确的数据库连接字符串。

方案一:修改docker-compose.yml
将DSN改为DATABASE_URL:

environment:
  DATABASE_URL: "host=postgres port=5432 user=postgres password=password dbname=postgres sslmode=disable timezone=UTC connect_timeout=5"

方案二:修改main.go
将读取环境变量的代码改为读取DSN:

db, err := sql.Open("postgres", os.Getenv("DSN"))

2. 添加数据库连接校验

sql.Open仅创建连接池,不会立即验证数据库连接是否可用,需要在启动时主动校验:

// connect to database
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
    log.Fatal(err)
}
// 添加连接校验
if err := db.Ping(); err != nil {
    log.Fatalf("failed to connect to database: %v", err)
}
defer db.Close()

3. 解决PostgreSQL就绪延迟问题

depends_on仅保证postgres服务先启动,但不保证数据库完全就绪。可以在main.go中添加连接重试逻辑:

// 连接重试逻辑
var db *sql.DB
var err error
for i := 0; i < 5; i++ {
    db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err == nil {
        if err := db.Ping(); err == nil {
            break
        }
    }
    log.Printf("retrying database connection... attempt %d", i+1)
    time.Sleep(2 * time.Second)
}
if err != nil || db == nil {
    log.Fatalf("failed to connect to database after 5 attempts: %v", err)
}
defer db.Close()

4. 修正路由参数错误

路由定义为/sales/{id},但代码中尝试获取sale_id参数,导致无法正确获取请求ID:

// getSale函数中修正参数获取
vars := mux.Vars(r)
id := vars["id"] // 原代码是vars["sale_id"]

// updateSale和deleteSale函数同理,将vars["sale_id"]改为vars["id"]

5. 优化错误处理避免服务崩溃

使用log.Fatal(err)会直接终止进程,应该返回HTTP错误响应给客户端:

// 以getSales为例修改错误处理
rows, err := db.Query("SELECT * FROM sales")
if err != nil {
    log.Printf("failed to query sales: %v", err)
    http.Error(w, "internal server error", http.StatusInternalServerError)
    return
}

内容的提问来源于stack exchange,提问作者Igor Grecco Lacourt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:54:53