首先需要自己安装
redis
并提前了解相关知识
前情提要
学习项目github地址,有需要可以从这里查看源码
配置
在 api.ini
中新增 redis
相关配置
...
[redis]
Host = 127.0.0.1:6379
Password =
MaxIdle = 30
MaxActive = 30
IdleTimeout = 200
缓存前缀 常量
打开 pkg/e
目录,新建 cache.go
,写入内容:
package e
const (
CACHE_ARTICLE = "ARTICLE"
CACHE_TAG = "TAG"
)
建立常量保存,方便后续管理
缓存中key
值问题
新建 service/cathe_service
目录,新建 article.go
:
// Package cache_service 缓存相关服务,单个或多个文章键名
package cache_service
import (
"github.com/kingsill/gin-example/pkg/e"
"strconv"
"strings"
)
// Article 定义缓存中可能的文章键名包含的要素
type Article struct {
ID int
TagID int
State int
PageNum int
PageSize int
}
// GetArticleKey 单个文章键名
func (a *Article) GetArticleKey() string {
//ARTICLE_1
return e.CACHE_ARTICLE + "_" + strconv.Itoa(a.ID)
}
// GetArticlesKey 多个文章键名? 一个文章信息更多的键名
func (a *Article) GetArticlesKey() string {
keys := []string{
e.CACHE_ARTICLE,
"LIST",
}
if a.ID > 0 {
keys = append(keys, strconv.Itoa(a.ID))
}
if a.TagID > 0 {
keys = append(keys, strconv.Itoa(a.TagID))
}
if a.State >= 0 {
keys = append(keys, strconv.Itoa(a.State))
}
if a.PageNum > 0 {
keys = append(keys, strconv.Itoa(a.PageNum))
}
if a.PageSize > 0 {
keys = append(keys, strconv.Itoa(a.PageSize))
}
//ARTICLE_LIST_...
return strings.Join(keys, "_")
}
同目录新建 tag.go
// Package cache_service 缓存相关服务,单个或多个tag键名
package cache_service
import (
"github.com/kingsill/gin-example/pkg/e"
"strconv"
"strings"
)
// Tag 定义缓存中可能的Tag键名包含的要素
type Tag struct {
ID int
Name string
State int
PageNum int
PageSize int
}
// GetTagsKey 获取全面的Tag键名
func (t *Tag) GetTagsKey() string {
keys := []string{
e.CACHE_TAG,
"LIST",
}
if t.Name != "" {
keys = append(keys, t.Name)
}
if t.State >= 0 {
keys = append(keys, strconv.Itoa(t.State))
}
if t.PageNum > 0 {
keys = append(keys, strconv.Itoa(t.PageNum))
}
if t.PageSize > 0 {
keys = append(keys, strconv.Itoa(t.PageSize))
}
return strings.Join(keys, "_")
}
Redis
工具包
- 下载
redigo
包,帮助使用redis
go get github.com/gomodule/redigo@latest
- 打开
pkg
目录,新建gredis/redis.go
,写入内容:
package gredis
import (
"encoding/json"
"github.com/gomodule/redigo/redis"
"github.com/kingsill/gin-example/pkg/setting"
"time"
)
// RedisConn 定义全局变量RedisConn为redis连接池
var RedisConn *redis.Pool
// Setup 初始化redis服务
func Setup() error {
//实例化RedisConn,从setting中加载定义的配置信息
RedisConn = &redis.Pool{
MaxIdle: setting.RedisSetting.MaxIdle,
MaxActive: setting.RedisSetting.MaxActive,
IdleTimeout: setting.RedisSetting.IdleTimeout,
Dial: func() (redis.Conn, error) { //创建并配置一个连接
c, err := redis.Dial("tcp", setting.RedisSetting.Host)
if err != nil {
return nil, err
}
if setting.RedisSetting.Password != "" {
if _, err := c.Do("AUTH", setting.RedisSetting.Password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error { //测试连接
_, err := c.Do("PING")
return err
},
}
return nil
}
// Set redis存储的key-value,添加数据
func Set(key string, data interface{}, time int) error {
conn := RedisConn.Get()
defer conn.Close()
//将data以json编码为value
value, err := json.Marshal(data)
if err != nil {
return err
}
_, err = conn.Do("SET", key, value)
if err != nil {
return err
}
//设置过期时间,单位为s
_, err = conn.Do("EXPIRE", key, time)
if err != nil {
return err
}
return nil
}
// Exists 检查当前redis中是否有当前key
func Exists(key string) bool {
conn := RedisConn.Get()
defer conn.Close()
exists, err := redis.Bool(conn.Do("EXISTS", key))
if err != nil {
return false
}
return exists
}
// Get 获取要查询的key对应的value
func Get(key string) ([]byte, error) {
conn := RedisConn.Get()
defer conn.Close()
reply, err := redis.Bytes(conn.Do("GET", key))
if err != nil {
return nil, err
}
return reply, nil
}
// Delete 删除redis中传入的键值对
func Delete(key string) (bool, error) {
conn := RedisConn.Get()
defer conn.Close()
return redis.Bool(conn.Do("DEL", key))
}
// LikeDeletes 删除所有包含指定键名的键值对
func LikeDeletes(key string) error {
conn := RedisConn.Get()
defer conn.Close()
keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
if err != nil {
return err
}
for _, key := range keys {
_, err = Delete(key)
if err != nil {
return err
}
}
return nil
}
redis 使用
在原来的逻辑中,以检索文件为例,我们直接在 mysql
中检索数据,现在我们在之前新增在 redis
中搜索,如果没有,再跳转到 mysql
并且我们将检索封装成一个函数,代码阅读更加直观
打开 service
目录,新建 article_service/article.go
package article_service
import (
"encoding/json"
"github.com/kingsill/gin-example/models"
"github.com/kingsill/gin-example/pkg/gredis"
"github.com/kingsill/gin-example/pkg/logging"
"github.com/kingsill/gin-example/service/cache_service"
)
// Article 建立文章结构体,方便信息存储及与gorm的互动
type Article struct {
ID int
TagID int
Title string
Desc string
Content string
CoverImageUrl string
State int
CreatedBy string
ModifiedBy string
PageNum int
PageSize int
}
// Add 新建文章服务
func (a *Article) Add() error {
//创建article实例
article := map[string]interface{}{
"tag_id": a.TagID,
"title": a.Title,
"desc": a.Desc,
"content": a.Content,
"created_by": a.CreatedBy,
"cover_image_url": a.CoverImageUrl,
"state": a.State,
}
if err := models.AddArticle(article); err != nil {
return err
}
return nil
}
// Edit 编辑文章服务
func (a *Article) Edit() error {
return models.EditArticle(a.ID, map[string]interface{}{
"tag_id": a.TagID,
"title": a.Title,
"desc": a.Desc,
"content": a.Content,
"cover_image_url": a.CoverImageUrl,
"state": a.State,
"modified_by": a.ModifiedBy,
})
}
// Get 查询文章服务
func (a *Article) Get() (*models.Article, error) {
var cacheArticle *models.Article
//推算文章键名
cache := cache_service.Article{ID: a.ID}
key := cache.GetArticleKey()
//根据文章键名在redis中查询
if gredis.Exists(key) {
data, err := gredis.Get(key)
if err != nil {
logging.Info(err)
} else {
json.Unmarshal(data, &cacheArticle)
return cacheArticle, nil
}
}
//redis中没有查询到则在mysql中查询
article, err := models.GetArticle(a.ID)
if err != nil {
return nil, err
}
//在redis中存储该组键值对,并设置过期时间
gredis.Set(key, article, 3600)
return article, nil
}
// GetAll 类比上面 get 进行理解
func (a *Article) GetAll() ([]*models.Article, error) {
var (
articles, cacheArticles []*models.Article
)
cache := cache_service.Article{
TagID: a.TagID,
State: a.State,
PageNum: a.PageNum,
PageSize: a.PageSize,
}
key := cache.GetArticlesKey()
if gredis.Exists(key) {
data, err := gredis.Get(key)
if err != nil {
logging.Info(err)
} else {
json.Unmarshal(data, &cacheArticles)
return cacheArticles, nil
}
}
articles, err := models.GetArticles(a.PageNum, a.PageSize, a.getMaps())
if err != nil {
return nil, err
}
gredis.Set(key, articles, 3600)
return articles, nil
}
// Delete 简单对之前函数的包装调用
func (a *Article) Delete() error {
return models.DeleteArticle(a.ID)
}
func (a *Article) ExistByID() (bool, error) {
return models.ExistArticleByID(a.ID)
}
func (a *Article) Count() (int, error) {
return models.GetArticleTotal(a.getMaps())
}
// 构建适用于多个文章查询等的条件映射
func (a *Article) getMaps() map[string]interface{} {
maps := make(map[string]interface{})
maps["deleted_on"] = 0
if a.State != -1 {
maps["state"] = a.State
}
if a.TagID != -1 {
maps["tag_id"] = a.TagID
}
return maps
}
过程中可能会对之前的函数进行一定小小的修改,根据报错进行修改即可
同样的,大家可以自行建立tag_service/tag.go