package service import ( "encoding/json" "gorm.io/datatypes" "gorm.io/gorm" "github.com/sundynix/pets-be/internal/model" ) // attachPostImages 把帖子的 ImageFileIDs 解析为可展示的 Images(URL 数组)。 // 机器人帖 ImageFileIDs 为空,保留其 Images(emoji)不变。 func (s *Service) attachPostImages(posts []model.Post) { perPost := make([][]string, len(posts)) all := []string{} for i := range posts { if len(posts[i].ImageFileIDs) == 0 { continue } var ids []string if json.Unmarshal(posts[i].ImageFileIDs, &ids) == nil && len(ids) > 0 { perPost[i] = ids all = append(all, ids...) } } if len(all) == 0 { return } urlMap := s.fileURLs(all) for i := range posts { if len(perPost[i]) == 0 { continue } urls := make([]string, 0, len(perPost[i])) for _, id := range perPost[i] { if u := urlMap[id]; u != "" { urls = append(urls, u) } } if b, err := json.Marshal(urls); err == nil { posts[i].Images = datatypes.JSON(b) } } } // tabTag 将 feed tab 映射为标签过滤(空表示不过滤) func tabTag(tab string) string { switch tab { case "新手求助": return "求助" case "晒宠": return "晒宠" case "经验": return "经验" default: // 推荐 / 关注 return "" } } // ListPosts 帖子分页列表 func (s *Service) ListPosts(tab string, offset, limit int) ([]model.Post, int64, error) { q := s.db.Model(&model.Post{}).Where("status = ?", model.PostPublished) if tag := tabTag(tab); tag != "" { q = q.Where("JSON_CONTAINS(tags, ?)", `"`+tag+`"`) } var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, err } var posts []model.Post if err := q.Order("id desc").Offset(offset).Limit(limit).Find(&posts).Error; err != nil { return nil, 0, err } s.attachPostImages(posts) return posts, total, nil } // GetPost 帖子详情 func (s *Service) GetPost(postID string) (*model.Post, error) { var p model.Post if err := s.db.First(&p, postID).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrNotFound } return nil, err } one := []model.Post{p} s.attachPostImages(one) return &one[0], nil } // PostInput 发帖入参 type PostInput struct { PetID *string Identity string Content string Tags datatypes.JSON Images datatypes.JSON ImageFileIDs datatypes.JSON } // CreatePost 发帖 func (s *Service) CreatePost(userID string, in PostInput) (*model.Post, error) { authorName := "匿名宠友" authorEmoji := "🐾" switch in.Identity { case "official": authorName = "肉垫计划官方" case "petName": if in.PetID != nil { var pet model.Pet if err := s.db.First(&pet, *in.PetID).Error; err == nil { authorName = pet.Name + "的铲屎官" authorEmoji = pet.Emoji } } } p := model.Post{ UserID: userID, PetID: in.PetID, AuthorName: authorName, AuthorEmoji: authorEmoji, Identity: in.Identity, Content: in.Content, Tags: in.Tags, Images: in.Images, ImageFileIDs: in.ImageFileIDs, Status: model.PostPublished, } if err := s.db.Create(&p).Error; err != nil { return nil, err } one := []model.Post{p} s.attachPostImages(one) return &one[0], nil } // LikePost 点赞(幂等:已赞则不重复计数) func (s *Service) LikePost(userID, postID string) (int, error) { if _, err := s.GetPost(postID); err != nil { return 0, err } err := s.db.Transaction(func(tx *gorm.DB) error { like := model.PostLike{PostID: postID, UserID: userID} res := tx.Where("post_id = ? AND user_id = ?", postID, userID).FirstOrCreate(&like) if res.Error != nil { return res.Error } if res.RowsAffected == 1 { // 新建才计数 return tx.Model(&model.Post{}).Where("id = ?", postID). UpdateColumn("like_count", gorm.Expr("like_count + 1")).Error } return nil }) if err != nil { return 0, err } return s.postLikeCount(postID) } // UnlikePost 取消点赞 func (s *Service) UnlikePost(userID, postID string) (int, error) { if _, err := s.GetPost(postID); err != nil { return 0, err } err := s.db.Transaction(func(tx *gorm.DB) error { res := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.PostLike{}) if res.Error != nil { return res.Error } if res.RowsAffected == 1 { return tx.Model(&model.Post{}).Where("id = ? AND like_count > 0", postID). UpdateColumn("like_count", gorm.Expr("like_count - 1")).Error } return nil }) if err != nil { return 0, err } return s.postLikeCount(postID) } func (s *Service) postLikeCount(postID string) (int, error) { var p model.Post if err := s.db.Select("like_count").First(&p, postID).Error; err != nil { return 0, err } return p.LikeCount, nil } // ListComments 评论分页 func (s *Service) ListComments(postID string, offset, limit int) ([]model.Comment, int64, error) { q := s.db.Model(&model.Comment{}).Where("post_id = ? AND status = ?", postID, "published") var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, err } var comments []model.Comment if err := q.Order("id desc").Offset(offset).Limit(limit).Find(&comments).Error; err != nil { return nil, 0, err } return comments, total, nil } // CreateComment 评论 func (s *Service) CreateComment(userID, postID string, content string) (*model.Comment, error) { if _, err := s.GetPost(postID); err != nil { return nil, err } var user model.User name := "宠友" if err := s.db.First(&user, userID).Error; err == nil && user.Nickname != "" { name = user.Nickname } comment := model.Comment{PostID: postID, UserID: userID, AuthorName: name, Content: content, Status: "published"} if err := s.db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&comment).Error; err != nil { return err } return tx.Model(&model.Post{}).Where("id = ?", postID). UpdateColumn("comment_count", gorm.Expr("comment_count + 1")).Error }); err != nil { return nil, err } return &comment, nil }