-
发生时间:2022-10-26
-
问题描述:同事在测试产线平台前端时,发现后端返回的数据的「创建日期」格式为
2022-10-25T07:05:02.063Z,可读性不好,希望后端修改一下。 -
问题类别:软件开发
-
原因分析:
- 产线平台后端使用 gorm 来完成数据库相关操作,其中需要为每张数据库表定义一个对应的结构体。
由于每张数据库表都拥有一些共同的字段,因此这些结构体都内嵌了一个公共的结构体
Model(如下所示)。
// Model represents the common information of every model. type Model struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt time.Time ` json:"create_time"` UpdatedAt time.Time ` json:"update_time"` }-
产线平台接收到前端的请求后,会进行相关的数据库操作,并将结果保存到对应的结构体中。 该结构体会作为响应结果的
data字段,被 gin 框架进行 json 序列化后再返回给用户。 -
go 将
time.Time类型的变量进行 json 序列化后的格式便是2022-10-25T07:05:02.063Z, 其中T为日期与时间的分隔符,,Z代表零时区,详见 What exactly does the T and Z mean in timestamp?
- 产线平台后端使用 gorm 来完成数据库相关操作,其中需要为每张数据库表定义一个对应的结构体。
由于每张数据库表都拥有一些共同的字段,因此这些结构体都内嵌了一个公共的结构体
-
解决方案:
- 比较直接的思路是在 json 序列化后再调整
data字段中所有time.Time字段的时间格式, 但如果data字段比较复杂时处理起来会比较麻烦,效率较低,代码显得也不优雅。 - 因此我一直在考虑如何在 json 序列化的时候就采用自定义的时间格式,而非序列化完成后再调整时间格式。
- 经过网上调查,在 stack overflow 找到一个解决方案(如下所示)。
其思路是将
time.Time封装成自定义类型,然后为新类型定义MarshalJSON方法,在该方法内修改时间格式即可。
type JSONTime time.Time func (t JSONTime)MarshalJSON() ([]byte, error) { stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format("2006-01-02 15:04:05")) return []byte(stamp), nil }- 于是我将
Model结构体中CreatedAt和UpdatedAt修改为JSONTime类型,如下所示。
// Model represents the common information of every model. type Model struct { ID uint `gorm:"primarykey" json:"id"` CreatedAt JSONTime ` json:"create_time"` UpdatedAt JSONTime ` json:"update_time"` }- 不幸的是,修改后,虽然编译可以通过,但在运行时会报错:
sql: converting argument $1 type: unsupported type model.JSONTime, a struct。 - 原来,gorm 只能处理
in64, float64, bool, []byte, string, time.Time, nil这些类型,默认不能识别自定义类型。 如果要使用自定义类型,那么自定义类型必须实现 Scanner 和 Valuer 接口,详见 gorm 官方文档。 简单解释一下,Scanner接口是告诉 gorm 如何将数据库支持的数据类型转换为自定义类型,Valuer接口是告诉 gorm 如何将自定义类型转换为数据库支持的数据类型。 - 因此,我还需要为
JSONTime定义Scan和Value这两个方法,如下所示。
// Scan scans value into JSONTime, implements sql.Scanner interface. func (t *JSONTime) Scan(value interface{}) error { v, ok := value.(time.Time) if !ok { return fmt.Errorf("invalid value: %s. type: %T", value, value) } *t = JSONTime(v) return nil } // Value returns json value, implement driver.Valuer interface. func (t JSONTime) Value() (driver.Value, error) { return time.Time(t), nil }- 再次不幸的是,修改后,gorm 不再自动给
CreatedAt和UpdatedAt字段赋值了,需要用户每次创建或更新条目时自行给条目中的时间字段的赋值。 虽然失去了一点便利性,但相比每次查询都要手动调整时间格式而言,这种方案还是更优的选择。
- 比较直接的思路是在 json 序列化后再调整
-
实施结果:采用上述解决方案后,产线平台后端返回的数据中的时间格式调整为
2022-10-25 07:05:02,可读性更好。 -
经验总结:
- Go 语言可以为自定义类型定义 json 序列化方法,也可以在 gorm 中使用自定义类型,只需要实现一些相应的接口即可。