package service import ( "errors" "strings" "time" "github.com/freefire/jiang13-bbs/model" "gorm.io/gorm" ) // PostService 帖子服务 type PostService struct { db *gorm.DB } func NewPostService(db *gorm.DB) *PostService { return &PostService{db: db} } // PostListQuery 帖子列表查询参数 type PostListQuery struct { BoardID uint Page int Size int Sort string // latest | hot Keyword string // 搜索关键词 Recommended bool // 仅精华帖 } // toPostListItems 将 Post 模型批量转为不含正文的列表项 func toPostListItems(posts []model.Post) []PostListItem { items := make([]PostListItem, 0, len(posts)) for _, p := range posts { items = append(items, PostListItem{ ID: p.ID, BoardID: p.BoardID, UserID: p.UserID, Title: p.Title, Tags: p.Tags, PostType: p.PostType, Pinned: p.Pinned, Recommended: p.Recommended, LikeCount: p.LikeCount, ViewCount: p.ViewCount, CommentCount: p.CommentCount, CreatedAt: p.CreatedAt, Board: p.Board, User: p.User, }) } return items } // PostListItem 帖子列表项(不含正文) type PostListItem struct { ID uint `json:"id"` BoardID uint `json:"board_id"` UserID uint `json:"user_id"` Title string `json:"title"` Tags string `json:"tags"` 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"` Board model.Board `json:"board"` User model.User `json:"user"` } // List 获取帖子列表 func (s *PostService) List(q PostListQuery) ([]PostListItem, int64, error) { if q.Page < 1 { q.Page = 1 } if q.Size < 1 || q.Size > 50 { q.Size = 20 } query := s.db.Model(&model.Post{}).Where("status = ?", model.ContentStatusPublished) if q.BoardID > 0 { query = query.Where("board_id = ?", q.BoardID) } if q.Keyword != "" { kw := "%" + q.Keyword + "%" query = query.Where("title ILIKE ? OR content ILIKE ? OR tags ILIKE ?", kw, kw, kw) } if q.Recommended { query = query.Where("recommended = ?", true) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } var posts []model.Post offset := (q.Page - 1) * q.Size // latest:按"最后活跃时间"排序——新帖与新评论都会浮到顶部。 // 取发帖时间与该帖最新一条已发布评论时间的较大值,相关子查询走 comments.post_id 索引 order := `pinned DESC, GREATEST(created_at, COALESCE(( SELECT MAX(c.created_at) FROM comments c WHERE c.post_id = posts.id AND c.status = 'published' AND c.deleted_at IS NULL ), created_at)) DESC` if q.Sort == "hot" { order = "pinned DESC, (like_count + comment_count * 2) DESC, created_at DESC" } if err := query.Order(order).Offset(offset).Limit(q.Size). Preload("Board").Preload("User").Find(&posts).Error; err != nil { return nil, 0, err } return toPostListItems(posts), total, nil } // ListByUser 获取指定用户发布的帖子(公开已发布) func (s *PostService) ListByUser(userID uint, page, size int) ([]PostListItem, int64, error) { if page < 1 { page = 1 } if size < 1 || size > 50 { size = 20 } query := s.db.Model(&model.Post{}).Where("user_id = ? AND status = ?", userID, model.ContentStatusPublished) var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } var posts []model.Post offset := (page - 1) * size if err := query.Order("pinned DESC, created_at DESC").Offset(offset).Limit(size). Preload("Board").Preload("User").Find(&posts).Error; err != nil { return nil, 0, err } return toPostListItems(posts), total, nil } // GetAuthorID 获取帖子作者 ID(不增加浏览量) func (s *PostService) GetAuthorID(postID uint) (uint, error) { var post model.Post if err := s.db.Select("user_id").First(&post, postID).Error; err != nil { return 0, err } return post.UserID, nil } // CountByUser 统计用户发帖数 func (s *PostService) CountByUser(userID uint) (int64, error) { var total int64 err := s.db.Model(&model.Post{}).Where("user_id = ? AND status = ?", userID, model.ContentStatusPublished).Count(&total).Error return total, err } // TogglePin 切换置顶状态(仅管理员可操作,由 handler 校验权限) func (s *PostService) TogglePin(id uint) (int, error) { var post model.Post if err := s.db.First(&post, id).Error; err != nil { return 0, err } var newPinned int if post.Pinned > 0 { newPinned = 0 } else { newPinned = 1 } result := s.db.Model(&post).Update("pinned", newPinned) if result.Error != nil { return 0, result.Error } return newPinned, nil } // ToggleRecommend 切换推荐状态(仅管理员可操作,由 handler 校验权限) func (s *PostService) ToggleRecommend(id uint) (bool, error) { var post model.Post if err := s.db.First(&post, id).Error; err != nil { return false, err } newVal := !post.Recommended result := s.db.Model(&post).Update("recommended", newVal) if result.Error != nil { return false, result.Error } return newVal, nil } // GetByID 获取帖子详情 func (s *PostService) GetByID(id uint) (*model.Post, error) { var post model.Post if err := s.db.Preload("Board").Preload("User").First(&post, id).Error; err != nil { return nil, err } // 增加浏览量 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) { title = strings.TrimSpace(title) content = strings.TrimSpace(content) if title == "" { return nil, errors.New("标题不能为空") } if content == "" { return nil, errors.New("内容不能为空") } if boardID == 0 { return nil, errors.New("请选择板块") } // 新用户 24h 冷静期校验 if err := s.checkNewUserCooldown(userID); err != nil { return nil, err } post := &model.Post{ BoardID: boardID, UserID: userID, Title: title, Content: content, Tags: tags, PostType: postType, Status: model.ContentStatusPublished, } if err := s.db.Create(post).Error; err != nil { return nil, err } // 预加载关联 s.db.Preload("Board").Preload("User").First(post, post.ID) return post, nil } // checkNewUserCooldown 新用户发帖 24h 冷静期 func (s *PostService) checkNewUserCooldown(userID uint) error { var user model.User if err := s.db.First(&user, userID).Error; err != nil { return err } // 注册不足 24 小时的新用户不能发帖 if time.Since(user.CreatedAt) < 24*time.Hour { return errors.New("新用户注册 24 小时后才能发帖") } return nil } // Update 更新帖子(仅作者或管理员可操作) func (s *PostService) Update(postID, userID uint, role string, 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("帖子不存在") } // 权限校验:作者本人或管理员 if post.UserID != userID && role != RoleAdmin { return nil, errors.New("无权限编辑此帖子") } updates := map[string]interface{}{} if title != "" { t := strings.TrimSpace(title) if t == "" { return nil, errors.New("标题不能为空") } updates["title"] = t } if content != "" { c := strings.TrimSpace(content) if c == "" { return nil, errors.New("内容不能为空") } updates["content"] = c } updates["tags"] = tags if err := s.db.Model(&post).Updates(updates).Error; err != nil { return nil, err } s.db.Preload("Board").Preload("User").First(&post, post.ID) return &post, nil } // Delete 删除帖子(仅作者或管理员可操作) func (s *PostService) Delete(postID, userID uint, role string) error { var post model.Post if err := s.db.First(&post, postID).Error; err != nil { return errors.New("帖子不存在") } // 权限校验:作者本人或管理员 if post.UserID != userID && role != RoleAdmin { return errors.New("无权限删除此帖子") } // 软删除(gorm DeletedAt) if err := s.db.Delete(&post).Error; err != nil { return err } return nil }