- 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.
324 lines
8.6 KiB
Go
324 lines
8.6 KiB
Go
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)
|
||
}
|