Gin 框架学习实录 · 第3篇:集成 GORM + MySQL,实现注册用户入库

前言

在上一篇中,我们已经实现了一个可以接收 JSON 请求的 /register 接口。那我们既然可以正常拿到请求参数 我们是不是就可以进一步完善功能,将用户数据真正写入 MySQL 数据库

项目结构更新

go 复制代码
gin-learn-notes/
├── config/
│   └── database.go       ✅ 新增:初始化数据库
├── model/
│   └── user.go           ✅ 新增:用户数据模型
├── controller/
│   └── user.go           ✅ 修改:写入数据库
├── main.go               ✅ 修改:初始化 DB

第一步:安装 GORM 和 MySQL 驱动

bash 复制代码
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

第二步:配置数据库连接

创建文件 config/database.go

go 复制代码
package config

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

var DB *gorm.DB // 全局数据库实例

func InitDB() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
       NamingStrategy: schema.NamingStrategy{
           SingularTable: true, // 表名不加 s,user 而不是 users
       },
   })
	if err != nil {
		panic("数据库连接失败:" + err.Error())
	}
	DB = db

	fmt.Println(" 数据库连接成功")
}

⚠️ 记得提前在自己的本地 MySQL 中创建好 demo 数据库。

关于 SingularTable: true

GORM 默认会把模型名转成复数作为表名:

  • 结构体名为 User,默认表名是 users
  • 加了 SingularTable: true 后,表名就会是 user(保持单数)

这是为了保持和我们手动建表时的命名一致,特别适合不想用复数表名的团队或开发者(比如我 😂)

第三步:定义数据模型

新建文件 model/user.go

go 复制代码
package model

type User struct {
    ID   uint   `gorm:"primaryKey"`
    Name string `gorm:"size:50"`
    Age  int    `gorm:"default:18"`
}

补充说明:GORM 字段标签解析 我们定义了用户模型结构体如下:

go 复制代码
type User struct {
    ID   uint   `gorm:"primaryKey"`
    Name string `gorm:"size:50"`
    Age  int    `gorm:"default:18"`
}

GORM 通过结构体字段上的 gorm:"..." 标签来自定义字段的行为,这里每个字段都做了精简的配置:

字段 标签说明 含义
ID primaryKey 设置为主键,GORM 会自动将其作为表的主键字段
Name size:50 设置该字段最大长度为 50(默认是 255)
Age default:18 设置该字段在插入时默认值为 18

这些标签在 AutoMigrate 自动建表时,会影响最终生成的表结构。

第四步:在 main.go 中初始化数据库并自动建表

go 复制代码
package main

import (
	"gin-learn-notes/config"
	"gin-learn-notes/model"
	"gin-learn-notes/router"
)

func main() {
	// 初始化数据库
	config.InitDB()

	// 自动建表
	config.DB.AutoMigrate(&model.User{})

	// 启动路由
	r := router.InitRouter()
	r.Run(":8080")
}

补充说明:为什么在 main.go 中自动建表?

我们上面在 main.go 中调用:

go 复制代码
config.DB.AutoMigrate(&model.User{})

这样可以让 GORM 在项目启动时,自动根据结构体模型创建(或更新)数据库表结构,这对开发调试非常方便:

  • 新增字段或模型后,无需手动写建表 SQL
  • 本地开发时可以快速迭代数据库结构

但是正式环境建议谨慎使用!

虽然 AutoMigrate() 很方便,但在 正式环境 中通常不建议自动执行建表操作,原因是:

  • 自动迁移可能会覆盖或误改已有表结构
  • 不可控,无法精细管理字段变更或约束
  • 正式环境更推荐使用手动 SQL 迁移版本化迁移工具 (如 golang-migrate

这里我是为了便于学习,才选择在 main.go 中自动建表;将来上线时,要使用更严谨的方式管理数据库结构变更。

第五步:在注册接口中写入数据库

我们修改 controller/user.go

go 复制代码
package controller

import (
	"github.com/gin-gonic/gin"
	"gin-learn-notes/config"
	"gin-learn-notes/model"
	"net/http"
)

type RegisterRequest struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func Register(c *gin.Context) {
	var req RegisterRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	user := model.User{
		Name: req.Name,
		Age:  req.Age,
	}

	if err := config.DB.Create(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "用户保存失败"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "注册成功",
		"user_id": user.ID,
	})
}

上面代码中我们在控制器中做了这一段逻辑 👇:

go 复制代码
user := model.User{
    Name: req.Name,
    Age:  req.Age,
}

意思是:

  • 将前端传来的参数(已经通过结构体绑定后的 req)封装为一个 User 模型对象
  • 这个结构体和数据库表字段是一一对应的(通过 GORM 映射)
go 复制代码
if err := config.DB.Create(&user).Error; err != nil {
    ...
}

这句是关键的数据库操作:

  • config.DB 是我们前面初始化好的 GORM 实例
  • Create(&user) 表示插入一条新记录到 user 表中
  • .Error 是 GORM 返回的执行结果错误(如果有)

如果写入失败(例如数据库连接中断、字段类型不匹配等),我们就返回一个 500 错误响应:

go 复制代码
c.JSON(http.StatusInternalServerError, gin.H{"error": "用户保存失败"})

如果写入成功,GORM 会自动为 user.ID 填充数据库生成的主键 ID,我们把它一并返回给前端:

go 复制代码
c.JSON(http.StatusOK, gin.H{
    "message": "注册成功",
    "user_id": user.ID,
})

这段代码就实现了一个完整的用户注册入库流程

前端提交 JSON → 后端绑定参数 → 构造模型 → 写入数据库 → 返回响应。

后面我们还可以为这一步添加参数校验、重复检查、密码加密、事务处理等功能,逐步完善。

第六步:测试!

启动程序:

bash 复制代码
go run main.go

使用 Postman 或 curl 发送 POST 请求:

json 复制代码
POST /register
{
  "name": "Alice",
  "age": 20
}

数据库中就会新增一条用户记录!

这样操作后我们就已经学会了:

  • 使用 GORM 连接 MySQL
  • 创建数据模型并自动建表
  • 在接口中插入数据

最后我们目前的结构目录就成了现在这样:

text 复制代码
gin-learn-notes/
├── config/            # 配置模块
│   └── database.go    # 数据库初始化
├── controller/        # 控制器层,处理路由逻辑
│   ├── hello.go
│   ├── index.go
│   └── user.go
├── model/             # 数据模型(对应数据表)
│   └── user.go
├── router/            # 路由注册
│   └── router.go
├── main.go            # 程序入口,初始化数据库与启动服务
├── go.mod             # Go Modules 管理文件
├── .gitignore
└── README.md

项目结构说明

  • config/:用于初始化各种全局配置(如数据库、后续可能的日志配置等)
  • controller/:控制器层,处理具体业务逻辑,对外暴露路由接口
  • model/:模型层,定义数据库表结构
  • router/:负责路由统一注册,保持 main.go 简洁
  • main.go:程序主入口,包含数据库初始化、建表、启动服务等

目前我们采用的是最基础的三层结构(控制器 → 模型 → 数据库),接下来可以继续拆出:

  • service/:业务逻辑服务层(推荐在第 4~5 篇加入)
  • request/:请求参数结构体封装(便于加校验标签)
  • middleware/:中间件,如 JWT、日志、跨域等

这样项目将更加清晰、可扩展。后面我们会逐步进行拆分封装。

最后

在本篇中,我们完成了用户注册接口的落地实现,并正式接入了数据库操作,主要包括:

  • 安装并集成 GORM + MySQL 驱动
  • 编写数据库初始化逻辑,并在启动时自动建表
  • 定义用户数据模型,并使用结构体标签控制字段行为
  • 将前端提交的数据写入数据库,并返回响应结果

至此,我们的 Gin 项目已经具备了从接收请求 → 处理参数 → 写入数据库 → 返回响应的基本能力,搭建起了一个初步的后端接口服务框架。

本篇对应代码提交记录

commit: 9f57513fdd089d9c8f7dad5d4a3c2577ccf24234

👉 GitHub 源码地址:github.com/luokakale-k...


下一篇我们将继续深入,学习使用 binding 标签配合 Gin 的内置校验器,实现请求参数的自动验证,提升接口的健壮性与开发体验。

同时,我们还将对请求参数结构体和响应结构体进行分离管理,让代码结构更加清晰、职责更明确,也为后续的统一返回格式和接口文档打好基础。

相关推荐
宾燕哥哥8 小时前
Go 语言基础学习文档
go
Way2top8 小时前
Go语言动手写Web框架 - Gee第一天
go
探索云原生10 小时前
Buildah 简明教程:让镜像构建更轻量,告别 Docker 依赖
linux·docker·云原生·go·cicd
越千年10 小时前
工作中常用到的二进制运算
后端·go
踏浪无痕16 小时前
信不信?一天让你从Java工程师变成Go开发者
后端·go
卡尔特斯1 天前
Go 语言入门核心概念总结
go
代码扳手2 天前
从0到1揭秘!Go语言打造高性能API网关的核心设计与实现
后端·go·api
未来魔导2 天前
go语言中json操作总结(下)
数据分析·go·json
未来魔导2 天前
Go-qdrant-API开启客服系统新模式
go·api·qdrant
喵个咪3 天前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:Makefile 在后端开发中的应用与 Windows 环境配置
后端·go