gorm基础:自定义数据类型

十、自定义数据类型

自定义的数据类型必须实现 Scanner 和 Valuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库

1. 存储结构体

go 复制代码
type Info struct {
  Status string `json:"status"`
  Addr   string `json:"addr"`
  Age    int    `json:"age"`
}

// Scan 从数据库中读取出来
func (i *Info) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }

  info := Info{}
  err := json.Unmarshal(bytes, &info)
  *i = info
  return err
}

// Value 存入数据库
func (i Info) Value() (driver.Value, error) {
  return json.Marshal(i)
}

type User struct {
  ID   uint
  Name string
  Info Info `gorm:"type:string"`
}
添加和查询
go 复制代码
DB.Create(&User{
  Name: "枫枫",
  Info: Info{
    Status: "牛逼",
    Addr:   "成都市",
    Age:    21,
  },
})

var user User
DB.Take(&user)
fmt.Println(user)

2. 枚举类型

枚举1.0

很多时候,我们会对一些状态进行判断,而这些状态都是有限的

例如,主机管理中,状态有 Running 运行中, OffLine 离线, Except 异常

如果存储字符串,不仅是浪费空间,每次判断还要多复制很多字符,最主要是后期维护麻烦

go 复制代码
type Host struct {
  ID     uint
  Name   string
  Status string
}

func main() {
  host := Host{}
  if host.Status == "Running" {
    fmt.Println("在线")
  }
  if host.Status == "Except" {
    fmt.Println("异常")
  }
  if host.Status == "OffLine" {
    fmt.Println("离线")
  }
}

后来,我们知道了用常量存储这些不变的值

go 复制代码
type Host struct {
  ID     uint
  Name   string
  Status string
}

const (
  Running = "Running"
  Except = "Except"
  OffLine = "OffLine"
) 

func main() {
  host := Host{}
  if host.Status == Running {
    fmt.Println("在线")
  }
  if host.Status == Except {
    fmt.Println("异常")
  }
  if host.Status == OffLine {
    fmt.Println("离线")
  }
}

虽然代码变多了,但是维护方便了

但是数据库中存储的依然是字符串,浪费空间这个问题并没有解决

枚举2.0

于是想到使用数字表示状态

go 复制代码
type Host struct {
  ID     uint
  Name   string
  Status int
}

const (
  Running = 1
  Except  = 2
  OffLine = 3
)

func main() {
  host := Host{}
  if host.Status == Running {
    fmt.Println("在线")
  }
  if host.Status == Except {
    fmt.Println("异常")
  }
  if host.Status == OffLine {
    fmt.Println("离线")
  }
}

但是,如果返回数据给前端,前端接收到的状态就是数字,不过问题不大,前端反正都要搞字符映射的

因为要做颜色差异显示

但是这并不是后端偷懒的理由

于是我们想到,在json序列化的时候,根据映射转换回去

go 复制代码
type Host struct {
  ID     uint   `json:"id"`
  Name   string `json:"name"`
  Status int    `json:"status"`
}

func (h Host) MarshalJSON() ([]byte, error) {
  var status string
  switch h.Status {
  case Running:
    status = "Running"
  case Except:
    status = "Except"
  case OffLine :
    status = "OffLine"
  }
  return json.Marshal(&struct {
    ID     uint   `json:"id"`
    Name   string `json:"name"`
    Status string `json:"status"`
  }{
    ID:     h.ID,
    Name:   h.Name,
    Status: status,
  })
}

const (
  Running = 1
  Except  = 2
  OffLine  = 3
)

func main() {
  host := Host{1, "枫枫", Running}
  data, _ := json.Marshal(host)
  fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}

这样写确实可以实现我们的需求,但是根本就不够通用,凡是用到枚举,都得给这个Struct实现MarshalJSON方法

枚举3.0

于是类型别名出来了

go 复制代码
type Status int

func (status Status) MarshalJSON() ([]byte, error) {
  var str string
  switch status {
  case Running:
    str = "Running"
  case Except:
    str = "Except"
  case OffLine:
    str = "Status"
  }
  return json.Marshal(str)
}

type Host struct {
  ID     uint   `json:"id"`
  Name   string `json:"name"`
  Status Status `json:"status"`
}

const (
  Running Status = 1
  Except  Status = 2
  OffLine Status = 3
)

func main() {
  host := Host{1, "枫枫", Running}
  data, _ := json.Marshal(host)
  fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}

嗯,代码简洁了不少,在使用层面已经没有问题了

但是,这个结构体怎么表示数据库中的字段呢?

golang中没有枚举

我们只能自己通过逻辑实现枚举

go 复制代码
type Weekday int

const (
  Sunday    Weekday = iota + 1 // EnumIndex = 1
  Monday                       // EnumIndex = 2
  Tuesday                      // EnumIndex = 3
  Wednesday                    // EnumIndex = 4
  Thursday                     // EnumIndex = 5
  Friday                       // EnumIndex = 6
  Saturday                     // EnumIndex = 7
)

var WeekStringList = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
var WeekTypeList = []Weekday{Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}

// String 转字符串
func (w Weekday) String() string {
  return WeekStringList[w-1]
}

// MarshalJSON 自定义类型转换为json
func (w Weekday) MarshalJSON() ([]byte, error) {
  return json.Marshal(w.String())
}

// EnumIndex 自定义类型转原始类型
func (w Weekday) EnumIndex() int {
  return int(w)
}

// ParseWeekDay 字符串转自定义类型
func ParseWeekDay(week string) Weekday {
  for i, i2 := range WeekStringList {
    if week == i2 {
      return WeekTypeList[i]
    }
  }
  return Monday
}

// ParseIntWeekDay 数字转自定义类型
func ParseIntWeekDay(week int) Weekday {
  return Weekday(week)
}

type DayInfo struct {
  Weekday Weekday   `json:"weekday"`
  Date    time.Time `json:"date"`
}

func main() {
  w := Sunday
  fmt.Println(w)
  dayInfo := DayInfo{Weekday: Sunday, Date: time.Now()}
  data, err := json.Marshal(dayInfo)
  fmt.Println(string(data), err)
  week := ParseWeekDay("Sunday")
  fmt.Println(week)
  week = ParseIntWeekDay(2)
  fmt.Println(week)
}

在需要输出的时候(print,json),自定义类型就变成了字符串

从外界接收的数据也能转换为自定义类型,这就是golang中的枚举,假枚举

相关推荐
web守墓人4 小时前
【gpt生成-其一】以go语言为例,详细描述一下 :语法规范BNF/EBNF形式化描述
前端·gpt·golang
yoke菜籽4 小时前
k8s报错kubelet.go:2461] “Error getting node“ err=“node \“k8s-master\“ not found“
golang·kubernetes·kubelet
Golinie7 小时前
Go-zero框架修改模版进行handler统一响应封装
golang·gozero·模版引擎
Villiam_AY7 小时前
go语言对http协议的支持
开发语言·http·golang
虽千万人 吾往矣8 小时前
golang context源码
android·开发语言·golang
Delphi菜鸟17 小时前
go环境安装mac
开发语言·后端·golang
Delphi菜鸟20 小时前
go+mysql+cocos实现游戏搭建
mysql·游戏·golang·gin·cocos2d
why15120 小时前
字节头条golang二面
开发语言·后端·golang