第29章:自定义数据类型
本章目标
学完本章后,你将能够:
- 理解
Scanner和Valuer接口 - 实现自定义 JSON、枚举、加密类型
- 使用
serializer标签简化序列化 - 根据需求选择 Scanner/Valuer 还是 Serializer
前置知识
- 需要先学习:第06-07章(模型标签)
- 需要了解:Go 接口基本概念
29.1 Scanner 和 Valuer 接口
Go 标准库 database/sql 定义了两个接口:
go
// Scanner:从数据库读取(数据库值 → Go 值)
type Scanner interface {
Scan(src interface{}) error
}
// Valuer:写入数据库(Go 值 → 数据库值)
type Valuer interface {
Value() (driver.Value, error)
}GORM 完全兼容这两个接口。实现它们,你就能自定义任何类型在数据库中的存储方式。
29.2 自定义 JSON 类型
go
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// JSONMap:存到数据库是 JSON 字符串,Go 里是 map
type JSONMap map[string]interface{}
// Scan:从数据库读取 JSON 字符串 → 还原为 map
func (j *JSONMap) Scan(value interface{}) error {
if value == nil {
*j = make(JSONMap)
return nil
}
bytes, ok := value.([]byte)
if !ok {
return fmt.Errorf("无法转换 %T 为 JSONMap", value)
}
return json.Unmarshal(bytes, j)
}
// Value:Go 的 map → 序列化为 JSON 字符串存入数据库
func (j JSONMap) Value() (driver.Value, error) {
if j == nil {
return nil, nil
}
return json.Marshal(j)
}使用:
go
type User struct {
ID uint
Name string
Settings JSONMap `gorm:"type:json"`
}
user := User{
Name: "张三",
Settings: JSONMap{
"theme": "dark",
"lang": "zh",
"notify": true,
},
}
db.Create(&user)
// Settings 自动存储为: {"theme":"dark","lang":"zh","notify":true}
// 读取时自动还原为 map29.3 自定义枚举类型
go
type Gender string
const (
GenderMale Gender = "male"
GenderFemale Gender = "female"
GenderOther Gender = "other"
)
func (g *Gender) Scan(value interface{}) error {
if value == nil {
*g = ""
return nil
}
switch v := value.(type) {
case []byte:
*g = Gender(v)
case string:
*g = Gender(v)
}
return nil
}
func (g Gender) Value() (driver.Value, error) {
return string(g), nil
}29.4 自定义加密类型
go
type EncryptedString string
var encryptionKey = []byte("0123456789abcdef") // 16字节密钥
// Value:写入数据库前加密
func (es EncryptedString) Value() (driver.Value, error) {
if es == "" {
return "", nil
}
encrypted, err := aesEncrypt([]byte(es), encryptionKey)
if err != nil {
return nil, err
}
// 返回 Base64 编码的密文
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// Scan:从数据库读取后解密
func (es *EncryptedString) Scan(value interface{}) error {
if value == nil {
*es = ""
return nil
}
var encryptedStr string
switch v := value.(type) {
case []byte:
encryptedStr = string(v)
case string:
encryptedStr = v
}
encrypted, _ := base64.StdEncoding.DecodeString(encryptedStr)
decrypted, err := aesDecrypt(encrypted, encryptionKey)
if err != nil {
return err
}
*es = EncryptedString(decrypted)
return nil
}使用:
go
type User struct {
ID uint
Name string
Secret EncryptedString `gorm:"type:text"` // 存入数据库自动加密
}
user := User{Name: "张三", Secret: "这是一条秘密信息"}
db.Create(&user) // 存入时自动加密
var found User
db.First(&found, user.ID)
fmt.Println(found.Secret) // 读取时自动解密 → "这是一条秘密信息"29.5 serializer 标签(简化方案)
很多场景不需要手写 Scanner/Valuer,GORM 内置了序列化器:
go
type UserSettings struct {
Theme string `json:"theme"`
Lang string `json:"lang"`
Notify bool `json:"notify"`
}
type User struct {
ID uint
Name string
Settings UserSettings `gorm:"serializer:json"` // 自动 JSON 序列化
Tags []string `gorm:"serializer:json"` // 切片自动 JSON
}| 序列化器 | 用途 |
|---|---|
serializer:json | JSON 序列化 |
serializer:unixtime | Unix 时间戳 |
serializer:unixmilli | Unix 毫秒时间戳 |
serializer:gob | Gob 二进制 |
| 自定义 | 实现 SerializerInterface |
29.6 选择建议
| 场景 | 推荐 |
|---|---|
| struct/map/slice 存为 JSON | serializer:json |
| 需要复杂加密逻辑 | 自定义 Scanner/Valuer |
| 兼容已有代码 | 自定义 Scanner/Valuer |
| 枚举类型 | 自定义类型 + Scan/Value |
| 简单序列化 | serializer 标签 |
本章小结
Scanner(读)和Valuer(写)实现类型的自定义存储serializer:json是存储 struct/map/slice 的最简方案- 加密类型:Valuer 中加密,Scanner 中解密
- GORM 兼容
database/sql的 Scanner/Valuer 接口
练习题
- 实现一个自定义 JSON 类型
JSONArray(存储为 JSON 数组字符串)。 - 实现一个
Status枚举类型(active/inactive/banned)。 - 使用
serializer:json存储用户偏好设置。 - 实现一个简单的加密字符串类型。
- (思考题)Scanner/Valuer 和 Serializer 有什么区别?为什么 GORM 要保留两套机制?