feat: 实现完整的板块级RBAC内容审核系统
- 新增站长/超级管理员/管理员/板块管理员四级角色体系 - 实现实时权限快照加载与细粒度权限校验 - 新增内容审核队列与前后端页面 - 重构用户权限管理与站点管理逻辑 - 新增评论/帖子审核状态与通知推送 - 优化前端权限控制与角色徽章展示 - 修正数据库迁移与默认值问题
This commit is contained in:
@@ -9,6 +9,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 帖子操作错误
|
||||
var (
|
||||
ErrPostNotFound = errors.New("帖子不存在")
|
||||
ErrPostForbidden = errors.New("无权限操作此帖子")
|
||||
)
|
||||
|
||||
// PostService 帖子服务
|
||||
type PostService struct {
|
||||
db *gorm.DB
|
||||
@@ -67,14 +73,14 @@ type PostListItem struct {
|
||||
PostType string `json:"post_type"`
|
||||
Pinned int `json:"pinned"`
|
||||
Recommended bool `json:"recommended"`
|
||||
LikeCount int `json:"like_count"`
|
||||
ViewCount int `json:"view_count"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
Liked bool `json:"liked"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LikeCount int `json:"like_count"`
|
||||
ViewCount int `json:"view_count"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
Liked bool `json:"liked"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastReply *LastReplyInfo `json:"last_reply,omitempty"`
|
||||
Board model.Board `json:"board"`
|
||||
User model.User `json:"user"`
|
||||
Board model.Board `json:"board"`
|
||||
User model.User `json:"user"`
|
||||
}
|
||||
|
||||
// fillLastReply 批量填充每帖最后一条已发布评论(发帖人+时间),
|
||||
@@ -263,19 +269,51 @@ func (s *PostService) ToggleRecommend(id uint) (bool, error) {
|
||||
return newVal, nil
|
||||
}
|
||||
|
||||
// GetByID 获取帖子详情
|
||||
func (s *PostService) GetByID(id uint) (*model.Post, error) {
|
||||
// visibleTo 非已发布帖子仅作者本人或对该板块有审核权的管理成员可见。
|
||||
// loadActor 为懒加载回调:仅访问非已发布帖且访问者非作者时才触发,避免常规浏览多查 DB
|
||||
func visibleToPost(post *model.Post, viewerID uint, loadActor func() *Actor) bool {
|
||||
if post.Status == model.ContentStatusPublished {
|
||||
return true
|
||||
}
|
||||
if viewerID > 0 && post.UserID == viewerID {
|
||||
return true
|
||||
}
|
||||
if loadActor == nil {
|
||||
return false
|
||||
}
|
||||
return loadActor().CanModerateBoard(post.BoardID)
|
||||
}
|
||||
|
||||
// GetByIDForViewer 获取帖子详情(带可见性校验);通过校验才计入浏览量。
|
||||
// viewerID 为当前登录用户(未登录传 0),loadActor 可传 nil
|
||||
func (s *PostService) GetByIDForViewer(id, viewerID uint, loadActor func() *Actor) (*model.Post, error) {
|
||||
var post model.Post
|
||||
if err := s.db.Preload("Board").Preload("User").First(&post, id).Error; err != nil {
|
||||
return nil, err
|
||||
return nil, ErrPostNotFound
|
||||
}
|
||||
// 增加浏览量
|
||||
if !visibleToPost(&post, viewerID, loadActor) {
|
||||
return nil, ErrPostNotFound
|
||||
}
|
||||
// 增加浏览量(待审/被拒内容不计)
|
||||
s.db.Model(&post).UpdateColumn("view_count", gorm.Expr("view_count + 1"))
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// Create 创建帖子
|
||||
func (s *PostService) Create(userID uint, boardID uint, title, content, tags, postType string) (*model.Post, error) {
|
||||
// EnsurePostVisible 校验帖子对当前访问者可见(评论列表等场景复用,不增加浏览量)
|
||||
func (s *PostService) EnsurePostVisible(postID, viewerID uint, loadActor func() *Actor) error {
|
||||
var post model.Post
|
||||
if err := s.db.Select("id", "user_id", "board_id", "status").First(&post, postID).Error; err != nil {
|
||||
return ErrPostNotFound
|
||||
}
|
||||
if !visibleToPost(&post, viewerID, loadActor) {
|
||||
return ErrPostNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create 创建帖子。status 由 handler 按角色计算:
|
||||
// 管理团队成员直发 published,普通用户进入 pending 等待审核
|
||||
func (s *PostService) Create(userID uint, boardID uint, title, content, tags, postType, status string) (*model.Post, error) {
|
||||
title = strings.TrimSpace(title)
|
||||
content = strings.TrimSpace(content)
|
||||
if title == "" {
|
||||
@@ -287,6 +325,9 @@ func (s *PostService) Create(userID uint, boardID uint, title, content, tags, po
|
||||
if boardID == 0 {
|
||||
return nil, errors.New("请选择板块")
|
||||
}
|
||||
if status != model.ContentStatusPending && status != model.ContentStatusPublished {
|
||||
status = model.ContentStatusPending
|
||||
}
|
||||
|
||||
// 新用户 24h 冷静期校验
|
||||
if err := s.checkNewUserCooldown(userID); err != nil {
|
||||
@@ -300,7 +341,7 @@ func (s *PostService) Create(userID uint, boardID uint, title, content, tags, po
|
||||
Content: content,
|
||||
Tags: tags,
|
||||
PostType: postType,
|
||||
Status: model.ContentStatusPublished,
|
||||
Status: status,
|
||||
}
|
||||
if err := s.db.Create(post).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -323,15 +364,15 @@ func (s *PostService) checkNewUserCooldown(userID uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新帖子(仅作者或管理员可操作)
|
||||
func (s *PostService) Update(postID, userID uint, role string, title, content, tags string) (*model.Post, error) {
|
||||
// Update 更新帖子(作者本人,或对该板块有审核权的管理成员)
|
||||
func (s *PostService) Update(actor *Actor, postID, userID uint, title, content, tags string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
if err := s.db.First(&post, postID).Error; err != nil {
|
||||
return nil, errors.New("帖子不存在")
|
||||
return nil, ErrPostNotFound
|
||||
}
|
||||
// 权限校验:作者本人或管理员
|
||||
if post.UserID != userID && role != RoleAdmin {
|
||||
return nil, errors.New("无权限编辑此帖子")
|
||||
// 权限校验:作者本人或板块审核权
|
||||
if post.UserID != userID && !actor.CanModerateBoard(post.BoardID) {
|
||||
return nil, ErrPostForbidden
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
@@ -358,15 +399,15 @@ func (s *PostService) Update(postID, userID uint, role string, title, content, t
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// Delete 删除帖子(仅作者或管理员可操作)
|
||||
func (s *PostService) Delete(postID, userID uint, role string) error {
|
||||
// Delete 删除帖子(作者本人,或对该板块有审核权的管理成员)
|
||||
func (s *PostService) Delete(actor *Actor, postID, userID uint) error {
|
||||
var post model.Post
|
||||
if err := s.db.First(&post, postID).Error; err != nil {
|
||||
return errors.New("帖子不存在")
|
||||
return ErrPostNotFound
|
||||
}
|
||||
// 权限校验:作者本人或管理员
|
||||
if post.UserID != userID && role != RoleAdmin {
|
||||
return errors.New("无权限删除此帖子")
|
||||
// 权限校验:作者本人或板块审核权
|
||||
if post.UserID != userID && !actor.CanModerateBoard(post.BoardID) {
|
||||
return ErrPostForbidden
|
||||
}
|
||||
// 软删除(gorm DeletedAt)
|
||||
if err := s.db.Delete(&post).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user