From 593b52096f3145632842aeb48fe6d639303b0157 Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Tue, 2 Jun 2026 17:20:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=86=85=E5=AD=98=E7=BC=93=E5=AD=98=EF=BC=8830s=20TTL=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 internal/cache/home_cache.go,实现带 TTL 的首页文章列表内存缓存 - frontend.go 首页 handler 优先读取缓存,未命中/过期时查询 DB 并回填 - PostService 注入失效回调,在 Create/Update/Delete/Approve 后自动失效缓存 - 减少首页每次请求都查 DB 的开销 --- internal/cache/home_cache.go | 55 ++++++++++++++++++++++++++ internal/router/deps_core.go | 2 + internal/router/deps_extra.go | 8 ++++ internal/router/frontend.go | 14 +++++-- internal/service/post_audit_service.go | 20 ++++++++-- internal/service/post_service.go | 22 +++++++++-- 6 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 internal/cache/home_cache.go diff --git a/internal/cache/home_cache.go b/internal/cache/home_cache.go new file mode 100644 index 0000000..538ce7d --- /dev/null +++ b/internal/cache/home_cache.go @@ -0,0 +1,55 @@ +package cache + +import ( + "sync" + "time" + + "metazone.cc/metalab/internal/model" +) + +// HomePageCache 首页内存缓存,30 秒 TTL,创建/更新/删除/审核通过时失效 +type HomePageCache struct { + mu sync.RWMutex + posts []model.Post + total int64 + expires time.Time + ttl time.Duration +} + +// NewHomePageCache 创建首页缓存,ttl 为缓存有效期 +func NewHomePageCache(ttl time.Duration) *HomePageCache { + return &HomePageCache{ttl: ttl} +} + +// Get 返回缓存数据,ok=false 表示缓存未命中或已过期 +func (c *HomePageCache) Get() (posts []model.Post, total int64, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + if time.Now().After(c.expires) { + return nil, 0, false + } + // 返回浅拷贝以防止调用方修改缓存内部数据 + copied := make([]model.Post, len(c.posts)) + copy(copied, c.posts) + return copied, c.total, true +} + +// Set 写入缓存数据 +func (c *HomePageCache) Set(posts []model.Post, total int64) { + c.mu.Lock() + defer c.mu.Unlock() + // 深拷贝一份避免外部修改影响缓存 + c.posts = make([]model.Post, len(posts)) + copy(c.posts, posts) + c.total = total + c.expires = time.Now().Add(c.ttl) +} + +// Invalidate 主动失效缓存 +func (c *HomePageCache) Invalidate() { + c.mu.Lock() + defer c.mu.Unlock() + c.posts = nil + c.total = 0 + c.expires = time.Time{} +} diff --git a/internal/router/deps_core.go b/internal/router/deps_core.go index 4da40bb..945dba5 100644 --- a/internal/router/deps_core.go +++ b/internal/router/deps_core.go @@ -4,6 +4,7 @@ import ( "log" "time" + "metazone.cc/metalab/internal/cache" "metazone.cc/metalab/internal/config" "metazone.cc/metalab/internal/controller" adminCtrl "metazone.cc/metalab/internal/controller/admin" @@ -42,6 +43,7 @@ type dependencies struct { favoriteCtrl *controller.FavoriteController favoriteService *service.FavoriteService trendsService *service.TrendsService + homeCache *cache.HomePageCache } func buildDepsCore(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) ( diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index ed61353..110ed70 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -1,6 +1,9 @@ package router import ( + "time" + + "metazone.cc/metalab/internal/cache" "metazone.cc/metalab/internal/config" "metazone.cc/metalab/internal/controller" adminCtrl "metazone.cc/metalab/internal/controller/admin" @@ -99,6 +102,10 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting maintenanceMdw := middleware.NewMaintenanceMiddleware(cfg, siteSettings, sessionMgr) + // 首页内存缓存(30s TTL),文章变更时失效 + homeCache := cache.NewHomePageCache(30 * time.Second) + postService.WithHomePageInvalidator(func() { homeCache.Invalidate() }) + return &dependencies{ authCtrl: authCtrl, authMdw: authMdw, maintenanceMdw: maintenanceMdw, settingsCtrl: settingsCtrl, @@ -119,5 +126,6 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting favoriteCtrl: favoriteCtrl, favoriteService: favoriteService, trendsService: trendsService, + homeCache: homeCache, } } diff --git a/internal/router/frontend.go b/internal/router/frontend.go index 1cb5acd..5162066 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -22,10 +22,16 @@ func setupFrontendRoutes(r *gin.Engine, cfg *config.Config, d *dependencies) { }) { pages.GET("/", func(c *gin.Context) { - posts, total, err := d.postService.List("", 1, 6) - if err != nil { - posts = nil - total = 0 + posts, total, ok := d.homeCache.Get() + if !ok { + var err error + posts, total, err = d.postService.List("", 1, 6) + if err != nil { + posts = nil + total = 0 + } else { + d.homeCache.Set(posts, total) + } } c.HTML(http.StatusOK, "home/index.html", common.BuildPageData(c, gin.H{ "Title": "首页", diff --git a/internal/service/post_audit_service.go b/internal/service/post_audit_service.go index 2ca9377..751e2cf 100644 --- a/internal/service/post_audit_service.go +++ b/internal/service/post_audit_service.go @@ -21,7 +21,11 @@ func (s *PostService) Update(postID uint, title, body string) error { post.PendingTitle = title post.PendingBody = body post.RejectReason = "" // 清空旧退回理由 - return s.repo.Update(post) + err := s.repo.Update(post) + if err == nil { + s.invalidateHomeCache() + } + return err } // 草稿/已退回:直接修改原文(现有逻辑) @@ -34,12 +38,20 @@ func (s *PostService) Update(postID uint, title, body string) error { post.RejectReason = "" } - return s.repo.Update(post) + err = s.repo.Update(post) + if err == nil { + s.invalidateHomeCache() + } + return err } // Delete 软删除(权限在 controller 层检查) func (s *PostService) Delete(postID uint) error { - return s.repo.SoftDelete(postID) + if err := s.repo.SoftDelete(postID); err != nil { + return err + } + s.invalidateHomeCache() + return nil } // SubmitForAudit 提交审核:draft → pending @@ -79,6 +91,7 @@ func (s *PostService) Approve(postID uint) error { if err := s.repo.Update(post); err != nil { return err } + s.invalidateHomeCache() // 通知作者修订审核通过 if s.notifier != nil { s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID) @@ -95,6 +108,7 @@ func (s *PostService) Approve(postID uint) error { if err := s.repo.Update(post); err != nil { return err } + s.invalidateHomeCache() // 通知作者审核通过 if s.notifier != nil { s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID) diff --git a/internal/service/post_service.go b/internal/service/post_service.go index 5ad629e..60f138a 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -13,10 +13,11 @@ import ( // PostService 帖子业务逻辑 type PostService struct { - repo postStore - ss *config.SiteSettings - notifier postNotifier - userRepo userExpReader + repo postStore + ss *config.SiteSettings + notifier postNotifier + userRepo userExpReader + onHomePageChanged func() } // NewPostService 构造函数 @@ -29,6 +30,11 @@ func NewPostService(repo postStore, ss *config.SiteSettings, notifier postNotifi } } +// WithHomePageInvalidator 注入首页缓存失效回调(文章变更时触发) +func (s *PostService) WithHomePageInvalidator(fn func()) { + s.onHomePageChanged = fn +} + // auditEnabled 审核是否开启 func (s *PostService) auditEnabled() bool { return s.ss.IsAuditEnabled() @@ -78,6 +84,7 @@ func (s *PostService) Create(userID uint, title, body string) (*model.Post, erro if err := s.repo.Create(post); err != nil { return nil, err } + s.invalidateHomeCache() return post, nil } @@ -156,6 +163,13 @@ func (s *PostService) CanCreatePost(userID uint) bool { return user.Exp >= 1 } +// invalidateHomeCache 失效首页缓存(异步安全调用) +func (s *PostService) invalidateHomeCache() { + if s.onHomePageChanged != nil { + s.onHomePageChanged() + } +} + // RecordRead 记录已登录用户阅读文章(已登录去重 + 防刷冷却) func (s *PostService) RecordRead(userID, postID uint) { firstRead, err := s.repo.RecordRead(userID, postID)