gorm time.Time 使用钩子函数解决反序列化问题

问题描述:

gorm中使用下面的CreatedAt 和UpdateAt,可以实现在记录创建和更新时自动更新下面两个字段。虽然使用默认的json解析,从json中到golang中,从golang中写入到数据库(mysql)都是标准的格式“2006-01-02 15:04:05”,但是从golang中解析到json中时是“RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"”。

原因:

time.Time的反序列化使用的是RFC3339Nano,不是希望的标准时间,返回到前台,前台表示拒绝……。只能后台改一下了。

type User struct {
	Id            int64     `json:"id"`
	CreatedAt     time.Time `json:"created_at" `
	UpdatedAt     time.Time `json:"updated_at" `
}

const (
	ANSIC       = "Mon Jan _2 15:04:05 2006"
	UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
	RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
	RFC822      = "02 Jan 06 15:04 MST"
	RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
	RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
	RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
	RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
	RFC3339     = "2006-01-02T15:04:05Z07:00"
	RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
	Kitchen     = "3:04PM"
	// Handy time stamps.
	Stamp      = "Jan _2 15:04:05"
	StampMilli = "Jan _2 15:04:05.000"
	StampMicro = "Jan _2 15:04:05.000000"
	StampNano  = "Jan _2 15:04:05.000000000"
)



// MarshalJSON implements the json.Marshaler interface.
// The time is a quoted string in RFC 3339 format, with sub-second precision added if present.
func (t Time) MarshalJSON() ([]byte, error) {
	if y := t.Year(); y < 0 || y >= 10000 {
		// RFC 3339 is clear that years are 4 digits exactly.
		// See golang.org/issue/4556#c15 for more discussion.
		return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
	}

	b := make([]byte, 0, len(RFC3339Nano)+2)
	b = append(b, '"')
	b = t.AppendFormat(b, RFC3339Nano)
	b = append(b, '"')
	return b, nil
}

解决方法:

1.自定义解析方式。在从golang中解析到json时自定义方法重载原生time的解析方法。

详细见下文。

2.使用string作为时间的接收类型。

使用string,不过需要把数据库里的字段类型也改,不然返回还是“2006-01-02T15:04:05.999999999Z07:00”。

3.使用时间戳,只发送给前端时间戳。

使用时间戳,只保存时间戳,返回给前端时间戳,让前端自己格式化。

 

第一种方法代码:

定义自己的时间类型(建议放在utils通用函数包里):

//完整方法
type ProgramModel struct {
	BaseModel
	Name      string    `json:"name" gorm:"column:name"`
	StartTime BaseModel `json:"start_time" gorm:"column:start_time"`
	EndTime   BaseModel `json:"end_time" gorm:"column:end_time"`
}

type BaseModel struct {
	gorm.Model
	Id        uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id"`
	CreatedAt MyTime `gorm:"column:created_at" json:"created_at"`
	UpdatedAt MyTime `gorm:"column:updated_at" json:"updated_at"`
}

type MyTime struct {
	time.Time
}

//重写 MarshaJSON 方法,在此方法中实现自定义格式的转换;程序中解析到JSON
func (t MyTime) MarshalJSON() ([]byte, error) {
	formatted := fmt.Sprintf("\"%s\"", t.Format(formatTime))
	return []byte(formatted), nil
}
//JSON中解析到程序中
func (t *MyTime) UnmarshalJSON(data []byte) (err error) {
	now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local)
	*t = MyTime{Time: now}
	return
}
//写入数据库时会调用该方法将自定义时间类型转换并写入数据库
func (t MyTime) Value() (driver.Value, error) {
	var zeroTime time.Time
	if t.Time.UnixNano() == zeroTime.UnixNano() {
		return nil, nil
	}
	return t.Time, nil
}
//读取数据库时会调用该方法将时间数据转换成自定义时间类型
func (t *MyTime) Scan(v interface{}) error {
	value, ok := v.(time.Time)
	if ok {
		*t = MyTime{Time: value}
		return nil
	}
	return fmt.Errorf("can not convert %v to timestamp", v)
}
//钩子函数
func (v BaseModel) BeforeCreate(scope *gorm.Scope) error {
	scope.SetColumn("created_at", time.Now())
	scope.SetColumn("updated_at", time.Now())
	return nil
}
//钩子函数
func (v BaseModel) BeforeUpdate(scope *gorm.Scope) error {
	scope.SetColumn("updated_at", time.Now())
	return nil
}

gorm的hook钩子函数:https://gorm.io/docs/hooks.html

使用,dutils是上面的包。而且也是时间类型。

type MineTime struct {
	Id            int64     `json:"id"`
	CreatedAt     dutils.MyTime `json:"created_at" gorm:"column:created_at"`
	UpdatedAt     dutils.MyTime `json:"updated_at" gorm:"column:updated_at"`
}

 结果:

这样就可以保证时间格式都是标准的格式了,而且会自动创建,更新时间。

参考博客:

https://www.cnblogs.com/mrylong/p/11326792.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值