diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 7307c30..18f346e 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -62,7 +62,7 @@ func InitializeApp() (*App, error) { logService := wire.ProvideLogService(operationLogService, loginLogService, dataChangeLogService) userHandler := wire.ProvideUserHandler(userService, userActivityService, postService, commentService, logService) channelRepository := repository.NewChannelRepository(db) - channelService := wire.ProvideChannelService(channelRepository) + channelService := wire.ProvideChannelService(channelRepository, cache) postHandler := wire.ProvidePostHandler(postService, userService, channelService, logService) commentHandler := handler.NewCommentHandler(commentService) chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache) diff --git a/internal/cache/keys.go b/internal/cache/keys.go index 0406b46..d9b8045 100644 --- a/internal/cache/keys.go +++ b/internal/cache/keys.go @@ -31,6 +31,9 @@ const ( PrefixUserInfo = "users:info" PrefixUserMe = "users:me" + // 频道相关(全量列表一条缓存,供 /channels、帖子 channel 填充共用) + PrefixChannelsAllList = "channels:all_list" + // 消息缓存相关 keyPrefixMsgHash = "msg_hash" // 消息详情 Hash keyPrefixMsgIndex = "msg_index" // 消息索引 Sorted Set @@ -116,6 +119,16 @@ func UserMeKey(userID string) string { return fmt.Sprintf("%s:%s", PrefixUserMe, userID) } +// ChannelsAllListKey 频道全量列表缓存键(含未启用,供 MapByIDs 解析历史帖子 channel) +func ChannelsAllListKey() string { + return PrefixChannelsAllList +} + +// InvalidateChannelList 失效频道列表缓存(管理端增删改频道后调用) +func InvalidateChannelList(c Cache) { + c.Delete(ChannelsAllListKey()) +} + // InvalidatePostList 失效帖子列表缓存 func InvalidatePostList(cache Cache) { cache.DeleteByPrefix(PrefixPostList) diff --git a/internal/repository/channel_repo.go b/internal/repository/channel_repo.go index 8c40f66..58e47cb 100644 --- a/internal/repository/channel_repo.go +++ b/internal/repository/channel_repo.go @@ -48,19 +48,6 @@ func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) { return &channel, nil } -// ListByIDs 按 ID 列表查询频道(用于帖子列表批量填充 channel 名称) -func (r *ChannelRepository) ListByIDs(ids []string) ([]*model.Channel, error) { - if len(ids) == 0 { - return nil, nil - } - var list []*model.Channel - err := r.db.Where("id IN ?", ids).Find(&list).Error - if err != nil { - return nil, err - } - return list, nil -} - func (r *ChannelRepository) Delete(id string) error { return r.db.Delete(&model.Channel{}, "id = ?", id).Error } diff --git a/internal/service/channel_service.go b/internal/service/channel_service.go index 15e84a0..80062fa 100644 --- a/internal/service/channel_service.go +++ b/internal/service/channel_service.go @@ -1,10 +1,16 @@ package service import ( + "time" + + "carrot_bbs/internal/cache" "carrot_bbs/internal/model" "carrot_bbs/internal/repository" ) +// 频道全量列表缓存 TTL(频道变更少,可较长;管理端写入后会主动失效) +const channelAllListCacheTTL = 10 * time.Minute + type ChannelService interface { ListActive() ([]*model.Channel, error) ListAll() ([]*model.Channel, error) @@ -16,32 +22,68 @@ type ChannelService interface { type channelServiceImpl struct { channelRepo *repository.ChannelRepository + cache cache.Cache } -func NewChannelService(channelRepo *repository.ChannelRepository) ChannelService { - return &channelServiceImpl{channelRepo: channelRepo} +func NewChannelService(channelRepo *repository.ChannelRepository, c cache.Cache) ChannelService { + return &channelServiceImpl{channelRepo: channelRepo, cache: c} } -func (s *channelServiceImpl) ListActive() ([]*model.Channel, error) { - return s.channelRepo.ListActive() +func (s *channelServiceImpl) invalidateChannelListCache() { + if s.cache != nil { + cache.InvalidateChannelList(s.cache) + } } -func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) { +func (s *channelServiceImpl) listAllFromDB() ([]*model.Channel, error) { return s.channelRepo.ListAll() } -func (s *channelServiceImpl) MapByIDs(ids []string) (map[string]*model.Channel, error) { - list, err := s.channelRepo.ListByIDs(ids) +func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) { + if s.cache == nil { + return s.listAllFromDB() + } + cs := cache.GetSettings() + return cache.GetOrLoadTyped(s.cache, cache.ChannelsAllListKey(), channelAllListCacheTTL, cs.JitterRatio, cs.NullTTL, s.listAllFromDB) +} + +func (s *channelServiceImpl) ListActive() ([]*model.Channel, error) { + all, err := s.ListAll() if err != nil { return nil, err } - m := make(map[string]*model.Channel, len(list)) - for _, ch := range list { - if ch != nil { - m[ch.ID] = ch + out := make([]*model.Channel, 0, len(all)) + for _, ch := range all { + if ch != nil && ch.IsActive { + c := *ch + out = append(out, &c) } } - return m, nil + return out, nil +} + +// MapByIDs 始终基于全量频道列表(Redis 命中则零 SQL,否则一次 ListAll),与 ListActive / 公开列表共用同一缓存快照。 +func (s *channelServiceImpl) MapByIDs(ids []string) (map[string]*model.Channel, error) { + if len(ids) == 0 { + return nil, nil + } + all, err := s.ListAll() + if err != nil { + return nil, err + } + byID := make(map[string]*model.Channel, len(all)) + for _, ch := range all { + if ch != nil { + byID[ch.ID] = ch + } + } + out := make(map[string]*model.Channel, len(ids)) + for _, id := range ids { + if ch, ok := byID[id]; ok { + out[id] = ch + } + } + return out, nil } func (s *channelServiceImpl) Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) { @@ -55,6 +97,7 @@ func (s *channelServiceImpl) Create(name, slug, description string, sortOrder in if err := s.channelRepo.Create(channel); err != nil { return nil, err } + s.invalidateChannelListCache() return channel, nil } @@ -71,10 +114,16 @@ func (s *channelServiceImpl) Update(id, name, slug, description string, sortOrde if err := s.channelRepo.Update(channel); err != nil { return nil, err } + s.invalidateChannelListCache() return channel, nil } func (s *channelServiceImpl) Delete(id string) error { - return s.channelRepo.Delete(id) + err := s.channelRepo.Delete(id) + if err != nil { + return err + } + s.invalidateChannelListCache() + return nil } diff --git a/internal/wire/service.go b/internal/wire/service.go index 0c71b30..50aa146 100644 --- a/internal/wire/service.go +++ b/internal/wire/service.go @@ -173,8 +173,8 @@ func ProvideVoteService( return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService) } -func ProvideChannelService(channelRepo *repository.ChannelRepository) service.ChannelService { - return service.NewChannelService(channelRepo) +func ProvideChannelService(channelRepo *repository.ChannelRepository, cacheBackend cache.Cache) service.ChannelService { + return service.NewChannelService(channelRepo, cacheBackend) } // ProvideChatService 提供聊天服务