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 }