feat(community): 评论改为独立二级页面 + 评论内容安全审核 + 评论数按实发实时纠正

前端:
- 新增 pages/comments 独立评论页(列表 + 底部输入条 + 回复),社区卡片点评论改为 navigateTo
- 输入条用普通流式布局 + 原生 adjust-position 做键盘避让,聚焦期间不 setData 碰输入框
- 评论只能配 1 张图;底部常驻「审核后公开」提示;发送按钮在途「审核中…」防连点
- 发布结果按状态区分:过审即时展示,其余提示「已提交,审核通过后展示」
- 评论页把已发布评论数回传社区列表,卡片数字实时更新

后端:
- CreateComment 文本判为违规时直接拒收(ErrContentRejected),不入库
- 社区列表/用户帖子列表用「已发布评论实时条数」覆盖 comment_count,
  修正异步过审/删待审/后台改状态导致的计数漂移
- IDCard 手机号返回完整号码(上一批遗留)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-08-03 16:04:21 +08:00
parent 51ead8f411
commit 7a55ee0f2e
12 changed files with 461 additions and 46 deletions
+34
View File
@@ -44,6 +44,35 @@ func (s *Service) attachPostImages(posts []model.Post) {
}
}
// attachCommentCounts 用「已发布评论」的实时条数覆盖 comment_count。
// 维护型计数器在这些路径上会漂移:图片评论异步过审后没补计数、删除待审评论时误减、
// 后台审核改状态没同步。直接按实际已发布条数算,永远和评论弹层「共 X 条」一致。
func (s *Service) attachCommentCounts(posts []model.Post) {
if len(posts) == 0 {
return
}
ids := make([]string, len(posts))
for i := range posts {
ids[i] = posts[i].ID
}
type row struct {
PostID string
N int
}
var rows []row
s.db.Model(&model.Comment{}).
Select("post_id, count(*) as n").
Where("post_id IN ? AND status = ?", ids, model.PostPublished).
Group("post_id").Scan(&rows)
cnt := make(map[string]int, len(rows))
for _, r := range rows {
cnt[r.PostID] = r.N
}
for i := range posts {
posts[i].CommentCount = cnt[posts[i].ID]
}
}
// tabTag 将 feed tab 映射为标签过滤(空表示不过滤)
func tabTag(tab string) string {
switch tab {
@@ -81,6 +110,7 @@ func (s *Service) ListPosts(userID, tab string, offset, limit int) ([]model.Post
return nil, 0, err
}
s.attachPostImages(posts)
s.attachCommentCounts(posts)
s.markFollowed(userID, posts)
return posts, total, nil
}
@@ -299,6 +329,10 @@ func (s *Service) CreateComment(userID, postID string, content string, images []
// 评论也过内容安全(scene=2 评论)。图片这里已经是可访问 URL
status, _ := s.moderateInitial(content, user.OpenID, 2, len(images) > 0)
// 文本被判违规:直接拒绝、不入库,让用户知道发不出去
if status == model.PostRejected {
return nil, ErrContentRejected
}
comment := model.Comment{
PostID: postID, UserID: userID, AuthorName: name, Content: content,
+3
View File
@@ -24,6 +24,9 @@ var ErrForbidden = errors.New("无权操作")
// ErrInvalidParam 参数不合法(handler 据此返回 40000
var ErrInvalidParam = errors.New("参数不合法")
// ErrContentRejected 内容安全判定为违规,直接拒绝发送(不入库)
var ErrContentRejected = errors.New("内容包含违规信息,无法发布")
// Service 业务逻辑聚合,方法按领域分散在各文件
type Service struct {
db *gorm.DB
+1
View File
@@ -215,6 +215,7 @@ func (s *Service) ListUserPosts(viewerID, userID string, offset, limit int) ([]m
return nil, 0, err
}
s.attachPostImages(posts)
s.attachCommentCounts(posts)
s.markFollowed(viewerID, posts)
return posts, total, nil
}