Files
jiang13-bbs/backend/handler/comment.go
freefire 42c395a864 feat: 实时消息与管理端运营能力
新增群聊/私信与公共 WebSocket 总线,站长用户内容档案,并将通知与待审角标改为推送驱动;同步精简管理仪表盘并修复主题换肤 DOM 冲突。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 22:58:16 +08:00

126 lines
4.4 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 (
"errors"
"net/http"
"strconv"
"github.com/freefire/jiang13-bbs/middleware"
"github.com/freefire/jiang13-bbs/model"
"github.com/freefire/jiang13-bbs/service"
"github.com/gin-gonic/gin"
)
// PostComments 获取帖子评论树(楼层分页:主评论算楼层,每楼附带全部子回复,时间正序)
func (h *Handlers) PostComments(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
// 待审/被拒帖子的评论不对公众开放
var viewerID uint
var actor *service.Actor
var loadActor func() *service.Actor
if claims := middleware.CurrentUser(c); claims != nil {
viewerID = claims.ID
loadActor = h.actorLoader(claims.ID)
if a, err := h.Auth.LoadActor(claims.ID); err == nil {
actor = a
}
}
if err := h.Post.EnsurePostVisible(uint(id), viewerID, loadActor); err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
}
boardID, _ := h.Post.GetBoardID(uint(id))
comments, floors, totalComments, err := h.Comment.ListFloorPaged(uint(id), boardID, viewerID, actor, page, size)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"comments": comments,
"total": floors, // 楼层数(主评论数)→ 前端分页与楼层号计算
"total_comments": totalComments, // 全部评论数(含回复)→ 展示徽标
"page": page,
"size": size,
})
}
// CreateCommentRequest 评论请求
type CreateCommentRequest struct {
Content string `json:"content" binding:"required,min=1"`
ParentID *uint `json:"parent_id"` // 非空 = 回复该评论(发为其子评论)
}
// CreateComment 创建评论(主评论或子回复)
func (h *Handlers) CreateComment(c *gin.Context) {
claims := middleware.CurrentUser(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"})
return
}
var req CreateCommentRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 管理团队成员评论免审;普通用户评论进入待审核队列,审核通过时才发业务通知
status := model.ContentStatusPending
if model.IsStaff(model.Role(claims.Role)) {
status = model.ContentStatusPublished
}
comment, parent, err := h.Comment.Create(claims.ID, uint(id), req.Content, req.ParentID, status)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if comment.Status == model.ContentStatusPublished {
if parent == nil {
// 主评论:通知帖子作者(排除自己评论自己的帖子)
if authorID, e := h.Post.GetAuthorID(uint(id)); e == nil {
h.Notification.Create(authorID, claims.ID, model.NotificationTypeComment, uint(id), comment.ID, req.Content)
}
} else {
// 子回复:通知父评论作者(Notification.Create 内部排除自我通知)
h.Notification.Create(parent.UserID, claims.ID, model.NotificationTypeReply, uint(id), comment.ID, req.Content)
}
if boardID, e := h.Post.GetBoardID(uint(id)); e == nil {
h.broadcastFeedChanged("comment", uint(id), boardID, claims.ID)
}
} else if comment.Status == model.ContentStatusPending {
if boardID, e := h.Post.GetBoardID(uint(id)); e == nil {
h.notifyPendingReview(boardID, claims.ID, uint(id), comment.ID, "有新评论待审核")
}
}
c.JSON(http.StatusOK, gin.H{"comment": comment})
}
// DeleteComment 删除评论
func (h *Handlers) DeleteComment(c *gin.Context) {
claims := middleware.CurrentUser(c)
cid, err := strconv.ParseUint(c.Param("cid"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的评论 ID"})
return
}
if err := h.Comment.Delete(h.loadActor(claims.ID), uint(cid), claims.ID); err != nil {
switch {
case errors.Is(err, service.ErrCommentNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrCommentForbidden):
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
return
}
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
}