package service import ( "context" "fmt" "strings" "carrot_bbs/internal/cache" "carrot_bbs/internal/model" "carrot_bbs/internal/pkg/utils" "carrot_bbs/internal/repository" ) // UserService 用户服务 type UserService struct { userRepo *repository.UserRepository systemMessageService SystemMessageService emailCodeService EmailCodeService } // NewUserService 创建用户服务 func NewUserService( userRepo *repository.UserRepository, systemMessageService SystemMessageService, emailService EmailService, cacheBackend cache.Cache, ) *UserService { return &UserService{ userRepo: userRepo, systemMessageService: systemMessageService, emailCodeService: NewEmailCodeService(emailService, cacheBackend), } } // SendRegisterCode 发送注册验证码 func (s *UserService) SendRegisterCode(ctx context.Context, email string) error { user, err := s.userRepo.GetByEmail(email) if err == nil && user != nil { return ErrEmailExists } return s.emailCodeService.SendCode(ctx, CodePurposeRegister, email) } // SendPasswordResetCode 发送找回密码验证码 func (s *UserService) SendPasswordResetCode(ctx context.Context, email string) error { user, err := s.userRepo.GetByEmail(email) if err != nil || user == nil { return ErrUserNotFound } return s.emailCodeService.SendCode(ctx, CodePurposePasswordReset, email) } // SendCurrentUserEmailVerifyCode 发送当前用户邮箱验证验证码 func (s *UserService) SendCurrentUserEmailVerifyCode(ctx context.Context, userID, email string) error { user, err := s.userRepo.GetByID(userID) if err != nil || user == nil { return ErrUserNotFound } targetEmail := strings.TrimSpace(email) if targetEmail == "" && user.Email != nil { targetEmail = strings.TrimSpace(*user.Email) } if targetEmail == "" || !utils.ValidateEmail(targetEmail) { return ErrInvalidEmail } if user.EmailVerified && user.Email != nil && strings.EqualFold(strings.TrimSpace(*user.Email), targetEmail) { return ErrEmailAlreadyVerified } existingUser, queryErr := s.userRepo.GetByEmail(targetEmail) if queryErr == nil && existingUser != nil && existingUser.ID != userID { return ErrEmailExists } return s.emailCodeService.SendCode(ctx, CodePurposeEmailVerify, targetEmail) } // VerifyCurrentUserEmail 验证当前用户邮箱 func (s *UserService) VerifyCurrentUserEmail(ctx context.Context, userID, email, verificationCode string) error { user, err := s.userRepo.GetByID(userID) if err != nil || user == nil { return ErrUserNotFound } targetEmail := strings.TrimSpace(email) if targetEmail == "" && user.Email != nil { targetEmail = strings.TrimSpace(*user.Email) } if targetEmail == "" || !utils.ValidateEmail(targetEmail) { return ErrInvalidEmail } if err := s.emailCodeService.VerifyCode(CodePurposeEmailVerify, targetEmail, verificationCode); err != nil { return err } existingUser, queryErr := s.userRepo.GetByEmail(targetEmail) if queryErr == nil && existingUser != nil && existingUser.ID != userID { return ErrEmailExists } user.Email = &targetEmail user.EmailVerified = true return s.userRepo.Update(user) } // SendChangePasswordCode 发送修改密码验证码 func (s *UserService) SendChangePasswordCode(ctx context.Context, userID string) error { user, err := s.userRepo.GetByID(userID) if err != nil || user == nil { return ErrUserNotFound } if user.Email == nil || strings.TrimSpace(*user.Email) == "" { return ErrEmailNotBound } return s.emailCodeService.SendCode(ctx, CodePurposeChangePassword, *user.Email) } // Register 用户注册 func (s *UserService) Register(ctx context.Context, username, email, password, nickname, phone, verificationCode string) (*model.User, error) { // 验证用户名 if !utils.ValidateUsername(username) { return nil, ErrInvalidUsername } // 注册必须提供邮箱并完成验证码校验 if email == "" || !utils.ValidateEmail(email) { return nil, ErrInvalidEmail } if err := s.emailCodeService.VerifyCode(CodePurposeRegister, email, verificationCode); err != nil { return nil, err } // 验证密码 if !utils.ValidatePassword(password) { return nil, ErrWeakPassword } // 验证手机号(如果提供) if phone != "" && !utils.ValidatePhone(phone) { return nil, ErrInvalidPhone } // 检查用户名是否已存在 existingUser, err := s.userRepo.GetByUsername(username) if err == nil && existingUser != nil { return nil, ErrUsernameExists } // 检查邮箱是否已存在(如果提供) if email != "" { existingUser, err = s.userRepo.GetByEmail(email) if err == nil && existingUser != nil { return nil, ErrEmailExists } } // 检查手机号是否已存在(如果提供) if phone != "" { existingUser, err = s.userRepo.GetByPhone(phone) if err == nil && existingUser != nil { return nil, ErrPhoneExists } } // 密码哈希 hashedPassword, err := utils.HashPassword(password) if err != nil { return nil, err } // 创建用户 user := &model.User{ Username: username, Nickname: nickname, EmailVerified: true, PasswordHash: hashedPassword, Status: model.UserStatusActive, } // 如果提供了邮箱,设置指针值 if email != "" { user.Email = &email } // 如果提供了手机号,设置指针值 if phone != "" { user.Phone = &phone } err = s.userRepo.Create(user) if err != nil { return nil, err } return user, nil } // Login 用户登录 func (s *UserService) Login(ctx context.Context, account, password string) (*model.User, error) { account = strings.TrimSpace(account) var ( user *model.User err error ) if utils.ValidateEmail(account) { user, err = s.userRepo.GetByEmail(account) } else if utils.ValidatePhone(account) { user, err = s.userRepo.GetByPhone(account) } else { user, err = s.userRepo.GetByUsername(account) } if err != nil || user == nil { return nil, ErrInvalidCredentials } if !utils.CheckPasswordHash(password, user.PasswordHash) { return nil, ErrInvalidCredentials } if user.Status != model.UserStatusActive { return nil, ErrUserBanned } return user, nil } // GetUserByID 根据ID获取用户 func (s *UserService) GetUserByID(ctx context.Context, id string) (*model.User, error) { return s.userRepo.GetByID(id) } // GetUserPostCount 获取用户帖子数(实时计算) func (s *UserService) GetUserPostCount(ctx context.Context, userID string) (int64, error) { return s.userRepo.GetPostsCount(userID) } // GetUserPostCountBatch 批量获取用户帖子数(实时计算) func (s *UserService) GetUserPostCountBatch(ctx context.Context, userIDs []string) (map[string]int64, error) { return s.userRepo.GetPostsCountBatch(userIDs) } // GetUserByIDWithFollowingStatus 根据ID获取用户(包含当前用户是否关注的状态) func (s *UserService) GetUserByIDWithFollowingStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, error) { user, err := s.userRepo.GetByID(userID) if err != nil { return nil, false, err } // 如果查询的是当前用户自己,不需要检查关注状态 if userID == currentUserID { return user, false, nil } isFollowing, err := s.userRepo.IsFollowing(currentUserID, userID) if err != nil { return user, false, err } return user, isFollowing, nil } // GetUserByIDWithMutualFollowStatus 根据ID获取用户(包含双向关注状态) func (s *UserService) GetUserByIDWithMutualFollowStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, bool, error) { user, err := s.userRepo.GetByID(userID) if err != nil { return nil, false, false, err } // 如果查询的是当前用户自己,不需要检查关注状态 if userID == currentUserID { return user, false, false, nil } // 当前用户是否关注了该用户 isFollowing, err := s.userRepo.IsFollowing(currentUserID, userID) if err != nil { return user, false, false, err } // 该用户是否关注了当前用户 isFollowingMe, err := s.userRepo.IsFollowing(userID, currentUserID) if err != nil { return user, isFollowing, false, err } return user, isFollowing, isFollowingMe, nil } // UpdateUser 更新用户 func (s *UserService) UpdateUser(ctx context.Context, user *model.User) error { return s.userRepo.Update(user) } // GetFollowers 获取粉丝 func (s *UserService) GetFollowers(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) { return s.userRepo.GetFollowers(userID, page, pageSize) } // GetFollowing 获取关注 func (s *UserService) GetFollowing(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) { return s.userRepo.GetFollowing(userID, page, pageSize) } // FollowUser 关注用户 func (s *UserService) FollowUser(ctx context.Context, followerID, followeeID string) error { blocked, err := s.userRepo.IsBlockedEitherDirection(followerID, followeeID) if err != nil { return err } if blocked { return ErrUserBlocked } // 检查是否已经关注 isFollowing, err := s.userRepo.IsFollowing(followerID, followeeID) if err != nil { return err } if isFollowing { return nil // 已经关注,直接返回成功 } // 创建关注关系 follow := &model.Follow{ FollowerID: followerID, FollowingID: followeeID, } err = s.userRepo.CreateFollow(follow) if err != nil { return err } // 刷新关注者的关注数(通过实际计数,更可靠) err = s.userRepo.RefreshFollowingCount(followerID) if err != nil { // 不回滚,计数可以通过其他方式修复 } // 刷新被关注者的粉丝数(通过实际计数,更可靠) err = s.userRepo.RefreshFollowersCount(followeeID) if err != nil { // 不回滚,计数可以通过其他方式修复 } // 发送关注通知给被关注者 if s.systemMessageService != nil { // 异步发送通知,不阻塞主流程 go func() { _ = s.systemMessageService.SendFollowNotification(context.Background(), followeeID, followerID) }() } return nil } // UnfollowUser 取消关注用户 func (s *UserService) UnfollowUser(ctx context.Context, followerID, followeeID string) error { // 检查是否已经关注 isFollowing, err := s.userRepo.IsFollowing(followerID, followeeID) if err != nil { return err } if !isFollowing { return nil // 没有关注,直接返回成功 } // 删除关注关系 err = s.userRepo.DeleteFollow(followerID, followeeID) if err != nil { return err } // 刷新关注者的关注数(通过实际计数,更可靠) err = s.userRepo.RefreshFollowingCount(followerID) if err != nil { } // 刷新被关注者的粉丝数(通过实际计数,更可靠) err = s.userRepo.RefreshFollowersCount(followeeID) if err != nil { } return nil } // BlockUser 拉黑用户,并自动清理双向关注/粉丝关系 func (s *UserService) BlockUser(ctx context.Context, blockerID, blockedID string) error { if blockerID == blockedID { return ErrInvalidOperation } return s.userRepo.BlockUserAndCleanupRelations(blockerID, blockedID) } // UnblockUser 取消拉黑 func (s *UserService) UnblockUser(ctx context.Context, blockerID, blockedID string) error { if blockerID == blockedID { return ErrInvalidOperation } return s.userRepo.UnblockUser(blockerID, blockedID) } // GetBlockedUsers 获取黑名单列表 func (s *UserService) GetBlockedUsers(ctx context.Context, blockerID string, page, pageSize int) ([]*model.User, int64, error) { return s.userRepo.GetBlockedUsers(blockerID, page, pageSize) } // IsBlocked 检查当前用户是否已拉黑目标用户 func (s *UserService) IsBlocked(ctx context.Context, blockerID, blockedID string) (bool, error) { return s.userRepo.IsBlocked(blockerID, blockedID) } // GetFollowingList 获取关注列表(字符串参数版本) func (s *UserService) GetFollowingList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) { // 转换字符串参数为整数 pageInt := 1 pageSizeInt := 20 if page != "" { _, err := fmt.Sscanf(page, "%d", &pageInt) if err != nil { pageInt = 1 } } if pageSize != "" { _, err := fmt.Sscanf(pageSize, "%d", &pageSizeInt) if err != nil { pageSizeInt = 20 } } users, _, err := s.userRepo.GetFollowing(userID, pageInt, pageSizeInt) return users, err } // GetFollowersList 获取粉丝列表(字符串参数版本) func (s *UserService) GetFollowersList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) { // 转换字符串参数为整数 pageInt := 1 pageSizeInt := 20 if page != "" { _, err := fmt.Sscanf(page, "%d", &pageInt) if err != nil { pageInt = 1 } } if pageSize != "" { _, err := fmt.Sscanf(pageSize, "%d", &pageSizeInt) if err != nil { pageSizeInt = 20 } } users, _, err := s.userRepo.GetFollowers(userID, pageInt, pageSizeInt) return users, err } // GetMutualFollowStatus 批量获取双向关注状态 func (s *UserService) GetMutualFollowStatus(ctx context.Context, currentUserID string, targetUserIDs []string) (map[string][2]bool, error) { return s.userRepo.GetMutualFollowStatus(currentUserID, targetUserIDs) } // CheckUsernameAvailable 检查用户名是否可用 func (s *UserService) CheckUsernameAvailable(ctx context.Context, username string) (bool, error) { user, err := s.userRepo.GetByUsername(username) if err != nil { return true, nil // 用户不存在,可用 } return user == nil, nil } // ChangePassword 修改密码 func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword, verificationCode string) error { // 获取用户 user, err := s.userRepo.GetByID(userID) if err != nil { return ErrUserNotFound } if user.Email == nil || strings.TrimSpace(*user.Email) == "" { return ErrEmailNotBound } if err := s.emailCodeService.VerifyCode(CodePurposeChangePassword, *user.Email, verificationCode); err != nil { return err } // 验证旧密码 if !utils.CheckPasswordHash(oldPassword, user.PasswordHash) { return ErrInvalidCredentials } // 哈希新密码 hashedPassword, err := utils.HashPassword(newPassword) if err != nil { return err } // 更新密码 user.PasswordHash = hashedPassword return s.userRepo.Update(user) } // ResetPasswordByEmail 通过邮箱重置密码 func (s *UserService) ResetPasswordByEmail(ctx context.Context, email, verificationCode, newPassword string) error { email = strings.TrimSpace(email) if !utils.ValidateEmail(email) { return ErrInvalidEmail } if !utils.ValidatePassword(newPassword) { return ErrWeakPassword } if err := s.emailCodeService.VerifyCode(CodePurposePasswordReset, email, verificationCode); err != nil { return err } user, err := s.userRepo.GetByEmail(email) if err != nil || user == nil { return ErrUserNotFound } hashedPassword, err := utils.HashPassword(newPassword) if err != nil { return err } user.PasswordHash = hashedPassword return s.userRepo.Update(user) } // Search 搜索用户 func (s *UserService) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.User, int64, error) { return s.userRepo.Search(keyword, page, pageSize) } // 错误定义 var ( ErrInvalidUsername = &ServiceError{Code: 400, Message: "invalid username"} ErrInvalidEmail = &ServiceError{Code: 400, Message: "invalid email"} ErrInvalidPhone = &ServiceError{Code: 400, Message: "invalid phone number"} ErrWeakPassword = &ServiceError{Code: 400, Message: "password too weak"} ErrUsernameExists = &ServiceError{Code: 400, Message: "username already exists"} ErrEmailExists = &ServiceError{Code: 400, Message: "email already exists"} ErrPhoneExists = &ServiceError{Code: 400, Message: "phone number already exists"} ErrUserNotFound = &ServiceError{Code: 404, Message: "user not found"} ErrUserBanned = &ServiceError{Code: 403, Message: "user is banned"} ErrUserBlocked = &ServiceError{Code: 403, Message: "blocked relationship exists"} ErrInvalidOperation = &ServiceError{Code: 400, Message: "invalid operation"} ErrEmailServiceUnavailable = &ServiceError{Code: 503, Message: "email service unavailable"} ErrVerificationCodeTooFrequent = &ServiceError{Code: 429, Message: "verification code sent too frequently"} ErrVerificationCodeInvalid = &ServiceError{Code: 400, Message: "invalid verification code"} ErrVerificationCodeExpired = &ServiceError{Code: 400, Message: "verification code expired"} ErrVerificationCodeUnavailable = &ServiceError{Code: 500, Message: "verification code storage unavailable"} ErrEmailAlreadyVerified = &ServiceError{Code: 400, Message: "email already verified"} ErrEmailNotBound = &ServiceError{Code: 400, Message: "email not bound"} ) // ServiceError 服务错误 type ServiceError struct { Code int Message string } func (e *ServiceError) Error() string { return e.Message } var ErrInvalidCredentials = &ServiceError{Code: 401, Message: "invalid username or password"}