934 lines
27 KiB
Go
934 lines
27 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
appErrors "github.com/topfans/backend/pkg/errors"
|
||
"github.com/topfans/backend/pkg/logger"
|
||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||
pb "github.com/topfans/backend/pkg/proto/user"
|
||
"github.com/topfans/backend/pkg/validator"
|
||
"github.com/topfans/backend/services/userService/repository"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// UserService 用户信息Service接口
|
||
type UserService interface {
|
||
// GetUser 获取用户信息
|
||
GetUser(req *pb.GetUserRequest) (*pb.GetUserResponse, error)
|
||
|
||
// GetFanProfile 获取粉丝档案
|
||
GetFanProfile(req *pb.GetFanProfileRequest) (*pb.GetFanProfileResponse, error)
|
||
|
||
// GetMyProfile 获取个人信息页
|
||
GetMyProfile(req *pb.GetMyProfileRequest, userID, starID int64) (*pb.GetMyProfileResponse, error)
|
||
|
||
// CheckNickname 检查昵称是否已被注册
|
||
CheckNickname(req *pb.CheckNicknameRequest) (*pb.CheckNicknameResponse, error)
|
||
|
||
// CheckMobile 检查手机号是否已被注册
|
||
CheckMobile(req *pb.CheckMobileRequest) (*pb.CheckMobileResponse, error)
|
||
|
||
// UpdateNickname 修改昵称
|
||
UpdateNickname(req *pb.UpdateNicknameRequest, userID, starID int64) (*pb.UpdateNicknameResponse, error)
|
||
|
||
// UpdatePassword 修改密码
|
||
UpdatePassword(req *pb.UpdatePasswordRequest, userID int64) (*pb.UpdatePasswordResponse, error)
|
||
|
||
// UpdateFanProfileSocial 更新粉丝档案的好友数量(内部RPC调用)
|
||
UpdateFanProfileSocial(req *pb.UpdateFanProfileSocialRequest) (*pb.UpdateFanProfileSocialResponse, error)
|
||
|
||
// UpdateCrystalBalance 更新水晶余额(内部RPC调用)
|
||
UpdateCrystalBalance(req *pb.UpdateCrystalBalanceRequest) (*pb.UpdateCrystalBalanceResponse, error)
|
||
|
||
// UpdateAssetsCount 更新资产数量(内部RPC调用)
|
||
UpdateAssetsCount(req *pb.UpdateAssetsCountRequest) (*pb.UpdateAssetsCountResponse, error)
|
||
|
||
// AddExhibitionHours 增加用户累计上架时长(内部RPC调用,用于galleryService展品下架时)
|
||
AddExhibitionHours(req *pb.AddExhibitionHoursRequest) (*pb.AddExhibitionHoursResponse, error)
|
||
|
||
// UpdateAvatar 更新用户头像
|
||
UpdateAvatar(req *pb.UpdateAvatarRequest, userID, starID int64) (*pb.UpdateAvatarResponse, error)
|
||
}
|
||
|
||
// userService 用户信息Service实现
|
||
type userService struct {
|
||
userRepo repository.UserRepository
|
||
fanProfileRepo repository.FanProfileRepository
|
||
db *gorm.DB
|
||
}
|
||
|
||
// NewUserService 创建用户信息Service实例
|
||
func NewUserService(
|
||
userRepo repository.UserRepository,
|
||
fanProfileRepo repository.FanProfileRepository,
|
||
db *gorm.DB,
|
||
) UserService {
|
||
return &userService{
|
||
userRepo: userRepo,
|
||
fanProfileRepo: fanProfileRepo,
|
||
db: db,
|
||
}
|
||
}
|
||
|
||
// GetUser 获取用户信息
|
||
func (s *userService) GetUser(req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return nil, fmt.Errorf("invalid user_id: %d", req.UserId)
|
||
}
|
||
|
||
// 2. 查询用户
|
||
user, err := s.userRepo.GetByID(req.UserId)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Warn("User not found",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.GetUserResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrUserNotFound),
|
||
}, nil // 返回响应对象,不返回 error
|
||
}
|
||
logger.Logger.Error("Failed to get user",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Error(err),
|
||
)
|
||
return &pb.GetUserResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil // 返回响应对象,不返回 error
|
||
}
|
||
|
||
// 3. 构建响应
|
||
response := &pb.GetUserResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
User: &pb.User{
|
||
Id: user.ID,
|
||
Mobile: user.Mobile,
|
||
AvatarUrl: getStringValue(user.AvatarURL),
|
||
GlobalWalletAddress: getStringValue(user.GlobalWalletAddr),
|
||
IsActive: user.IsActive,
|
||
CreatedAt: user.CreatedAt,
|
||
},
|
||
}
|
||
|
||
logger.Logger.Debug("Get user successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// GetFanProfile 获取粉丝档案
|
||
func (s *userService) GetFanProfile(req *pb.GetFanProfileRequest) (*pb.GetFanProfileResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.GetFanProfileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID),
|
||
}, nil
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.GetFanProfileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 查询粉丝档案
|
||
fanProfile, err := s.fanProfileRepo.GetByUserAndStar(req.UserId, req.StarId)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
logger.Logger.Warn("Fan profile not found",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.GetFanProfileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrFanProfileNotFound),
|
||
}, nil // 返回响应对象,不返回 error
|
||
}
|
||
logger.Logger.Error("Failed to get fan profile",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Error(err),
|
||
)
|
||
return &pb.GetFanProfileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil // 返回响应对象,不返回 error
|
||
}
|
||
|
||
// 4. 构建响应
|
||
response := &pb.GetFanProfileResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Profile: ModelToProtoFanProfile(fanProfile),
|
||
}
|
||
|
||
logger.Logger.Debug("Get fan profile successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// GetMyProfile 获取个人信息页(聚合用户信息和粉丝档案)
|
||
func (s *userService) GetMyProfile(req *pb.GetMyProfileRequest, userID, starID int64) (*pb.GetMyProfileResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(userID) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, fmt.Errorf("invalid user_id: %d", userID)
|
||
}
|
||
|
||
if !validator.ValidateStarID(starID) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrInvalidStarID
|
||
}
|
||
|
||
// 2. 查询用户信息
|
||
user, err := s.userRepo.GetByID(userID)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Warn("User not found",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, appErrors.ErrUserNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get user",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||
}
|
||
|
||
// 3. 查询当前粉丝档案
|
||
currentFanProfile, err := s.fanProfileRepo.GetByUserAndStar(userID, starID)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
logger.Logger.Warn("Fan profile not found",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrFanProfileNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get fan profile",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get fan profile: %w", err)
|
||
}
|
||
|
||
// 5. 查询用户的所有粉丝身份
|
||
fanProfiles, err := s.fanProfileRepo.GetByUserID(userID)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get fan profiles",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get fan profiles: %w", err)
|
||
}
|
||
|
||
// 5. 构建响应
|
||
pbFanProfiles := make([]*pb.FanProfile, 0, len(fanProfiles))
|
||
for _, profile := range fanProfiles {
|
||
pbFanProfiles = append(pbFanProfiles, &pb.FanProfile{
|
||
Id: profile.ID,
|
||
UserId: profile.UserID,
|
||
StarId: profile.StarID,
|
||
Nickname: profile.Nickname,
|
||
Level: int32(profile.Level),
|
||
Times: int32(profile.Times),
|
||
Social: int32(profile.Social),
|
||
CoinBalance: profile.CoinBalance,
|
||
CrystalBalance: profile.CrystalBalance,
|
||
Tags: []string(profile.Tags),
|
||
CreatedAt: profile.CreatedAt,
|
||
})
|
||
}
|
||
|
||
response := &pb.GetMyProfileResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
User: &pb.User{
|
||
Id: user.ID,
|
||
Mobile: user.Mobile,
|
||
AvatarUrl: getStringValue(user.AvatarURL),
|
||
GlobalWalletAddress: getStringValue(user.GlobalWalletAddr),
|
||
IsActive: user.IsActive,
|
||
CreatedAt: user.CreatedAt,
|
||
},
|
||
FanProfile: ModelToProtoFanProfile(currentFanProfile),
|
||
FanProfiles: pbFanProfiles,
|
||
}
|
||
|
||
logger.Logger.Debug("Get my profile successful",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// CheckNickname 检查昵称是否已被注册
|
||
func (s *userService) CheckNickname(req *pb.CheckNicknameRequest) (*pb.CheckNicknameResponse, error) {
|
||
// 1. 参数验证
|
||
if req.Nickname == "" {
|
||
logger.Logger.Warn("Nickname is empty")
|
||
return &pb.CheckNicknameResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidNickname),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 验证昵称格式
|
||
valid, msg := validator.ValidateNickname(req.Nickname)
|
||
if !valid {
|
||
logger.Logger.Warn("Invalid nickname format",
|
||
zap.String("nickname", req.Nickname),
|
||
zap.String("error", msg),
|
||
)
|
||
return &pb.CheckNicknameResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidNickname),
|
||
}, nil
|
||
}
|
||
|
||
// 3. 查询昵称是否已存在
|
||
exists, err := s.fanProfileRepo.ExistsByNickname(req.Nickname)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to check nickname",
|
||
zap.String("nickname", req.Nickname),
|
||
zap.Error(err),
|
||
)
|
||
return &pb.CheckNicknameResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
|
||
logger.Logger.Info("Check nickname result",
|
||
zap.String("nickname", req.Nickname),
|
||
zap.Bool("exists", exists),
|
||
)
|
||
|
||
// 4. 构建响应
|
||
return &pb.CheckNicknameResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Exists: exists,
|
||
}, nil
|
||
}
|
||
|
||
// CheckMobile 检查手机号是否已被注册
|
||
func (s *userService) CheckMobile(req *pb.CheckMobileRequest) (*pb.CheckMobileResponse, error) {
|
||
// 1. 参数验证
|
||
if req.Mobile == "" {
|
||
logger.Logger.Warn("Mobile is empty")
|
||
return &pb.CheckMobileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidMobile),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 验证手机号格式
|
||
if !validator.ValidateMobile(req.Mobile) {
|
||
logger.Logger.Warn("Invalid mobile format",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return &pb.CheckMobileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidMobile),
|
||
}, nil
|
||
}
|
||
|
||
// 3. 查询手机号是否已存在
|
||
exists, err := s.userRepo.ExistsByMobile(req.Mobile)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to check mobile",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Error(err),
|
||
)
|
||
return &pb.CheckMobileResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
|
||
logger.Logger.Info("Check mobile result",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Bool("exists", exists),
|
||
)
|
||
|
||
// 4. 构建响应
|
||
return &pb.CheckMobileResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Exists: exists,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateNickname 修改昵称
|
||
func (s *userService) UpdateNickname(req *pb.UpdateNicknameRequest, userID, starID int64) (*pb.UpdateNicknameResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(userID) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, fmt.Errorf("invalid user_id: %d", userID)
|
||
}
|
||
|
||
if !validator.ValidateStarID(starID) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrInvalidStarID
|
||
}
|
||
|
||
valid, msg := validator.ValidateNickname(req.Nickname)
|
||
if !valid {
|
||
logger.Logger.Warn("Invalid nickname",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.String("error", msg),
|
||
)
|
||
return nil, fmt.Errorf("invalid nickname: %s", msg)
|
||
}
|
||
|
||
// 2. 验证粉丝档案是否存在
|
||
_, err := s.fanProfileRepo.GetByUserAndStar(userID, starID)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
logger.Logger.Warn("Fan profile not found",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrFanProfileNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get fan profile",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get fan profile: %w", err)
|
||
}
|
||
|
||
// 3. 更新昵称
|
||
if err := s.fanProfileRepo.UpdateNickname(userID, starID, req.Nickname); err != nil {
|
||
logger.Logger.Error("Failed to update nickname",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to update nickname: %w", err)
|
||
}
|
||
|
||
// 4. 查询更新后的粉丝档案
|
||
fanProfile, err := s.fanProfileRepo.GetByUserAndStar(userID, starID)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get updated fan profile",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get updated fan profile: %w", err)
|
||
}
|
||
|
||
// 5. 构建响应
|
||
response := &pb.UpdateNicknameResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
FanProfile: ModelToProtoFanProfile(fanProfile),
|
||
}
|
||
|
||
logger.Logger.Info("Update nickname successful",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.String("new_nickname", req.Nickname),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// UpdatePassword 修改密码
|
||
func (s *userService) UpdatePassword(req *pb.UpdatePasswordRequest, userID int64) (*pb.UpdatePasswordResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(userID) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, fmt.Errorf("invalid user_id: %d", userID)
|
||
}
|
||
|
||
if req.OldPassword == "" {
|
||
logger.Logger.Warn("Old password is empty",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, appErrors.ErrInvalidPassword
|
||
}
|
||
|
||
valid, msg := validator.ValidatePassword(req.NewPassword)
|
||
if !valid {
|
||
logger.Logger.Warn("Invalid new password",
|
||
zap.Int64("user_id", userID),
|
||
zap.String("error", msg),
|
||
)
|
||
if msg == "password too short" {
|
||
return nil, appErrors.ErrPasswordTooShort
|
||
}
|
||
return nil, fmt.Errorf("invalid password: %s", msg)
|
||
}
|
||
|
||
// 2. 查询用户
|
||
user, err := s.userRepo.GetByID(userID)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Warn("User not found",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, appErrors.ErrUserNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get user",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||
}
|
||
|
||
// 3. 验证旧密码
|
||
if !s.userRepo.VerifyPassword(user, req.OldPassword) {
|
||
logger.Logger.Warn("Invalid old password",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, appErrors.ErrInvalidPassword
|
||
}
|
||
|
||
// 4. 加密新密码
|
||
newPasswordHash, err := repository.HashPassword(req.NewPassword)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to hash new password",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to hash password: %w", err)
|
||
}
|
||
|
||
// 5. 更新密码和updated_at,清除Token(使用事务)
|
||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||
// 更新密码
|
||
user.PasswordHash = newPasswordHash
|
||
user.UpdatedAt = time.Now().UnixMilli()
|
||
// 清除Token(设为nil)
|
||
user.AccessToken = nil
|
||
user.TokenExpiresAt = nil
|
||
|
||
if err := tx.Model(user).Updates(map[string]interface{}{
|
||
"password_hash": user.PasswordHash,
|
||
"updated_at": user.UpdatedAt,
|
||
"access_token": nil,
|
||
"token_expires_at": nil,
|
||
}).Error; err != nil {
|
||
logger.Logger.Error("Failed to update password in transaction",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return fmt.Errorf("failed to update password: %w", err)
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
logger.Logger.Info("Update password successful",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
|
||
return &pb.UpdatePasswordResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// UpdateFanProfileSocial 更新粉丝档案的好友数量(内部RPC调用)
|
||
func (s *userService) UpdateFanProfileSocial(req *pb.UpdateFanProfileSocialRequest) (*pb.UpdateFanProfileSocialResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.UpdateFanProfileSocialResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID),
|
||
}, nil
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.UpdateFanProfileSocialResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 更新 social 字段
|
||
newSocial, err := s.fanProfileRepo.UpdateSocial(req.UserId, req.StarId, req.Delta)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to update fan profile social",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int32("delta", req.Delta),
|
||
zap.Error(err),
|
||
)
|
||
|
||
// 判断错误类型
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
return &pb.UpdateFanProfileSocialResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrFanProfileNotFound),
|
||
}, nil
|
||
}
|
||
|
||
return &pb.UpdateFanProfileSocialResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
|
||
logger.Logger.Info("Update fan profile social successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int32("delta", req.Delta),
|
||
zap.Int32("new_social", newSocial),
|
||
)
|
||
|
||
return &pb.UpdateFanProfileSocialResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
NewSocial: newSocial,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateCrystalBalance 更新水晶余额(内部RPC调用)
|
||
func (s *userService) UpdateCrystalBalance(req *pb.UpdateCrystalBalanceRequest) (*pb.UpdateCrystalBalanceResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.UpdateCrystalBalanceResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID),
|
||
}, nil
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.UpdateCrystalBalanceResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 更新水晶余额
|
||
newBalance, err := s.fanProfileRepo.UpdateCrystalBalance(req.UserId, req.StarId, req.Delta, req.ChangeType, req.SourceId, req.Description)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to update crystal balance",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int64("delta", req.Delta),
|
||
zap.Error(err),
|
||
)
|
||
|
||
// 判断错误类型
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
return &pb.UpdateCrystalBalanceResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrFanProfileNotFound),
|
||
}, nil
|
||
}
|
||
|
||
return &pb.UpdateCrystalBalanceResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
|
||
logger.Logger.Info("Update crystal balance successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int64("delta", req.Delta),
|
||
zap.Int64("new_balance", newBalance),
|
||
)
|
||
|
||
return &pb.UpdateCrystalBalanceResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
NewBalance: newBalance,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateAssetsCount 更新资产数量(内部RPC调用)
|
||
func (s *userService) UpdateAssetsCount(req *pb.UpdateAssetsCountRequest) (*pb.UpdateAssetsCountResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID),
|
||
}, nil
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID),
|
||
}, nil
|
||
}
|
||
|
||
// 2. 根据 delta 的正负调用不同的方法
|
||
var err error
|
||
var newCount int32
|
||
|
||
if req.Delta > 0 {
|
||
// 增加资产数量
|
||
err = s.fanProfileRepo.IncrementAssetsCount(req.UserId, req.StarId, req.Delta)
|
||
if err == nil {
|
||
// 查询更新后的值
|
||
profile, getErr := s.fanProfileRepo.GetByUserAndStar(req.UserId, req.StarId)
|
||
if getErr == nil {
|
||
newCount = profile.AssetsCount
|
||
}
|
||
}
|
||
} else if req.Delta < 0 {
|
||
// 减少资产数量
|
||
err = s.fanProfileRepo.DecrementAssetsCount(req.UserId, req.StarId, -req.Delta)
|
||
if err == nil {
|
||
// 查询更新后的值
|
||
profile, getErr := s.fanProfileRepo.GetByUserAndStar(req.UserId, req.StarId)
|
||
if getErr == nil {
|
||
newCount = profile.AssetsCount
|
||
}
|
||
}
|
||
} else {
|
||
// delta == 0,直接查询当前值
|
||
profile, getErr := s.fanProfileRepo.GetByUserAndStar(req.UserId, req.StarId)
|
||
if getErr != nil {
|
||
if errors.Is(getErr, appErrors.ErrFanProfileNotFound) {
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrFanProfileNotFound),
|
||
}, nil
|
||
}
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
newCount = profile.AssetsCount
|
||
}
|
||
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to update assets count",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int32("delta", req.Delta),
|
||
zap.Error(err),
|
||
)
|
||
|
||
// 判断错误类型
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrFanProfileNotFound),
|
||
}, nil
|
||
}
|
||
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, nil
|
||
}
|
||
|
||
logger.Logger.Info("Update assets count successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int32("delta", req.Delta),
|
||
zap.Int32("new_count", newCount),
|
||
)
|
||
|
||
return &pb.UpdateAssetsCountResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
NewCount: newCount,
|
||
}, nil
|
||
}
|
||
|
||
// AddExhibitionHours 增加用户累计上架时长并触发升级检查(内部RPC调用)
|
||
func (s *userService) AddExhibitionHours(req *pb.AddExhibitionHoursRequest) (*pb.AddExhibitionHoursResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(req.UserId) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", req.UserId),
|
||
)
|
||
return &pb.AddExhibitionHoursResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidUserID),
|
||
}, nil
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return &pb.AddExhibitionHoursResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInvalidStarID),
|
||
}, nil
|
||
}
|
||
|
||
if req.ExhibitionHours <= 0 {
|
||
logger.Logger.Warn("Invalid exhibition_hours",
|
||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||
)
|
||
return &pb.AddExhibitionHoursResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_BAD_REQUEST,
|
||
Message: "exhibition_hours must be positive",
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// 2. 增加累计上架时长并同步等级
|
||
newLevel, levelDelta, crystalReward, err := s.fanProfileRepo.AddExhibitionHours(req.UserId, req.StarId, req.ExhibitionHours, req.SourceId)
|
||
if err != nil {
|
||
logger.Logger.Error("AddExhibitionHours failed",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||
zap.Error(err))
|
||
return &pb.AddExhibitionHoursResponse{
|
||
Base: appErrors.BuildBaseResponse(appErrors.ErrInternalServer),
|
||
}, err
|
||
}
|
||
|
||
logger.Logger.Info("AddExhibitionHours successful",
|
||
zap.Int64("user_id", req.UserId),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||
zap.Int32("new_level", newLevel),
|
||
zap.Int32("level_delta", levelDelta),
|
||
zap.Int64("crystal_reward", crystalReward))
|
||
|
||
return &pb.AddExhibitionHoursResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
NewLevel: newLevel,
|
||
LevelDelta: levelDelta,
|
||
CrystalReward: crystalReward,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateAvatar 更新用户头像
|
||
func (s *userService) UpdateAvatar(req *pb.UpdateAvatarRequest, userID, starID int64) (*pb.UpdateAvatarResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateUserID(userID) {
|
||
logger.Logger.Warn("Invalid user_id",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, fmt.Errorf("invalid user_id: %d", userID)
|
||
}
|
||
|
||
if !validator.ValidateStarID(starID) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrInvalidStarID
|
||
}
|
||
|
||
if req.AvatarUrl == "" {
|
||
logger.Logger.Warn("Avatar URL is empty",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, fmt.Errorf("avatar_url cannot be empty")
|
||
}
|
||
|
||
// 2. 验证URL格式(简单验证)
|
||
if len(req.AvatarUrl) > 500 {
|
||
logger.Logger.Warn("Avatar URL too long",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int("length", len(req.AvatarUrl)),
|
||
)
|
||
return nil, fmt.Errorf("avatar_url too long, max length is 500")
|
||
}
|
||
|
||
// 3. 更新头像
|
||
if err := s.fanProfileRepo.UpdateAvatar(userID, starID, req.AvatarUrl); err != nil {
|
||
if errors.Is(err, appErrors.ErrFanProfileNotFound) {
|
||
logger.Logger.Warn("Fan profile not found",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return nil, appErrors.ErrFanProfileNotFound
|
||
}
|
||
logger.Logger.Error("Failed to update avatar",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to update avatar: %w", err)
|
||
}
|
||
|
||
logger.Logger.Info("Update avatar successful",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.String("avatar_url", req.AvatarUrl),
|
||
)
|
||
|
||
// 4. 构建响应
|
||
return &pb.UpdateAvatarResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
AvatarUrl: req.AvatarUrl,
|
||
}, nil
|
||
}
|