【Go】使用Gin+Gorm进行开发时的一些踩坑总结

前言

最近在使用Gin+Gorm进行运维集中化后端的开发,期间遇到一些问题,这里进行记录总结,希望也能帮到遇到同样问题的朋友。

正文

嵌套结构体绑定时缺失字段

这是一个实际开发中遇到的问题,为了兼容多种类型的数据库,定义了一个基础的数据库信息的结构体,如下:

// GeneralDB 基础数据库类型
type GeneralDB struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	Username string `yaml:"username"`
	Password string `yaml:"password"`
	Database string `yaml:"database"`
}

针对PG数据库的话,会多出时区的配置,结构体要这样写:

type PostgreDB struct {
	GeneralDB `mapstructure:",squash"`
	TimeZone  string `yaml:"time_zone"`
}

必须加上mapstructure:",squash"的tag,如果不加,进行解析的时候,不会报错,但是绑定不到结构体上;

Gorm进行更新时没有反应

这里写了两种更新逻辑,一种是兼容软删除数据的更新方式,使用Save,一种是使用Updates进行兼容多列和单列的更新:

// UpdatePrometheus 更新Prometheus信息
//
//	@receiver dao *PrometheusDao
//	@param p monitor.Prometheus
//	@return prometheus monitor.Prometheus, err error
func (dao *PrometheusDao) UpdatePrometheus(p monitor.Prometheus) (prometheus monitor.Prometheus, err error) {
	session := global.DB.Session(&gorm.Session{QueryFields: true})
	err = session.Unscoped().Save(&p).Error
	if err != nil {
		global.Logger.Debugf("数据库操作异常, 异常信息: %s.\n", err)
	}
	return p, err
}

// UpdataPrometheusMap 使用Map更新信息
//
//	@receiver dao *PrometheusDao
//	@param id uint, param map[string]interface{}
//	@return prometheus monitor.Prometheus, err error
func (dao *PrometheusDao) UpdataPrometheusMap(id uint, param map[string]interface{}) (prometheus monitor.Prometheus, err error) {
	session := global.DB.Session(&gorm.Session{QueryFields: true})
	err = session.Model(&monitor.Prometheus{}).Where("id = ?", id).Updates(param).Error
	if err != nil {
		global.Logger.Debugf("数据库操作异常, 异常信息: %s.\n", err)
	}
	return prometheus, err
}

在Service层进行数据库方法的调用时,UpdatePrometheus可以正常完成数据更新,但是UpdatePrometheusMap不可以,经过查证,入参的类型必须是map[string]interface{},而我之前设置的map[string]string

Viper解析yaml配置

使用Viper包进行配置文件的解析,其中有部分配置文件需要程序运行过程中监听配置的修改,然后进行重载,在我的需求中,配置文件会有多个,所以就要有多个viper实例,对于这点官方文档也给了回答:
在这里插入图片描述
一个viper实例只能针对一个名称的配置文件进行监听和加载(但是支持多个路径和后缀),所以有几个配置就要有几个viper实例,因此,实现一个能够适用所有业务配置结构的初始化函数:

func initConfigFile(filename string, config interface{}) *viper.Viper {
	v := viper.New()
	// 设定配置读取路径,允许设置多个路径
	v.AddConfigPath(global.RunPath)
	// 设置配置文件名称,viper会分别去上面设置的几个路径下去寻找
	v.SetConfigName(filename)
	v.SetConfigType("yaml")
	if err := v.ReadInConfig(); err != nil {
		global.Logger.Errorf("Failed to load config file %s, %s.\n", filename, err.Error())
		return v
	}
	// 开启配置文件监视,监听文件的变更,仅当文件保存时才会刷新
	v.WatchConfig()
	v.OnConfigChange(func(e fsnotify.Event) {
		global.Logger.Infof("Config file changed: %s, will reload it.\n", filename)
		if err := v.Unmarshal(&config); err != nil {
			global.Logger.Errorf("Reload the config file failed, %s.\n", err.Error())
		}
	})
	if err := v.Unmarshal(&config); err != nil {
		global.Logger.Errorf("Load the config file failed, %s.\n", err.Error())
	}
	return v
}

这样就能实现配置的热加载更新了,如果是在windows开发,注意一点,那就是使用其他文本工具修改配置文件测试更新时,可能会产生2或4次刷新:
在这里插入图片描述
这个和Windows的系统调用有关,不用在意,win11用户直接使用自带的notepad打开就不会有这个问题了:
在这里插入图片描述
在这里插入图片描述

Gorm使用自定义时间类型

按照以下方式自定义一个LocalTime即可:

type LocalTime time.Time

func (t *LocalTime) MarshalJSON() ([]byte, error) {
	tTime := time.Time(*t)
	return []byte(fmt.Sprintf("\"%v\"", tTime.Format("2006-01-02 15:04:05"))), nil
}

func (t *LocalTime) Value() (driver.Value, error) {
	var zero time.Time
	tlt := time.Time(*t)
	if tlt.UnixNano() == zero.UnixNano() {
		return nil, nil
	}
	return tlt, nil
}

func (t *LocalTime) Scan(v any) error {
	if value, ok := v.(time.Time); ok {
		*t = LocalTime(value)
		return nil
	}
	return fmt.Errorf("can not convert %v to timestamp", v)
}

进行model的设置时,这样指定:

type HardModel struct {
	ID        uint      `gorm:"primaryKey" json:"id"` // 主键ID
	CreatedAt LocalTime `json:"create_at"`            // 创建时间
	UpdatedAt LocalTime `json:"update_at"`            // 更新时间
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Meepoljd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值