package handlers import ( "net/http" "strings" "github.com/gin-gonic/gin" "go_blog/models" "gorm.io/gorm" ) // adminCommentPageSize 限制后台每页显示的评论数量。 const adminCommentPageSize = 30 // commentListView 是 Comment 加上后台列表所需的派生展示字段。 type commentListView struct { models.Comment GravatarURL string Initial string AvatarColor string MaskedEmail string StatusLabel string StatusBadge string ArticleTitle string ArticleSlug string } // CommentListPage 渲染后台评论审核列表,可通过 // ?status=pending|approved|rejected|all 按状态筛选。 func CommentListPage(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) status := strings.TrimSpace(c.Query("status")) if status == "" { status = "pending" } q := db.Model(&models.Comment{}).Order("created_at DESC") switch status { case "approved": q = q.Where("status = ?", models.CommentApproved) case "rejected": q = q.Where("status = ?", models.CommentRejected) case "all": // 不过滤 default: // pending status = "pending" q = q.Where("status = ?", models.CommentPending) } var comments []models.Comment q.Limit(adminCommentPageSize).Find(&comments) // 用一次查询拉取引用的文章,避免 N+1。 ids := make(map[uint]struct{}) for _, cm := range comments { ids[cm.ArticleID] = struct{}{} } articleMap := make(map[uint]models.Article) if len(ids) > 0 { idList := make([]uint, 0, len(ids)) for id := range ids { idList = append(idList, id) } var articles []models.Article db.Unscoped().Where("id IN ?", idList).Find(&articles) for _, a := range articles { articleMap[a.ID] = a } } views := make([]commentListView, 0, len(comments)) // SECURITY_TODO #15:后台列表遵循平台开关——关闭时不发起 Gravatar // 请求(改为使用前端占位头像)。 useGravatar := models.GetCommentConfig().UseGravatar for _, cm := range comments { gravURL := "" if useGravatar { gravURL = cm.GravatarURL(40) } v := commentListView{ Comment: cm, GravatarURL: gravURL, MaskedEmail: cm.MaskedEmail(), Initial: cm.AuthorInitial(), AvatarColor: avatarColorFor(cm.ID), } if a, ok := articleMap[cm.ArticleID]; ok { v.ArticleTitle = a.Title v.ArticleSlug = a.Slug } switch cm.Status { case models.CommentPending: v.StatusLabel = tr["comment_status_pending"] v.StatusBadge = "bg-yellow-100 text-yellow-700" case models.CommentApproved: v.StatusLabel = tr["comment_status_approved"] v.StatusBadge = "bg-green-100 text-green-700" case models.CommentRejected: v.StatusLabel = tr["comment_status_rejected"] v.StatusBadge = "bg-red-100 text-red-700" } views = append(views, v) } // 标签徽章使用的待审数量。 var pendingCount int64 db.Model(&models.Comment{}).Where("status = ?", models.CommentPending).Count(&pendingCount) data := DefaultData(c) data["Title"] = tr["admin_comments_title"] data["Comments"] = views data["Status"] = status data["PendingCount"] = pendingCount if msg := c.Query("saved"); msg == "1" { switch c.Query("msg") { case "approved": data["Success"] = tr["comment_approved"] case "rejected": data["Success"] = tr["comment_rejected"] case "deleted": data["Success"] = tr["comment_deleted"] default: data["Success"] = tr["settings_saved"] } } c.HTML(http.StatusOK, "comment_list", data) } } // CommentApprove 将评论标记为通过。 func CommentApprove(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { if id := parseUintParam(c, "id"); id == 0 { APIError(c, http.StatusBadRequest, "api_invalid_request") return } else { db.Model(&models.Comment{}).Where("id = ?", id).Update("status", models.CommentApproved) } APIOK(c, "/admin/comments?status=pending&saved=1&msg=approved", nil) } } // CommentReject 将评论标记为拒绝(前端隐藏,后台列表保留)。 func CommentReject(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { if id := parseUintParam(c, "id"); id == 0 { APIError(c, http.StatusBadRequest, "api_invalid_request") return } else { db.Model(&models.Comment{}).Where("id = ?", id).Update("status", models.CommentRejected) } APIOK(c, "/admin/comments?status=pending&saved=1&msg=rejected", nil) } } // CommentDelete 软删除一条评论。 func CommentDelete(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { if id := parseUintParam(c, "id"); id == 0 { APIError(c, http.StatusBadRequest, "api_invalid_request") return } else { db.Where("id = ?", id).Delete(&models.Comment{}) } APIOK(c, "/admin/comments?status=all&saved=1&msg=deleted", nil) } }