package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/novablog/server/internal/database" "github.com/novablog/server/internal/middleware" "github.com/novablog/server/internal/models" "gorm.io/gorm" ) // CommentHandler 评论处理器 type CommentHandler struct{} // NewCommentHandler 创建评论处理器 func NewCommentHandler() *CommentHandler { return &CommentHandler{} } // CreateCommentRequest 创建评论请求 type CreateCommentRequest struct { PostID string `json:"post_id" binding:"required"` ParentID *uint `json:"parent_id"` Content string `json:"content" binding:"required,min=1,max=2000"` } // CreateComment 创建评论 func (h *CommentHandler) CreateComment(c *gin.Context) { var req CreateCommentRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 尝试从上下文获取用户 ID(如果有认证中间件) userID, isLoggedIn := middleware.GetUserID(c) // 如果没有登录,返回错误(评论需要登录) if !isLoggedIn || userID == 0 { c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再评论"}) return } comment := models.Comment{ PostID: req.PostID, UserID: userID, ParentID: req.ParentID, Content: req.Content, Status: "approved", // 默认直接通过,可改为 pending 需审核 } if err := database.DB.Create(&comment).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create comment"}) return } // 加载用户信息 database.DB.Preload("User").First(&comment, comment.ID) c.JSON(http.StatusCreated, comment) } // GetComments 获取文章评论列表 func (h *CommentHandler) GetComments(c *gin.Context) { postID := c.Query("post_id") if postID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "post_id is required"}) return } // 分页参数 page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } // 获取顶级评论(非回复) var comments []models.Comment var total int64 query := database.DB.Model(&models.Comment{}). Where("post_id = ? AND status = ? AND parent_id IS NULL", postID, "approved") query.Count(&total) // 使用 Joins 显式加载用户信息,确保数据完整 if err := query. Joins("LEFT JOIN users ON users.id = comments.user_id"). Preload("Replies.User"). Order("comments.created_at DESC"). Offset((page - 1) * pageSize). Limit(pageSize). Find(&comments).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get comments"}) return } // 手动加载每个评论的用户信息(确保 Preload 正确工作) for i := range comments { if comments[i].UserID > 0 { var user models.User if err := database.DB.First(&user, comments[i].UserID).Error; err == nil { comments[i].User = user } } // 加载回复的用户信息 for j := range comments[i].Replies { if comments[i].Replies[j].UserID > 0 { var replyUser models.User if err := database.DB.First(&replyUser, comments[i].Replies[j].UserID).Error; err == nil { comments[i].Replies[j].User = replyUser } } } } c.JSON(http.StatusOK, gin.H{ "data": comments, "pagination": gin.H{ "page": page, "page_size": pageSize, "total": total, "total_page": (total + int64(pageSize) - 1) / int64(pageSize), }, }) } // DeleteComment 删除评论(仅限本人或管理员) func (h *CommentHandler) DeleteComment(c *gin.Context) { userID, _ := middleware.GetUserID(c) role, _ := c.Get("role") idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid comment id"}) return } var comment models.Comment if err := database.DB.First(&comment, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "comment not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"}) return } // 检查权限:本人或管理员可删除 if comment.UserID != userID && role != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) return } // 软删除 if err := database.DB.Delete(&comment).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete comment"}) return } c.JSON(http.StatusOK, gin.H{"message": "comment deleted"}) }