package dto import ( "context" "encoding/json" "with_you/internal/model" ) // ==================== Post 转换 ==================== // ConvertPostImageToResponse 将PostImage转换为PostImageResponse func ConvertPostImageToResponse(img *model.PostImage) PostImageResponse { if img == nil { return PostImageResponse{} } return PostImageResponse{ ID: img.ID, URL: img.URL, ThumbnailURL: img.ThumbnailURL, PreviewURL: img.PreviewURL, PreviewURLLarge: img.PreviewURLLarge, Width: img.Width, Height: img.Height, } } // ConvertPostImagesToResponse 将PostImage列表转换为响应列表 func ConvertPostImagesToResponse(images []model.PostImage) []PostImageResponse { result := make([]PostImageResponse, 0, len(images)) for i := range images { result = append(result, ConvertPostImageToResponse(&images[i])) } return result } func postChannelBrief(post *model.Post, channelByID map[string]*model.Channel) *PostChannelBrief { 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) } // SegmentsOrDefault 如果 segments 为空但 content 非空, // 返回降级的单 text segment;否则原样返回。 func SegmentsOrDefault(segments model.MessageSegments, content string) model.MessageSegments { if len(segments) > 0 { return segments } if content != "" { return model.MessageSegments{ {Type: string(SegmentTypeText), Data: map[string]any{"text": content}}, } } return nil } // ConvertPostToResponse 将Post转换为PostResponse(列表用);channelByID 可为 nil func ConvertPostToResponse(post *model.Post, channelByID map[string]*model.Channel, isLiked, isFavorited bool) *PostResponse { if post == nil { return nil } images := make([]PostImageResponse, 0) for _, img := range post.Images { images = append(images, ConvertPostImageToResponse(&img)) } var author *UserResponse if post.User != nil { author = ConvertUserToResponse(post.User) } return &PostResponse{ ID: post.ID, UserID: post.UserID, ChannelID: post.ChannelID, Title: post.Title, Content: post.Content, Segments: SegmentsOrDefault(post.Segments, post.Content), Images: images, Status: string(post.Status), LikesCount: post.LikesCount, CommentsCount: post.CommentsCount, FavoritesCount: post.FavoritesCount, SharesCount: post.SharesCount, ViewsCount: post.ViewsCount, IsPinned: post.IsPinned, IsLocked: post.IsLocked, IsVote: post.IsVote, CreatedAt: FormatTime(post.CreatedAt), UpdatedAt: FormatTime(post.UpdatedAt), ContentEditedAt: FormatTimePointer(post.ContentEditedAt), Author: author, IsLiked: isLiked, IsFavorited: isFavorited, Channel: postChannelBrief(post, channelByID), } } // ConvertPostsToResponse 将Post列表转换为响应列表;channelByID 可为 nil func ConvertPostsToResponse(posts []*model.Post, channelByID map[string]*model.Channel, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse { result := make([]*PostResponse, 0, len(posts)) for _, post := range posts { isLiked := false isFavorited := false if isLikedMap != nil { isLiked = isLikedMap[post.ID] } if isFavoritedMap != nil { isFavorited = isFavoritedMap[post.ID] } result = append(result, ConvertPostToResponse(post, channelByID, isLiked, isFavorited)) } return result } // BuildPostResponse 构建单个帖子响应(包含交互状态) // 这是一个语义化的辅助函数,便于 Handler 层调用 func BuildPostResponse(post *model.Post, channelByID map[string]*model.Channel, isLiked, isFavorited bool) *PostResponse { return ConvertPostToResponse(post, channelByID, isLiked, isFavorited) } // BuildPostsWithInteractionResponse 批量构建帖子响应(包含交互状态) // 这是一个语义化的辅助函数,便于 Handler 层调用 func BuildPostsWithInteractionResponse(posts []*model.Post, channelByID map[string]*model.Channel, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse { return ConvertPostsToResponse(posts, channelByID, isLikedMap, isFavoritedMap) } // ==================== Comment 转换 ==================== // ConvertCommentToResponse 将Comment转换为CommentResponse func ConvertCommentToResponse(comment *model.Comment, isLiked bool) *CommentResponse { if comment == nil { return nil } var author *UserResponse if comment.User != nil { author = ConvertUserToResponse(comment.User) } // 转换子回复(扁平化结构) var replies []*CommentResponse if len(comment.Replies) > 0 { replies = make([]*CommentResponse, 0, len(comment.Replies)) for _, reply := range comment.Replies { replies = append(replies, ConvertCommentToResponse(reply, false)) } } // TargetID 就是 ParentID,前端根据这个 ID 找到被回复用户的昵称 var targetID *string if comment.ParentID != nil && *comment.ParentID != "" { targetID = comment.ParentID } // 解析图片JSON var images []CommentImageResponse if comment.Images != "" { var urlList []string if err := json.Unmarshal([]byte(comment.Images), &urlList); err == nil { images = make([]CommentImageResponse, 0, len(urlList)) for _, url := range urlList { images = append(images, CommentImageResponse{URL: url}) } } } return &CommentResponse{ ID: comment.ID, PostID: comment.PostID, UserID: comment.UserID, ParentID: comment.ParentID, RootID: comment.RootID, Content: comment.Content, Segments: SegmentsOrDefault(comment.Segments, comment.Content), Images: images, LikesCount: comment.LikesCount, RepliesCount: comment.RepliesCount, CreatedAt: FormatTime(comment.CreatedAt), Author: author, IsLiked: isLiked, TargetID: targetID, Replies: replies, } } // IsLikedChecker 点赞状态检查器接口 type IsLikedChecker interface { IsLiked(ctx context.Context, commentID, userID string) bool } // ConvertCommentToResponseWithUser 将Comment转换为CommentResponse(根据用户ID检查点赞状态) func ConvertCommentToResponseWithUser(comment *model.Comment, userID string, checker IsLikedChecker) *CommentResponse { if comment == nil { return nil } // 检查当前用户是否点赞了该评论 isLiked := false if userID != "" && checker != nil { isLiked = checker.IsLiked(context.Background(), comment.ID, userID) } var author *UserResponse if comment.User != nil { author = ConvertUserToResponse(comment.User) } // 转换子回复(扁平化结构),递归检查点赞状态 var replies []*CommentResponse if len(comment.Replies) > 0 { replies = make([]*CommentResponse, 0, len(comment.Replies)) for _, reply := range comment.Replies { replies = append(replies, ConvertCommentToResponseWithUser(reply, userID, checker)) } } // TargetID 就是 ParentID,前端根据这个 ID 找到被回复用户的昵称 var targetID *string if comment.ParentID != nil && *comment.ParentID != "" { targetID = comment.ParentID } // 解析图片JSON var images []CommentImageResponse if comment.Images != "" { var urlList []string if err := json.Unmarshal([]byte(comment.Images), &urlList); err == nil { images = make([]CommentImageResponse, 0, len(urlList)) for _, url := range urlList { images = append(images, CommentImageResponse{URL: url}) } } } return &CommentResponse{ ID: comment.ID, PostID: comment.PostID, UserID: comment.UserID, ParentID: comment.ParentID, RootID: comment.RootID, Content: comment.Content, Segments: SegmentsOrDefault(comment.Segments, comment.Content), Images: images, LikesCount: comment.LikesCount, RepliesCount: comment.RepliesCount, CreatedAt: FormatTime(comment.CreatedAt), Author: author, IsLiked: isLiked, TargetID: targetID, Replies: replies, } } // ConvertCommentsToResponseWithUser 将Comment列表转换为响应列表(根据用户ID检查点赞状态) func ConvertCommentsToResponseWithUser(comments []*model.Comment, userID string, checker IsLikedChecker) []*CommentResponse { result := make([]*CommentResponse, 0, len(comments)) for _, comment := range comments { result = append(result, ConvertCommentToResponseWithUser(comment, userID, checker)) } return result }