feat(api): add message segments support for posts and comments
All checks were successful
Build Backend / build (push) Successful in 19m52s
Build Backend / build-docker (push) Successful in 1m29s

Add rich message segment support enabling structured content (text, images, votes, mentions) in posts and comments. Includes segments field in models, DTOs, handlers, and services. Also adds @mention notification processing and migrates vote data to embedded segments.
This commit is contained in:
lafay
2026-04-23 22:29:34 +08:00
parent 15e7c99353
commit 05c7f8a5eb
14 changed files with 364 additions and 154 deletions

View File

@@ -1,4 +1,4 @@
package service
package service
import (
"context"
@@ -9,6 +9,7 @@ import (
"time"
"with_you/internal/cache"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/cursor"
"with_you/internal/pkg/hook"
@@ -25,7 +26,7 @@ const (
// PostService 帖子服务接口
type PostService interface {
// 帖子CRUD
Create(ctx context.Context, userID, title, content string, images []string, channelID *string) (*model.Post, error)
Create(ctx context.Context, userID, title, content string, segments model.MessageSegments, images []string, channelID *string) (*model.Post, error)
GetByID(ctx context.Context, id string) (*model.Post, error)
Update(ctx context.Context, post *model.Post) error
UpdateWithImages(ctx context.Context, post *model.Post, images *[]string) error
@@ -98,13 +99,27 @@ type PostListResult struct {
}
// Create 创建帖子
func (s *postServiceImpl) Create(ctx context.Context, userID, title, content string, images []string, channelID *string) (*model.Post, error) {
func (s *postServiceImpl) Create(ctx context.Context, userID, title, content string, segments model.MessageSegments, images []string, channelID *string) (*model.Post, error) {
// 如果没有提供 content 但有 segments从 segments 提取文本摘要
if content == "" && len(segments) > 0 {
content = dto.ExtractTextContent(segments)
}
// 如果有 segments 但 content 为空,将 content 设为空字符串NOT NULL 约束)
if content == "" {
content = " "
}
// 检查 segments 中是否包含投票
isVote := dto.HasVoteSegment(segments)
post := &model.Post{
UserID: userID,
ChannelID: channelID,
Title: title,
Content: content,
Segments: segments,
Status: model.PostStatusPending,
IsVote: isVote,
}
err := s.postRepo.Create(post, images)
@@ -127,6 +142,12 @@ func (s *postServiceImpl) Create(ctx context.Context, userID, title, content str
// 失效帖子列表缓存
cache.InvalidatePostList(s.cache)
// 异步处理 @提及通知
if len(segments) > 0 {
bgCtx := context.WithoutCancel(ctx)
go s.processMentionNotifications(bgCtx, userID, post.ID, post.Title, segments)
}
// 异步执行审核流
go s.reviewPostAsync(post.ID, userID, title, content, images)
@@ -246,6 +267,27 @@ func (s *postServiceImpl) notifyModerationRejected(userID, reason string) {
}()
}
// processMentionNotifications 处理帖子中 @提及的通知
func (s *postServiceImpl) processMentionNotifications(ctx context.Context, authorID, postID, postTitle string, segments model.MessageSegments) {
if s.systemMessageService == nil {
return
}
mentionedUserIDs := dto.ExtractMentionedUsers(segments)
if len(mentionedUserIDs) == 0 {
return
}
for _, targetUserID := range mentionedUserIDs {
if targetUserID == authorID {
continue
}
if err := s.systemMessageService.SendMentionNotification(ctx, targetUserID, authorID, postID); err != nil {
log.Printf("[WARN] Failed to send mention notification to user %s: %v", targetUserID, err)
}
}
}
// GetByID 根据ID获取帖子
func (s *postServiceImpl) GetByID(ctx context.Context, id string) (*model.Post, error) {
return s.postRepo.GetByID(id)