1277 lines
42 KiB
Go
1277 lines
42 KiB
Go
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
|
||
}
|