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/follow_service.go
Victor_Jay a0df66587b fix: 修复关注/粉丝列表隐私检查假阳性导致本人也无法访问
- follow_repo.go: GetFollowListPublic 改用显式 WHERE uid=? 避免 GORM 主键解析潜在问题
- follow_service.go: GetFollowListPublic 失败时默认公开并记录日志,而非返回错误
- follow_controller.go: 错误兜底时 Accessible 默认 true,避免误锁用户
2026-06-01 20:22:50 +08:00

217 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"fmt"
"log"
"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 {
log.Printf("[FollowService] ListFollowers GetFollowListPublic error uid=%d: %v, fallback to public", userID, err)
listPublic = true
}
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 {
log.Printf("[FollowService] ListFollowing GetFollowListPublic error uid=%d: %v, fallback to public", userID, err)
listPublic = true
}
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
}