feat: 评论支持回复(两级 + @某人)
不做无限层级——手机屏幕撑不住层层缩进,读到第四层就没法看了。
微信、小红书、B站清一色是两级:一级评论 + 其下的回复列表,回复里
用「回复 @某人」表达指向谁。
数据结构:comments 加 parent_id + reply_to_name。回复的回复会被
压平到同一条一级评论下(parent_id 取爷爷的),同时把被回复人的名字
记进 reply_to_name —— 这样既保住两级,又不丢「在跟谁说话」的信息。
几个容易做错的地方:
- 一次查完这一页所有一级评论的回复,不是每条一次查询(N+1)
- 一级评论默认只带 3 条回复,其余点「展开全部 N 条」再拉,避免热门
评论一次返回几百条
- 删一级评论要连它下面的回复一起删,否则回复变成挂在空处的孤儿;
帖子的 comment_count 也要按实际删除条数减,不是减 1
- total 统计含回复,和帖子上显示的数字对得上
- replies 为空时返回 [],不是 null
实测四条评论(含一条回复的回复):
▸ 甲:一级评论 (2 条回复)
└ 乙:多久了?
└ 甲 回复 乙:三天了
▸ 乙:另一条一级评论 (0 条回复)
删掉第一条一级评论后 total 4→1,comment_count 同步。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,8 +113,9 @@ func (h *Handler) ListComments(c *gin.Context) {
|
||||
}
|
||||
|
||||
type commentReq struct {
|
||||
Content string `json:"content"`
|
||||
Images []string `json:"images"`
|
||||
Content string `json:"content"`
|
||||
Images []string `json:"images"`
|
||||
ParentID string `json:"parent_id"` // 回复某条评论时传它的 id
|
||||
}
|
||||
|
||||
// CreateComment POST /api/posts/:id/comments
|
||||
@@ -128,7 +129,7 @@ func (h *Handler) CreateComment(c *gin.Context) {
|
||||
response.FailParams(c, "评论内容不能为空")
|
||||
return
|
||||
}
|
||||
comment, err := h.svc.CreateComment(middleware.UserID(c), idParam(c, "id"), req.Content, req.Images)
|
||||
comment, err := h.svc.CreateComment(middleware.UserID(c), idParam(c, "id"), req.Content, req.Images, req.ParentID)
|
||||
if err != nil {
|
||||
respondErr(c, err)
|
||||
return
|
||||
@@ -171,3 +172,13 @@ func (h *Handler) DeleteOwnComment(c *gin.Context) {
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
// ListReplies GET /api/comments/:id/replies 展开某条评论的全部回复
|
||||
func (h *Handler) ListReplies(c *gin.Context) {
|
||||
list, err := h.svc.ListReplies(middleware.UserID(c), idParam(c, "id"))
|
||||
if err != nil {
|
||||
respondErr(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user