- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文 - 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等 - 代码、字符串字面量、日志消息保持英文原文,零逻辑改动 - go build/vet 通过,go test -count=1 ./... 全绿
161 lines
4.5 KiB
Go
161 lines
4.5 KiB
Go
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 := c.Param("id"); id != "" {
|
|
db.Model(&models.Comment{}).Where("id = ?", id).Update("status", models.CommentApproved)
|
|
}
|
|
c.Redirect(http.StatusFound, "/admin/comments?status=pending&saved=1&msg=approved")
|
|
}
|
|
}
|
|
|
|
// CommentReject 将评论标记为拒绝(前端隐藏,后台列表保留)。
|
|
func CommentReject(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if id := c.Param("id"); id != "" {
|
|
db.Model(&models.Comment{}).Where("id = ?", id).Update("status", models.CommentRejected)
|
|
}
|
|
c.Redirect(http.StatusFound, "/admin/comments?status=pending&saved=1&msg=rejected")
|
|
}
|
|
}
|
|
|
|
// CommentDelete 软删除一条评论。
|
|
func CommentDelete(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if id := c.Param("id"); id != "" {
|
|
db.Where("id = ?", id).Delete(&models.Comment{})
|
|
}
|
|
c.Redirect(http.StatusFound, "/admin/comments?status=all&saved=1&msg=deleted")
|
|
}
|
|
}
|