Compare commits

...

2 Commits

Author SHA1 Message Date
lafay
a887e8ea23 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.
2026-03-25 01:03:34 +08:00
lafay
7c14cf5bab feat(post): enhance post handling with channel integration
- Updated PostHandler to include channel information in post responses.
- Introduced PostChannelBrief DTO to represent channel details associated with posts.
- Modified post conversion functions to support channel data, ensuring proper mapping of channel IDs to channel names.
- Enhanced ChannelRepository and ChannelService to facilitate batch retrieval of channels by IDs.
- Updated wire generation to inject channel service into post handler for improved functionality.
2026-03-25 00:58:41 +08:00
8 changed files with 194 additions and 48 deletions

View File

@@ -61,7 +61,9 @@ func InitializeApp() (*App, error) {
dataChangeLogService := wire.ProvideDataChangeLogService(asyncLogManager, dataChangeLogRepository) dataChangeLogService := wire.ProvideDataChangeLogService(asyncLogManager, dataChangeLogRepository)
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)
postHandler := wire.ProvidePostHandler(postService, userService, logService) channelRepository := repository.NewChannelRepository(db)
channelService := wire.ProvideChannelService(channelRepository, cache)
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)
messageService := wire.ProvideMessageService(db, messageRepository, cache) messageService := wire.ProvideMessageService(db, messageRepository, cache)
@@ -87,8 +89,6 @@ func InitializeApp() (*App, error) {
voteRepository := repository.NewVoteRepository(db) voteRepository := repository.NewVoteRepository(db)
voteService := wire.ProvideVoteService(voteRepository, postRepository, cache, postAIService, systemMessageService) voteService := wire.ProvideVoteService(voteRepository, postRepository, cache, postAIService, systemMessageService)
voteHandler := handler.NewVoteHandler(voteService, postService) voteHandler := handler.NewVoteHandler(voteService, postService)
channelRepository := repository.NewChannelRepository(db)
channelService := wire.ProvideChannelService(channelRepository)
channelHandler := handler.NewChannelHandler(channelService) channelHandler := handler.NewChannelHandler(channelService)
scheduleRepository := repository.NewScheduleRepository(db) scheduleRepository := repository.NewScheduleRepository(db)
scheduleService := wire.ProvideScheduleService(scheduleRepository) scheduleService := wire.ProvideScheduleService(scheduleRepository)

View File

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

View File

@@ -120,6 +120,12 @@ type AdminUpdateUserStatusRequest struct {
// ==================== Post DTOs ==================== // ==================== Post DTOs ====================
// PostChannelBrief 帖子所属频道(列表/详情卡片展示)
type PostChannelBrief struct {
ID string `json:"id"`
Name string `json:"name"`
}
// PostImageResponse 帖子图片响应 // PostImageResponse 帖子图片响应
type PostImageResponse struct { type PostImageResponse struct {
ID string `json:"id"` ID string `json:"id"`
@@ -154,6 +160,7 @@ type PostResponse struct {
Author *UserResponse `json:"author"` Author *UserResponse `json:"author"`
IsLiked bool `json:"is_liked"` IsLiked bool `json:"is_liked"`
IsFavorited bool `json:"is_favorited"` IsFavorited bool `json:"is_favorited"`
Channel *PostChannelBrief `json:"channel,omitempty"`
} }
// PostDetailResponse 帖子详情响应 // PostDetailResponse 帖子详情响应
@@ -179,6 +186,7 @@ type PostDetailResponse struct {
Author *UserResponse `json:"author"` Author *UserResponse `json:"author"`
IsLiked bool `json:"is_liked"` IsLiked bool `json:"is_liked"`
IsFavorited bool `json:"is_favorited"` IsFavorited bool `json:"is_favorited"`
Channel *PostChannelBrief `json:"channel,omitempty"`
} }
// ==================== Comment DTOs ==================== // ==================== Comment DTOs ====================

View File

@@ -31,8 +31,42 @@ func ConvertPostImagesToResponse(images []model.PostImage) []PostImageResponse {
return result return result
} }
// ConvertPostToResponse 将Post转换为PostResponse列表用 func postChannelBrief(post *model.Post, channelByID map[string]*model.Channel) *PostChannelBrief {
func ConvertPostToResponse(post *model.Post, isLiked, isFavorited bool) *PostResponse { if post == nil || post.ChannelID == nil || *post.ChannelID == "" || channelByID == nil {
return nil
}
ch := channelByID[*post.ChannelID]
if ch == nil {
return nil
}
return &PostChannelBrief{ID: ch.ID, Name: ch.Name}
}
// CollectPostChannelIDs 从帖子列表收集去重后的频道 ID用于批量查库填充 channel 名称)
func CollectPostChannelIDs(posts []*model.Post) []string {
seen := make(map[string]struct{})
out := make([]string, 0)
for _, p := range posts {
if p == nil || p.ChannelID == nil || *p.ChannelID == "" {
continue
}
id := *p.ChannelID
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
return out
}
// PostChannelBriefForPost 根据帖子与频道映射生成频道摘要(供 Handler 手动组装响应)
func PostChannelBriefForPost(post *model.Post, channelByID map[string]*model.Channel) *PostChannelBrief {
return postChannelBrief(post, channelByID)
}
// ConvertPostToResponse 将Post转换为PostResponse列表用channelByID 可为 nil
func ConvertPostToResponse(post *model.Post, channelByID map[string]*model.Channel, isLiked, isFavorited bool) *PostResponse {
if post == nil { if post == nil {
return nil return nil
} }
@@ -69,11 +103,12 @@ func ConvertPostToResponse(post *model.Post, isLiked, isFavorited bool) *PostRes
Author: author, Author: author,
IsLiked: isLiked, IsLiked: isLiked,
IsFavorited: isFavorited, IsFavorited: isFavorited,
Channel: postChannelBrief(post, channelByID),
} }
} }
// ConvertPostToDetailResponse 将Post转换为PostDetailResponse // ConvertPostToDetailResponse 将Post转换为PostDetailResponsechannelByID 可为 nil
func ConvertPostToDetailResponse(post *model.Post, isLiked, isFavorited bool) *PostDetailResponse { func ConvertPostToDetailResponse(post *model.Post, channelByID map[string]*model.Channel, isLiked, isFavorited bool) *PostDetailResponse {
if post == nil { if post == nil {
return nil return nil
} }
@@ -110,11 +145,12 @@ func ConvertPostToDetailResponse(post *model.Post, isLiked, isFavorited bool) *P
Author: author, Author: author,
IsLiked: isLiked, IsLiked: isLiked,
IsFavorited: isFavorited, IsFavorited: isFavorited,
Channel: postChannelBrief(post, channelByID),
} }
} }
// ConvertPostsToResponse 将Post列表转换为响应列表每个帖子独立检查点赞/收藏状态) // ConvertPostsToResponse 将Post列表转换为响应列表每个帖子独立检查点赞/收藏状态)channelByID 可为 nil
func ConvertPostsToResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse { func ConvertPostsToResponse(posts []*model.Post, channelByID map[string]*model.Channel, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse {
result := make([]*PostResponse, 0, len(posts)) result := make([]*PostResponse, 0, len(posts))
for _, post := range posts { for _, post := range posts {
isLiked := false isLiked := false
@@ -125,21 +161,21 @@ func ConvertPostsToResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[
if isFavoritedMap != nil { if isFavoritedMap != nil {
isFavorited = isFavoritedMap[post.ID] isFavorited = isFavoritedMap[post.ID]
} }
result = append(result, ConvertPostToResponse(post, isLiked, isFavorited)) result = append(result, ConvertPostToResponse(post, channelByID, isLiked, isFavorited))
} }
return result return result
} }
// BuildPostResponse 构建单个帖子响应(包含交互状态) // BuildPostResponse 构建单个帖子响应(包含交互状态)
// 这是一个语义化的辅助函数,便于 Handler 层调用 // 这是一个语义化的辅助函数,便于 Handler 层调用
func BuildPostResponse(post *model.Post, isLiked, isFavorited bool) *PostResponse { func BuildPostResponse(post *model.Post, channelByID map[string]*model.Channel, isLiked, isFavorited bool) *PostResponse {
return ConvertPostToResponse(post, isLiked, isFavorited) return ConvertPostToResponse(post, channelByID, isLiked, isFavorited)
} }
// BuildPostsWithInteractionResponse 批量构建帖子响应(包含交互状态) // BuildPostsWithInteractionResponse 批量构建帖子响应(包含交互状态)
// 这是一个语义化的辅助函数,便于 Handler 层调用 // 这是一个语义化的辅助函数,便于 Handler 层调用
func BuildPostsWithInteractionResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse { func BuildPostsWithInteractionResponse(posts []*model.Post, channelByID map[string]*model.Channel, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse {
return ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap) return ConvertPostsToResponse(posts, channelByID, isLikedMap, isFavoritedMap)
} }
// ==================== Comment 转换 ==================== // ==================== Comment 转换 ====================

View File

@@ -17,18 +17,33 @@ import (
// PostHandler 帖子处理器 // PostHandler 帖子处理器
type PostHandler struct { type PostHandler struct {
postService service.PostService postService service.PostService
userService service.UserService userService service.UserService
channelService service.ChannelService
} }
// NewPostHandler 创建帖子处理器 // NewPostHandler 创建帖子处理器
func NewPostHandler(postService service.PostService, userService service.UserService) *PostHandler { func NewPostHandler(postService service.PostService, userService service.UserService, channelService service.ChannelService) *PostHandler {
return &PostHandler{ return &PostHandler{
postService: postService, postService: postService,
userService: userService, userService: userService,
channelService: channelService,
} }
} }
// channelMapForPosts 批量解析帖子中的频道 ID用于填充响应里的 channel 名称
func (h *PostHandler) channelMapForPosts(posts []*model.Post) map[string]*model.Channel {
ids := dto.CollectPostChannelIDs(posts)
if len(ids) == 0 {
return nil
}
m, err := h.channelService.MapByIDs(ids)
if err != nil {
return nil
}
return m
}
// Create 创建帖子 // Create 创建帖子
func (h *PostHandler) Create(c *gin.Context) { func (h *PostHandler) Create(c *gin.Context) {
userID := c.GetString("user_id") userID := c.GetString("user_id")
@@ -61,7 +76,8 @@ func (h *PostHandler) Create(c *gin.Context) {
return return
} }
response.Success(c, dto.ConvertPostToResponse(post, false, false)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.ConvertPostToResponse(post, chMap, false, false))
} }
// GetByID 获取帖子(不增加浏览量) // GetByID 获取帖子(不增加浏览量)
@@ -123,6 +139,9 @@ func (h *PostHandler) GetByID(c *gin.Context) {
IsFavorited: isFavorited, IsFavorited: isFavorited,
} }
chMap := h.channelMapForPosts([]*model.Post{post})
responseData.Channel = dto.PostChannelBriefForPost(post, chMap)
response.Success(c, responseData) response.Success(c, responseData)
} }
@@ -251,8 +270,8 @@ func (h *PostHandler) List(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(posts)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize) response.Paginated(c, postResponses, total, page, pageSize)
} }
@@ -308,8 +327,8 @@ func (h *PostHandler) ListByCursor(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(result.Items)
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
// 构建游标分页响应 // 构建游标分页响应
cursorResp := &dto.PostCursorPageResponse{ cursorResp := &dto.PostCursorPageResponse{
@@ -378,7 +397,8 @@ func (h *PostHandler) Update(c *gin.Context) {
currentUserID := c.GetString("user_id") currentUserID := c.GetString("user_id")
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), post.ID, currentUserID) isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), post.ID, currentUserID)
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
} }
// Delete 删除帖子 // Delete 删除帖子
@@ -437,7 +457,8 @@ func (h *PostHandler) Like(c *gin.Context) {
// 获取交互状态 // 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID) isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
} }
// Unlike 取消点赞 // Unlike 取消点赞
@@ -466,7 +487,8 @@ func (h *PostHandler) Unlike(c *gin.Context) {
// 获取交互状态 // 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID) isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
} }
// Favorite 收藏帖子 // Favorite 收藏帖子
@@ -495,7 +517,8 @@ func (h *PostHandler) Favorite(c *gin.Context) {
// 获取交互状态 // 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID) isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
} }
// Unfavorite 取消收藏 // Unfavorite 取消收藏
@@ -524,7 +547,8 @@ func (h *PostHandler) Unfavorite(c *gin.Context) {
// 获取交互状态 // 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID) isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited)) chMap := h.channelMapForPosts([]*model.Post{post})
response.Success(c, dto.BuildPostResponse(post, chMap, isLiked, isFavorited))
} }
// GetUserPosts 获取用户帖子列表(支持游标分页和偏移分页) // GetUserPosts 获取用户帖子列表(支持游标分页和偏移分页)
@@ -557,8 +581,8 @@ func (h *PostHandler) GetUserPosts(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(posts)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize) response.Paginated(c, postResponses, total, page, pageSize)
} }
@@ -588,8 +612,8 @@ func (h *PostHandler) GetUserPostsByCursor(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(result.Items)
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
// 构建游标分页响应 // 构建游标分页响应
cursorResp := &dto.PostCursorPageResponse{ cursorResp := &dto.PostCursorPageResponse{
@@ -623,8 +647,8 @@ func (h *PostHandler) GetFavorites(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(posts)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize) response.Paginated(c, postResponses, total, page, pageSize)
} }
@@ -659,8 +683,8 @@ func (h *PostHandler) Search(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(posts)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(posts, chMap, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize) response.Paginated(c, postResponses, total, page, pageSize)
} }
@@ -687,8 +711,8 @@ func (h *PostHandler) SearchByCursor(c *gin.Context) {
} }
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID) isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构 chMap := h.channelMapForPosts(result.Items)
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap) postResponses := dto.BuildPostsWithInteractionResponse(result.Items, chMap, isLikedMap, isFavoritedMap)
// 构建游标分页响应 // 构建游标分页响应
cursorResp := &dto.PostCursorPageResponse{ cursorResp := &dto.PostCursorPageResponse{

View File

@@ -1,13 +1,20 @@
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)
MapByIDs(ids []string) (map[string]*model.Channel, error)
Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error)
Update(id, name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) Update(id, name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error)
Delete(id string) error Delete(id string) error
@@ -15,18 +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) listAllFromDB() ([]*model.Channel, error) {
return s.channelRepo.ListAll()
} }
func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) { func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) {
return s.channelRepo.ListAll() 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
}
out := make([]*model.Channel, 0, len(all))
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 {
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) { func (s *channelServiceImpl) Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) {
@@ -40,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
} }
@@ -56,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
} }

View File

@@ -64,9 +64,10 @@ func ProvideUserHandler(
func ProvidePostHandler( func ProvidePostHandler(
postService service.PostService, postService service.PostService,
userService service.UserService, userService service.UserService,
channelService service.ChannelService,
logService *service.LogService, logService *service.LogService,
) *handler.PostHandler { ) *handler.PostHandler {
h := handler.NewPostHandler(postService, userService) h := handler.NewPostHandler(postService, userService, channelService)
if ps, ok := postService.(interface{ SetLogService(*service.LogService) }); ok { if ps, ok := postService.(interface{ SetLogService(*service.LogService) }); ok {
ps.SetLogService(logService) ps.SetLogService(logService)
} }

View File

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