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

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)"`
}

错误原因定位

  1. 空指针直接诱因:c.SaveUploadedFile返回值是error,但原代码错误地访问了.Error字段,导致空指针 dereference。
  2. 路径问题:/../assets/uploads/是错误的绝对路径写法,大概率不存在该目录,且权限可能不足。
  3. 表单解析冲突:手动调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 08:51:02