需求背景

在用 gin 开发网站的时候,每个页面都需要相同的页眉和页脚。 里面有些变量是通用的,例如:

  • 公司 logo
  • 电话
  • 公司名称
  • 备案号

等等。

但是,每次都在 controller 里从数据库里读取这些配置,有两个弊端:

  1. 每个页面的 controller 都需要重复写这个配置参数传入逻辑,啰嗦
  2. 这些配置极少变化,每次都读取数据库,没有必要。可以通过缓存来优化

实现逻辑

  • 增加一个 template func,方便 template 中直接调用,省去 controller 中传参的步骤
  • 定义一个全局缓存,sync.Map 类型。

实现代码

cache.go

langs 作为可选参数,用来实现多语言配置的实现。例如,中文 logo,英文 logo。 如果不需要多语言支持,把这个参数去掉即可。

var cache sync.Map

func CacheGet(key string, langs ...string) any {
	if len(langs) == 1 && langs[0] != "" {
		key = fmt.Sprintf("%s_%s", key, langs[0])
	}

	value, found := cache.Load(key)
	if !found {
		// 单个 coroutine 内是顺序执行的,所以不用担心一次渲染模板导致拉取多次
		log.Println("Fail to load data from cache. Init cache ...")
		InitCache()
		log.Println("Finish initing cache!")
	}

	value, _ = cache.Load(key)
	return value
}

func InitCache() {
	var settingList []models.Setting
	models.DB.Find(&settingList)

	for _, v := range settingList {
		cache.Store(v.KeyName, v.Value)
	}
}

template

<img src={{ CacheGet "logo_white" .lang }} alt="" class="mx-auto"/>

为何使用 sync.Map 而不是 Map

主要是为了线程安全,参考这里

https://zhuanlan.zhihu.com/p/342241598

这个需求场景到是不关心是否线程安全,主要是多个 coroutine 同时读写一个 Map 会导致报错。只能选择 sync.Map。

同时也发现了,几个不错的 golang gin 的缓存库,虽然最后没用上 (场景很简单,不需要引入 cache 库):

  • 用于 gin 缓存返回结果 (rendered view): https://github.com/gin-contrib/cache
  • gin cache 的改良版: https://github.com/chenyahui/gin-cache
  • go 的老牌缓存库: https://github.com/patrickmn/go-cache
  • https://github.com/allegro/bigcache