Golang Gorm空指针解引用Panic问题排查及代码优化求助
排查Go后端Panic:invalid memory address or nil pointer dereference
首次用Golang搭建后端服务,遇到Panic错误:panic serving runtime error: invalid memory address or nil pointer dereference,以下是相关代码、错误详情及排查修复方案:
main.go(package main)
负责加载环境变量、初始化Server与Database:
func main() { err := godotenv.Load(".env") if err != nil { log.Fatal(err) } // 数据库配置变量 host := os.Getenv("POSTGRES_HOST") user := os.Getenv("POSTGRES_USER") port := os.Getenv("POSTGRES_PORT") password := os.Getenv("POSTGRES_PASSWORD") dbname := os.Getenv("POSTGRES_DB") // 服务器配置变量 PORT := os.Getenv("PORT") JWT_SECRET := os.Getenv("JWT_SECRET") DB_URL := os.Getenv("DATA_BASE_URL") server, err := server.NewServer(context.Background(), &server.Config{ JWTSecret: JWT_SECRET, Port: PORT, DatabaseUrl: DB_URL, }) if err != nil { log.Fatal(err) } db := config.Database{ Host: host, Port: port, User: user, Password: password, Dbname: dbname, } db.Open() server.Start(BindRoutes) } func BindRoutes(s server.Server, r *mux.Router, db *config.Database) { r.HandleFunc("/blog/newblog", handler.CreateNewBlog(s, db)).Methods(http.MethodPost) }
Server包
定义Config、Server接口、Broker结构体及相关方法:
package server import ( "context" "errors" "log" "net/http" "github.com/felipeolayao1998/backdiariodeuninge/config" "github.com/gorilla/mux" ) type Config struct { Port string JWTSecret string DatabaseUrl string } type Server interface { Config() *Config } type Broker struct { config *Config router *mux.Router } func (b *Broker) Config() *Config { return b.config } func NewServer(ctx context.Context, config *Config) (*Broker, error) { if config.Port == "" { return nil, errors.New("port is required") } if config.JWTSecret == "" { return nil, errors.New("jwt secret is required") } if config.DatabaseUrl == "" { return nil, errors.New("database url is required") } broker := &Broker{ config: config, router: mux.NewRouter(), } return broker, nil } func (b *Broker) Start(binder func(s Server, r *mux.Router, db *config.Database)) { b.router = mux.NewRouter() binder(b, b.router, &config.Database{}) log.Println("starting server on port", b.config.Port) if err := http.ListenAndServe(b.config.Port, b.router); err != nil { log.Println("error starting server:", err) } else { log.Fatalf("server stopped") } }
Models包
定义Blog结构体:
package Models type Blog struct { Id string `json:"id" gorm:"type:string;default:string;primary_key;unique"` Name string `json:"name" gorm:"type:varchar(255);not null"` Content string `json:"content" gorm:"type:varchar(2000);not null"` Likes int `json:"likes" gorm:"type:integer" ` }
Handler
调用db.Db.Create时触发Panic:
func CreateNewBlog(s server.Server, db *config.Database) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json") w.WriteHeader(http.StatusOK) db.Db.Create(&models.Blog{Id: "1", Name: "Felipe", Content: "fdasfasfa", Likes: 1}) //问题触发点 } }
错误日志
2023/04/07 21:25:46 starting server on port :8000 0xc00043a028 &{a2081199-a75e-4b3e-9551-3442a0fe0d21 Felipe contenido ejempo 1} 2023/04/07 21:25:55 http: panic serving [::1]:59553: runtime error: invalid memory address or nil pointer dereference goroutine 18 [running]: net/http.(*conn).serve.func1() C:/Program Files/Go/src/net/http/server.go:1850 +0xbf panic({0x1620000, 0x1c52680}) C:/Program Files/Go/src/runtime/panic.go:890 +0x262 gorm.io/gorm.(*DB).Create(0x17e44c0?, {0x15d11c0?, 0xc00041c180?}) C:/Users/Bymet/go/pkg/mod/gorm.io/gorm@v1.24.7-0.20230306060331-85eaf9eeda11/finisher_api.go:18 +0x22 github.com/felipeolayao1998/backdiariodeuninge/handler.CreateNewBlog.func1({0x17ea340, 0xc000424000}, 0xc00042c100) D:/PodCast/PaginaWeb/backend/handler/handlerBlog.go:69 +0x3e5
错误根源
Server.Start传递了未初始化的空Database实例
在server.Broker.Start方法中,调用binder时传入的是&config.Database{}——这是一个全新的空结构体,完全没用到main函数里已经调用db.Open()初始化好的数据库实例。Handler拿到的Database实例未初始化
main函数里创建并初始化的db根本没传到Handler里,Handler用的是空实例,它的Db字段(GORM的DB对象)是nil,调用db.Db.Create时必然触发nil指针解引用panic。
修复方案
1. 修改Server.Start方法,接收已初始化的Database实例
修改server包的Start方法,让它能接收外部传入的db实例,而不是自己创建空的:
// server包的Start方法修改后 func (b *Broker) Start(binder func(s Server, r *mux.Router, db *config.Database), db *config.Database) { b.router = mux.NewRouter() binder(b, b.router, db) // 传入main里初始化好的db log.Println("starting server on port", b.config.Port) if err := http.ListenAndServe(b.config.Port, b.router); err != nil { log.Println("error starting server:", err) } else { log.Fatalf("server stopped") } }
2. 在main函数中传递初始化好的db到Server.Start
替换main函数最后一行的调用:
// main.go中原来的server.Start(BindRoutes)改成 server.Start(BindRoutes, &db)
3. 确保config.Database的Open方法正确赋值Db字段
检查config包的Database实现,必须在Open()成功后把GORM的DB实例赋值给结构体的Db字段,示例:
package config import ( "gorm.io/driver/postgres" "gorm.io/gorm" ) type Database struct { Host string Port string User string Password string Dbname string Db *gorm.DB // 必须被正确初始化的字段 } func (d *Database) Open() error { dsn := "host=" + d.Host + " user=" + d.User + " password=" + d.Password + " dbname=" + d.Dbname + " port=" + d.Port + " sslmode=disable" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return err } d.Db = db // 关键:把初始化后的DB实例赋值给结构体 return nil }
4. 增强Handler的错误检查(可选)
在Handler里先检查Db是否为nil,避免panic:
func CreateNewBlog(s server.Server, db *config.Database) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json") // 先检查数据库是否初始化 if db.Db == nil { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"error": "数据库未初始化"}`)) return } // 创建博客并检查错误 err := db.Db.Create(&models.Blog{Id: "1", Name: "Felipe", Content: "fdasfasfa", Likes: 1}).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"error": "创建博客失败"}`)) return } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"message": "博客创建成功"}`)) } }
内容的提问来源于stack exchange,提问作者Felipe Olaya Ospina
相关产品推荐
相关产品推荐

