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 和区域隔离;国家只是用户资料快照,不能把同区域的跨国家邀请误判为不存在。 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 { // H5 绑定入口只服务已完成资料用户;注册资料页绑定走 CompleteOnboarding 的原子写链路。 return userdomain.User{}, xerr.New(xerr.ProfileRequired, "profile 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.RegionID != inviter.RegionID { // 产品边界是同区域邀请;跨区域统一按未找到处理,避免 H5 用搜索接口枚举其他区域用户。 return xerr.New(xerr.NotFound, "inviter not found") } return nil }