Golang创建用户时文件上传触发空指针引用运行时错误排查
问题:添加文件上传逻辑后触发空指针错误
移除文件上传逻辑(直接用字符串存储头像)时,创建用户功能完全正常,但添加文件上传代码后出现以下错误:
runtime error: invalid memory address or nil pointer dereference
相关代码
原控制器代码
func UserCreate(c *gin.Context) { // Parse the form data with a higher maxMemory and parseMultipartForm if err := c.Request.ParseMultipartForm(10 << 20); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to parse form data: " + err.Error(), }) return } // Get data from request body var body struct { Username string `form:"username"` FirstName string `form:"first_name"` LastName string `form:"last_name"` Email string `form:"email"` About string `form:"about"` U_RoleID uint `form:"role_id"` IsActive bool `form:"is_active"` Password string `form:"password"` } if err := c.Bind(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to read request body: " + err.Error(), }) return } // Get the file of avatar file, err := c.FormFile("avatar") if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to upload image", }) return } //Save the avatar file extension := filepath.Ext(file.Filename) timestamp := time.Now().Unix() NewFileName := fmt.Sprintf("%s_%d%s", body.Username, timestamp, extension) if err := c.SaveUploadedFile(file, "/../assets/uploads/"+NewFileName).Error; err != nil { // Log the error fmt.Println("Error saving avatar file:", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to save avatar file", }) return } //Hash password with bcrypt hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to hash password", }) return } //create a User user := models.User{Username: body.Username, FirstName: body.FirstName, LastName: body.LastName, Email: body.Email, About: body.About, Avatar: NewFileName, U_RoleID: body.U_RoleID, IsActive: body.IsActive, Password: string(hash)} result := initializers.DB.Create(&user) if result.Error != nil { // Log the error fmt.Println("Error creating user:", result.Error) c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to create new user", }) return } //return User data c.JSON(http.StatusOK, gin.H{ "User": user, }) }
数据库初始化代码
package initializers import ( "log" "os" "github.com/joho/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func ConnectToDB() *gorm.DB { //Load .env file err := godotenv.Load(".env") if err != nil { log.Fatal("Error reading .env file: ", err) } //Get Environment Variables dbUser := os.Getenv("DB_USER") dbPassword := os.Getenv("DB_PASSWORD") dbHost := os.Getenv("DB_HOST") dbPort := os.Getenv("DB_PORT") dbName := os.Getenv("DB_NAME") //MySQL connection string dsn := dbUser + ":" + dbPassword + "@tcp(" + dbHost + ":" + dbPort + ")/" + dbName + "?charset=utf8mb4&parseTime=True&loc=Local" // Connect to MySQL database db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err.Error()) } // Assign the database instance to the global variable DB = db return DB } func CloseDB() error { if DB != nil { sqlDB, err := DB.DB() if err != nil { return err } sqlDB.Close() } return nil }
用户模型代码
type User struct { gorm.Model Username string `gorm:"type:varchar(64);unique"` FirstName string `gorm:"type:varchar(64)"` LastName string `gorm:"type:varchar(64)"` Email string `gorm:"type:varchar(128);unique"` About string `gorm:"type:text"` Avatar string `gorm:"type:text"` U_RoleID uint `gorm:"index"` IsActive bool Password string `gorm:"type:varchar(255)"` }
错误原因定位
- 空指针直接诱因:
c.SaveUploadedFile返回值是error,但原代码错误地访问了.Error字段,导致空指针 dereference。 - 路径问题:
/../assets/uploads/是错误的绝对路径写法,大概率不存在该目录,且权限可能不足。 - 表单解析冲突:手动调用
c.Request.ParseMultipartForm后,Gin内置的c.Bind可能出现解析异常。
修复方案
1. 修正文件保存的错误写法
将c.SaveUploadedFile(...).Error改为直接判断返回的error:
if err := c.SaveUploadedFile(file, "./assets/uploads/"+NewFileName); err != nil { fmt.Println("Error saving avatar file:", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to save avatar file", }) return }
2. 确保上传目录存在
在保存文件前检查并创建目录:
uploadDir := "./assets/uploads/" if err := os.MkdirAll(uploadDir, 0755); err != nil { fmt.Println("Error creating upload directory:", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create upload directory", }) return }
3. 优化表单解析逻辑
移除手动调用的c.Request.ParseMultipartForm,改用c.ShouldBind自动处理multipart表单:
// 移除手动解析代码 // if err := c.Request.ParseMultipartForm(10 << 20); err != nil { // c.JSON(http.StatusBadRequest, gin.H{ // "error": "Failed to parse form data: " + err.Error(), // }) // return // } // 替换c.Bind为c.ShouldBind if err := c.ShouldBind(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to read request body: " + err.Error(), }) return }
4. 可选:支持无头像上传
如果允许用户不上传头像,修改文件获取逻辑:
file, err := c.FormFile("avatar") var NewFileName string if err == nil { // 处理文件上传逻辑 } else { // 设置默认头像或空字符串 NewFileName = "default_avatar.png" }
完整修复后的控制器代码
func UserCreate(c *gin.Context) { // Get data from request body var body struct { Username string `form:"username"` FirstName string `form:"first_name"` LastName string `form:"last_name"` Email string `form:"email"` About string `form:"about"` U_RoleID uint `form:"role_id"` IsActive bool `form:"is_active"` Password string `form:"password"` } if err := c.ShouldBind(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to read request body: " + err.Error(), }) return } // Get the file of avatar (optional) file, err := c.FormFile("avatar") var NewFileName string if err == nil { // Save the avatar file extension := filepath.Ext(file.Filename) timestamp := time.Now().Unix() NewFileName = fmt.Sprintf("%s_%d%s", body.Username, timestamp, extension) uploadDir := "./assets/uploads/" // Create directory if not exists if err := os.MkdirAll(uploadDir, 0755); err != nil { fmt.Println("Error creating upload directory:", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create upload directory", }) return } if err := c.SaveUploadedFile(file, uploadDir+NewFileName); err != nil { fmt.Println("Error saving avatar file:", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to save avatar file", }) return } } else { // Set default avatar if no file uploaded NewFileName = "default_avatar.png" } // Hash password with bcrypt hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to hash password", }) return } // Create a User user := models.User{ Username: body.Username, FirstName: body.FirstName, LastName: body.LastName, Email: body.Email, About: body.About, Avatar: NewFileName, U_RoleID: body.U_RoleID, IsActive: body.IsActive, Password: string(hash), } result := initializers.DB.Create(&user) if result.Error != nil { fmt.Println("Error creating user:", result.Error) c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to create new user", }) return } // Return User data c.JSON(http.StatusOK, gin.H{ "User": user, }) }
内容的提问来源于stack exchange,提问作者ghanshyam
相关产品推荐
相关产品推荐

