2026-06-30 10:27:29 +08:00

91 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}