package services import ( "bbs-go/internal/models/constants" "bbs-go/internal/pkg/event" "bbs-go/internal/pkg/search" "errors" "math" "net/http" "github.com/mlogclub/simple/common/dates" "github.com/mlogclub/simple/common/strs" "github.com/mlogclub/simple/sqls" "github.com/mlogclub/simple/web/params" "gorm.io/gorm" "bbs-go/internal/cache" "bbs-go/internal/models" "bbs-go/internal/repositories" ) var TopicService = newTopicService() func newTopicService() *topicService { return &topicService{} } type topicService struct{} func (s *topicService) Get(id int64) *models.Topic { return repositories.TopicRepository.Get(sqls.DB(), id) } func (s *topicService) Take(where ...interface{}) *models.Topic { return repositories.TopicRepository.Take(sqls.DB(), where...) } func (s *topicService) Find(cnd *sqls.Cnd) []models.Topic { return repositories.TopicRepository.Find(sqls.DB(), cnd) } func (s *topicService) FindOne(cnd *sqls.Cnd) *models.Topic { return repositories.TopicRepository.FindOne(sqls.DB(), cnd) } func (s *topicService) FindPageByParams(params *params.QueryParams) (list []models.Topic, paging *sqls.Paging) { return repositories.TopicRepository.FindPageByParams(sqls.DB(), params) } func (s *topicService) FindPageByCnd(cnd *sqls.Cnd) (list []models.Topic, paging *sqls.Paging) { return repositories.TopicRepository.FindPageByCnd(sqls.DB(), cnd) } func (s *topicService) Count(cnd *sqls.Cnd) int64 { return repositories.TopicRepository.Count(sqls.DB(), cnd) } func (s *topicService) Updates(id int64, columns map[string]interface{}) error { if err := repositories.TopicRepository.Updates(sqls.DB(), id, columns); err != nil { return err } // 添加索引 search.UpdateTopicIndex(s.Get(id)) return nil } func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error { if err := repositories.TopicRepository.UpdateColumn(sqls.DB(), id, name, value); err != nil { return err } // 添加索引 search.UpdateTopicIndex(s.Get(id)) return nil } // Delete 删除 func (s *topicService) Delete(topicId, deleteUserId int64, r *http.Request) error { topic := s.Get(topicId) if topic == nil { return nil } err := repositories.TopicRepository.UpdateColumn(sqls.DB(), topicId, "status", constants.StatusDeleted) if err == nil { // 添加索引 search.DeleteTopicIndex(topicId) // 删掉标签文章 TopicTagService.DeleteByTopicId(topicId) // 发送事件 event.Send(event.TopicDeleteEvent{ UserId: topic.UserId, TopicId: topic.Id, DeleteUserId: deleteUserId, }) } return err } // Undelete 取消删除 func (s *topicService) Undelete(id int64) error { err := repositories.TopicRepository.UpdateColumn(sqls.DB(), id, "status", constants.StatusOk) if err == nil { // 删掉标签文章 TopicTagService.UndeleteByTopicId(id) // 添加索引 search.UpdateTopicIndex(s.Get(id)) } return err } // 更新 func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content, hideContent string) error { if len(title) == 0 { return errors.New("标题不能为空") } if strs.RuneLen(title) > 128 { return errors.New("标题长度不能超过128") } node := repositories.TopicNodeRepository.Get(sqls.DB(), nodeId) if node == nil || node.Status != constants.StatusOk { return errors.New("节点不存在") } err := sqls.DB().Transaction(func(tx *gorm.DB) error { var ( tagIds []int64 err error ) if err = repositories.TopicRepository.Updates(sqls.DB(), topicId, map[string]interface{}{ "node_id": nodeId, "title": title, "content": content, "hide_content": hideContent, }); err != nil { return err } // 创建帖子对应标签 if tagIds, err = repositories.TagRepository.GetOrCreates(tx, tags); err != nil { return err } repositories.TopicTagRepository.DeleteTopicTags(tx, topicId) // 先删掉所有的标签 repositories.TopicTagRepository.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签 return nil }) // 添加索引 search.UpdateTopicIndex(s.Get(topicId)) return err } // 推荐 func (s *topicService) SetRecommend(topicId int64, recommend bool) error { topic := s.Get(topicId) if topic == nil || topic.Status != constants.StatusOk { return errors.New("帖子不存在") } if topic.Recommend == recommend { // 推荐状态没变更 return nil } if recommend { if err := s.Updates(topicId, map[string]interface{}{ "recommend": recommend, "recommend_time": dates.NowTimestamp(), }); err != nil { return err } } else { if err := s.UpdateColumn(topicId, "recommend", recommend); err != nil { return err } } // 发送事件 event.Send(event.TopicRecommendEvent{ TopicId: topicId, Recommend: recommend, }) // 添加索引 search.UpdateTopicIndex(s.Get(topicId)) return nil } // GetTopicTags 话题的标签 func (s *topicService) GetTopicTags(topicId int64) []models.Tag { topicTags := repositories.TopicTagRepository.Find(sqls.DB(), sqls.NewCnd().Where("topic_id = ?", topicId)) var tagIds []int64 for _, topicTag := range topicTags { tagIds = append(tagIds, topicTag.TagId) } return cache.TagCache.GetList(tagIds) } // GetTopics 帖子列表(最新、推荐、关注、节点) func (s *topicService) GetTopics(user *models.User, nodeId, cursor int64) (topics []models.Topic, nextCursor int64, hasMore bool) { var limit int = 20 if nodeId == constants.NodeIdFollow { if user != nil { return s._GetFollowTopics(user.Id, cursor) } return } else { return s._GetNodeTopics(nodeId, cursor, limit) } } // _GetNodeTopics 帖子列表(最新、推荐、节点) func (s *topicService) _GetNodeTopics(nodeId, cursor int64, limit int) (topics []models.Topic, nextCursor int64, hasMore bool) { cnd := sqls.NewCnd() if nodeId > 0 { cnd.Eq("node_id", nodeId) } if nodeId == constants.NodeIdRecommend { cnd.Eq("recommend", true) } if cursor > 0 { cnd.Lt("last_comment_time", cursor) } cnd.Eq("status", constants.StatusOk).Desc("last_comment_time").Limit(limit) topics = repositories.TopicRepository.Find(sqls.DB(), cnd) if len(topics) > 0 { nextCursor = topics[len(topics)-1].LastCommentTime hasMore = len(topics) >= limit } else { nextCursor = cursor } return } // _GetFollowTopics 关注帖子列表 func (s *topicService) _GetFollowTopics(userId int64, cursor int64) (topics []models.Topic, nextCursor int64, hasMore bool) { var limit = 20 cnd := sqls.NewCnd().Eq("user_id", userId) cnd.Eq("data_type", constants.EntityTopic) if cursor > 0 { cnd.Lt("create_time", cursor) } cnd.Desc("create_time").Limit(limit) userFeeds := repositories.UserFeedRepository.Find(sqls.DB(), cnd) if len(userFeeds) > 0 { nextCursor = userFeeds[len(userFeeds)-1].CreateTime hasMore = len(userFeeds) >= limit } else { nextCursor = cursor } var topicIds []int64 for _, item := range userFeeds { topicIds = append(topicIds, item.DataId) } topics = TopicService.GetTopicByIds(topicIds) return } // 指定标签下话题列表 func (s *topicService) GetTagTopics(tagId, cursor int64) (topics []models.Topic, nextCursor int64, hasMore bool) { limit := 20 topicTags := repositories.TopicTagRepository.Find(sqls.DB(), sqls.NewCnd(). Eq("tag_id", tagId). Eq("status", constants.StatusOk). Desc("last_comment_time").Limit(limit)) if len(topicTags) > 0 { nextCursor = topicTags[len(topicTags)-1].LastCommentTime var topicIds []int64 for _, topicTag := range topicTags { topicIds = append(topicIds, topicTag.TopicId) } topicsMap := s.GetTopicInIds(topicIds) if topicsMap != nil { for _, topicTag := range topicTags { if topic, found := topicsMap[topicTag.TopicId]; found { topics = append(topics, topic) } } } } else { nextCursor = cursor } hasMore = len(topicTags) >= limit return } func (s *topicService) GetTopicByIds(topicIds []int64) (topics []models.Topic) { topicsMap := s.GetTopicInIds(topicIds) for _, topicId := range topicIds { topic, found := topicsMap[topicId] if found { topics = append(topics, topic) } } return } // GetTopicInIds 根据编号批量获取主题 func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]models.Topic { if len(topicIds) == 0 { return nil } var topics []models.Topic sqls.DB().Where("id in (?)", topicIds).Find(&topics) topicsMap := make(map[int64]models.Topic, len(topics)) for _, topic := range topics { topicsMap[topic.Id] = topic } return topicsMap } // 浏览数+1 func (s *topicService) IncrViewCount(topicId int64) { sqls.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId) } // 当帖子被评论的时候,更新最后回复时间、回复数量+1 func (s *topicService) onComment(tx *gorm.DB, topicId int64, comment *models.Comment) error { if err := repositories.TopicRepository.Updates(tx, topicId, map[string]interface{}{ "last_comment_time": comment.CreateTime, "last_comment_user_id": comment.UserId, "comment_count": gorm.Expr("comment_count + 1"), }); err != nil { return err } if err := tx.Exec("update t_topic_tag set last_comment_time = ?, last_comment_user_id = ? where topic_id = ?", comment.CreateTime, comment.UserId, topicId).Error; err != nil { return err } return nil } func (s *topicService) ScanByUser(userId int64, callback func(topics []models.Topic)) { var cursor int64 = 0 for { list := repositories.TopicRepository.Find(sqls.DB(), sqls.NewCnd(). Eq("user_id", userId).Gt("id", cursor).Asc("id").Limit(1000)) if len(list) == 0 { break } cursor = list[len(list)-1].Id callback(list) } } func (s *topicService) Scan(callback func(topics []models.Topic)) { var cursor int64 = 0 for { list := repositories.TopicRepository.Find(sqls.DB(), sqls.NewCnd(). Gt("id", cursor).Asc("id").Limit(1000)) if len(list) == 0 { break } cursor = list[len(list)-1].Id callback(list) } } // 倒序扫描 func (s *topicService) ScanDesc(callback func(topics []models.Topic)) { var cursor int64 = math.MaxInt64 for { list := repositories.TopicRepository.Find(sqls.DB(), sqls.NewCnd(). Lt("id", cursor).Desc("id").Limit(1000)) if len(list) == 0 { break } cursor = list[len(list)-1].Id callback(list) } } // 倒序扫描 func (s *topicService) ScanDescWithDate(dateFrom, dateTo int64, callback func(topics []models.Topic)) { var cursor int64 = math.MaxInt64 for { list := repositories.TopicRepository.Find(sqls.DB(), sqls.NewCnd(). Cols("id", "status", "create_time", "update_time"). Lt("id", cursor).Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000)) if len(list) == 0 { break } cursor = list[len(list)-1].Id callback(list) } } func (s *topicService) GetUserTopics(userId, cursor int64) (topics []models.Topic, nextCursor int64, hasMore bool) { limit := 20 cnd := sqls.NewCnd() if userId > 0 { cnd.Eq("user_id", userId) } if cursor > 0 { cnd.Lt("id", cursor) } cnd.Eq("status", constants.StatusOk).Desc("id").Limit(limit) topics = repositories.TopicRepository.Find(sqls.DB(), cnd) if len(topics) > 0 { nextCursor = topics[len(topics)-1].Id hasMore = len(topics) >= limit } else { nextCursor = cursor } return } func (s *topicService) GetStickyTopics(nodeId int64, limit int) []models.Topic { if nodeId > 0 { return s.Find(sqls.NewCnd().Where("node_id = ? and sticky = true and status = ?", nodeId, constants.StatusOk).Desc("sticky_time").Limit(limit)) } else { return s.Find(sqls.NewCnd().Where("sticky = true and status = ?", constants.StatusOk).Desc("sticky_time").Limit(limit)) } } func (s *topicService) SetSticky(topicId int64, sticky bool) error { topic := s.Get(topicId) if topic == nil || topic.Status != constants.StatusOk { return errors.New("话题不存在") } if topic.Sticky == sticky { return nil } if sticky { return s.Updates(topicId, map[string]interface{}{ "sticky": true, "sticky_time": dates.NowTimestamp(), }) } else { return s.Updates(topicId, map[string]interface{}{ "sticky": false, }) } }