package user import ( "context" "strings" "hyapp/pkg/appcode" "hyapp/pkg/xerr" invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) // SearchInviteReferrer 通过邀请码搜索可绑定邀请人。 // 搜索结果只允许同 App、同国家、已完成资料且 active 的普通用户,避免 H5 枚举跨国家用户资料。 func (s *Service) SearchInviteReferrer(ctx context.Context, userID int64, inviteCode string) (userdomain.User, error) { inviteCode = invitedomain.NormalizeCode(inviteCode) if userID <= 0 { return userdomain.User{}, xerr.New(xerr.InvalidArgument, "user_id is required") } if !invitedomain.ValidCode(inviteCode) { // 对外搜索入口只接受合法邀请码;非法输入直接拒绝,不落入 NotFound 分支。 return userdomain.User{}, xerr.New(xerr.InvalidInviteCode, "invalid invite code") } if s.userRepository == nil { return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured") } current, err := s.userRepository.GetUser(ctx, userID) if err != nil { return userdomain.User{}, err } if !current.ProfileCompleted { // 当前账号未完成资料时没有稳定国家边界,不能用这个入口搜索邀请人。 return userdomain.User{}, xerr.New(xerr.ProfileRequired, "profile required") } if current.Country == "" { return userdomain.User{}, xerr.New(xerr.InvalidArgument, "current user country is required") } inviter, err := s.userRepository.FindInviteReferrerByCode(ctx, appcode.FromContext(ctx), inviteCode) if err != nil { return userdomain.User{}, err } if err := validateInviteReferrerPair(current, inviter); err != nil { return userdomain.User{}, err } return s.refreshExpiredUser(ctx, inviter, "") } // BindInviteReferrer 原子绑定当前用户的邀请人。 // H5 只能传用户输入的邀请码;归属用户由 repository 在事务内重新解析并复核。 func (s *Service) BindInviteReferrer(ctx context.Context, userID int64, inviteCode string, requestID string) (userdomain.User, error) { inviteCode = invitedomain.NormalizeCode(inviteCode) if userID <= 0 { return userdomain.User{}, xerr.New(xerr.InvalidArgument, "user_id is required") } if !invitedomain.ValidCode(inviteCode) { return userdomain.User{}, xerr.New(xerr.InvalidInviteCode, "invalid invite code") } if s.userRepository == nil { return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured") } return s.userRepository.BindInviteReferrer(ctx, userdomain.BindInviteReferrerCommand{ AppCode: appcode.FromContext(ctx), InvitedUserID: userID, InviteCode: inviteCode, RequestID: strings.TrimSpace(requestID), BoundAtMs: s.now().UnixMilli(), }) } func validateInviteReferrerPair(invited userdomain.User, inviter userdomain.User) error { if invited.UserID <= 0 || inviter.UserID <= 0 { return xerr.New(xerr.NotFound, "inviter not found") } if invited.UserID == inviter.UserID { // 自邀不进入邀请码解析,避免把当前用户自己的邀请码可用性暴露出去。 return xerr.New(xerr.InvalidInviteCode, "invalid invite code") } if !inviter.ProfileCompleted || !inviter.CanLogin() { return xerr.New(xerr.NotFound, "inviter not found") } if invited.Country == "" || inviter.Country == "" || invited.Country != inviter.Country { // 产品要求只能搜索和绑定本国家邀请人;跨国家统一返回 not found,避免泄漏目标用户是否存在。 return xerr.New(xerr.NotFound, "inviter not found") } return nil }