Files
soul-yongping/soul-api/internal/cache/cache.go
Alex-larget c24caf63c5 性能优化
2026-03-17 14:02:09 +08:00

172 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}