refactor: introduce Wire dependency injection and interface-based architecture
- Add Google Wire for compile-time dependency injection - Replace concrete service types with interfaces across handlers - Remove global state (DB, cache) in favor of constructor injection - Split monolithic files into focused modules: - config: separate files for each config domain - dto: converters split by domain (user, post, message, group) - cache: separate metrics.go and redis_cache.go - Introduce unified apperrors package with string-based error codes - Add transaction support with context-aware repository methods - Upgrade golang.org/x/crypto from 0.17.0 to 0.26.0
This commit is contained in:
282
internal/dto/post_converter.go
Normal file
282
internal/dto/post_converter.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/model"
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ==================== 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,
|
||||
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
|
||||
}
|
||||
|
||||
// ConvertPostToResponse 将Post转换为PostResponse(列表用)
|
||||
func ConvertPostToResponse(post *model.Post, 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,
|
||||
Title: post.Title,
|
||||
Content: 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),
|
||||
Author: author,
|
||||
IsLiked: isLiked,
|
||||
IsFavorited: isFavorited,
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertPostToDetailResponse 将Post转换为PostDetailResponse
|
||||
func ConvertPostToDetailResponse(post *model.Post, isLiked, isFavorited bool) *PostDetailResponse {
|
||||
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 &PostDetailResponse{
|
||||
ID: post.ID,
|
||||
UserID: post.UserID,
|
||||
Title: post.Title,
|
||||
Content: 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),
|
||||
Author: author,
|
||||
IsLiked: isLiked,
|
||||
IsFavorited: isFavorited,
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertPostsToResponse 将Post列表转换为响应列表(每个帖子独立检查点赞/收藏状态)
|
||||
func ConvertPostsToResponse(posts []*model.Post, 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, isLiked, isFavorited))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BuildPostResponse 构建单个帖子响应(包含交互状态)
|
||||
// 这是一个语义化的辅助函数,便于 Handler 层调用
|
||||
func BuildPostResponse(post *model.Post, isLiked, isFavorited bool) *PostResponse {
|
||||
return ConvertPostToResponse(post, isLiked, isFavorited)
|
||||
}
|
||||
|
||||
// BuildPostsWithInteractionResponse 批量构建帖子响应(包含交互状态)
|
||||
// 这是一个语义化的辅助函数,便于 Handler 层调用
|
||||
func BuildPostsWithInteractionResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse {
|
||||
return ConvertPostsToResponse(posts, 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,
|
||||
Images: images,
|
||||
LikesCount: comment.LikesCount,
|
||||
RepliesCount: comment.RepliesCount,
|
||||
CreatedAt: FormatTime(comment.CreatedAt),
|
||||
Author: author,
|
||||
IsLiked: isLiked,
|
||||
TargetID: targetID,
|
||||
Replies: replies,
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertCommentsToResponse 将Comment列表转换为响应列表
|
||||
func ConvertCommentsToResponse(comments []*model.Comment, isLiked bool) []*CommentResponse {
|
||||
result := make([]*CommentResponse, 0, len(comments))
|
||||
for _, comment := range comments {
|
||||
result = append(result, ConvertCommentToResponse(comment, isLiked))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 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,
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user