Files
soul-yongping/soul-api/internal/cache/cache.go

172 lines
4.6 KiB
Go
Raw Normal View History

package cache
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"soul-api/internal/database"
"soul-api/internal/model"
"soul-api/internal/redis"
)
const defaultTimeout = 2 * time.Second
// KeyBookParts 目录接口缓存 key后台更新章节/内容时需 Del
const KeyBookParts = "soul:book:parts"
// KeyBookHot 热门章节,格式 soul:book:hot:{limit}
func KeyBookHot(limit int) string { return "soul:book:hot:" + fmt.Sprint(limit) }
const KeyBookRecommended = "soul:book:recommended"
const KeyBookStats = "soul:book:stats"
const KeyConfigMiniprogram = "soul:config:miniprogram"
// Get 从 Redis 读取,未配置或失败返回 nil调用方回退 DB
func Get(ctx context.Context, key string, dest interface{}) bool {
client := redis.Client()
if client == nil {
return false
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
val, err := client.Get(ctx, key).Bytes()
if err != nil {
return false
}
if dest != nil && len(val) > 0 {
_ = json.Unmarshal(val, dest)
}
return true
}
// Set 写入 Redis失败仅打日志不阻塞
func Set(ctx context.Context, key string, val interface{}, ttl time.Duration) {
client := redis.Client()
if client == nil {
return
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
data, err := json.Marshal(val)
if err != nil {
log.Printf("cache.Set marshal %s: %v", key, err)
return
}
if err := client.Set(ctx, key, data, ttl).Err(); err != nil {
log.Printf("cache.Set %s: %v (非致命)", key, err)
}
}
// Del 删除 key失败仅打日志
func Del(ctx context.Context, key string) {
client := redis.Client()
if client == nil {
return
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
if err := client.Del(ctx, key).Err(); err != nil {
log.Printf("cache.Del %s: %v (非致命)", key, err)
}
}
// BookPartsTTL 目录接口缓存 TTL后台更新时主动 Del此为兜底时长
const BookPartsTTL = 10 * time.Minute
// InvalidateBookParts 后台更新章节/内容时调用,使目录接口缓存失效
func InvalidateBookParts() {
Del(context.Background(), KeyBookParts)
}
// InvalidateBookCache 使热门、推荐、统计等书籍相关缓存失效(与 InvalidateBookParts 同时调用)
func InvalidateBookCache() {
ctx := context.Background()
Del(ctx, KeyBookRecommended)
Del(ctx, KeyBookStats)
for _, limit := range []int{3, 10, 20, 50} {
Del(ctx, KeyBookHot(limit))
}
}
// InvalidateConfig 配置变更时调用,使小程序 config 缓存失效
func InvalidateConfig() {
Del(context.Background(), KeyConfigMiniprogram)
}
// BookRelatedTTL 书籍相关接口 TTLhot/recommended/stats
const BookRelatedTTL = 5 * time.Minute
// ConfigTTL 配置接口 TTL
const ConfigTTL = 10 * time.Minute
// KeyChapterContent 章节正文缓存,格式 soul:chapter:content:{mid},存原始 HTML 字符串
func KeyChapterContent(mid int) string { return "soul:chapter:content:" + fmt.Sprint(mid) }
// ChapterContentTTL 章节正文 TTL后台更新时主动 Del
const ChapterContentTTL = 30 * time.Minute
// GetString 读取字符串(不经过 JSON适合大文本 content
func GetString(ctx context.Context, key string) (string, bool) {
client := redis.Client()
if client == nil {
return "", false
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
val, err := client.Get(ctx, key).Result()
if err != nil {
return "", false
}
return val, true
}
// SetString 写入字符串(不经过 JSON适合大文本 content
func SetString(ctx context.Context, key string, val string, ttl time.Duration) {
client := redis.Client()
if client == nil {
return
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
if err := client.Set(ctx, key, val, ttl).Err(); err != nil {
log.Printf("cache.SetString %s: %v (非致命)", key, err)
}
}
// InvalidateChapterContent 章节内容更新时调用mid<=0 时忽略
func InvalidateChapterContent(mid int) {
if mid <= 0 {
return
}
Del(context.Background(), KeyChapterContent(mid))
}
// InvalidateChapterContentByID 按业务 id 使章节内容缓存失效(内部查 mid 后调用 InvalidateChapterContent
func InvalidateChapterContentByID(id string) {
if id == "" {
return
}
var mid int
if err := database.DB().Model(&model.Chapter{}).Where("id = ?", id).Pluck("mid", &mid).Error; err != nil || mid <= 0 {
return
}
InvalidateChapterContent(mid)
}