88 lines
3.4 KiB
Go
88 lines
3.4 KiB
Go
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
|
|
}
|