feat(channel): enhance channel service with caching and repository updates
- 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:
@@ -62,7 +62,7 @@ func InitializeApp() (*App, error) {
|
|||||||
logService := wire.ProvideLogService(operationLogService, loginLogService, dataChangeLogService)
|
logService := wire.ProvideLogService(operationLogService, loginLogService, dataChangeLogService)
|
||||||
userHandler := wire.ProvideUserHandler(userService, userActivityService, postService, commentService, logService)
|
userHandler := wire.ProvideUserHandler(userService, userActivityService, postService, commentService, logService)
|
||||||
channelRepository := repository.NewChannelRepository(db)
|
channelRepository := repository.NewChannelRepository(db)
|
||||||
channelService := wire.ProvideChannelService(channelRepository)
|
channelService := wire.ProvideChannelService(channelRepository, cache)
|
||||||
postHandler := wire.ProvidePostHandler(postService, userService, channelService, logService)
|
postHandler := wire.ProvidePostHandler(postService, userService, channelService, logService)
|
||||||
commentHandler := handler.NewCommentHandler(commentService)
|
commentHandler := handler.NewCommentHandler(commentService)
|
||||||
chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache)
|
chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache)
|
||||||
|
|||||||
13
internal/cache/keys.go
vendored
13
internal/cache/keys.go
vendored
@@ -31,6 +31,9 @@ const (
|
|||||||
PrefixUserInfo = "users:info"
|
PrefixUserInfo = "users:info"
|
||||||
PrefixUserMe = "users:me"
|
PrefixUserMe = "users:me"
|
||||||
|
|
||||||
|
// 频道相关(全量列表一条缓存,供 /channels、帖子 channel 填充共用)
|
||||||
|
PrefixChannelsAllList = "channels:all_list"
|
||||||
|
|
||||||
// 消息缓存相关
|
// 消息缓存相关
|
||||||
keyPrefixMsgHash = "msg_hash" // 消息详情 Hash
|
keyPrefixMsgHash = "msg_hash" // 消息详情 Hash
|
||||||
keyPrefixMsgIndex = "msg_index" // 消息索引 Sorted Set
|
keyPrefixMsgIndex = "msg_index" // 消息索引 Sorted Set
|
||||||
@@ -116,6 +119,16 @@ func UserMeKey(userID string) string {
|
|||||||
return fmt.Sprintf("%s:%s", PrefixUserMe, userID)
|
return fmt.Sprintf("%s:%s", PrefixUserMe, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChannelsAllListKey 频道全量列表缓存键(含未启用,供 MapByIDs 解析历史帖子 channel)
|
||||||
|
func ChannelsAllListKey() string {
|
||||||
|
return PrefixChannelsAllList
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateChannelList 失效频道列表缓存(管理端增删改频道后调用)
|
||||||
|
func InvalidateChannelList(c Cache) {
|
||||||
|
c.Delete(ChannelsAllListKey())
|
||||||
|
}
|
||||||
|
|
||||||
// InvalidatePostList 失效帖子列表缓存
|
// InvalidatePostList 失效帖子列表缓存
|
||||||
func InvalidatePostList(cache Cache) {
|
func InvalidatePostList(cache Cache) {
|
||||||
cache.DeleteByPrefix(PrefixPostList)
|
cache.DeleteByPrefix(PrefixPostList)
|
||||||
|
|||||||
@@ -48,19 +48,6 @@ func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) {
|
|||||||
return &channel, nil
|
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 {
|
func (r *ChannelRepository) Delete(id string) error {
|
||||||
return r.db.Delete(&model.Channel{}, "id = ?", id).Error
|
return r.db.Delete(&model.Channel{}, "id = ?", id).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"carrot_bbs/internal/cache"
|
||||||
"carrot_bbs/internal/model"
|
"carrot_bbs/internal/model"
|
||||||
"carrot_bbs/internal/repository"
|
"carrot_bbs/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 频道全量列表缓存 TTL(频道变更少,可较长;管理端写入后会主动失效)
|
||||||
|
const channelAllListCacheTTL = 10 * time.Minute
|
||||||
|
|
||||||
type ChannelService interface {
|
type ChannelService interface {
|
||||||
ListActive() ([]*model.Channel, error)
|
ListActive() ([]*model.Channel, error)
|
||||||
ListAll() ([]*model.Channel, error)
|
ListAll() ([]*model.Channel, error)
|
||||||
@@ -16,32 +22,68 @@ type ChannelService interface {
|
|||||||
|
|
||||||
type channelServiceImpl struct {
|
type channelServiceImpl struct {
|
||||||
channelRepo *repository.ChannelRepository
|
channelRepo *repository.ChannelRepository
|
||||||
|
cache cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChannelService(channelRepo *repository.ChannelRepository) ChannelService {
|
func NewChannelService(channelRepo *repository.ChannelRepository, c cache.Cache) ChannelService {
|
||||||
return &channelServiceImpl{channelRepo: channelRepo}
|
return &channelServiceImpl{channelRepo: channelRepo, cache: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelServiceImpl) ListActive() ([]*model.Channel, error) {
|
func (s *channelServiceImpl) invalidateChannelListCache() {
|
||||||
return s.channelRepo.ListActive()
|
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()
|
return s.channelRepo.ListAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelServiceImpl) MapByIDs(ids []string) (map[string]*model.Channel, error) {
|
func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) {
|
||||||
list, err := s.channelRepo.ListByIDs(ids)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := make(map[string]*model.Channel, len(list))
|
out := make([]*model.Channel, 0, len(all))
|
||||||
for _, ch := range list {
|
for _, ch := range all {
|
||||||
|
if ch != nil && ch.IsActive {
|
||||||
|
c := *ch
|
||||||
|
out = append(out, &c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
if ch != nil {
|
||||||
m[ch.ID] = ch
|
byID[ch.ID] = ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
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) {
|
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 {
|
if err := s.channelRepo.Create(channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.invalidateChannelListCache()
|
||||||
return channel, nil
|
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 {
|
if err := s.channelRepo.Update(channel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.invalidateChannelListCache()
|
||||||
return channel, nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelServiceImpl) Delete(id string) error {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ func ProvideVoteService(
|
|||||||
return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService)
|
return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideChannelService(channelRepo *repository.ChannelRepository) service.ChannelService {
|
func ProvideChannelService(channelRepo *repository.ChannelRepository, cacheBackend cache.Cache) service.ChannelService {
|
||||||
return service.NewChannelService(channelRepo)
|
return service.NewChannelService(channelRepo, cacheBackend)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideChatService 提供聊天服务
|
// ProvideChatService 提供聊天服务
|
||||||
|
|||||||
Reference in New Issue
Block a user