This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/GORM/02-模型定义.md
T
2026-04-28 20:02:42 +08:00

206 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [GORM, Go, ORM, 模型, struct tag, 字段映射, 主键, 表名]
create time: 2026-04-28 00:00
---
# 模型定义
## 概述
模型(Model)是 GORM 操作的核心入口——一个普通的 Go struct 被 GORM「看见」后,就可以直接进行数据库操作。本章讲解如何通过 struct tag、命名约定和接口来自定义 GORM 对模型的解读。
## 什么是 Model?
> [!definition] Model
> Model 是一个包含以下任一条件的 struct:
> 1. 有主键字段
> 2. 定义了 `TableName()` 方法
> 3. 被 `AutoMigrate`、`Create`、`Where` 等方法引用
```go
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:64;not null"`
}
// 这就是一个最简单的 GORM Model
```
## 表名规则
### 默认命名策略
GORM 使用 `Table` 方法来推导表名:
| 代码行为 | 结果 |
|----------|------|
| 默认 | struct 名的蛇形复数形式(`User` → `users`) |
| 实现 `TableName() string` | 返回的字符串 |
| 使用 `db.Table("xxx")` | 查询时使用的表名(不会修改 Model 的 TableName) |
```go
func (User) TableName() string {
return "sys_user" // 显式指定表名
}
```
> [!tip] 多环境表名
> 不同环境下表名前缀不同?可以用环境变量 + `TableName()` 实现动态切换:
> ```go
> func (User) TableName() string {
> if os.Getenv("ENV") == "test" {
> return "test_sys_user"
> }
> return "sys_user"
> }
> ```
## 字段 Tag 详解
### 完整 Tag 语法
```
gorm:"column:name;type:bigint;not null;default:0;uniqueIndex;index;comment:用户ID"
```
| Tag 关键字 | 作用 | 示例 |
|------------|------|------|
| `column` | 映射列名 | `column:user_id` |
| `type` | 列类型覆盖 | `type:varchar(128)` |
| `not null` | NOT NULL 约束 | `not null` |
| `default` | 默认值 | `default:'unknown'` |
| `primaryKey` | 主键 | `primaryKey` |
| `autoIncrement` | 自增 | `autoIncrement` |
| `uniqueIndex` | 唯一索引 | `uniqueIndex:idx_name` |
| `index` | 普通索引 | `index:idx_status` |
| `comment` | 注释(MySQL) | `comment:用户名` |
| `<-` | 字段写入控制 | `<-:true` / `<-:false` / `<-:create` / `<-:update` |
| `->` | 只读/只写 | `->:true`(只读) `/ <-:false`(不写入) |
| `-` | 忽略此字段 | `-` |
### 写入权限控制
```go
type Product struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"not null;<-:create"` // 仅创建时可写入
Price float64 `gorm:"not null;<-:true;->:true"` // 读写均可(默认)
CreatedAt time.Time `gorm:"<-:create;->:true;autoTime"` // 仅创建时自动填充
UpdatedAt time.Time `gorm:"<-:update;->:true;autoTime"` // 仅更新时自动填充
ViewCount int `gorm:"->:false;<-:false"` // 完全忽略(非 DB 字段)
}
```
> [!example] 方向记忆法
> `<-` 表示数据**流向数据库**(写入),`->` 表示数据**从数据库流出**(读取)。箭头方向就是数据的方向。
## 字段类型映射
| Go 类型 | 推荐 DB 类型 | 说明 |
|---------|-------------|------|
| `int`, `int64` | BIGINT | GORM 默认以 `int64` 处理 |
| `uint`, `uint64` | BIGINT UNSIGNED | — |
| `string` | VARCHAR(n) | 需手动指定 size |
| `bool` | TINYINT(1) / BOOLEAN | — |
| `float64` | DOUBLE | — |
| `time.Time` | DATETIME / TIMESTAMP | 配合 `autoTime` tag |
| `[]byte` | BLOB / BYTEA | 二进制数据 |
| `json.RawMessage` | JSON | JSON 序列化字段 |
### `autoTime` 自动时间戳
```go
type Article struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"size:128;not null"`
CreatedAt time.Time `gorm:"autoTime;createTime"` // 创建时自动填充
UpdatedAt time.Time `gorm:"autoTime;updateTime"` // 更新时自动填充
}
```
> [!note] createTime / updateTime
> 这两个子 tag 是 GORM v2 的增强功能,配合 `autoTime` 使用。单独 `autoTime` 只更新时间,加上 `createTime` 后才会在插入时设置 CreatedAt。
## 主键策略
### 默认策略:ID uint
```go
type User struct {
ID uint // GORM 默认将名为 ID 的字段视为主键
Name string
}
```
### UUID 主键
```go
import "github.com/google/uuid"
type Order struct {
ID uuid.UUID `gorm:"type:char(36);primaryKey"`
Code string `gorm:"uniqueIndex;not null"`
}
// 在 Create 前生成 UUID
order.ID = uuid.New()
db.Create(&order)
```
### 复合主键
```go
type OrderItem struct {
OrderID uint `gorm:"primaryKey"`
ProductID uint `gorm:"primaryKey"`
Quantity int `gorm:"not null"`
}
// 生成表结构:PRIMARY KEY (order_id, product_id)
```
> [!tip] 复合主键注意事项
> 使用复合主键时,`First()` 和 `Take()` 将无法工作(因为它们期望单主键),必须使用 `Where()` 精确定位。
## 自定义类型作为字段
当内置类型不够用时,可以实现 GORM 接口:
```go
type MyType int
func (m MyType) GORMDataType() string {
return "INT"
}
func (m MyType) Value() (driver.Value, error) {
return int(m), nil // ScannerValuer 接口的 Value 方法
}
```
## 模型设计流程图
```mermaid
flowchart TD
A[定义 struct] --> B{有无 ID 字段?}
B -->|有| C["ID 作主键<br/>uint/int64"]
B -->|无| D{自定义 TableName?}
D -->|实现了| E[按 TableName 返回值]
D -->|未实现| F["struct 名蛇形复数"]
C --> G[添加字段 tag]
E --> G
G --> H{需要特殊类型?}
H -->|是| I[实现 GORMDataType / Valuer]
H -->|否| J[完成]
I --> J
style A fill:#4FC08D,color:#fff
style C fill:#3B82F6,color:#fff
style I fill:#EAB308,color:#fff
style J fill:#A0AEC0,color:#fff
```
## 关联笔记
- [[01-安装与初始化]]
- [[03-CRUD 操作]]
- [[12-自定义字段类型]]