This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/service/reaction_service.go
Victor_Jay 680df7371a feat: 实现关注系统 + 通知侧边栏分类 TAB + 点赞聚合通知
- 新增关注系统:UserFollow 模型、FollowService(toggle/status/列表隐私控制)
- User 新增 FollowersCount/FollowingCount/FollowListPublic/NotifyPrefs 字段
- Space 页面增加四态关注按钮(关注/已关注/回关/已互粉)+ 粉丝/关注数链接
- 新增 /space/:uid/followers 和 /space/:uid/following 列表页
- 新增 NotifyFollow/NotifyLikeAggregated 通知类型
- ReactionService 点赞时写入 daily_like_summary,访问时生成聚合通知
- FollowService 关注时触发 NotifyFollow 通知
- 消息中心侧边栏升级为分类 TAB(全部/系统通知/@艾特/点赞/关注)
- NotificationService 新增 ListByCategory 按分类分页查询
2026-06-01 16:30:46 +08:00

177 lines
4.8 KiB
Go

package service
import (
"fmt"
"time"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// ReactionType 反应类型
type ReactionType string
const (
ReactionNone ReactionType = "none"
ReactionLiked ReactionType = "liked"
ReactionDisliked ReactionType = "disliked"
)
// ReactionService 赞/踩业务逻辑
type ReactionService struct {
db *gorm.DB
repo ReactionStore
}
// NewReactionService 构造函数
func NewReactionService(db *gorm.DB, repo ReactionStore) *ReactionService {
return &ReactionService{db: db, repo: repo}
}
// ToggleLike 切换点赞(含踩互斥逻辑)
// 返回:当前是否已点赞
func (s *ReactionService) ToggleLike(userID, postID uint) (bool, error) {
var isLiked bool
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
// 查当前状态
_, likeErr := txRepo.FindLike(userID, postID)
hasLike := likeErr == nil
_, dislikeErr := txRepo.FindDislike(userID, postID)
hasDislike := dislikeErr == nil
if hasLike {
// 已赞 → 取消赞
if err := txRepo.DeleteLike(userID, postID); err != nil {
return fmt.Errorf("取消点赞失败: %w", err)
}
if err := txRepo.IncrPostLikes(postID, -1); err != nil {
return fmt.Errorf("更新点赞计数失败: %w", err)
}
isLiked = false
} else {
// 无赞 → 加赞(若已有踩,先移除踩)
if hasDislike {
if err := txRepo.DeleteDislike(userID, postID); err != nil {
return fmt.Errorf("移除踩记录失败: %w", err)
}
if err := txRepo.IncrPostDislikes(postID, -1); err != nil {
return fmt.Errorf("更新踩计数失败: %w", err)
}
}
if err := txRepo.CreateLike(&model.PostLike{UserID: userID, PostID: postID}); err != nil {
return fmt.Errorf("创建点赞记录失败: %w", err)
}
if err := txRepo.IncrPostLikes(postID, 1); err != nil {
return fmt.Errorf("更新点赞计数失败: %w", err)
}
isLiked = true
}
return nil
})
if err == nil && isLiked {
// 新赞 → 记录到每日点赞汇总(非关键路径)
s.recordLikeForAggregation(postID, userID)
}
return isLiked, err
}
// recordLikeForAggregation 记录点赞到每日汇总(非关键路径,失败不阻塞)
func (s *ReactionService) recordLikeForAggregation(postID uint, likerID uint) {
authorID, err := s.repo.GetPostAuthorID(postID)
if err != nil {
return
}
// 不记录自己赞自己
if authorID == likerID {
return
}
date := time.Now().Format("2006-01-02")
likerUID := fmt.Sprintf("%d", likerID)
_ = s.repo.UpsertDailyLikeSummary(authorID, date, likerUID)
}
// ToggleDislike 切换踩(含赞互斥逻辑)
// 返回:当前是否已踩
func (s *ReactionService) ToggleDislike(userID, postID uint) (bool, error) {
var isDisliked bool
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
// 查当前状态
_, likeErr := txRepo.FindLike(userID, postID)
hasLike := likeErr == nil
_, dislikeErr := txRepo.FindDislike(userID, postID)
hasDislike := dislikeErr == nil
if hasDislike {
// 已踩 → 取消踩
if err := txRepo.DeleteDislike(userID, postID); err != nil {
return fmt.Errorf("取消踩失败: %w", err)
}
if err := txRepo.IncrPostDislikes(postID, -1); err != nil {
return fmt.Errorf("更新踩计数失败: %w", err)
}
isDisliked = false
} else {
// 无踩 → 加踩(若已有赞,先移除赞)
if hasLike {
if err := txRepo.DeleteLike(userID, postID); err != nil {
return fmt.Errorf("移除点赞记录失败: %w", err)
}
if err := txRepo.IncrPostLikes(postID, -1); err != nil {
return fmt.Errorf("更新点赞计数失败: %w", err)
}
}
if err := txRepo.CreateDislike(&model.PostDislike{UserID: userID, PostID: postID}); err != nil {
return fmt.Errorf("创建踩记录失败: %w", err)
}
if err := txRepo.IncrPostDislikes(postID, 1); err != nil {
return fmt.Errorf("更新踩计数失败: %w", err)
}
isDisliked = true
}
return nil
})
return isDisliked, err
}
// GetReaction 查询用户对文章的反应状态
func (s *ReactionService) GetReaction(userID, postID uint) (ReactionType, error) {
_, likeErr := s.repo.FindLike(userID, postID)
if likeErr == nil {
return ReactionLiked, nil
}
_, dislikeErr := s.repo.FindDislike(userID, postID)
if dislikeErr == nil {
return ReactionDisliked, nil
}
return ReactionNone, nil
}
// GetPostLikesCount 查询文章点赞数(公开展示)
func (s *ReactionService) GetPostLikesCount(postID uint) (int, error) {
var post model.Post
if err := s.db.Select("likes_count").First(&post, postID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return 0, common.ErrPostNotFound
}
return 0, fmt.Errorf("查询文章失败: %w", err)
}
return post.LikesCount, nil
}