Files
backend/internal/handler/comment_handler.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

324 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"encoding/json"
"strconv"
"github.com/gin-gonic/gin"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/cursor"
"with_you/internal/pkg/response"
"with_you/internal/service"
)
// CommentHandler 评论处理器
type CommentHandler struct {
commentService service.CommentService
}
// NewCommentHandler 创建评论处理器
func NewCommentHandler(commentService service.CommentService) *CommentHandler {
return &CommentHandler{
commentService: commentService,
}
}
// Create 创建评论
func (h *CommentHandler) Create(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
type CreateRequest struct {
PostID string `json:"post_id" binding:"required"`
Content string `json:"content"`
Segments model.MessageSegments `json:"segments"`
ParentID *string `json:"parent_id"`
Images []string `json:"images"`
}
var req CreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 验证评论必须有内容或图片或segments
if req.Content == "" && len(req.Images) == 0 && len(req.Segments) == 0 {
response.BadRequest(c, "评论内容或图片不能同时为空")
return
}
// 如果没有content但有segments从segments提取文本
content := req.Content
if content == "" && len(req.Segments) > 0 {
content = dto.ExtractTextContent(req.Segments)
}
// 将图片列表转换为JSON字符串
var imagesJSON string
if len(req.Images) > 0 {
imagesBytes, _ := json.Marshal(req.Images)
imagesJSON = string(imagesBytes)
}
comment, err := h.commentService.Create(c.Request.Context(), req.PostID, userID, content, req.Segments, req.ParentID, imagesJSON, req.Images)
if err != nil {
response.InternalServerError(c, "failed to create comment")
return
}
response.Success(c, dto.ConvertCommentToResponse(comment, false))
}
// GetByID 获取单条评论详情
func (h *CommentHandler) GetByID(c *gin.Context) {
userID := c.GetString("user_id")
id := c.Param("id")
comment, err := h.commentService.GetByID(c.Request.Context(), id)
if err != nil {
response.NotFound(c, "comment not found")
return
}
resp := dto.ConvertCommentToResponse(comment, h.commentService.IsLiked(c.Request.Context(), id, userID))
response.Success(c, resp)
}
// GetByPostID 获取帖子评论
func (h *CommentHandler) GetByPostID(c *gin.Context) {
userID := c.GetString("user_id")
postID := c.Param("id")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
comments, total, err := h.commentService.GetByPostID(c.Request.Context(), postID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get comments")
return
}
// 转换为响应结构,检查每个评论的点赞状态
commentResponses := dto.ConvertCommentsToResponseWithUser(comments, userID, h.commentService)
response.Paginated(c, commentResponses, total, page, pageSize)
}
// GetReplies 获取回复
func (h *CommentHandler) GetReplies(c *gin.Context) {
userID := c.GetString("user_id")
parentID := c.Param("id")
comments, err := h.commentService.GetReplies(c.Request.Context(), parentID)
if err != nil {
response.InternalServerError(c, "failed to get replies")
return
}
// 转换为响应结构,检查每个回复的点赞状态
commentResponses := dto.ConvertCommentsToResponseWithUser(comments, userID, h.commentService)
response.Success(c, commentResponses)
}
// GetRepliesByRootID 根据根评论ID分页获取回复扁平化
func (h *CommentHandler) GetRepliesByRootID(c *gin.Context) {
userID := c.GetString("user_id")
rootID := c.Param("id")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
replies, total, err := h.commentService.GetRepliesByRootID(c.Request.Context(), rootID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get replies")
return
}
// 转换为响应结构,检查每个回复的点赞状态
replyResponses := dto.ConvertCommentsToResponseWithUser(replies, userID, h.commentService)
response.Paginated(c, replyResponses, total, page, pageSize)
}
// Update 更新评论
func (h *CommentHandler) Update(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
id := c.Param("id")
comment, err := h.commentService.GetByID(c.Request.Context(), id)
if err != nil {
response.NotFound(c, "comment not found")
return
}
if comment.UserID != userID {
response.Forbidden(c, "cannot update others' comment")
return
}
type UpdateRequest struct {
Content string `json:"content" binding:"required"`
}
var req UpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
comment.Content = req.Content
err = h.commentService.Update(c.Request.Context(), userID, comment)
if err != nil {
response.HandleError(c, err, "failed to update comment")
return
}
response.Success(c, dto.ConvertCommentToResponse(comment, false))
}
// Delete 删除评论
func (h *CommentHandler) Delete(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
id := c.Param("id")
comment, err := h.commentService.GetByID(c.Request.Context(), id)
if err != nil {
response.NotFound(c, "comment not found")
return
}
if comment.UserID != userID {
response.Forbidden(c, "cannot delete others' comment")
return
}
err = h.commentService.Delete(c.Request.Context(), userID, id)
if err != nil {
response.HandleError(c, err, "failed to delete comment")
return
}
response.SuccessWithMessage(c, "comment deleted", nil)
}
// Like 点赞评论
func (h *CommentHandler) Like(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
id := c.Param("id")
err := h.commentService.Like(c.Request.Context(), id, userID)
if err != nil {
response.InternalServerError(c, "failed to like comment")
return
}
response.SuccessWithMessage(c, "liked", nil)
}
// Unlike 取消点赞评论
func (h *CommentHandler) Unlike(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
id := c.Param("id")
err := h.commentService.Unlike(c.Request.Context(), id, userID)
if err != nil {
response.InternalServerError(c, "failed to unlike comment")
return
}
response.SuccessWithMessage(c, "unliked", nil)
}
// GetByPostIDByCursor 游标分页获取帖子评论
func (h *CommentHandler) GetByPostIDByCursor(c *gin.Context) {
userID := c.GetString("user_id")
postID := c.Param("id")
// 解析游标分页请求
req := h.parseCursorRequest(c)
// 调用游标分页服务
result, err := h.commentService.GetCommentsByCursor(c.Request.Context(), postID, req)
if err != nil {
response.InternalServerError(c, "failed to get comments")
return
}
// 转换为响应结构,检查每个评论的点赞状态
commentResponses := dto.ConvertCommentsToResponseWithUser(result.Items, userID, h.commentService)
// 构建游标分页响应
cursorResp := &dto.CommentCursorPageResponse{
Items: commentResponses,
NextCursor: result.NextCursor,
PrevCursor: result.PrevCursor,
HasMore: result.HasMore,
}
response.Success(c, cursorResp)
}
// GetRepliesByRootIDByCursor 游标分页获取根评论的回复
func (h *CommentHandler) GetRepliesByRootIDByCursor(c *gin.Context) {
userID := c.GetString("user_id")
rootID := c.Param("id")
// 解析游标分页请求
req := h.parseCursorRequest(c)
// 调用游标分页服务
result, err := h.commentService.GetRepliesByCursor(c.Request.Context(), rootID, req)
if err != nil {
response.InternalServerError(c, "failed to get replies")
return
}
// 转换为响应结构,检查每个回复的点赞状态
replyResponses := dto.ConvertCommentsToResponseWithUser(result.Items, userID, h.commentService)
// 构建游标分页响应
cursorResp := &dto.CommentCursorPageResponse{
Items: replyResponses,
NextCursor: result.NextCursor,
PrevCursor: result.PrevCursor,
HasMore: result.HasMore,
}
response.Success(c, cursorResp)
}
// parseCursorRequest 解析游标分页请求参数
func (h *CommentHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
cursorStr := c.Query("cursor")
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
return cursor.NewPageRequest(cursorStr, direction, pageSize)
}