package service import ( "context" "errors" "fmt" "time" appErrors "github.com/topfans/backend/pkg/errors" "github.com/topfans/backend/pkg/logger" "github.com/topfans/backend/pkg/models" pbCommon "github.com/topfans/backend/pkg/proto/common" pb "github.com/topfans/backend/pkg/proto/social" "github.com/topfans/backend/services/socialService/config" "github.com/topfans/backend/services/socialService/repository" "go.uber.org/zap" "gorm.io/gorm" ) // FriendService 好友服务接口 type FriendService interface { // ========== 好友请求相关 ========== // SendFriendRequest 发送好友请求 SendFriendRequest(ctx context.Context, req *pb.SendFriendRequestRequest, userID, starID int64) (*pb.SendFriendRequestResponse, error) // GetFriendRequests 获取好友请求列表 GetFriendRequests(ctx context.Context, req *pb.GetFriendRequestsRequest, userID, starID int64) (*pb.GetFriendRequestsResponse, error) // HandleFriendRequest 处理好友请求(接受/拒绝) HandleFriendRequest(ctx context.Context, req *pb.HandleFriendRequestRequest, userID, starID int64) (*pb.HandleFriendRequestResponse, error) // ========== 好友关系相关 ========== // GetFriendList 获取好友列表 GetFriendList(ctx context.Context, req *pb.GetFriendListRequest, userID, starID int64) (*pb.GetFriendListResponse, error) // DeleteFriend 删除好友 DeleteFriend(ctx context.Context, req *pb.DeleteFriendRequest, userID, starID int64) (*pb.DeleteFriendResponse, error) // SetFriendRemark 设置好友备注 SetFriendRemark(ctx context.Context, req *pb.SetFriendRemarkRequest, userID, starID int64) (*pb.SetFriendRemarkResponse, error) // CheckFriendship 检查好友关系 CheckFriendship(ctx context.Context, req *pb.CheckFriendshipRequest) (*pb.CheckFriendshipResponse, error) // GetFriendCount 获取好友数量 GetFriendCount(ctx context.Context, req *pb.GetFriendCountRequest, userID, starID int64) (*pb.GetFriendCountResponse, error) // SearchUserForFriend 查找用户信息(用于加好友) SearchUserForFriend(ctx context.Context, req *pb.SearchUserForFriendRequest, userID, starID int64) (*pb.SearchUserForFriendResponse, error) // ========== 随机用户相关 ========== // GetRandomUsers 获取随机用户(同一明星下) GetRandomUsers(ctx context.Context, req *pb.GetRandomUsersRequest, userID, starID int64) (*pb.GetRandomUsersResponse, error) // ========== 分页用户相关 ========== // GetUsersPaged 获取分页用户列表(同一明星身份下) GetUsersPaged(ctx context.Context, req *pb.GetUsersPagedRequest, userID, starID int64) (*pb.GetUsersPagedResponse, error) } // friendService 好友服务实现 type friendService struct { socialRepo repository.SocialRepository userClient UserServiceClient // RPC客户端,用于调用userService db *gorm.DB config *config.SocialConfig } // NewFriendService 创建好友服务实例 func NewFriendService( socialRepo repository.SocialRepository, userClient UserServiceClient, db *gorm.DB, ) FriendService { return &friendService{ socialRepo: socialRepo, userClient: userClient, db: db, config: config.GlobalSocialConfig, } } // ========== 好友请求相关实现 ========== // SendFriendRequest 发送好友请求(支持按昵称搜索) func (s *friendService) SendFriendRequest(ctx context.Context, req *pb.SendFriendRequestRequest, userID, starID int64) (*pb.SendFriendRequestResponse, error) { // 1. 如果是搜索模式(search_mode=true 且提供了昵称),返回匹配的用户列表 if req.SearchMode && req.Nickname != "" { return s.searchUsersByNickname(ctx, req, userID, starID) } // 2. 参数验证 - 正常发送请求模式 if req.FriendUserId == 0 { logger.Logger.Warn("Invalid friend_user_id", zap.Int64("friend_user_id", req.FriendUserId)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidFriendUserID), }, nil } if userID == req.FriendUserId { logger.Logger.Warn("Cannot add self as friend", zap.Int64("user_id", userID)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrCannotAddSelf), }, nil } // 3. 验证对方用户是否存在 exists, err := s.userClient.ValidateUser(ctx, req.FriendUserId) if err != nil { logger.Logger.Error("Failed to validate user", zap.Int64("friend_user_id", req.FriendUserId), zap.Error(err)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if !exists { logger.Logger.Warn("Friend user not found", zap.Int64("friend_user_id", req.FriendUserId)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrUserNotFound), }, nil } // 4. 验证对方的粉丝档案是否存在(必须是同一个明星的粉丝) exists, err = s.userClient.ValidateFanProfile(ctx, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to validate fan profile", zap.Int64("friend_user_id", req.FriendUserId), zap.Int64("star_id", starID), zap.Error(err)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if !exists { logger.Logger.Warn("Friend user is not a fan of this star", zap.Int64("friend_user_id", req.FriendUserId), zap.Int64("star_id", starID)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrNotFanOfStar), }, nil } // 4. 检查是否已经是好友 isFriend, err := s.socialRepo.CheckFriendship(userID, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId), zap.Error(err)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if isFriend { logger.Logger.Warn("Already friends", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrAlreadyFriends), }, nil } // 5. 检查是否有待处理的请求 latestRequest, err := s.socialRepo.GetLatestRequest(userID, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to get latest request", zap.Error(err)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if latestRequest != nil { // 5.1 如果有待处理的请求 if latestRequest.Status == models.FriendRequestStatusPending { logger.Logger.Warn("Request already pending", zap.Int64("request_id", latestRequest.ID)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrRequestAlreadyPending), }, nil } // 5.2 如果最近被拒绝,检查是否在冷却期内 if latestRequest.Status == models.FriendRequestStatusRejected { if latestRequest.ProcessedAt != nil && s.config.IsInCooldownPeriod(*latestRequest.ProcessedAt) { remainingDays := s.config.CalculateRemainingCooldownDays(*latestRequest.ProcessedAt) logger.Logger.Warn("Request in cooldown period", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId), zap.Int("remaining_days", remainingDays), ) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage( pbCommon.StatusCode_STATUS_TOO_MANY_REQUESTS, fmt.Sprintf("请求已被拒绝,请 %d 天后再试", remainingDays), ), }, nil } } } // 6. 创建好友请求 now := time.Now().UnixMilli() expiresAt := s.config.CalculateExpiryTime(now) friendRequest := &models.FriendRequest{ FromUserID: userID, ToUserID: req.FriendUserId, StarID: starID, Message: &req.Message, Status: models.FriendRequestStatusPending, ExpiresAt: &expiresAt, } if err := s.socialRepo.CreateRequest(friendRequest); err != nil { logger.Logger.Error("Failed to create friend request", zap.Error(err)) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } logger.Logger.Info("Friend request created", zap.Int64("request_id", friendRequest.ID), zap.Int64("from_user_id", userID), zap.Int64("to_user_id", req.FriendUserId), zap.Int64("star_id", starID), ) // 7. 构建响应 return &pb.SendFriendRequestResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "好友请求已发送", Timestamp: time.Now().UnixMilli(), }, RequestId: friendRequest.ID, Status: friendRequest.Status, CreatedAt: friendRequest.CreatedAt, ExpiresAt: *friendRequest.ExpiresAt, }, nil } // GetFriendRequests 获取好友请求列表 func (s *friendService) GetFriendRequests(ctx context.Context, req *pb.GetFriendRequestsRequest, userID, starID int64) (*pb.GetFriendRequestsResponse, error) { // 1. 参数验证 if req.Type != models.FriendRequestTypeReceived && req.Type != models.FriendRequestTypeSent { logger.Logger.Warn("Invalid request type", zap.String("type", req.Type)) return &pb.GetFriendRequestsResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidAction), }, nil } page := req.Page if page <= 0 { page = 1 } pageSize := req.PageSize if pageSize <= 0 { pageSize = 10 } if pageSize > 50 { pageSize = 50 // 限制最大页大小 } // 2. 查询请求列表 requests, total, err := s.socialRepo.GetRequestsByUser(userID, starID, req.Type, req.Status, int(page), int(pageSize)) if err != nil { logger.Logger.Error("Failed to get requests", zap.Error(err)) return &pb.GetFriendRequestsResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 3. 填充用户信息 items, err := s.fillRequestUserInfo(ctx, requests, req.Type, starID) if err != nil { logger.Logger.Error("Failed to fill user info", zap.Error(err)) return &pb.GetFriendRequestsResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 4. 构建响应 return &pb.GetFriendRequestsResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, Items: items, Total: total, Page: page, PageSize: pageSize, }, nil } // HandleFriendRequest 处理好友请求(接受/拒绝) func (s *friendService) HandleFriendRequest(ctx context.Context, req *pb.HandleFriendRequestRequest, userID, starID int64) (*pb.HandleFriendRequestResponse, error) { // 1. 参数验证 if req.RequestId == 0 { logger.Logger.Warn("Invalid request_id", zap.Int64("request_id", req.RequestId)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage(pbCommon.StatusCode_STATUS_BAD_REQUEST, "request_id不能为空"), }, nil } if req.Action != "accept" && req.Action != "reject" { logger.Logger.Warn("Invalid action", zap.String("action", req.Action)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidAction), }, nil } // 2. 查询请求 friendRequest, err := s.socialRepo.GetRequestByID(req.RequestId) if err != nil { logger.Logger.Error("Failed to get request", zap.Int64("request_id", req.RequestId), zap.Error(err)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if friendRequest == nil { logger.Logger.Warn("Request not found", zap.Int64("request_id", req.RequestId)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrFriendRequestNotFound), }, nil } // 3. 验证权限(只有接收者可以处理) if friendRequest.ToUserID != userID { logger.Logger.Warn("User is not the receiver", zap.Int64("user_id", userID), zap.Int64("to_user_id", friendRequest.ToUserID), ) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrCannotProcessOwnRequest), }, nil } // 4. 验证状态(只能处理pending状态的请求) if friendRequest.Status != models.FriendRequestStatusPending { logger.Logger.Warn("Request is not pending", zap.Int64("request_id", req.RequestId), zap.String("status", friendRequest.Status), ) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrRequestAlreadyProcessed), }, nil } // 5. 检查是否已过期 if friendRequest.ExpiresAt != nil && s.config.IsExpired(*friendRequest.ExpiresAt) { logger.Logger.Warn("Request expired", zap.Int64("request_id", req.RequestId)) // 更新状态为已过期 processedAt := time.Now().UnixMilli() _ = s.socialRepo.UpdateRequestStatus(req.RequestId, models.FriendRequestStatusExpired, &processedAt) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrRequestExpired), }, nil } // 6. 检查是否已经是好友(处理并发场景:A和B互相发送请求,其中一个已被接受) isFriend, err := s.socialRepo.CheckFriendship(friendRequest.FromUserID, friendRequest.ToUserID, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Error(err)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if isFriend { // 已经是好友了,将此请求标记为已接受(避免重复处理) processedAt := time.Now().UnixMilli() _ = s.socialRepo.UpdateRequestStatus(req.RequestId, models.FriendRequestStatusAccepted, &processedAt) logger.Logger.Info("Users are already friends, marking request as accepted", zap.Int64("request_id", req.RequestId), zap.Int64("from_user_id", friendRequest.FromUserID), zap.Int64("to_user_id", friendRequest.ToUserID), ) return &pb.HandleFriendRequestResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "你们已经是好友了", Timestamp: time.Now().UnixMilli(), }, Action: req.Action, FriendshipCreated: false, // 好友关系之前就已存在 ProcessedAt: processedAt, }, nil } // 7. 处理请求 processedAt := time.Now().UnixMilli() var newStatus string var friendshipCreated bool = false if req.Action == "accept" { // 7.1 接受请求 newStatus = models.FriendRequestStatusAccepted // 7.1.0 检查双方好友数量是否已达上限 maxFriends := config.GetMaxFriends() fromCount, _ := s.socialRepo.CountFriends(friendRequest.FromUserID, starID) toCount, _ := s.socialRepo.CountFriends(friendRequest.ToUserID, starID) if fromCount >= int64(maxFriends) { logger.Logger.Warn("From user reached max friends limit", zap.Int64("from_user_id", friendRequest.FromUserID), zap.Int64("current_count", fromCount), zap.Int("max_friends", maxFriends), ) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage( pbCommon.StatusCode_STATUS_FORBIDDEN, fmt.Sprintf("对方好友数量已达上限(最多%d个)", maxFriends), ), Action: req.Action, FriendshipCreated: false, ProcessedAt: processedAt, }, nil } if toCount >= int64(maxFriends) { logger.Logger.Warn("To user reached max friends limit", zap.Int64("to_user_id", friendRequest.ToUserID), zap.Int64("current_count", toCount), zap.Int("max_friends", maxFriends), ) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage( pbCommon.StatusCode_STATUS_FORBIDDEN, fmt.Sprintf("你的好友数量已达上限(最多%d个),无法接受新好友", maxFriends), ), Action: req.Action, FriendshipCreated: false, ProcessedAt: processedAt, }, nil } // 使用事务确保原子性 err = s.db.Transaction(func(tx *gorm.DB) error { // 7.1.1 更新请求状态 if err := s.socialRepo.UpdateRequestStatus(req.RequestId, newStatus, &processedAt); err != nil { return fmt.Errorf("更新请求状态失败: %w", err) } // 7.1.2 创建双向好友关系 if err := s.socialRepo.CreateFriendshipPair(friendRequest.FromUserID, friendRequest.ToUserID, starID); err != nil { return fmt.Errorf("创建好友关系失败: %w", err) } // 7.1.3 更新双方的social字段(好友数量) // TODO: 调用userService更新fan_profiles表的social字段 // 暂时省略,后续实现 return nil }) if err != nil { logger.Logger.Error("Failed to accept friend request", zap.Error(err)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } friendshipCreated = true logger.Logger.Info("Friend request accepted", zap.Int64("request_id", req.RequestId), zap.Int64("from_user_id", friendRequest.FromUserID), zap.Int64("to_user_id", friendRequest.ToUserID), ) } else { // 7.2 拒绝请求 newStatus = models.FriendRequestStatusRejected if err := s.socialRepo.UpdateRequestStatus(req.RequestId, newStatus, &processedAt); err != nil { logger.Logger.Error("Failed to reject friend request", zap.Error(err)) return &pb.HandleFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } logger.Logger.Info("Friend request rejected", zap.Int64("request_id", req.RequestId), zap.Int64("from_user_id", friendRequest.FromUserID), zap.Int64("to_user_id", friendRequest.ToUserID), ) } // 8. 构建响应 return &pb.HandleFriendRequestResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, Action: req.Action, FriendshipCreated: friendshipCreated, ProcessedAt: processedAt, }, nil } // ========== 好友关系相关实现 ========== // GetFriendList 获取好友列表 func (s *friendService) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest, userID, starID int64) (*pb.GetFriendListResponse, error) { // 1. 参数验证 page := req.Page if page <= 0 { page = 1 } pageSize := req.PageSize if pageSize <= 0 { pageSize = 20 } if pageSize > 50 { pageSize = 50 // 限制最大页大小 } // 2. 查询好友列表 friendships, total, err := s.socialRepo.GetFriendsByUser(userID, starID, req.Keyword, int(page), int(pageSize)) if err != nil { logger.Logger.Error("Failed to get friends", zap.Error(err)) return &pb.GetFriendListResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 3. 填充好友信息 items, err := s.fillFriendshipUserInfo(ctx, friendships, starID) if err != nil { logger.Logger.Error("Failed to fill friend info", zap.Error(err)) return &pb.GetFriendListResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 4. 构建响应 return &pb.GetFriendListResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, Items: items, Total: total, Page: page, PageSize: pageSize, }, nil } // DeleteFriend 删除好友 func (s *friendService) DeleteFriend(ctx context.Context, req *pb.DeleteFriendRequest, userID, starID int64) (*pb.DeleteFriendResponse, error) { // 1. 参数验证 if req.FriendUserId == 0 { logger.Logger.Warn("Invalid friend_user_id", zap.Int64("friend_user_id", req.FriendUserId)) return &pb.DeleteFriendResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidFriendUserID), }, nil } // 2. 检查是否为好友 isFriend, err := s.socialRepo.CheckFriendship(userID, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Error(err)) return &pb.DeleteFriendResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if !isFriend { logger.Logger.Warn("Not friends", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId)) return &pb.DeleteFriendResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrNotFriends), }, nil } // 3. 删除双向好友关系(使用事务) if err := s.socialRepo.DeleteFriendshipPair(userID, req.FriendUserId, starID); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger.Warn("Friendship not found", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId)) return &pb.DeleteFriendResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrNotFriends), }, nil } logger.Logger.Error("Failed to delete friendship", zap.Error(err)) return &pb.DeleteFriendResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 4. 更新双方的social字段(好友数量) // TODO: 调用userService更新fan_profiles表的social字段 // 暂时省略,后续实现 logger.Logger.Info("Friend deleted", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId), zap.Int64("star_id", starID), ) // 5. 构建响应 return &pb.DeleteFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "已删除好友", Timestamp: time.Now().UnixMilli(), }, Success: true, }, nil } // SetFriendRemark 设置好友备注 func (s *friendService) SetFriendRemark(ctx context.Context, req *pb.SetFriendRemarkRequest, userID, starID int64) (*pb.SetFriendRemarkResponse, error) { // 1. 参数验证 if req.FriendUserId == 0 { logger.Logger.Warn("Invalid friend_user_id", zap.Int64("friend_user_id", req.FriendUserId)) return &pb.SetFriendRemarkResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidFriendUserID), }, nil } // 验证备注长度 if len(req.Remark) > 50 { logger.Logger.Warn("Remark too long", zap.String("remark", req.Remark)) return &pb.SetFriendRemarkResponse{ Base: appErrors.BuildBaseResponseWithMessage(pbCommon.StatusCode_STATUS_BAD_REQUEST, "备注长度不能超过50个字符"), }, nil } // 2. 检查是否为好友 isFriend, err := s.socialRepo.CheckFriendship(userID, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Error(err)) return &pb.SetFriendRemarkResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } if !isFriend { logger.Logger.Warn("Not friends", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId)) return &pb.SetFriendRemarkResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrNotFriends), }, nil } // 3. 更新备注 if err := s.socialRepo.UpdateRemark(userID, req.FriendUserId, starID, req.Remark); err != nil { logger.Logger.Error("Failed to update remark", zap.Error(err)) return &pb.SetFriendRemarkResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } logger.Logger.Info("Friend remark updated", zap.Int64("user_id", userID), zap.Int64("friend_user_id", req.FriendUserId), zap.String("remark", req.Remark), ) // 4. 构建响应 return &pb.SetFriendRemarkResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "备注已更新", Timestamp: time.Now().UnixMilli(), }, Remark: req.Remark, }, nil } // CheckFriendship 检查好友关系 func (s *friendService) CheckFriendship(ctx context.Context, req *pb.CheckFriendshipRequest) (*pb.CheckFriendshipResponse, error) { // 1. 参数验证 if req.UserId == 0 || req.FriendUserId == 0 { logger.Logger.Warn("Invalid user IDs", zap.Int64("user_id", req.UserId), zap.Int64("friend_user_id", req.FriendUserId), ) return &pb.CheckFriendshipResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID), }, nil } // 注意:这里没有starID参数,需要从上下文或其他地方获取 // 暂时使用0,后续需要修改 starID := int64(0) // TODO: 从上下文获取starID // 2. 检查好友关系 isFriend, err := s.socialRepo.CheckFriendship(req.UserId, req.FriendUserId, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Error(err)) return &pb.CheckFriendshipResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 3. 构建响应 return &pb.CheckFriendshipResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, IsFriend: isFriend, }, nil } // GetFriendCount 获取好友数量 func (s *friendService) GetFriendCount(ctx context.Context, req *pb.GetFriendCountRequest, userID, starID int64) (*pb.GetFriendCountResponse, error) { // 1. 统计好友数量 count, err := s.socialRepo.CountFriends(userID, starID) if err != nil { logger.Logger.Error("Failed to count friends", zap.Error(err)) return &pb.GetFriendCountResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 2. 构建响应 return &pb.GetFriendCountResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, Count: count, }, nil } // ========== 辅助方法 ========== // fillRequestUserInfo 填充请求的用户信息 func (s *friendService) fillRequestUserInfo(ctx context.Context, requests []*models.FriendRequest, requestType string, starID int64) ([]*pb.FriendRequest, error) { if len(requests) == 0 { return []*pb.FriendRequest{}, nil } // 收集需要查询的用户ID userIDs := make([]int64, 0, len(requests)*2) for _, req := range requests { userIDs = append(userIDs, req.FromUserID, req.ToUserID) } // 批量查询用户信息和粉丝档案 userInfoMap, err := s.userClient.GetUsersByIDs(ctx, userIDs, starID) if err != nil { return nil, fmt.Errorf("批量查询用户信息失败: %w", err) } // 填充信息 items := make([]*pb.FriendRequest, 0, len(requests)) for _, req := range requests { item := &pb.FriendRequest{ Id: req.ID, FromUserId: req.FromUserID, ToUserId: req.ToUserID, StarId: req.StarID, Message: getStringValue(req.Message), Status: req.Status, CreatedAt: req.CreatedAt, ExpiresAt: getInt64Value(req.ExpiresAt), ProcessedAt: getInt64Value(req.ProcessedAt), } // 填充发送者信息 if fromUser, ok := userInfoMap[req.FromUserID]; ok { item.FromUserNickname = fromUser.Nickname item.FromUserAvatar = fromUser.Avatar item.FromUserFanLevel = fromUser.FanLevel } // 填充接收者信息 if toUser, ok := userInfoMap[req.ToUserID]; ok { item.ToUserNickname = toUser.Nickname item.ToUserAvatar = toUser.Avatar item.ToUserFanLevel = toUser.FanLevel } items = append(items, item) } return items, nil } // fillFriendshipUserInfo 填充好友关系的用户信息 func (s *friendService) fillFriendshipUserInfo(ctx context.Context, friendships []*models.Friendship, starID int64) ([]*pb.Friendship, error) { if len(friendships) == 0 { return []*pb.Friendship{}, nil } // 收集需要查询的好友ID friendIDs := make([]int64, 0, len(friendships)) for _, f := range friendships { friendIDs = append(friendIDs, f.FriendID) } // 批量查询好友信息和粉丝档案 userInfoMap, err := s.userClient.GetUsersByIDs(ctx, friendIDs, starID) if err != nil { return nil, fmt.Errorf("批量查询用户信息失败: %w", err) } // 填充信息 items := make([]*pb.Friendship, 0, len(friendships)) for _, f := range friendships { item := &pb.Friendship{ Id: f.ID, UserId: f.UserID, FriendId: f.FriendID, StarId: f.StarID, Status: f.Status, Remark: getStringValue(f.Remark), Intimacy: f.Intimacy, CreatedAt: f.CreatedAt, } // 填充好友信息 if friendUser, ok := userInfoMap[f.FriendID]; ok { item.FriendNickname = friendUser.Nickname item.FriendAvatar = friendUser.Avatar item.FriendFanLevel = friendUser.FanLevel item.FriendSocial = friendUser.Social } items = append(items, item) } return items, nil } // getStringValue 获取字符串指针的值,如果为nil返回空字符串 func getStringValue(s *string) string { if s == nil { return "" } return *s } // getInt64Value 获取int64指针的值,如果为nil返回0 func getInt64Value(i *int64) int64 { if i == nil { return 0 } return *i } // SearchUserForFriend 查找粉丝档案信息(用于加好友) func (s *friendService) SearchUserForFriend(ctx context.Context, req *pb.SearchUserForFriendRequest, userID, starID int64) (*pb.SearchUserForFriendResponse, error) { // 1. 参数验证 friendFanProfileID := req.GetFriendFanProfileId() if friendFanProfileID == 0 { logger.Logger.Warn("Invalid friend_fan_profile_id", zap.Int64("friend_fan_profile_id", friendFanProfileID)) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_BAD_REQUEST, Message: "friend_fan_profile_id不能为空", Timestamp: time.Now().UnixMilli(), }, }, nil } // 2. 查询当前用户的粉丝档案(用于后续比较和查询关系) var myFanProfile models.FanProfile if err := s.db.WithContext(ctx).Where("user_id = ? AND star_id = ? AND is_active = ?", userID, starID, true).First(&myFanProfile).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger.Warn("Current user's fan profile not found", zap.Int64("user_id", userID), zap.Int64("star_id", starID), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_NOT_FOUND, Message: "当前用户的粉丝档案不存在", Timestamp: time.Now().UnixMilli(), }, }, nil } logger.Logger.Error("Failed to get current user's fan profile", zap.Int64("user_id", userID), zap.Error(err), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: "获取当前用户信息失败", Timestamp: time.Now().UnixMilli(), }, }, nil } // 3. 查询目标粉丝档案 var fanProfile models.FanProfile if err := s.db.WithContext(ctx).Where("id = ? AND is_active = ?", friendFanProfileID, true).First(&fanProfile).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger.Warn("Fan profile not found", zap.Int64("friend_fan_profile_id", friendFanProfileID), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_NOT_FOUND, Message: "粉丝档案不存在", Timestamp: time.Now().UnixMilli(), }, }, nil } logger.Logger.Error("Failed to get fan profile", zap.Int64("friend_fan_profile_id", friendFanProfileID), zap.Error(err), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: "获取粉丝档案失败", Timestamp: time.Now().UnixMilli(), }, }, nil } // 4. 不能查找自己 if friendFanProfileID == myFanProfile.ID { logger.Logger.Warn("Cannot search for self", zap.Int64("my_fan_profile_id", myFanProfile.ID)) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_BAD_REQUEST, Message: "不能查找自己", Timestamp: time.Now().UnixMilli(), }, }, nil } // 5. 检查是否在同一明星下(不能跨明星加好友) if fanProfile.StarID != starID { logger.Logger.Warn("Cannot add friend across different stars", zap.Int64("current_star_id", starID), zap.Int64("target_star_id", fanProfile.StarID), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_BAD_REQUEST, Message: "不能添加其他明星的粉丝为好友", Timestamp: time.Now().UnixMilli(), }, }, nil } // 6. 查询好友关系状态(使用粉丝档案ID) relationshipStatus, canSendRequest, cooldownEndsAt := s.determineRelationshipStatus(ctx, myFanProfile.ID, friendFanProfileID, starID) // 6. 构建响应 avatar := "" if fanProfile.AvatarURL != nil { avatar = *fanProfile.AvatarURL } userResult := &pb.FanProfileSearchResult{ FanProfileId: fanProfile.ID, UserId: fanProfile.UserID, Nickname: fanProfile.Nickname, Avatar: avatar, FanLevel: fanProfile.Level, RelationshipStatus: relationshipStatus, CanSendRequest: canSendRequest, CooldownEndsAt: cooldownEndsAt, } logger.Logger.Info("SearchUserForFriend successful", zap.Int64("user_id", userID), zap.Int64("friend_fan_profile_id", friendFanProfileID), zap.String("relationship_status", relationshipStatus), ) return &pb.SearchUserForFriendResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, User: userResult, }, nil } // determineRelationshipStatus 判断与目标用户的关系状态 // 返回值:relationshipStatus, canSendRequest, cooldownEndsAt func (s *friendService) determineRelationshipStatus(ctx context.Context, userID, friendUserID, starID int64) (string, bool, int64) { // 1. 检查是否已经是好友 isFriend, err := s.socialRepo.CheckFriendship(userID, friendUserID, starID) if err != nil { logger.Logger.Error("Failed to check friendship", zap.Int64("user_id", userID), zap.Int64("friend_user_id", friendUserID), zap.Error(err), ) } if isFriend { return "friend", false, 0 } // 2. 检查是否有待处理的好友请求 // 2.1 检查我发送给对方的请求 sentRequest, err := s.socialRepo.GetLatestRequest(userID, friendUserID, starID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger.Error("Failed to get sent friend request", zap.Int64("from_user_id", userID), zap.Int64("to_user_id", friendUserID), zap.Error(err), ) } if sentRequest != nil && sentRequest.Status == models.FriendRequestStatusPending { // 我已发送请求,等待对方处理 return "pending_sent", false, 0 } // 2.2 检查对方发送给我的请求 receivedRequest, err := s.socialRepo.GetLatestRequest(friendUserID, userID, starID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { logger.Logger.Error("Failed to get received friend request", zap.Int64("from_user_id", friendUserID), zap.Int64("to_user_id", userID), zap.Error(err), ) } if receivedRequest != nil && receivedRequest.Status == models.FriendRequestStatusPending { // 对方已发送请求给我,等待我处理 return "pending_received", false, 0 } // 3. 检查是否在冷静期(被拒绝后的冷静期) if sentRequest != nil && sentRequest.Status == models.FriendRequestStatusRejected && sentRequest.ProcessedAt != nil { cooldownPeriod := time.Duration(s.config.TimeConstraints.RejectionCooldownDays*24) * time.Hour processedTime := time.UnixMilli(*sentRequest.ProcessedAt) cooldownEndsAt := processedTime.Add(cooldownPeriod) if time.Now().Before(cooldownEndsAt) { // 在冷静期内 return "rejected", false, cooldownEndsAt.UnixMilli() } } // 4. 陌生人,可以发送好友请求 return "stranger", true, 0 } // searchUsersByNickname 按昵称搜索用户(搜索模式) func (s *friendService) searchUsersByNickname(ctx context.Context, req *pb.SendFriendRequestRequest, userID, starID int64) (*pb.SendFriendRequestResponse, error) { // 1. 参数验证 nickname := req.Nickname if nickname == "" { return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage(pbCommon.StatusCode_STATUS_BAD_REQUEST, "昵称不能为空"), }, nil } // 昵称长度限制 if len(nickname) < 1 || len(nickname) > 50 { return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponseWithMessage(pbCommon.StatusCode_STATUS_BAD_REQUEST, "昵称长度需在1-50个字符之间"), }, nil } // 2. 搜索匹配的用户 profiles, err := s.socialRepo.GetFanProfilesByNickname(starID, nickname, 20) if err != nil { logger.Logger.Error("Failed to search users by nickname", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("nickname", nickname), zap.Error(err), ) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 3. 获取当前用户的粉丝档案ID myFanProfile, err := s.socialRepo.GetFanProfileByUserIDAndStarID(userID, starID) if err != nil { logger.Logger.Error("Failed to get current user's fan profile", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) return &pb.SendFriendRequestResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 4. 转换为搜索结果并排除自己 matchedUsers := make([]*pb.FanProfileSearchResult, 0, len(profiles)) for _, profile := range profiles { // 排除自己 if profile.UserID == userID { continue } // 判断关系状态 relationshipStatus, canSendRequest, cooldownEndsAt := s.determineRelationshipStatus(ctx, myFanProfile.ID, profile.ID, starID) avatar := "" if profile.AvatarURL != nil { avatar = *profile.AvatarURL } matchedUsers = append(matchedUsers, &pb.FanProfileSearchResult{ FanProfileId: profile.ID, UserId: profile.UserID, Nickname: profile.Nickname, Avatar: avatar, FanLevel: profile.Level, RelationshipStatus: relationshipStatus, CanSendRequest: canSendRequest, CooldownEndsAt: cooldownEndsAt, }) } logger.Logger.Info("SearchUsersByNickname successful", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("nickname", nickname), zap.Int("matched_count", len(matchedUsers)), ) // 5. 构建响应 return &pb.SendFriendRequestResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, MatchedUsers: matchedUsers, }, nil } // ========== 随机用户相关实现 ========== // GetRandomUsers 获取随机用户(同一明星下) func (s *friendService) GetRandomUsers(ctx context.Context, req *pb.GetRandomUsersRequest, userID, starID int64) (*pb.GetRandomUsersResponse, error) { // 1. 参数验证 if starID <= 0 { logger.Logger.Warn("Invalid star_id", zap.Int64("star_id", starID)) return &pb.GetRandomUsersResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID), }, nil } // 2. 确定返回数量 count := int(req.Count) if count <= 0 { count = 1 // 默认返回1个 } if count > 100 { count = 100 // 最大限制100个 } // 3. 调用 repository 获取随机用户 randomUsers, err := s.socialRepo.GetRandomUsersByStar(starID, count) if err != nil { logger.Logger.Error("Failed to get random users", zap.Int64("star_id", starID), zap.Int("count", count), zap.Error(err), ) return &pb.GetRandomUsersResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 4. 转换为 Protobuf 消息 pbUsers := make([]*pb.RandomUser, 0, len(randomUsers)) for _, user := range randomUsers { pbUsers = append(pbUsers, &pb.RandomUser{ UserId: user.UserID, Nickname: user.Nickname, }) } // 5. 构建响应 return &pb.GetRandomUsersResponse{ Base: appErrors.BuildBaseResponse(nil), Users: pbUsers, }, nil } // GetUsersPaged 获取分页用户列表(同一明星身份下),当前用户始终在第一位 func (s *friendService) GetUsersPaged(ctx context.Context, req *pb.GetUsersPagedRequest, userID, starID int64) (*pb.GetUsersPagedResponse, error) { page := req.Page if page <= 0 { page = 1 } pageSize := req.PageSize if pageSize <= 0 { pageSize = 20 } if pageSize > 100 { pageSize = 100 } // 不排除任何用户,获取所有用户(包括当前用户) users, total, err := s.socialRepo.GetUsersByStarPaged(starID, 0, int(page), int(pageSize)) if err != nil { logger.Logger.Error("GetUsersByStarPaged failed", zap.Int64("star_id", starID), zap.Int64("user_id", userID), zap.Error(err), ) return &pb.GetUsersPagedResponse{ Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer), }, nil } // 获取当前用户的档案信息 var currentUserProfile *models.FanProfile currentUserProfile, err = s.socialRepo.GetFanProfileByUserIDAndStarID(userID, starID) if err != nil { logger.Logger.Warn("GetFanProfileByUserIDAndStarID failed, user may not have profile", zap.Int64("star_id", starID), zap.Int64("user_id", userID), zap.Error(err), ) } // 收集所有用户ID allUserIDs := make([]int64, 0, len(users)) for _, user := range users { allUserIDs = append(allUserIDs, user.UserID) } // 批量查询展位占用数量 slotCounts, err := s.socialRepo.GetSlotCountsByUsers(allUserIDs, starID) if err != nil { logger.Logger.Warn("GetSlotCountsByUsers failed", zap.Int64("star_id", starID), zap.Error(err), ) // 不影响主流程,使用空map slotCounts = make(map[int64]int64) } pbUsers := make([]*pb.PagedUser, 0, len(users)) isFirstPage := page == 1 for _, user := range users { // 如果是第一页且是当前用户,跳过(稍后单独插入到第一位) if isFirstPage && user.UserID == userID { continue } occupiedSlots := slotCounts[user.UserID] remainingSlots := int32(user.SlotLimit) - int32(occupiedSlots) if remainingSlots < 0 { remainingSlots = 0 } pbUsers = append(pbUsers, &pb.PagedUser{ UserId: user.UserID, GalleryOwnerId: user.UserID, Nickname: user.Nickname, Level: user.Level, SharedBoothSlotsRemaining: remainingSlots, }) } // 如果是第一页,将当前用户插入到列表第一位 if isFirstPage && currentUserProfile != nil { occupiedSlots := slotCounts[currentUserProfile.UserID] remainingSlots := int32(currentUserProfile.SlotLimit) - int32(occupiedSlots) if remainingSlots < 0 { remainingSlots = 0 } pbUsers = append([]*pb.PagedUser{{ UserId: currentUserProfile.UserID, GalleryOwnerId: currentUserProfile.UserID, Nickname: currentUserProfile.Nickname, Level: currentUserProfile.Level, SharedBoothSlotsRemaining: remainingSlots, }}, pbUsers...) } return &pb.GetUsersPagedResponse{ Base: appErrors.BuildBaseResponse(nil), Users: pbUsers, Total: total, Page: page, PageSize: pageSize, }, nil }