feat(P2): 粉丝/关注数 + 用户主页统计 (getProfile/getCounts)

This commit is contained in:
Sisyphus
2026-04-25 19:58:16 +08:00
parent 2322383036
commit ec56c73016
7 changed files with 227 additions and 127 deletions

View File

@@ -65,3 +65,15 @@ type UpdateProfileRequest struct {
type RefreshRequest struct { type RefreshRequest struct {
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
} }
type GetProfileRequest struct {
AccountID uint `json:"account_id"`
}
type GetProfileResponse struct {
Account FindByIDResponse `json:"account"`
VideoCount int64 `json:"video_count"`
TotalLikes int64 `json:"total_likes"`
FollowerCount int64 `json:"follower_count"`
VloggerCount int64 `json:"vlogger_count"`
}

View File

@@ -126,7 +126,35 @@ func SetRouter(db *gorm.DB, cache *rediscache.Client, rmq *rabbitmq.RabbitMQ) *g
protectedSocialGroup.POST("/unfollow", socialLimiter, socialHandler.Unfollow) protectedSocialGroup.POST("/unfollow", socialLimiter, socialHandler.Unfollow)
protectedSocialGroup.POST("/getAllFollowers", socialHandler.GetAllFollowers) protectedSocialGroup.POST("/getAllFollowers", socialHandler.GetAllFollowers)
protectedSocialGroup.POST("/getAllVloggers", socialHandler.GetAllVloggers) protectedSocialGroup.POST("/getAllVloggers", socialHandler.GetAllVloggers)
protectedSocialGroup.POST("/getCounts", socialHandler.GetCounts)
} }
accountGroup.POST("/getProfile", func(c *gin.Context) {
var req account.GetProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if req.AccountID == 0 {
c.JSON(400, gin.H{"error": "account_id is required"})
return
}
acc, err := accountService.FindByID(c.Request.Context(), req.AccountID)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
videoCount, _ := videoRepository.CountByAuthor(c.Request.Context(), req.AccountID)
totalLikes, _ := videoRepository.TotalLikesByAuthor(c.Request.Context(), req.AccountID)
followerCount, _ := socialRepository.CountFollowers(c.Request.Context(), req.AccountID)
vloggerCount, _ := socialRepository.CountVloggers(c.Request.Context(), req.AccountID)
c.JSON(200, account.GetProfileResponse{
Account: account.FindByIDResponse{ID: acc.ID, Username: acc.Username, AvatarURL: acc.AvatarURL, Bio: acc.Bio},
VideoCount: videoCount, TotalLikes: totalLikes,
FollowerCount: followerCount, VloggerCount: vloggerCount,
})
})
// feed // feed
feedRepository := feed.NewFeedRepository(db) feedRepository := feed.NewFeedRepository(db)
feedService := feed.NewFeedService(feedRepository, likeRepository, cache) feedService := feed.NewFeedService(feedRepository, likeRepository, cache)

View File

@@ -1,33 +1,40 @@
package social package social
import "feedsystem_video_go/internal/account" import "feedsystem_video_go/internal/account"
type Social struct { type Social struct {
ID uint `gorm:"primaryKey"` ID uint `gorm:"primaryKey"`
FollowerID uint `gorm:"not null;index:idx_social_follower;uniqueIndex:idx_social_follower_vlogger"` FollowerID uint `gorm:"not null;index:idx_social_follower;uniqueIndex:idx_social_follower_vlogger"`
VloggerID uint `gorm:"not null;index:idx_social_vlogger;uniqueIndex:idx_social_follower_vlogger"` VloggerID uint `gorm:"not null;index:idx_social_vlogger;uniqueIndex:idx_social_follower_vlogger"`
} }
type FollowRequest struct { type FollowRequest struct {
VloggerID uint `json:"vlogger_id"` VloggerID uint `json:"vlogger_id"`
} }
type UnfollowRequest struct { type UnfollowRequest struct {
VloggerID uint `json:"vlogger_id"` VloggerID uint `json:"vlogger_id"`
} }
type GetAllFollowersRequest struct { type GetAllFollowersRequest struct {
VloggerID uint `json:"vlogger_id"` VloggerID uint `json:"vlogger_id"`
} }
type GetAllFollowersResponse struct { type GetAllFollowersResponse struct {
Followers []*account.Account `json:"followers"` Followers []*account.Account `json:"followers"`
} FollowerCount int64 `json:"follower_count"`
}
type GetAllVloggersRequest struct {
FollowerID uint `json:"follower_id"` type GetAllVloggersResponse struct {
} Vloggers []*account.Account `json:"vloggers"`
VloggerCount int64 `json:"vlogger_count"`
type GetAllVloggersResponse struct { }
Vloggers []*account.Account `json:"vloggers"`
type SocialCounts struct {
FollowerCount int64 `json:"follower_count"`
VloggerCount int64 `json:"vlogger_count"`
} }
type GetAllVloggersRequest struct {
FollowerID uint `json:"follower_id"`
}

View File

@@ -94,7 +94,8 @@ func (h *SocialHandler) GetAllFollowers(c *gin.Context) {
if followers == nil { if followers == nil {
followers = []*account.Account{} followers = []*account.Account{}
} }
c.JSON(http.StatusOK, GetAllFollowersResponse{Followers: followers}) followerCount, _ := h.service.CountFollowers(c.Request.Context(), vloggerID)
c.JSON(http.StatusOK, GetAllFollowersResponse{Followers: followers, FollowerCount: followerCount})
} }
func (h *SocialHandler) GetAllVloggers(c *gin.Context) { func (h *SocialHandler) GetAllVloggers(c *gin.Context) {
@@ -122,5 +123,17 @@ func (h *SocialHandler) GetAllVloggers(c *gin.Context) {
if vloggers == nil { if vloggers == nil {
vloggers = []*account.Account{} vloggers = []*account.Account{}
} }
c.JSON(http.StatusOK, GetAllVloggersResponse{Vloggers: vloggers}) vloggerCount, _ := h.service.CountVloggers(c.Request.Context(), followerID)
c.JSON(http.StatusOK, GetAllVloggersResponse{Vloggers: vloggers, VloggerCount: vloggerCount})
}
func (h *SocialHandler) GetCounts(c *gin.Context) {
accountID, err := jwt.GetAccountID(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
followerCount, _ := h.service.CountFollowers(c.Request.Context(), accountID)
vloggerCount, _ := h.service.CountVloggers(c.Request.Context(), accountID)
c.JSON(http.StatusOK, SocialCounts{FollowerCount: followerCount, VloggerCount: vloggerCount})
} }

View File

@@ -91,3 +91,19 @@ func (r *SocialRepository) IsFollowed(ctx context.Context, social *Social) (bool
} }
return count > 0, nil return count > 0, nil
} }
func (r *SocialRepository) CountFollowers(ctx context.Context, vloggerID uint) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&Social{}).Where("vlogger_id = ?", vloggerID).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r *SocialRepository) CountVloggers(ctx context.Context, followerID uint) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&Social{}).Where("follower_id = ?", followerID).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}

View File

@@ -1,93 +1,101 @@
package social package social
import ( import (
"context" "context"
"errors" "errors"
"feedsystem_video_go/internal/account" "feedsystem_video_go/internal/account"
"feedsystem_video_go/internal/middleware/rabbitmq" "feedsystem_video_go/internal/middleware/rabbitmq"
) )
type SocialService struct { type SocialService struct {
repo *SocialRepository repo *SocialRepository
accountrepo *account.AccountRepository accountrepo *account.AccountRepository
socialMQ *rabbitmq.SocialMQ socialMQ *rabbitmq.SocialMQ
} }
func NewSocialService(repo *SocialRepository, accountrepo *account.AccountRepository, socialMQ *rabbitmq.SocialMQ) *SocialService { func NewSocialService(repo *SocialRepository, accountrepo *account.AccountRepository, socialMQ *rabbitmq.SocialMQ) *SocialService {
return &SocialService{repo: repo, accountrepo: accountrepo, socialMQ: socialMQ} return &SocialService{repo: repo, accountrepo: accountrepo, socialMQ: socialMQ}
} }
func (s *SocialService) Follow(ctx context.Context, social *Social) error { func (s *SocialService) Follow(ctx context.Context, social *Social) error {
_, err := s.accountrepo.FindByID(ctx, social.FollowerID) _, err := s.accountrepo.FindByID(ctx, social.FollowerID)
if err != nil { if err != nil {
return err return err
} }
_, err = s.accountrepo.FindByID(ctx, social.VloggerID) _, err = s.accountrepo.FindByID(ctx, social.VloggerID)
if err != nil { if err != nil {
return err return err
} }
if social.FollowerID == social.VloggerID { if social.FollowerID == social.VloggerID {
return errors.New("can not follow self") return errors.New("can not follow self")
} }
isFollowed, err := s.repo.IsFollowed(ctx, social) isFollowed, err := s.repo.IsFollowed(ctx, social)
if err != nil { if err != nil {
return err return err
} }
if isFollowed { if isFollowed {
return errors.New("already followed") return errors.New("already followed")
} }
if s.socialMQ != nil { if s.socialMQ != nil {
s.socialMQ.Follow(ctx, social.FollowerID, social.VloggerID) s.socialMQ.Follow(ctx, social.FollowerID, social.VloggerID)
} }
return s.repo.Follow(ctx, social) return s.repo.Follow(ctx, social)
} }
func (s *SocialService) Unfollow(ctx context.Context, social *Social) error { func (s *SocialService) Unfollow(ctx context.Context, social *Social) error {
_, err := s.accountrepo.FindByID(ctx, social.FollowerID) _, err := s.accountrepo.FindByID(ctx, social.FollowerID)
if err != nil { if err != nil {
return err return err
} }
_, err = s.accountrepo.FindByID(ctx, social.VloggerID) _, err = s.accountrepo.FindByID(ctx, social.VloggerID)
if err != nil { if err != nil {
return err return err
} }
isFollowed, err := s.repo.IsFollowed(ctx, social) isFollowed, err := s.repo.IsFollowed(ctx, social)
if err != nil { if err != nil {
return err return err
} }
if !isFollowed { if !isFollowed {
return errors.New("not followed") return errors.New("not followed")
} }
if s.socialMQ != nil { if s.socialMQ != nil {
s.socialMQ.UnFollow(ctx, social.FollowerID, social.VloggerID) s.socialMQ.UnFollow(ctx, social.FollowerID, social.VloggerID)
} }
return s.repo.Unfollow(ctx, social) return s.repo.Unfollow(ctx, social)
} }
func (s *SocialService) GetAllFollowers(ctx context.Context, VloggerID uint) ([]*account.Account, error) { func (s *SocialService) GetAllFollowers(ctx context.Context, VloggerID uint) ([]*account.Account, error) {
_, err := s.accountrepo.FindByID(ctx, VloggerID) _, err := s.accountrepo.FindByID(ctx, VloggerID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.repo.GetAllFollowers(ctx, VloggerID) return s.repo.GetAllFollowers(ctx, VloggerID)
} }
func (s *SocialService) GetAllVloggers(ctx context.Context, FollowerID uint) ([]*account.Account, error) { func (s *SocialService) GetAllVloggers(ctx context.Context, FollowerID uint) ([]*account.Account, error) {
_, err := s.accountrepo.FindByID(ctx, FollowerID) _, err := s.accountrepo.FindByID(ctx, FollowerID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.repo.GetAllVloggers(ctx, FollowerID) return s.repo.GetAllVloggers(ctx, FollowerID)
} }
func (s *SocialService) IsFollowed(ctx context.Context, social *Social) (bool, error) { func (s *SocialService) CountFollowers(ctx context.Context, vloggerID uint) (int64, error) {
_, err := s.accountrepo.FindByID(ctx, social.FollowerID) return s.repo.CountFollowers(ctx, vloggerID)
if err != nil { }
return false, err
} func (s *SocialService) CountVloggers(ctx context.Context, followerID uint) (int64, error) {
_, err = s.accountrepo.FindByID(ctx, social.VloggerID) return s.repo.CountVloggers(ctx, followerID)
if err != nil { }
return false, err
} func (s *SocialService) IsFollowed(ctx context.Context, social *Social) (bool, error) {
return s.repo.IsFollowed(ctx, social) _, err := s.accountrepo.FindByID(ctx, social.FollowerID)
} if err != nil {
return false, err
}
_, err = s.accountrepo.FindByID(ctx, social.VloggerID)
if err != nil {
return false, err
}
return s.repo.IsFollowed(ctx, social)
}

View File

@@ -102,3 +102,19 @@ func (vr *VideoRepository) ChangePopularity(ctx context.Context, id uint, change
} }
return nil return nil
} }
func (vr *VideoRepository) CountByAuthor(ctx context.Context, authorID uint) (int64, error) {
var count int64
if err := vr.db.WithContext(ctx).Model(&Video{}).Where("author_id = ?", authorID).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (vr *VideoRepository) TotalLikesByAuthor(ctx context.Context, authorID uint) (int64, error) {
var total int64
if err := vr.db.WithContext(ctx).Model(&Video{}).Where("author_id = ?", authorID).Select("COALESCE(SUM(likes_count), 0)").Scan(&total).Error; err != nil {
return 0, err
}
return total, nil
}