feat: 实现完整的板块级RBAC内容审核系统
- 新增站长/超级管理员/管理员/板块管理员四级角色体系 - 实现实时权限快照加载与细粒度权限校验 - 新增内容审核队列与前后端页面 - 重构用户权限管理与站点管理逻辑 - 新增评论/帖子审核状态与通知推送 - 优化前端权限控制与角色徽章展示 - 修正数据库迁移与默认值问题
This commit is contained in:
@@ -9,6 +9,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 评论操作错误
|
||||
var (
|
||||
ErrCommentNotFound = errors.New("评论不存在")
|
||||
ErrCommentForbidden = errors.New("无权限删除此评论")
|
||||
)
|
||||
|
||||
// CommentService 评论服务
|
||||
type CommentService struct {
|
||||
db *gorm.DB
|
||||
@@ -114,25 +120,33 @@ func (s *CommentService) ListFloorPaged(postID uint, page, size int) ([]CommentN
|
||||
return result, floors, totalComments, nil
|
||||
}
|
||||
|
||||
// Create 创建评论(parentID 为 nil 时发主评论/楼层,否则发为对应评论的子回复)
|
||||
// Create 创建评论(parentID 为 nil 时发主评论/楼层,否则发为对应评论的子回复)。
|
||||
// status 由 handler 按角色计算:管理团队直发 published,普通用户进入 pending;
|
||||
// pending 评论不计入 comment_count,审核通过时才 +1。
|
||||
// 返回:新评论、父评论(子回复时非 nil,供通知定位被回复人)
|
||||
func (s *CommentService) Create(userID, postID uint, content string, parentID *uint) (*model.Comment, *model.Comment, error) {
|
||||
func (s *CommentService) Create(userID, postID uint, content string, parentID *uint, status string) (*model.Comment, *model.Comment, error) {
|
||||
content = strings.TrimSpace(content)
|
||||
if content == "" {
|
||||
return nil, nil, errors.New("评论内容不能为空")
|
||||
}
|
||||
if status != model.ContentStatusPending && status != model.ContentStatusPublished {
|
||||
status = model.ContentStatusPending
|
||||
}
|
||||
|
||||
// 检查帖子是否存在且未锁定评论
|
||||
// 检查帖子存在且已发布(待审/被拒帖子不接受评论)
|
||||
var post model.Post
|
||||
if err := s.db.First(&post, postID).Error; err != nil {
|
||||
return nil, nil, errors.New("帖子不存在")
|
||||
}
|
||||
if post.Status != model.ContentStatusPublished {
|
||||
return nil, nil, errors.New("帖子不存在")
|
||||
}
|
||||
|
||||
comment := &model.Comment{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
Content: content,
|
||||
Status: model.ContentStatusPublished,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
// 子回复:校验父评论(同帖、已发布),继承楼层根与层级
|
||||
@@ -159,8 +173,10 @@ func (s *CommentService) Create(userID, postID uint, content string, parentID *u
|
||||
if err := s.db.Create(comment).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 更新帖子评论数
|
||||
s.db.Model(&post).UpdateColumn("comment_count", gorm.Expr("comment_count + 1"))
|
||||
// 仅已发布评论立即计入评论数;待审评论通过审核时才 +1
|
||||
if comment.Status == model.ContentStatusPublished {
|
||||
s.db.Model(&post).UpdateColumn("comment_count", gorm.Expr("comment_count + 1"))
|
||||
}
|
||||
// 预加载用户
|
||||
s.db.Preload("User").First(comment, comment.ID)
|
||||
return comment, parent, nil
|
||||
@@ -210,15 +226,22 @@ func (s *CommentService) CountByUser(userID uint) (int64, error) {
|
||||
return total, err
|
||||
}
|
||||
|
||||
// Delete 删除评论(仅作者或管理员可操作),级联软删整棵子树
|
||||
func (s *CommentService) Delete(commentID, userID uint, role string) error {
|
||||
// Delete 删除评论(作者本人,或对帖子所属板块有审核权的管理成员),
|
||||
// 级联软删整棵子树
|
||||
func (s *CommentService) Delete(actor *Actor, commentID, userID uint) error {
|
||||
var comment model.Comment
|
||||
if err := s.db.First(&comment, commentID).Error; err != nil {
|
||||
return errors.New("评论不存在")
|
||||
return ErrCommentNotFound
|
||||
}
|
||||
// 权限校验:作者本人或管理员
|
||||
if comment.UserID != userID && role != RoleAdmin {
|
||||
return errors.New("无权限删除此评论")
|
||||
// 权限校验:作者本人或板块审核权
|
||||
if comment.UserID != userID {
|
||||
var post model.Post
|
||||
if err := s.db.Select("id", "board_id").First(&post, comment.PostID).Error; err != nil {
|
||||
return ErrCommentNotFound
|
||||
}
|
||||
if !actor.CanModerateBoard(post.BoardID) {
|
||||
return ErrCommentForbidden
|
||||
}
|
||||
}
|
||||
// 收集子树:取同楼层全部评论,多轮标记出以目标为祖先的集合(含自身,深度有限必然收敛)
|
||||
var all []model.Comment
|
||||
@@ -247,11 +270,27 @@ func (s *CommentService) Delete(commentID, userID uint, role string) error {
|
||||
for id := range descendants {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// 软删子树并按实际条数递减帖子评论数
|
||||
// 只有已发布评论曾计入 comment_count,递减时排除待审/被拒评论。
|
||||
// all 含同楼层全部子评论(删除主评论时目标自身不在 all 内,需单独计入)
|
||||
publishedCnt := 0
|
||||
if comment.Status == model.ContentStatusPublished {
|
||||
publishedCnt++
|
||||
}
|
||||
for _, c := range all {
|
||||
if c.ID == comment.ID {
|
||||
continue
|
||||
}
|
||||
if descendants[c.ID] && c.Status == model.ContentStatusPublished {
|
||||
publishedCnt++
|
||||
}
|
||||
}
|
||||
// 软删子树并按已发布条数递减帖子评论数
|
||||
if err := s.db.Delete(&model.Comment{}, ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
s.db.Model(&model.Post{}).Where("id = ?", comment.PostID).
|
||||
UpdateColumn("comment_count", gorm.Expr("GREATEST(comment_count - ?, 0)", len(ids)))
|
||||
if publishedCnt > 0 {
|
||||
s.db.Model(&model.Post{}).Where("id = ?", comment.PostID).
|
||||
UpdateColumn("comment_count", gorm.Expr("GREATEST(comment_count - ?, 0)", publishedCnt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user