feat: 实时消息与管理端运营能力

新增群聊/私信与公共 WebSocket 总线,站长用户内容档案,并将通知与待审角标改为推送驱动;同步精简管理仪表盘并修复主题换肤 DOM 冲突。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-15 22:58:16 +08:00
parent 29db9ae9b5
commit 42c395a864
55 changed files with 7607 additions and 346 deletions

View File

@@ -35,16 +35,16 @@ const maxReplyDepth = 10
// ListFloorPaged 楼层分页获取帖子评论树:
// 分页与楼层号只针对主评论(parent_id IS NULL),每楼附带全部子评论(时间正序)。
// viewer 可为作者本人或该板可审者时并入 pending;游客仅 published。
// 返回:树根列表、楼层数、全部评论数(含回复)。
func (s *CommentService) ListFloorPaged(postID uint, page, size int) ([]CommentNode, int64, int64, error) {
func (s *CommentService) ListFloorPaged(postID, boardID, viewerID uint, actor *Actor, page, size int) ([]CommentNode, int64, int64, error) {
if page < 1 {
page = 1
}
if size < 1 || size > 100 {
size = 20
}
base := s.db.Model(&model.Comment{}).
Where("post_id = ? AND status = ?", postID, model.ContentStatusPublished)
base := applyCommentListVisibility(s.db.Model(&model.Comment{}).Where("post_id = ?", postID), boardID, viewerID, actor)
var floors, totalComments int64
if err := base.Session(&gorm.Session{}).
@@ -75,11 +75,9 @@ func (s *CommentService) ListFloorPaged(postID uint, page, size int) ([]CommentN
for i, r := range roots {
rootIDs[i] = r.ID
}
replyQ := applyCommentListVisibility(s.db.Where("root_id IN (?)", rootIDs), boardID, viewerID, actor)
var replies []model.Comment
if err := s.db.Where("root_id IN (?) AND status = ?", rootIDs, model.ContentStatusPublished).
Order("created_at ASC").
Preload("User").
Find(&replies).Error; err != nil {
if err := replyQ.Order("created_at ASC").Preload("User").Find(&replies).Error; err != nil {
return nil, 0, 0, err
}
@@ -120,6 +118,20 @@ func (s *CommentService) ListFloorPaged(postID uint, page, size int) ([]CommentN
return result, floors, totalComments, nil
}
// applyCommentListVisibility 评论流可见性:published,或 pending 且(作者 / 该板可审)
func applyCommentListVisibility(db *gorm.DB, boardID, viewerID uint, actor *Actor) *gorm.DB {
if viewerID == 0 || actor == nil {
return db.Where("status = ?", model.ContentStatusPublished)
}
if actor.CanModerateBoard(boardID) {
return db.Where("status IN ?", []string{model.ContentStatusPublished, model.ContentStatusPending})
}
return db.Where(
"status = ? OR (status = ? AND user_id = ?)",
model.ContentStatusPublished, model.ContentStatusPending, viewerID,
)
}
// Create 创建评论(parentID 为 nil 时发主评论/楼层,否则发为对应评论的子回复)。
// status 由 handler 按角色计算:管理团队直发 published,普通用户进入 pending;
// pending 评论不计入 comment_count,审核通过时才 +1。