gorm快速入门 附带小案例 | 豆包MarsCode AI刷题

188 阅读9分钟

使用 Gin 和 GORM 实现用户注册与登录功能

引言

本文将详细介绍 GORM 的基础知识,然后通过一个实际的示例展示如何使用 Gin 和 GORM 实现用户注册与登录功能。

GORM 基础知识

GORM 是 Golang 的 ORM(对象关系映射)库,可以简化与数据库的交互。使用 GORM,你可以更方便地进行数据模型的定义、CRUD(创建、读取、更新、删除)操作以及关系管理。下面我们将分步骤介绍 GORM 的基本使用方法。

1. 初始化数据库连接

GORM 支持多种数据库,通过简单的配置即可连接到目标数据库。以下是连接 MySQL 数据库的示例:

package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

// 初始化数据库连接
func initDB() *gorm.DB {
    dsn := "username:password@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("连接数据库失败:", err)
    }
    return db
}

在这个例子中,我们定义了一个 initDB 函数,它使用 gorm.Open 方法打开一个数据库连接,并返回一个数据库实例。如果连接失败,则记录错误并退出程序。

2. 定义模型

在 GORM 中,模型是与数据库表对应的结构体。使用 struct 标签来定义字段属性,指定字段的类型和约束。例如,下面的代码定义了一个 User 结构体:

type User struct {
    ID    uint   `gorm:"primaryKey"` // 主键
    Name  string `gorm:"size:100"`   // 最大长度100
    Email string `gorm:"unique;size:100"` // 唯一性约束
    Age   int    // 年龄
}

每个结构体字段的标签用于定义该字段在数据库中的行为。这些标签是 GORM 特有的,能够帮助我们进行更多的配置:

  • gorm:"primaryKey":指定该字段为主键,数据库将自动为该字段生成唯一值。

  • gorm:"size:100":设置字符串字段的最大长度为 100。数据库在创建该字段时将使用该长度限制。

  • gorm:"unique":确保该字段的值在表中是唯一的,如果插入重复值,数据库将返回错误。

  • gorm:"not null":确保该字段不能为空,数据库将对该字段施加非空约束。

  • gorm:"default:'default_value'":为字段设置默认值,如果插入记录时未提供该字段的值,则使用默认值。

  • gorm:"index":为字段创建索引,增加该字段的查询效率。

通过这些标签,GORM 能够自动生成相应的 SQL 语句,处理字段的约束和类型。

3. 自动迁移

GORM 提供了自动迁移功能,允许开发者根据模型结构自动创建或更新数据库表。以下是一个简单的迁移示例,若数据库中无该表它会根据User结构体自动创建一个users表;若有该表,则会在表中添加结构体有但表中没有的字段:

func migrate(db *gorm.DB) {
    db.AutoMigrate(&User{})
}

调用 AutoMigrate 方法后,GORM 将根据 User 结构体的定义自动创建或更新数据库中的 users 表。

4. 插入数据

使用 Create 方法可以向数据库插入数据。以下是一个简单的示例:

user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
result := db.Create(&user)

此代码将一个新的用户插入到 users 表中。如果插入成功,user 对象将被更新以包含新生成的 ID。

5. 查询数据

GORM 支持多种查询方式,例如:

  • 单条查询:可以使用主键直接查询。
var user User
db.First(&user, 1) // 根据主键查询,获取 ID 为 1 的用户
  • 条件查询:可以使用 Where 方法进行条件查询。
var users []User
db.Where("age > ?", 20).Find(&users) // 查询年龄大于 20 的所有用户

这种查询方式灵活且强大,能够适应不同的查询需求。

6. 更新和删除数据

GORM 允许通过 UpdateDelete 方法对数据进行修改和删除。例如:

db.Model(&user).Update("Age", 26) // 更新用户的年龄
db.Delete(&user, 1) // 删除主键为 1 的记录

这里的 Model 方法用于指定要更新的对象,然后调用 Update 方法修改特定字段。使用 Delete 方法可以直接删除指定记录。

批量更新

GORM 支持批量更新,允许我们一次性更新多个记录。例如,如果我们想将所有年龄大于 30 的用户的状态设置为“活跃”:

db.Model(&User{}).Where("age > ?", 30).Update("status", "active")
更新多个字段

如果我们需要更新多个字段,可以使用 Updates 方法并传递一个包含字段和值的结构体或 map

db.Model(&user).Updates(User{Name: "Alice", Age: 30}) 
// 或者使用 map
db.Model(&user).Updates(map[string]interface{}{"Name": "Alice", "Age": 30})
条件删除

与条件查询类似,GORM 也支持条件删除。例如,如果我们要删除所有状态为“禁用”的用户:

db.Where("status = ?", "disabled").Delete(&User{})
软删除

GORM 支持软删除功能,当启用了软删除后,记录不会被真正删除,而是更新一个 deleted_at 字段来标记删除状态。要启用软删除,可以在模型中定义 gorm.DeletedAt 类型的字段:

type User struct {
    ID        uint
    Name      string
    Age       int
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

在启用了软删除的模型中,执行 Delete 操作会将 DeletedAt 字段设置为当前时间,而不会物理删除记录。查询时,默认会过滤掉已删除的记录。如果要查询已删除的记录,可以使用 Unscoped 方法:

var users []User
db.Unscoped().Where("status = ?", "disabled").Find(&users)
批量删除

批量删除与条件删除类似,我们可以使用条件过滤来一次性删除多条记录:

db.Where("age < ?", 18).Delete(&User{})
回调扩展

GORM 支持定义钩子(Callback)函数,用于在更新或删除操作之前或之后执行自定义逻辑。例如,我们可以定义一个更新之前的钩子函数:

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    if u.Age < 0 {
        return errors.New("年龄不能为负数")
    }
    return
}

在执行更新操作时,GORM 会自动调用这个钩子,帮助我们实现更严格的数据控制。

通过这些扩展功能,我们可以更灵活地操作数据库,满足不同场景下的数据管理需求。

7. 事务支持

使用事务可以确保一组操作的原子性。在 GORM 中,可以使用 Transaction 方法来实现事务操作。以下是一个示例:

err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err // 如果插入用户失败,返回错误
    }
    return nil // 所有操作成功,返回 nil
})

在这个示例中,所有在事务中的操作都是在同一个数据库连接上进行的。如果任何操作出错,事务会被回滚,确保数据的一致性。

8. 预加载关联数据

GORM 还支持预加载关联数据,适用于一对多或多对多的关系。这对于减少查询次数和提高性能非常有用。例如,以下代码预加载用户的所有订单:

db.Preload("Orders").Find(&user)

这将查询用户的同时,也查询与该用户关联的所有订单,从而避免了后续的额外查询。

项目实现流程

接下来,我们将通过一个具体的项目,展示如何使用 Gin 和 GORM 实现用户注册和登录功能。

如果对 Gin 还不太了解,可以先跳转至 GO框架之gin入门

项目结构

创建一个项目结构如下:

project/
├── main.go
└── model/
    └── user.go

1. 安装依赖

确保在项目中安装 Gin 和 GORM:

go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

这些命令将安装所需的依赖库。

2. 定义用户模型

model/user.go 文件中定义用户结构体:

package model

type User struct {
    gorm.Model
    Name  string `gorm:"size:100"`  // 名称,最大长度100
    Email string `gorm:"unique;size:100"` // 唯一性约束的邮箱
    Age   int
}

这里使用了 gorm.Model 结构体,它包含了 ID、创建时间、更新时间和删除时间等字段。

3. 数据库连接与迁移

main.go 中,设置数据库连接并进行自动迁移:

package main

var db *gorm.DB

func initDB() {
    dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    var err error
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    db.AutoMigrate(&model.User{}) // 自动迁移
}

在这个示例中,我们初始化数据库连接,并调用 AutoMigrate 方法来确保数据库表的结构与模型一致。

4. 实现注册功能

添加用户注册的路由和处理函数:

func register(c *gin.Context) {
    var user model.User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    result := db.Create(&user)
    if result.Error != nil {
        c.JSON(400, gin.H{"error": result.Error.Error()})
        return
    }
    c.JSON(201, user) // 返回创建的用户
}

这里的 register 函数处理用户的注册请求,使用 ShouldBindJSON 方法将请求体中的 JSON 数据绑定到 user 结构体中,并将其保存到数据库。

5. 实现登录功能

添加用户登录的路由和处理函数:

func login(c *gin.Context) {
    var user model.User
    var input struct {
        Email string `json:"email"`
        Name  string `json:"name"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := db.Where("email = ? AND name = ?", input.Email, input.Name).First(&user).Error; err != nil {
        c.JSON(401, gin.H{"error": "Invalid credentials"})
        return
    }
    c.JSON(200, user) // 登录成功,返回用户信息
}

登录函数检查提供的电子

邮件和名称是否存在,并返回相应的结果。

6. 设置路由

main.go 中设置路由:

func main() {
    initDB() // 初始化数据库连接

    r := gin.Default()
    r.POST("/register", register) // 注册路由
    r.POST("/login", login)       // 登录路由

    r.Run(":8080") // 启动服务
}

此处,我们使用 Gin 框架设置 HTTP 路由,以处理用户注册和登录请求。

7. 测试 API

使用 Postman 或 curl 测试 API。

  • 注册用户
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com", "age": 25}'
  • 登录用户
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com", "name": "Alice"}'

通过这些请求,你可以测试用户注册和登录功能。

8. 完整代码

完整的 main.go 文件如下:

package main

import (
    "github.com/gin-gonic/gin"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
    "project/model"
)

var db *gorm.DB

func initDB() {
    dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    var err error
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    db.AutoMigrate(&model.User{}) // 自动迁移
}

func register(c *gin.Context) {
    var user model.User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    result := db.Create(&user)
    if result.Error != nil {
        c.JSON(400, gin.H{"error": result.Error.Error()})
        return
    }
    c.JSON(201, user)
}

func login(c *gin.Context) {
    var user model.User
    var input struct {
        Email string `json:"email"`
        Name  string `json:"name"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := db.Where("email = ? AND name = ?", input.Email, input.Name).First(&user).Error; err != nil {
        c.JSON(401, gin.H{"error": "Invalid credentials"})
        return
    }
    c.JSON(200, user) 
}

func main() {
    initDB() // 初始化数据库连接

    r := gin.Default()
    r.POST("/register", register) 
    r.POST("/login", login)       

    r.Run(":8080") // 启动服务
}

结论

本篇文章通过一个小案例加深了对 GORM 的理解和使用,但是,在实际应用中,请务必添加数据加密(如密码哈希)、输入验证和更复杂的错误处理,以提升系统的安全性和稳定性。