【Gin框架入门到精通系列05】Gin连接数据库

GIN框架介绍以及使用 李文周的博客 阅读详情
AI摘要

本文是Gin框架入门到精通系列的第五篇,介绍Gin与数据库交互。涵盖Go语言操作数据库的多种方式及对比,Gin项目数据库代码分层架构。详细讲解GORM的CRUD操作、关联关系处理、事务管理,还给出性能优化、代码组织、数据验证等实用技巧。

📚 原创系列: “Gin框架入门到精通系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Gin框架技术文章。

📑 Gin框架学习系列导航

本文是【Gin框架入门到精通系列】的第5篇,点击下方链接查看更多文章

👉 数据交互篇
  1. Gin连接数据库👈 当前位置
  2. Gin中的中间件机制
  3. Gin中的参数验证
  4. Gin中的Cookie和Session管理
  5. Gin中的文件上传与处理

🔍 查看完整系列文章

📖 文章导读

在本文中,您将学习到:

  • Go语言连接数据库的多种方法及其优缺点对比
  • GORM这一流行ORM框架的基础概念与使用场景
  • 如何在Gin项目中正确配置并管理数据库连接
  • 数据模型的定义及与数据库表的映射规则
  • GORM框架中完整的CRUD(增删改查)操作实现方法
  • 如何处理模型之间的一对一、一对多、多对多关系
  • 数据库事务的正确使用方式与实践案例
  • GORM性能优化技巧与最佳实践
  • 如何组织大型项目的数据访问层代码
  • 数据验证与错误处理的实用策略

数据库操作是几乎所有Web应用的核心功能,掌握Gin与数据库的集成将极大提升您开发完整Web应用的能力。本文将为您打下扎实的数据库操作基础,并深入探讨数据库操作的高级技巧。

[外链图片转存中…(img-qFhgndbL-1742921672913)]

一、导言部分

1.1 本节知识点概述

本文是Gin框架入门到精通系列的第五篇文章,主要介绍Gin框架如何与数据库进行交互。我们将全面介绍:

  • Gin框架中数据库交互的基本原理
  • Go语言操作MySQL数据库的几种方式
  • GORM框架的基本概念与安装配置
  • 使用GORM连接MySQL数据库
  • 数据模型定义与表结构映射
  • GORM完整的CRUD操作与最佳实践
  • 模型关联关系处理
  • 数据库事务管理
  • 性能优化与代码组织

通过本文的学习,你将掌握在Gin项目中集成数据库的全面知识,为构建高性能、可维护的Web应用打下坚实基础。

1.2 学习目标说明

完成本节学习后,你将能够:

  • 理解Gin项目中与数据库交互的多种方式
  • 在Gin项目中正确配置并连接MySQL数据库
  • 使用GORM框架定义数据模型并映射到数据库表
  • 实现基本的数据库配置与连接管理
  • 掌握合理的数据库操作代码组织方式
  • 实现常见的CRUD操作和关联查询
  • 使用事务保证数据一致性
  • 优化数据库操作性能
  • 设计可维护的数据访问层架构

1.3 预备知识要求

学习本教程需要以下预备知识:

  • 基本的Go语言知识
  • MySQL数据库基础
  • 已完成前四篇教程的学习
  • 了解基本的SQL语法和数据库概念

📌 小知识:Gin框架本身不包含任何数据库操作功能,它专注于HTTP请求处理和路由管理,数据库交互需要通过第三方库实现,这体现了Go语言的"小而美"的设计哲学。

二、理论讲解

2.1 数据库在Web应用中的地位

几乎所有的Web应用都需要持久化数据存储,数据库作为核心组件,承担着数据存储、查询和管理的重要职责。在Gin框架构建的Web应用中,数据库交互模块通常负责:

  • 用户信息的存储与认证
  • 业务数据的持久化与管理
  • 系统配置与状态的维护
  • 数据分析与统计的基础支持

Gin作为一个轻量级Web框架,本身并不包含数据库操作的功能,而是通过集成第三方库来实现与数据库的交互。这种设计思想符合Go语言"组合优于继承"的哲学,使得开发者可以根据项目需求灵活选择合适的数据库解决方案。

2.2 Go语言操作数据库的方式

在Go语言中,有多种方式可以操作数据库,下面我们详细介绍主流的三种方式:

2.2.1 使用标准库 database/sql

Go语言标准库提供了 database/sql 包,它定义了一套通用的数据库接口,可以与各种SQL和SQL-like数据库交互。但需要注意的是,database/sql 包本身并不包含具体数据库的驱动,需要额外导入相应的驱动包。

package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/go-sql-driver/mysql" // 导入MySQL驱动
)

func main() {
    // 连接MySQL数据库
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 检查连接
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }
    
    // 执行查询
    rows, err := db.Query("SELECT id, name FROM users WHERE status = ?", 1)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    
    // 处理结果
    for rows.Next() {
        var id int
        var name string
        err = rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("id: %d, name: %s\n", id, name)
    }
}

优点:

  • 是标准库的一部分,无需额外依赖
  • 直接操作SQL,灵活性高
  • 性能较好,控制粒度精细

缺点:

  • 需要手写SQL语句,容易出错
  • 代码冗长,重复性工作多
  • 没有自动映射到结构体的功能
2.2.2 使用ORM框架(如GORM)

ORM(Object-Relational Mapping)对象关系映射框架可以将数据库表映射为Go结构体,简化数据库操作。GORM是Go语言中最流行的ORM框架之一,提供了丰富的功能和简洁的API。

package main

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

// 定义User模型
type User struct {
    ID     uint           `gorm:"primaryKey"`                // 主键
    Name   string         `gorm:"type:varchar(100);not null"` // 字符串类型,不能为空
    Email  string         `gorm:"uniqueIndex;not null"`      // 唯一索引,不能为空
    Age       int            `gorm:"default:18"`                // 默认值为18
    Birthday  *time.Time     // 生日可以为空
    CreatedAt time.Time      `gorm:"autoCreateTime"`            // 创建时间
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`            // 更新时间
    DeletedAt gorm.DeletedAt `gorm:"index"`                     // 软删除支持
}

func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
    
    // 查询活跃用户
    var users []User
    db.Where("status = ?", 1).Find(&users)
    
    // 处理结果
    for _, user := range users {
        fmt.Printf("ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)
    }
}

优点:

  • 自动映射到Go结构体,代码简洁
  • 提供丰富的查询构建器,无需手写SQL
  • 支持钩子函数,方便处理业务逻辑
  • 自动处理数据库迁移和模式变更

缺点:

  • 引入额外依赖
  • 对于复杂查询,可能性能不如原生SQL
  • 有一定的学习成本
2.2.3 使用SQL构建器(如Squirrel、sqlx)

SQL构建器是介于原生SQL和ORM之间的解决方案,它提供了构建SQL语句的API,同时保留了对SQL的直接控制。

package main

import (
    "fmt"
    "log"
    
    "github.com/jmoiron/sqlx"
    _ "github.com/go-sql-driver/mysql"
    sq "github.com/Masterminds/squirrel"
)

// 用户结构体
type User struct {
    ID     int    `db:"id"`
    Name   string `db:"name"`
    Email  string `db:"email"`
    Status int    `db:"status"`
}

func main() {
    // 连接数据库
    db, err := sqlx.Connect("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    
    // 使用Squirrel构建SQL
    query, args, err := sq.Select("id", "name", "email").
        From("users").
        Where(sq.Eq{"status": 1}).
        OrderBy("id DESC").
        Limit(10).
        ToSql()
    
    if err != nil {
        log.Fatal(err)
    }
    
    // 执行查询并映射到结构体
    var users []User
    err = db.Select(&users, query, args...)
    if err != nil {
        log.Fatal(err)
    }
    
    // 处理结果
    for _, user := range users {
        fmt.Printf("ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)
    }
}

优点:

  • 比原生SQL更安全、更易维护
  • 比ORM更灵活,性能更好
  • 对SQL有更直接的控制

缺点:

  • 不如ORM功能丰富
  • 仍需要了解SQL语法
  • 没有自动迁移等高级特性
2.2.4 三种方式的对比
特性database/sqlORM框架SQL构建器
学习曲线中等较陡较平
代码量较多较少中等
灵活性高中等高
性能最佳可能有开销接近原生
功能丰富度基础功能非常丰富中等
维护难度较高较低中等
适用场景性能关键型应用快速开发,普通Web应用需要SQL控制但又想避免直接写SQL

💡 最佳实践:在实际项目中,可以根据需求混合使用不同的方式。例如,使用GORM处理常规CRUD操作,对于特别复杂或性能敏感的查询,可以回退到原生SQL或SQL构建器。

2.3 Gin项目中的数据库代码组织

在Gin项目中,合理组织数据库相关代码不仅能提高代码可读性,还能促进团队协作和项目维护。以下是一种常见的分层架构:

project/
├── cmd/
│   └── main.go                 # 程序入口
├── config/
│   └── database.go             # 数据库配置
├── models/
│   ├── user.go                 # 用户模型
│   └── product.go              # 产品模型
├── repositories/
│   ├── user_repository.go      # 用户数据访问层
│   └── product_repository.go   # 产品数据访问层
├── services/
│   ├── user_service.go         # 用户业务逻辑层
│   └── product_service.go      # 产品业务逻辑层
└── handlers/
    ├── user_handler.go         # 用户API处理器
    └── product_handler.go      # 产品API处理器
2.3.1 分层架构说明
  1. 模型层(Models):

    • 定义数据结构和数据库表映射关系
    • 通常包含数据验证和基本的CRUD方法
  2. 数据访问层(Repositories):

    • 封装所有数据库操作
    • 提供数据访问接口,隐藏数据库实现细节
    • 可以实现数据库事务管理
  3. 业务逻辑层(Services):

    • 实现核心业务逻辑
    • 协调多个Repository的操作
    • 处理事务边界和业务规则验证
  4. 控制器层(Handlers):

    • 处理HTTP请求和响应
    • 调用适当的Service完成业务操作
    • 不直接与数据库交互

这种分层架构的优势在于:

  • 关注点分离:每一层只关注自己的职责
  • 可测试性:每一层都可以独立测试
  • 可维护性:修改一层不会影响其他层
  • 可扩展性:可以轻松替换特定层的实现

🔍 代码示例:在后续章节我们将详细介绍每一层的具体实现,特别是如何正确使用依赖注入模式组织这些层次间的关系。

三、代码实践

3.1 CRUD基本操作实现

在Web应用中,CRUD(Create、Read、Update、Delete)操作是最基础、最常用的数据库交互方式。GORM提供了丰富的API来简化这些操作,下面我们将通过一个用户管理系统的实例来详细介绍这些操作。

我们先来定义一个完整的用户模型,它将是后续操作的基础:

// models/user.go
package models

import (
    "gorm.io/gorm"
    "time"
)

// User 用户模型
type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`                   // 用户ID,主键
    Name      string         `gorm:"size:100;not null" json:"name"`          // 用户名,非空
    Email     string         `gorm:"size:100;uniqueIndex" json:"email"`      // 邮箱,唯一索引
    Age       int            `gorm:"default:18" json:"age"`                  // 年龄,默认值18
    CreatedAt time.Time      `json:"created_at"`                             // 创建时间,自动维护
    UpdatedAt time.Time      `json:"updated_at"`                             // 更新时间,自动维护
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`                         // 软删除时间,对客户端隐藏
}

💡 最佳实践:将模型定义在专门的models目录下,每个模型一个文件,这样可以使代码结构更清晰,便于维护。

3.1.1 创建(Create)操作

创建记录是数据库操作的基础,GORM提供了多种方式来创建记录:

基本创建操作
// handlers/user_handler.go
package handlers

import (
    "github.com/gin-gonic/gin"
    "myapp/database"
    "myapp/models"
)

// CreateUser 创建单个用户
// 路由: POST /users
func CreateUser(c *gin.Context) {
    db := database.GetDB()
    
    // 从请求绑定JSON数据到User结构体
    var user models.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(500, gin.H{"error": "创建用户失败: " + result.Error.Error()})
        return
    }
    
    // 返回创建成功的用户信息
    c.JSON(201, user) // 使用201 Created状态码
}

📌 说明:ShouldBindJSON方法自动将请求体中的JSON数据绑定到Go结构体,GORM的Create方法将对象插入数据库并自动填充ID、CreatedAt和UpdatedAt字段。

批量创建

当需要一次创建多条记录时,批量创建可以提高性能:

// BatchCreateUsers 批量创建用户
// 路由: POST /users/batch
func BatchCreateUsers(c *gin.Context) {
    db := database.GetDB()
    
    var users []models.User
    if err := c.ShouldBindJSON(&users); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 批量创建记录
    result := db.Create(&users)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "批量创建用户失败: " + result.Error.Error()})
        return
    }
    
    c.JSON(201, gin.H{
        "message": "批量创建成功",
        "count": len(users),      // 创建的记录数
        "rows_affected": result.RowsAffected, // 影响的行数
        "users": users,           // 返回所有创建的用户信息
    })
}

⚡ 性能提示:对于大量数据,可以使用CreateInBatches方法指定批次大小:

db.CreateInBatches(&users, 100) // 每次创建100条记录
使用Map创建

有时候我们不想定义完整的结构体,可以直接使用Map创建记录:

// CreateUserFromMap 使用Map创建用户
// 路由: POST /users/map
func CreateUserFromMap(c *gin.Context) {
    db := database.GetDB()
    
    // 从请求绑定Map数据
    var userMap map[string]interface{}
    if err := c.ShouldBindJSON(&userMap); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 使用Map创建记录
    result := db.Model(&models.User{}).Create(userMap)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "创建用户失败: " + result.Error.Error()})
        return
    }
    
    c.JSON(201, gin.H{
        "message": "创建成功",
        "data": userMap,
        "rows_affected": result.RowsAffected,
    })
}

⚠️ 注意:使用Map创建时,无法利用结构体标签中的验证规则,需要手动验证数据。

3.1.2 读取(Read)操作

读取操作是最常用的数据库操作,GORM提供了丰富的查询方法:

查询所有记录
// GetAllUsers 获取所有用户
// 路由: GET /users
func GetAllUsers(c *gin.Context) {
    db := database.GetDB()
    
    var users []models.User
    result := db.Find(&users)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "获取用户列表失败: " + result.Error.Error()})
        return
    }
    
    c.JSON(200, gin.H{
        "total": len(users),
        "users": users,
    })
}
查询单条记录
// GetUserByID 根据ID获取单个用户
// 路由: GET /users/:id
func GetUserByID(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    var user models.User
    
    // First方法查询第一条匹配的记录
    result := db.First(&user, id)
    if result.Error != nil {
        if result.Error == gorm.ErrRecordNotFound {
            c.JSON(404, gin.H{"error": "用户不存在"})
        } else {
            c.JSON(500, gin.H{"error": "查询用户失败: " + result.Error.Error()})
        }
        return
    }
    
    c.JSON(200, user)
}

📝 GORM查询方法对比:

  • First: 获取第一条记录,按主键排序
  • Last: 获取最后一条记录,按主键排序
  • Take: 获取一条记录,不指定排序
  • Find: 获取所有匹配的记录
条件查询

在实际应用中,我们经常需要根据特定条件查询数据:

// GetUsersByCondition 条件查询用户
// 路由: GET /users/search
// 查询参数:
//   - name: 用户名(模糊匹配)
//   - email: 邮箱(精确匹配)
//   - min_age: 最小年龄
func GetUsersByCondition(c *gin.Context) {
    db := database.GetDB()
    
    var users []models.User
    
    // 获取查询参数
    name := c.Query("name")
    email := c.Query("email")
    minAge := c.DefaultQuery("min_age", "0")
    
    // 构建查询
    query := db
    
    // 动态添加查询条件
    if name != "" {
        query = query.Where("name LIKE ?", "%"+name+"%")
    }
    
    if email != "" {
        query = query.Where("email = ?", email)
    }
    
    if minAge != "0" {
        query = query.Where("age >= ?", minAge)
    }
    
    // 执行查询
    result := query.Find(&users)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "查询用户失败: " + result.Error.Error()})
        return
    }
    
    c.JSON(200, gin.H{
        "count": len(users),
        "users": users,
    })
}

🔍 GORM条件查询技巧:

// 等于条件
db.Where("name = ?", "张三").Find(&users)

// AND条件组合
db.Where("name = ? AND age >= ?", "张三", 18).Find(&users)

// 使用结构体
db.Where(&models.User{Name: "张三", Age: 18}).Find(&users)

// 使用Map
db.Where(map[string]interface{}{"name": "张三", "age": 18}).Find(&users)

// 使用IN条件
db.Where("name IN ?", []string{"张三", "李四"}).Find(&users)

// 使用原生SQL
db.Where("age > ? AND name LIKE ?", 18, "%张%").Find(&users)
3.1.3 更新(Update)操作

更新操作用于修改已存在的记录,GORM提供了多种更新方式:

更新整个对象
// UpdateUser 更新单个用户
// 路由: PUT /users/:id
func UpdateUser(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    
    // 检查用户是否存在
    var existingUser models.User
    if err := db.First(&existingUser, id).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    // 绑定更新数据
    var updateData models.User
    if err := c.ShouldBindJSON(&updateData); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // Updates方法只会更新非零值字段
    result := db.Model(&existingUser).Updates(updateData)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "更新用户失败: " + result.Error.Error()})
        return
    }
    
    // 返回更新后的用户
    db.First(&existingUser, id)
    c.JSON(200, existingUser)
}

⚠️ 注意:Updates方法默认只会更新非零值字段。如果需要更新零值字段,可以使用Map或使用Select指定字段。

选择性更新字段

在微服务和RESTful API中,经常使用PATCH请求来部分更新资源:

// UpdateUserFields 选择性更新用户字段
// 路由: PATCH /users/:id
func UpdateUserFields(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    
    // 绑定更新数据(使用Map接收任意字段)
    var updateFields map[string]interface{}
    if err := c.ShouldBindJSON(&updateFields); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 更新指定字段(使用Map可以更新零值)
    result := db.Model(&models.User{}).Where("id = ?", id).Updates(updateFields)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "更新用户失败: " + result.Error.Error()})
        return
    }
    
    if result.RowsAffected == 0 {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    // 返回更新后的用户
    var updatedUser models.User
    db.First(&updatedUser, id)
    c.JSON(200, updatedUser)
}

🧩 高级更新技巧:

// 更新单个列
db.Model(&user).Update("name", "新名字")

// 使用SQL表达式更新
db.Model(&user).Update("age", gorm.Expr("age + ?", 1)) // 年龄+1

// 选择指定字段更新
db.Model(&user).Select("name", "age").Updates(updateData)

// 排除指定字段更新
db.Model(&user).Omit("email").Updates(updateData)

// 条件更新
db.Model(&models.User{}).Where("age > ?", 18).Update("status", "adult")
3.1.4 删除(Delete)操作

GORM支持软删除和硬删除两种方式:

软删除

如果模型包含gorm.DeletedAt字段,默认会启用软删除功能,被"删除"的记录不会从数据库中真正删除:

// DeleteUser 软删除用户
// 路由: DELETE /users/:id
func DeleteUser(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    
    // 软删除用户(设置DeletedAt,但不实际删除记录)
    result := db.Delete(&models.User{}, id)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "删除用户失败: " + result.Error.Error()})
        return
    }
    
    if result.RowsAffected == 0 {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    c.JSON(200, gin.H{"message": "用户已删除"})
}

📌 软删除注意事项:

  • 软删除的记录在普通查询中会被自动过滤掉
  • 可以使用Unscoped().Find(&users)查询包含已删除记录的所有数据
  • 软删除对数据安全和数据恢复非常有帮助
永久删除(硬删除)

在某些情况下,我们需要永久删除记录,可以使用Unscoped()跳过软删除机制:

// PermanentDeleteUser 永久删除用户(硬删除)
// 路由: DELETE /users/:id/permanent
func PermanentDeleteUser(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    
    // 永久删除用户(硬删除,记录将从数据库中彻底删除)
    result := db.Unscoped().Delete(&models.User{}, id)
    if result.Error != nil {
        c.JSON(500, gin.H{"error": "永久删除用户失败: " + result.Error.Error()})
        return
    }
    
    if result.RowsAffected == 0 {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    c.JSON(200, gin.H{"message": "用户已永久删除"})
}

⚠️ 警告:永久删除操作不可撤销,应谨慎使用,并通常需要较高的权限。在生产环境中,建议为此类操作添加额外的安全检查。

3.2 完整的数据库接口实现

在实际项目中,我们需要设计一个完整的API系统,下面展示如何组织路由和控制器逻辑,实现用户管理功能:

// routes/user_routes.go - 用户相关路由配置
package routes

import (
    "github.com/gin-gonic/gin"
    "myapp/controllers"
    "myapp/middleware"
)

// SetupUserRoutes 配置用户相关路由
func SetupUserRoutes(router *gin.Engine) {
    // 用户资源路由组
    users := router.Group("/users")
    {
        // 基本CRUD操作
        users.GET("", controllers.GetAllUsers)         // 获取用户列表
        users.GET("/:id", controllers.GetUserByID)     // 获取单个用户
        users.POST("", controllers.CreateUser)         // 创建用户
        users.PUT("/:id", controllers.UpdateUser)      // 更新用户
        users.PATCH("/:id", controllers.UpdateUserFields) // 部分更新用户
        users.DELETE("/:id", controllers.DeleteUser)   // 删除用户
        
        // 高级查询路由
        users.GET("/search", controllers.GetUsersByCondition) // 条件查询
        
        // 批量操作路由
        users.POST("/batch", controllers.BatchCreateUsers) // 批量创建
        
        // 管理员专用路由,添加认证中间件
        admin := users.Group("/admin", middleware.AdminAuth())
        {
            admin.DELETE("/:id/permanent", controllers.PermanentDeleteUser) // 永久删除
        }
    }
}

在控制器中,我们可以添加更多的功能,比如分页支持:

// controllers/user_controller.go - 用户控制器中的分页列表实现
package controllers

import (
    "github.com/gin-gonic/gin"
    "myapp/database"
    "myapp/models"
    "strconv"
)

// 获取分页参数
func getPagination(c *gin.Context) (page, pageSize int) {
    // 从查询参数中获取page和page_size,默认为1和10
    page, _ = strconv.Atoi(c.DefaultQuery("page", "1"))
    pageSize, _ = strconv.Atoi(c.DefaultQuery("page_size", "10"))
    
    // 限制页面大小,防止请求过大数据
    if pageSize > 100 {
        pageSize = 100
    }
    
    // 确保page至少为1
    if page < 1 {
        page = 1
    }
    
    return page, pageSize
}

// GetAllUsers 获取所有用户(支持分页)
// 路由: GET /users?page=1&page_size=10
func GetAllUsers(c *gin.Context) {
    db := database.GetDB()
    
    // 获取分页参数
    page, pageSize := getPagination(c)
    offset := (page - 1) * pageSize
    
    // 总记录数
    var total int64
    var users []models.User
    
    // 先计算总记录数
    db.Model(&models.User{}).Count(&total)
    
    // 再获取分页数据
    if err := db.Offset(offset).Limit(pageSize).Find(&users).Error; err != nil {
        c.JSON(500, gin.H{"error": "获取用户列表失败: " + err.Error()})
        return
    }
    
    // 返回分页数据和元数据
    c.JSON(200, gin.H{
        "total": total,                // 总记录数
        "page": page,                  // 当前页码
        "page_size": pageSize,         // 每页大小
        "total_pages": (total + int64(pageSize) - 1) / int64(pageSize), // 总页数
        "has_more": int64(offset+len(users)) < total, // 是否有更多数据
        "users": users,                // 用户数据
    })
}

// ... 其他控制器方法实现 ...

3.3 关联关系处理

关系型数据库的核心特性之一是表之间的关联关系,GORM提供了丰富的API来处理这些关系:

📊 GORM支持的关联关系类型:

关系类型描述例子
属于(Belongs To)一个模型属于另一个模型,外键在当前模型用户属于部门
拥有一个(Has One)一个模型拥有另一个模型,外键在关联模型用户拥有一个资料
拥有多个(Has Many)一个模型拥有多个关联模型,外键在关联模型用户拥有多篇文章
多对多(Many To Many)两个模型互相关联,通过中间表连接用户与角色的关系
3.3.1 定义关联模型

下面是一个完整的关联关系示例,包含了各种类型的关联:

// models/user.go - 用户模型及其关联关系
package models

import (
    "gorm.io/gorm"
    "time"
)

// User 用户模型
type User struct {
    ID       uint      `gorm:"primaryKey" json:"id"`
    Name     string    `json:"name"`
    Email    string    `json:"email"`
    Password string    `json:"-"` // 密码不返回给客户端
    
    // 一对一关系 - 用户拥有一个个人资料
    Profile  Profile   `gorm:"foreignKey:UserID" json:"profile,omitempty"`
    
    // 一对多关系 - 用户拥有多篇文章
    Articles []Article `gorm:"foreignKey:AuthorID" json:"articles,omitempty"`
    
    // 多对多关系 - 用户可以掌握多种语言,语言也可以被多个用户掌握
    Languages []Language `gorm:"many2many:user_languages;" json:"languages,omitempty"`
    
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

// Profile 用户个人资料模型
type Profile struct {
    ID         uint   `gorm:"primaryKey" json:"id"`
    UserID     uint   `gorm:"uniqueIndex" json:"user_id"` // 外键
    Avatar     string `json:"avatar"`    // 头像URL
    Bio        string `json:"bio"`       // 个人简介
    SocialLink string `json:"social_link"` // 社交链接
    CreatedAt  time.Time `json:"created_at"`
    UpdatedAt  time.Time `json:"updated_at"`
}

// Article 文章模型
type Article struct {
    ID        uint      `gorm:"primaryKey" json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    AuthorID  uint      `json:"author_id"` // 外键,关联用户
    Author    User      `gorm:"foreignKey:AuthorID" json:"author,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// Language 语言模型
type Language struct {
    ID   uint   `gorm:"primaryKey" json:"id"`
    Name string `gorm:"uniqueIndex" json:"name"` // 语言名称,唯一
    Code string `gorm:"size:10" json:"code"`     // 语言代码,如en-US
    
    // 多对多关系的反向引用
    Users []User `gorm:"many2many:user_languages;" json:"users,omitempty"`
}

⚡ GORM关联标签说明:

  • foreignKey: 指定外键名
  • references: 指定引用的列名
  • many2many: 指定多对多关系的中间表名
  • joinForeignKey: 指定连接表的外键名
  • joinReferences: 指定连接表引用的列名
3.3.2 关联操作示例

下面是一些常见的关联操作实例:

创建带关联数据的记录
// CreateUserWithAssociations 创建用户及其关联数据
// 路由: POST /users/with-associations
func CreateUserWithAssociations(c *gin.Context) {
    db := database.GetDB()
    
    // 使用嵌套结构接收关联数据
    var userData struct {
        Name      string          `json:"name"`
        Email     string          `json:"email"`
        Password  string          `json:"password"`
        Profile   models.Profile  `json:"profile"`
        Languages []models.Language `json:"languages"`
    }
    
    if err := c.ShouldBindJSON(&userData); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 构建用户对象及其关联
    user := models.User{
        Name:      userData.Name,
        Email:     userData.Email,
        Password:  userData.Password, // 注意:实际应用中应先加密密码
        Profile:   userData.Profile,
        Languages: userData.Languages,
    }
    
    // 创建用户及关联数据
    // AutoMigrate会自动处理关联表的创建
    if err := db.Create(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": "创建用户失败: " + err.Error()})
        return
    }
    
    c.JSON(201, user)
}

📝 说明:使用db.Create创建记录时,GORM会自动插入关联数据。如果不想自动保存关联,可以使用db.Omit忽略关联字段,如:db.Omit("Languages").Create(&user)。

预加载关联数据

查询时,GORM默认不会加载关联数据,需要使用Preload方法显式加载:

// GetUserWithAssociations 获取用户及其所有关联数据
// 路由: GET /users/:id/with-associations
func GetUserWithAssociations(c *gin.Context) {
    db := database.GetDB()
    
    id := c.Param("id")
    var user models.User
    
    // 预加载所有关联数据
    result := db.Preload("Profile").      // 加载个人资料
              Preload("Articles").        // 加载文章
              Preload("Languages").       // 加载语言
              Preload("Articles.Author"). // 加载文章作者(嵌套预加载)
              First(&user, id)
    
    if result.Error != nil {
        if result.Error == gorm.ErrRecordNotFound {
            c.JSON(404, gin.H{"error": "用户不存在"})
        } else {
            c.JSON(500, gin.H{"error": "查询用户失败: " + result.Error.Error()})
        }
        return
    }
    
    c.JSON(200, user)
}

🚀 GORM预加载技巧:

  • 使用Preload("Articles", "title LIKE ?", "%Gin%")可以在预加载时添加条件
  • 使用Joins("Profile")可以通过JOIN语句预加载,减少SQL查询次数
  • 使用Preload(clause.Associations)可以预加载所有关联
  • 嵌套预加载使用点分隔,如Preload("Articles.Comments")
添加关联数据

向已有记录添加新的关联:

// AddLanguageToUser 向用户添加语言技能
// 路由: POST /users/:id/languages
func AddLanguageToUser(c *gin.Context) {
    db := database.GetDB()
    
    userID := c.Param("id")
    var user models.User
    
    // 检查用户是否存在
    if err := db.First(&user, userID).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    // 接收要添加的语言数据
    var language models.Language
    if err := c.ShouldBindJSON(&language); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 先检查语言是否已存在
    var existingLanguage models.Language
    err := db.Where("name = ?", language.Name).First(&existingLanguage).Error
    
    if err != nil {
        // 语言不存在,先创建语言
        if err := db.Create(&language).Error; err != nil {
            c.JSON(500, gin.H{"error": "创建语言失败: " + err.Error()})
            return
        }
        existingLanguage = language
    }
    
    // 向用户添加语言关联
    // Association方法用于获取关联管理器
    err = db.Model(&user).Association("Languages").Append(&existingLanguage)
    if err != nil {
        c.JSON(500, gin.H{"error": "添加语言关联失败: " + err.Error()})
        return
    }
    
    c.JSON(200, gin.H{
        "message": "语言添加成功",
        "language": existingLanguage,
    })
}

📌 关联方法说明:

  • Append: 添加新的关联
  • Replace: 替换现有关联
  • Delete: 删除指定关联
  • Clear: 清空所有关联
  • Count: 统计关联数量

3.4 事务管理

在处理复杂的数据操作,特别是涉及多个表的更新时,事务可以确保数据一致性:

3.4.1 手动事务管理
// CreateUserAndArticlesTx 使用事务创建用户和文章
// 路由: POST /users/with-articles-tx
func CreateUserAndArticlesTx(c *gin.Context) {
    db := database.GetDB()
    
    var userData struct {
        User     models.User      `json:"user"`
        Articles []models.Article `json:"articles"`
    }
    
    if err := c.ShouldBindJSON(&userData); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 开始事务
    tx := db.Begin()
    
    // 使用defer确保函数退出前回滚事务(如果未提交)
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback() // 发生panic时回滚
        }
    }()
    
    // 1. 创建用户
    if err := tx.Create(&userData.User).Error; err != nil {
        tx.Rollback() // 手动回滚事务
        c.JSON(500, gin.H{"error": "创建用户失败: " + err.Error()})
        return
    }
    
    // 2. 设置文章的作者ID
    for i := range userData.Articles {
        userData.Articles[i].AuthorID = userData.User.ID
    }
    
    // 3. 创建文章
    if err := tx.Create(&userData.Articles).Error; err != nil {
        tx.Rollback() // 手动回滚事务
        c.JSON(500, gin.H{"error": "创建文章失败: " + err.Error()})
        return
    }
    
    // 4. 提交事务
    if err := tx.Commit().Error; err != nil {
        c.JSON(500, gin.H{"error": "提交事务失败: " + err.Error()})
        return
    }
    
    c.JSON(201, gin.H{
        "message": "用户和文章创建成功",
        "user": userData.User,
        "articles": userData.Articles,
        "articles_count": len(userData.Articles),
    })
}

⚠️ 事务注意事项:

  • 手动管理事务时,务必正确处理Begin、Commit和Rollback
  • 使用defer和recover可以确保发生panic时事务也能正确回滚
  • 事务内的所有操作必须使用同一个事务对象tx
3.4.2 使用事务函数

GORM提供了一个更简洁的事务API,自动处理提交和回滚:

// TransferBetweenAccounts 账户间转账(使用事务函数)
// 路由: POST /accounts/transfer
func TransferBetweenAccounts(c *gin.Context) {
    db := database.GetDB()
    
    var transferData struct {
        FromAccountID uint    `json:"from_account_id" binding:"required"`
        ToAccountID   uint    `json:"to_account_id" binding:"required"`
        Amount        float64 `json:"amount" binding:"required,gt=0"`
    }
    
    if err := c.ShouldBindJSON(&transferData); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 使用事务函数包装整个转账流程
    // 如果返回error,事务会自动回滚
    // 如果返回nil,事务会自动提交
    err := db.Transaction(func(tx *gorm.DB) error {
        // 1. 检查转出账户余额
        var fromAccount models.Account
        if err := tx.Where("id = ?", transferData.FromAccountID).
                 First(&fromAccount).Error; err != nil {
            return fmt.Errorf("转出账户不存在: %w", err)
        }
        
        // 2. 检查余额是否充足
        if fromAccount.Balance < transferData.Amount {
            return errors.New("余额不足")
        }
        
        // 3. 扣减转出账户
        if err := tx.Model(&fromAccount).
                 Update("balance", gorm.Expr("balance - ?", transferData.Amount)).
                 Error; err != nil {
            return fmt.Errorf("扣减转出账户失败: %w", err)
        }
        
        // 4. 增加转入账户
        result := tx.Model(&models.Account{}).
                 Where("id = ?", transferData.ToAccountID).
                 Update("balance", gorm.Expr("balance + ?", transferData.Amount))
        
        if result.Error != nil {
            return fmt.Errorf("增加转入账户失败: %w", result.Error)
        }
        
        if result.RowsAffected == 0 {
            return errors.New("转入账户不存在")
        }
        
        // 5. 创建交易记录
        txRecord := models.Transaction{
            FromAccountID: transferData.FromAccountID,
            ToAccountID:   transferData.ToAccountID,
            Amount:        transferData.Amount,
            Status:        "completed",
            Description:   fmt.Sprintf("从账户#%d转账到账户#%d", transferData.FromAccountID, transferData.ToAccountID),
        }
        
        if err := tx.Create(&txRecord).Error; err != nil {
            return fmt.Errorf("创建交易记录失败: %w", err)
        }
        
        // 事务成功,返回nil自动提交
        return nil
    })
    
    // 处理事务结果
    if err != nil {
        c.JSON(500, gin.H{"error": "转账失败: " + err.Error()})
        return
    }
    
    c.JSON(200, gin.H{"message": "转账成功"})
}

🔄 事务函数优势:

  • 代码更简洁,无需手动管理提交和回滚
  • 自动处理panic情况
  • 更好的错误处理和传递
  • 嵌套事务支持

四、实用技巧

4.1 GORM性能优化

在实际开发中,数据库操作的性能至关重要。下面是一些GORM性能优化的技巧:

4.1.1 选择性加载字段

只加载需要的字段可以显著提升性能:

// 只加载特定字段
var users []struct {
    ID    uint
    Name  string
    Email string
}

db.Model(&models.User{}).Select("id", "name", "email").Find(&users)
4.1.2 避免预加载不必要的关联

在不需要关联数据时避免预加载:

// 按需预加载
var condition bool // 某些条件

query := db
if condition {
    query = query.Preload("Profile")
}
query.Find(&users)
4.1.3 批量操作

对于大量数据的操作,使用批量方式更高效:

// 批量插入
var users []models.User
// ... 准备数据

// 将批量大小设置为100
db.CreateInBatches(users, 100)
4.1.4 合理使用索引

为经常查询的字段添加索引:

type User struct {
    ID        uint   `gorm:"primaryKey"`
    Email     string `gorm:"uniqueIndex"`
    Name      string `gorm:"index:idx_name"`
    CreatedAt time.Time
}
4.1.5 使用原生SQL查询

对于复杂查询,有时候原生SQL比ORM更高效:

type Result struct {
    Name  string
    Total int
}

var results []Result
db.Raw("SELECT name, COUNT(*) as total FROM users GROUP BY name").Scan(&results)

4.2 使用仓储模式组织代码

在大型项目中,将数据库操作封装到仓储层(Repository)可以提高代码的可维护性和可测试性:

// repositories/user_repository.go
package repositories

import (
    "gorm.io/gorm"
    "myapp/models"
)

type UserRepository struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) FindAll(page, pageSize int) ([]models.User, int64, error) {
    var users []models.User
    var total int64
    
    offset := (page - 1) * pageSize
    
    // 计算总数
    if err := r.db.Model(&models.User{}).Count(&total).Error; err != nil {
        return nil, 0, err
    }
    
    // 获取数据
    err := r.db.Offset(offset).Limit(pageSize).Find(&users).Error
    return users, total, err
}

func (r *UserRepository) FindByID(id uint) (models.User, error) {
    var user models.User
    err := r.db.First(&user, id).Error
    return user, err
}

func (r *UserRepository) Create(user *models.User) error {
    return r.db.Create(user).Error
}

func (r *UserRepository) Update(user *models.User) error {
    return r.db.Save(user).Error
}

func (r *UserRepository) Delete(id uint) error {
    return r.db.Delete(&models.User{}, id).Error
}

// 其他特定查询方法
func (r *UserRepository) FindByEmail(email string) (models.User, error) {
    var user models.User
    err := r.db.Where("email = ?", email).First(&user).Error
    return user, err
}

// services/user_service.go
package services

import (
    "myapp/models"
    "myapp/repositories"
)

type UserService struct {
    userRepo *repositories.UserRepository
}

func NewUserService(userRepo *repositories.UserRepository) *UserService {
    return &UserService{userRepo: userRepo}
}

func (s *UserService) GetAllUsers(page, pageSize int) ([]models.User, int64, error) {
    return s.userRepo.FindAll(page, pageSize)
}

func (s *UserService) GetUserByID(id uint) (models.User, error) {
    return s.userRepo.FindByID(id)
}

func (s *UserService) CreateUser(user *models.User) error {
    // 业务逻辑验证
    if user.Email == "" {
        return errors.New("邮箱不能为空")
    }
    
    // 检查邮箱是否已存在
    existingUser, err := s.userRepo.FindByEmail(user.Email)
    if err == nil && existingUser.ID > 0 {
        return errors.New("该邮箱已被注册")
    }
    
    return s.userRepo.Create(user)
}

// controllers/user_controller.go
package controllers

import (
    "github.com/gin-gonic/gin"
    "myapp/models"
    "myapp/services"
    "strconv"
)

type UserController struct {
    userService *services.UserService
}

func NewUserController(userService *services.UserService) *UserController {
    return &UserController{userService: userService}
}

func (c *UserController) GetAllUsers(ctx *gin.Context) {
    page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
    pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "10"))
    
    users, total, err := c.userService.GetAllUsers(page, pageSize)
    if err != nil {
        ctx.JSON(500, gin.H{"error": "获取用户列表失败"})
        return
    }
    
    ctx.JSON(200, gin.H{
        "total": total,
        "page": page,
        "page_size": pageSize,
        "users": users,
    })
}

// 主函数中的依赖注入
func main() {
    db := database.GetDB()
    
    // 创建仓储
    userRepo := repositories.NewUserRepository(db)
    
    // 创建服务
    userService := services.NewUserService(userRepo)
    
    // 创建控制器
    userController := controllers.NewUserController(userService)
    
    // 设置路由
    r := gin.Default()
    r.GET("/users", userController.GetAllUsers)
    // ...其他路由
    
    r.Run(":8080")
}

4.3 数据验证与错误处理

在数据库操作中,验证和错误处理至关重要:

4.3.1 使用验证器

结合Gin的验证器功能进行数据验证:

type CreateUserRequest struct {
    Name     string `json:"name" binding:"required,min=2,max=50"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required,min=6"`
    Age      int    `json:"age" binding:"gte=0,lte=120"`
}

func CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 继续处理...
}
4.3.2 统一错误处理

创建统一的错误响应格式:

// 自定义错误
type AppError struct {
    Code    int
    Message string
    Details interface{}
}

func (e *AppError) Error() string {
    return e.Message
}

// 错误处理中间件
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) > 0 {
            err := c.Errors.Last().Err
            
            switch e := err.(type) {
            case *AppError:
                c.JSON(e.Code, gin.H{
                    "error": e.Message,
                    "details": e.Details,
                })
            case validator.ValidationErrors:
                // 处理验证错误
                errs := make(map[string]string)
                for _, fe := range e {
                    field := strings.ToLower(fe.Field())
                    errs[field] = getValidationMessage(fe)
                }
                c.JSON(400, gin.H{"errors": errs})
            default:
                // 其他错误
                c.JSON(500, gin.H{"error": "服务器内部错误"})
            }
            
            c.Abort()
        }
    }
}

4.4 高级查询技巧

GORM支持多种高级查询功能:

4.4.1 子查询
// 使用子查询
subQuery := db.Model(&models.User{}).
    Select("id").
    Where("age > ?", 18)

// 获取所有文章,其作者年龄大于18
var articles []models.Article
db.Where("author_id IN (?)", subQuery).Find(&articles)
4.4.2 复杂条件查询
// 使用Scopes分离查询逻辑
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        offset := (page - 1) * pageSize
        return db.Offset(offset).Limit(pageSize)
    }
}

func ActiveUsers(db *gorm.DB) *gorm.DB {
    return db.Where("is_active = ?", true)
}

// 使用方式
var users []models.User
db.Scopes(Paginate(1, 10), ActiveUsers).Find(&users)
4.4.3 聚合函数和原生SQL
// 聚合函数
type Result struct {
    Date  string
    Count int
}

var results []Result

// 按日期统计新用户
db.Model(&models.User{}).
    Select("DATE(created_at) as date, COUNT(*) as count").
    Group("date").
    Order("date DESC").
    Scan(&results)

五、小结与延伸

5.1 知识点回顾

在本文中,我们学习了:

  1. Gin框架与数据库交互的基本原理
  2. GORM框架的安装配置和基本使用
  3. 数据模型定义与表结构映射
  4. 基本的CRUD操作实现
  5. 关联关系的处理
  6. 事务管理
  7. 性能优化和最佳实践

5.2 进阶学习资源

  1. 官方文档:

    • GORM官方文档:https://gorm.io/docs/
    • Gin官方文档:https://gin-gonic.com/docs/
  2. 推荐书籍和文章:

    • 《Go Database Programming》
    • 《Clean Architecture》- Robert C. Martin
  3. 开源项目:

    • go-clean-arch:https://github.com/bxcodec/go-clean-arch
    • gin-gonic/examples:https://github.com/gin-gonic/examples

5.3 下一篇预告

在下一篇文章中,我们将深入探讨Gin框架中的中间件机制,包括:

  • 中间件概念和原理
  • 内置中间件使用
  • 自定义中间件开发
  • 常见应用场景(日志、认证、限流等)

敬请期待!


👨‍💻 关于作者与Gopher部落

"Gopher部落"专注于Go语言技术分享,提供从入门到精通的完整学习路线。

🌟 为什么关注我们?

  1. 系统化学习路径:本系列文章循序渐进,带你完整掌握Gin框架开发
  2. 实战驱动教学:理论结合实践,每篇文章都有可操作的代码示例
  3. 持续更新内容:定期分享最新Go生态技术动态与大厂实践经验
  4. 专业技术社区:加入我们的技术交流群,与众多Go开发者共同成长

📱 关注方式

  1. 微信公众号:搜索 “Gopher部落” 或 “GopherTribe”
  2. 优快云专栏:点击页面右上角"关注"按钮

💡 读者福利

关注公众号回复 “Gin框架” 即可获取:

  • 完整Gin框架学习路线图
  • Gin项目实战源码
  • Gin框架面试题大全PDF
  • 定制学习计划指导

期待与您在Go语言的学习旅程中共同成长!

golang——Gin框架及路由介绍 gin框架中采用的路由库是基于httprouter做的支持Restful风格的API,意思是"表面层状态转化",是一个互联网应用程序的API设计理念:URL定位资源。可以创建路由组,为了管理相同的URL。import ("net/http"//将应用切换到“发布模式”以提升性能//创建路由//默认使用了两个中间件Logger(),Recovery()})})//路由组1})})//路由组2})})//监听端口,默认绑定端口8080。 阅读详情

相关推荐

【Gin框架入门到精通系列】全系列导航手册

本文是Gin框架入门到精通系列的导航手册,系统梳理了从入门到精通的25篇学习资源。包含基础入门、中间件、高级功能、测试部署和架构实战等全方位内容,为您提供清晰的Gin框架学习路径。

GopherTribe的博客 3396

Gin 框架介绍与快速入门

TOC]Gin是一个轻量级的Go语言Web框架,它具有高性能和简洁的设计。由于其快速的路由匹配和处理性能,Gin成为Go语言中最受欢迎的Web框架之一。

淘小欣的博客 6606

05 基于Gin框架的项目开发—单表数据库操作

本文详细介绍了基于DDD和分层架构的业务模块的Gin框架后端开发流程,包含完整的CRUD操作实现。

1 1025

【go语言】Gin框架介绍和使用

Gin中的中间件必须是一个 gin.HandlerFunc 类型的。// StatCost 是一个统计耗时请求耗时的中间件c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值// 调用该请求的剩余处理程序c.Next()// 不调用该请求的剩余处理程序// 计算耗时。

加油,考上985的研究生 5024

2021-05-21 对比GO的Web框架Gin和Beego

对比GO的Web框架Gin和Beego 本人的Git新建了一个Go_Project 链接:https://github.com/DianaTSY/GO_Project.git 我尝试了2个web项目,其中一个使用了gin框架,另一个运用了beego框架 两个项目的前端都使用的是HTML结合Bootstrap库的css + Mysql 对我个人而言,我更偏向于gin框架 因为在数据库操作的时候更简单,也更容易理解(我在这里频频出错) 主要的不同: 路由配置层面 g.GET("/index", func(

涂涂努力ing 4452

docker部署go gin框架 Linux环境

Linux 环境下 docker 部署 go gin 详细步骤

幸享龙枫的博客 1737

Gin框架

Gin介绍与代码实例包括:渲染模板、定义模板分割、渲染静态文件与目录、重定向 配置跨域、创建路由组与路由、解析路由参数、上传文件实例、注册与实现中间件。

weixin_44058223的博客 4957

使用Gin框架构建Go Web API并通过前端与其交互

我们将使用Gin框架构建一个简单的图书管理系统,并通过一个基本的前端页面与API进行交互。我们将详细介绍项目结构、代码实现以及如何解决常见问题。

spiker_的博客 1885

01 基于Gin框架的项目开发—搭建基本项目

本文详细介绍了Go语言开发环境的搭建和基础项目创建流程。

1 847

【UE5 C++开发指南】从零配置Visual Studio 2022开发环境

本文详细指导如何从零开始为UE5 C++开发配置Visual Studio 2022环境。文章强调了VS2022与UE5的深度兼容性,并逐步讲解了社区版下载、核心工作负载(如“使用C++的游戏开发”)与组件安装、界面优化、专属工具设置以及首个C++项目的创建与验证流程,帮助开发者高效搭建专业开发环境。

weixin_29016865的博客 392

Springboot(五十六)SpringBoot3集成SkyWalking

Dskywalking.collector.backend_service:表示skywalking的collector服务的IP及端口。注意上图中红框标注的位置,我们可以根据启动Springboot项目启动时候控制台输出的ID进行筛选。注意上图中红框标注的位置,我们可以根据启动Springboot项目启动时候控制台输出的ID进行筛选。其他的版本的 APM 地址(这个我不需要,我们是在docker中部署的,留给有需要的同学)其他的java 版本的 Agents 地址。

camellia的博客 2600

gin框架入门实战教程10_12

gin

huan 1103

相机标定-机器视觉基础(理论推导、Halcon和OpenCV相机标定)

相机标定是获得目标工件精准坐标信息的基础。首先,必须进行相机内参标定,构建一个模型消除图像畸变;其次,需要对相机和机器人的映射关系进行手眼标定,构建一个模型将图像坐标系上的点映射到世界坐标系。主要分为背景知识、相机内外参模型推导、编程代码实现三个部分。

beyond951的博客 7301

javaer快速入门 goweb框架 gin

/gin的上下文向客户端写入JSON数据",})//响应字符串context.String(200, "Hello 枫枫!")c.Error(errors.New("演示响应错误")) //输出在控制台的呃呃error信息//这样就可以实现返回自定以json 而不需要复写// 定义返回给前端的通用类型 后面的是tag :跟客户端进行序列化时候对应的key 首字母大写 给包外访问权限。

qq_55272229的博客 1203

第八章 MobileNetv3网络详解

本文介绍了基于互补的搜索技术和新颖的架构设计的MobileNets的下一代。MobileNetV3通过硬件感知网络架构搜索(NAS)和NetAdapt算法相结合,针对移动电话CPU进行优化,随后通过新颖的架构改进技术进行了进一步提高。本文开始探索自动化搜索算法和网络设计如何相互协作,以利用互补的方法改进整体技术水平。通过此过程,我们创建了两个新的MobileNet模型:MobileNetV3-Large和MobileNetV3-Small,分别针对高资源和低资源使用情况。

ADICDFHL的博客 1万+

Windows系统下复现PointNet++算法

本文详细介绍了如何在Windows系统下复现PointNet++算法,内容包括程序下载、环境配置、数据集准备以及三个主要任务(语义分割、分类、部件分割)的实现步骤和常见报错解决方法。通过遵循本文的指导,用户可以顺利完成PointNet++算法的复现。

LingYi_5的博客 4326

【Go】Gin框架实战之云餐厅外卖

文章目录Gin入门基础01.Gin简介02.HTTP请求和参数解析创建Engine处理HTTP请求RouterGroup04. 多数据格式返回请求结果05. 中间件的编写和使用Next()的作用07.Gin访问和使用数据库云餐厅项目项目简介项目简介 Gin入门基础 01.Gin简介 02.HTTP请求和参数解析 创建Engine 处理HTTP请求 RouterGroup 04. 多数据格式返回请求结果 05. 中间件的编写和使用 Next()的作用 07.Gin访问和使用数据库 增删改 使用E

王大阳的博客 1587

[QMT量化交易小白入门]-105、震荡市最适合的策略:ETF多标的动态网格策略,回测年化收益47.81%

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写200篇左右。QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。

木头大左的博客 2062

goweb框架-gin

基数树(Radix Tree),又称前缀树(Trie),是一种用于快速检索的数据结构,特别适用于处理字符串的前缀匹配问题。节点:基数树由多个节点组成,每个节点代表字符串中的一个字符。根节点:代表空字符串,是树的起点。路径:从根节点到某一节点的序列,代表一个字符串的前缀。叶子节点:代表一个完整的字符串或模式。压缩:如果一个节点的所有子节点都只有一个子节点,该节点可以被压缩,从而节省空间。基数树的工作原理基于字符串的公共前缀,通过共享这些前缀来减少存储空间。

z3551906947的博客 1586

23、管状剪纸结构的研究与功能生成连杆机构的综合设计

本文研究了管状剪纸结构的压缩运动特性,通过剪纸技术将非刚性可折叠的Yoshimura和Kresling折纸结构转化为刚性可折叠结构,并提出径向可闭合剪纸(RC-kiri)构型。推导了位移与折叠角之间的数学关系,设计了带外部LET接头的RC-kiri模型,建立了力-位移理论模型并通过Abaqus进行有限元验证,制作了3D打印物理模型以测量直径变化。同时,研究了四杆和六杆功能生成连杆机构的综合设计方法,基于负闭合运动链和最小二乘法实现函数生成。结果表明理论、仿真与实验整体吻合良好,误差来源得到分析,为机器人、机

nokia的博客 167

【python基础】一文掌握 Jupyter Notebook 的使用(超级详细!)

是一个非营利组织,致力于为多种编程语言的交互式计算开发开源软件、开放标准和服务。Jupyter 于 2014 年由 Fernando Pérez 从IPython衍生出来,支持几十种编程语言的执行环境。Jupyter 项目主要开发并支持(.ipynb 文件格式)、JupyterHub和JupyterLab等交互式计算产品。

数据知道的博客 1万+

2021-07-05-golang-gin框架

文章目录简介安装使用示例快速入门不同的请求方式-RESTful API返回JSON、XML等格式获取querystring请求参数获取form表单参数获取path(URI)参数参数绑定文件上传重定向路由分组使用中间件自定义HTTP配置创建日志设置并获取cookie 简介 (官方)Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 tim

雪碧味西瓜的博客 984

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Go Web轻量级框架Gin学习系列:HTTP请求日志

我们知道,用户向服务器发起的每一次Web请求,都会通过HTTP协议头部或Body携带许多的请求元信息给服务器,如请求的URL地址,请求方法,请求头部和请求IP地址等等诸多原始信息,而在Gin框架中,我们可以使用日志的方式记录和输出这些信息,记录用户的每一次请求行为。 下面是一条Gin框架在控制台中输出的日志: [GIN] 2019/05/04 - 22:08:56 | 200 | 5.9...

weixin_34267123的博客 1572

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

gin框架05--Multipart/Urlencoded 绑定

gin框架05--Multipart/Urlencoded 绑定介绍案例说明 介绍 本文介绍 gin中multipart/urlencoded绑定, 用户 post 指定数据后,如符合要求则给出对应的输出。 案例 代码 package main import ( "github.com/gin-gonic/gin" ) type LoginForm struct { User string `form:"user" binding:"required"` Password string `

脚步不能达到的地方,眼光可以达到;眼光不能达到的地方,精神可以飞到 1073

go语言day21 goland使用gin框架、gorm框架操作mysql数据库redis数据库 使用宝塔创建redis数据库 写一个投票项目

这里实现一个查询所有参赛选手的业务需求,也就是控制器接收活动aid,根据aid查询并返回player表的对应aid的所有players信息。去mysql数据库查询活动需要的信息:该活动aid 对应的所有参赛选手的id然后去redis数据库添加缓存信。这里的mysql连接对象dao.Db调用的增删改查方法默认封装了事务处理,并且会根据实体类Player的TableName()方法返回值确定查询的实体类对应的表名。查找到了正确的value活动信息,从数据库中根据value中的id查询每个选手的详细信息。

weixin_45939821的博客 1124

Golang入门到精通

课程名称 Golang入门到精通 自我介绍 网名: 风萧萧兮 学历:本科 目前在职、5年Java开发经验、1年的Golang开发经验 做课程的目的:利用业余时间希望和大家一起学习、分享、交流 Golang入门到精通-入门篇(偏基础) 第01讲 为什么要学习Golang 第02讲 Golang环境安装 第03讲 Golang开发工具 第04讲 Golang语言结构 第05讲 Golang基础语法 第06讲 Golang数据类型 第07讲 Golang变量 第08讲 Golang常

u010887012的博客 930
上一篇: 【Gin框架入门到精通系列04】Gin中的路由分组
下一篇: 【Gin框架入门到精通系列06】Gin中的中间件机制
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值