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 按分类分页查询
This commit is contained in:
213
internal/service/follow_service.go
Normal file
213
internal/service/follow_service.go
Normal file
@ -0,0 +1,213 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FollowStatus 关注关系状态
|
||||
type FollowStatus struct {
|
||||
IFollow bool `json:"i_follow"`
|
||||
TheyFollow bool `json:"they_follow"`
|
||||
}
|
||||
|
||||
// FollowListResult 关注/粉丝列表结果
|
||||
type FollowListResult struct {
|
||||
Items []model.UserFollow `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
ListPublic bool `json:"list_public"`
|
||||
Accessible bool `json:"accessible"`
|
||||
}
|
||||
|
||||
// followNotifier 关注通知接口(ISP)
|
||||
type followNotifier interface {
|
||||
NotifyFollow(followerID, followeeID uint)
|
||||
}
|
||||
|
||||
// FollowService 关注业务逻辑
|
||||
type FollowService struct {
|
||||
db *gorm.DB
|
||||
repo FollowStore
|
||||
notifier followNotifier
|
||||
}
|
||||
|
||||
// NewFollowService 构造函数
|
||||
func NewFollowService(db *gorm.DB, repo FollowStore) *FollowService {
|
||||
return &FollowService{db: db, repo: repo}
|
||||
}
|
||||
|
||||
// SetNotifier 注入通知服务
|
||||
func (s *FollowService) SetNotifier(n followNotifier) {
|
||||
s.notifier = n
|
||||
}
|
||||
|
||||
// Toggle 关注/取消关注,返回操作后的关注状态(true=已关注)
|
||||
func (s *FollowService) Toggle(followerID, followeeID uint) (bool, error) {
|
||||
if followerID == followeeID {
|
||||
return false, fmt.Errorf("不能关注自己")
|
||||
}
|
||||
|
||||
var isFollowing bool
|
||||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
txRepo := s.repo.WithTx(tx)
|
||||
|
||||
exists, err := txRepo.Exists(followerID, followeeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询关注状态失败: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
if err := txRepo.Delete(followerID, followeeID); err != nil {
|
||||
return fmt.Errorf("取消关注失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrFollowingCount(followerID, -1); err != nil {
|
||||
return fmt.Errorf("更新关注数失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrFollowersCount(followeeID, -1); err != nil {
|
||||
return fmt.Errorf("更新粉丝数失败: %w", err)
|
||||
}
|
||||
isFollowing = false
|
||||
} else {
|
||||
follow := &model.UserFollow{
|
||||
FollowerID: followerID,
|
||||
FolloweeID: followeeID,
|
||||
}
|
||||
if err := txRepo.Create(follow); err != nil {
|
||||
return fmt.Errorf("关注失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrFollowingCount(followerID, 1); err != nil {
|
||||
return fmt.Errorf("更新关注数失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrFollowersCount(followeeID, 1); err != nil {
|
||||
return fmt.Errorf("更新粉丝数失败: %w", err)
|
||||
}
|
||||
isFollowing = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil && isFollowing {
|
||||
s.sendFollowNotification(followerID, followeeID)
|
||||
}
|
||||
|
||||
return isFollowing, err
|
||||
}
|
||||
|
||||
// sendFollowNotification 发送关注通知(非关键路径)
|
||||
func (s *FollowService) sendFollowNotification(followerID, followeeID uint) {
|
||||
if s.notifier != nil && followerID != followeeID {
|
||||
s.notifier.NotifyFollow(followerID, followeeID)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatus 查询当前用户对目标用户的关注关系
|
||||
func (s *FollowService) GetStatus(currentUserID, targetUserID uint) (*FollowStatus, error) {
|
||||
if currentUserID == 0 {
|
||||
return &FollowStatus{}, nil
|
||||
}
|
||||
|
||||
iFollow, err := s.repo.Exists(currentUserID, targetUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询我是否关注: %w", err)
|
||||
}
|
||||
|
||||
theyFollow, err := s.repo.Exists(targetUserID, currentUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询对方是否关注: %w", err)
|
||||
}
|
||||
|
||||
return &FollowStatus{
|
||||
IFollow: iFollow,
|
||||
TheyFollow: theyFollow,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListFollowers 粉丝列表(含隐私控制)
|
||||
func (s *FollowService) ListFollowers(userID, currentUserID uint, page, pageSize int) (*FollowListResult, error) {
|
||||
listPublic, err := s.repo.GetFollowListPublic(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询公开设置: %w", err)
|
||||
}
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
|
||||
accessible := listPublic || currentUserID == userID
|
||||
|
||||
var items []model.UserFollow
|
||||
var total int64
|
||||
|
||||
if accessible {
|
||||
items, total, err = s.repo.ListFollowers(userID, p.Offset(), p.PageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询粉丝列表: %w", err)
|
||||
}
|
||||
} else {
|
||||
total, err = s.repo.CountFollowers(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询粉丝数: %w", err)
|
||||
}
|
||||
items = []model.UserFollow{}
|
||||
}
|
||||
|
||||
if items == nil {
|
||||
items = []model.UserFollow{}
|
||||
}
|
||||
|
||||
return &FollowListResult{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
TotalPages: common.PageCount(total, p.PageSize),
|
||||
ListPublic: listPublic,
|
||||
Accessible: accessible,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListFollowing 关注列表(含隐私控制)
|
||||
func (s *FollowService) ListFollowing(userID, currentUserID uint, page, pageSize int) (*FollowListResult, error) {
|
||||
listPublic, err := s.repo.GetFollowListPublic(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询公开设置: %w", err)
|
||||
}
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
|
||||
accessible := listPublic || currentUserID == userID
|
||||
|
||||
var items []model.UserFollow
|
||||
var total int64
|
||||
|
||||
if accessible {
|
||||
items, total, err = s.repo.ListFollowing(userID, p.Offset(), p.PageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询关注列表: %w", err)
|
||||
}
|
||||
} else {
|
||||
total, err = s.repo.CountFollowing(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询关注数: %w", err)
|
||||
}
|
||||
items = []model.UserFollow{}
|
||||
}
|
||||
|
||||
if items == nil {
|
||||
items = []model.UserFollow{}
|
||||
}
|
||||
|
||||
return &FollowListResult{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
TotalPages: common.PageCount(total, p.PageSize),
|
||||
ListPublic: listPublic,
|
||||
Accessible: accessible,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user