下载工作台
Gin Web 开发

GORM 与 MySQL 建模

试读上半部分 · 解锁后可读全文

第 4 章 · GORM 与 MySQL 建模

本章目标:使用 GORM 定义 ProductUser 模型;执行 AutoMigrate 自动建表;字段与 django-webfastapi-webflask-web 对齐:slug + is_published,价格 int64 分;User 含 email、password_hash(bcrypt)、role;可选 docker compose 启动 MySQL 8;对照 fastapi-web ch04/ch05 SQLAlchemy 模型。

学时建议:4~5 小时(含 2 小时跟练)

前置:完成 gin-web ch03;基本 SQL;Docker 可选。系统 SQL/库表设计见 go-database ch01~ch07;database/sql 与 GORM 深入见 go-database ch08~ch12


4.1 场景说明:持久化商品与用户

ch02 的 mock 数据需迁入 MySQL。api-go-demo 与全站课程统一商品字段,禁止使用旧字段 is_active 或 float 元价格。

实体字段类型说明
Productslugvarchar(64) uniqueURL 标识
Productnamevarchar(200)名称
Productpricebigint,如 6800 = 68.00 元
Productstockint库存
Productis_publishedbool上架状态
Productdescriptiontext可选描述
Useremailvarchar(255) unique登录名
Userpassword_hashvarchar(255)bcrypt,非明文
Userrolevarchar(32)user / admin
internal/
├── model/
│   ├── product.go
│   └── user.go
└── database/
    ├── mysql.go
    └── migrate.go

对照章节

主题flask-web ch05fastapi-web ch05gin-web ch04
ORMSQLAlchemySQLAlchemy 2.0GORM
迁移Flask-MigrateAlembicAutoMigrate / migrate
价格分或 DecimalDecimal 元int64 分

4.2 逐步操作表

步骤操作验证
1go get gorm.io/gorm gorm.io/driver/mysqlgo.mod 更新
2docker compose up -d mysql(选修)3306 可连
3配置 DATABASE_URLconfig.Load 通过
4编写 Product/User model编译通过
5AutoMigrateproductsusers 存在
6SeedProducts3 条种子数据
7查询 is_published=true返回 2 条
8重复 slug 插入唯一约束报错

4.3 Docker Compose(选修)

项目根 docker-compose.yml

services:
  mysql:
    image: mysql:8.0
    container_name: api-go-demo-mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: api_demo
      MYSQL_USER: api_demo
      MYSQL_PASSWORD: api_demo_pass
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

volumes:
  mysql_data:
docker compose up -d
docker compose ps

.env 中 DSN:

DATABASE_URL=api_demo:api_demo_pass@tcp(127.0.0.1:3306)/api_demo?charset=utf8mb4&parseTime=True&loc=Local
参数作用
charset=utf8mb4支持 emoji 与中文
parseTime=Truetime.Time 正确扫描
loc=Local本地时区

4.4 依赖与 MySQL 连接

go get gorm.io/gorm@v1.25.12
go get gorm.io/driver/mysql@v1.5.7
// internal/database/mysql.go
package database

import (
    "time"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

func NewMySQL(dsn string, debug bool) (*gorm.DB, error) {
    lvl := logger.Warn
    if debug {
        lvl = logger.Info
    }
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(lvl),
        NowFunc: func() time.Time {
            return time.Now().Local()
        },
    })
    if err != nil {
        return nil, err
    }
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    sqlDB.SetMaxOpenConns(25)
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetConnMaxLifetime(time.Hour)
    return db, nil
}

DSN 来自 ch03 DATABASE_URL;GORM mysql driver 接受 user:pass@tcp(host:port)/db?params 格式。


4.5 Product 模型(完整)

// internal/model/product.go
package model

import "time"

type Product struct {
    ID          uint      `gorm:"primaryKey"`
    Slug        string    `gorm:"uniqueIndex;size:64;not null"`
    Name        string    `gorm:"size:200;not null"`
    Price       int64     `gorm:"not null"` // 单位:分
    Stock       int       `gorm:"default:0;not null"`
    IsPublished bool      `gorm:"column:is_published;default:false;not null"`
    Description string    `gorm:"type:text"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

func (Product) TableName() string { return "products" }

// PriceYuan 仅展示用,禁止写入 DB
func (p Product) PriceYuan() float64 {
    return float64(p.Price) / 100.0
}
GORM tag数据库效果
uniqueIndexUNIQUE KEY on slug
column:is_published列名 snake_case
size:64VARCHAR(64)

4.6 User 模型(完整)

// internal/model/user.go
package model

import "time"

type User struct {
    ID           uint      `gorm:"primaryKey"`
    Email        string    `gorm:"uniqueIndex;size:255;not null"`
    PasswordHash string    `gorm:"column:password_hash;size:255;not null"`
    Role         string    `gorm:"size:32;default:user;not null"` // user | admin
    IsActive     bool      `gorm:"column:is_active;default:true;not null"`
    CreatedAt    time.Time
    UpdatedAt    time.Time
}

func (User) TableName() string { return "users" }
角色权限预告(ch06 RBAC)
user读已发布商品、改自己资料
admin商品 CRUD、上下架

注意:密码存 password_hash,由 ch06 bcrypt.GenerateFromPassword 写入,模型层不含明文密码字段。


以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。