feat(channel): enhance channel service with caching and repository updates
All checks were successful
Build Backend / build (push) Successful in 13m4s
Build Backend / build-docker (push) Successful in 1m22s

- Updated ChannelService to include caching for channel lists, improving performance and reducing database load.
- Introduced cache invalidation methods to ensure channel list consistency after modifications.
- Modified ChannelRepository to remove unused ListByIDs method, streamlining the repository interface.
- Updated wire generation to inject cache into ChannelService for enhanced functionality.
- Added new cache key constants for channel-related data management.
This commit is contained in:
lafay
2026-03-25 01:03:34 +08:00
parent 7c14cf5bab
commit a887e8ea23
5 changed files with 78 additions and 29 deletions

View File

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

View File

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

View File

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

View File

@@ -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 提供聊天服务