package auth import ( "context" "strings" "hyapp/pkg/appcode" "hyapp/pkg/xerr" authdomain "hyapp/services/user-service/internal/domain/auth" invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) // QuickCreateAccount 创建一个已完成资料、已设置密码、可立即登录的账号。 // 该能力只复用内部领域事务,不依赖 firebase/mock provider,方便游戏方联调时快速拿 uid/token。 func (s *Service) QuickCreateAccount(ctx context.Context, password string, registration authdomain.ThirdPartyRegistration, meta Meta) (authdomain.Token, error) { meta.AppCode = appcode.Normalize(meta.AppCode) registration.AppCode = meta.AppCode registration = normalizeThirdPartyRegistration(registration, meta) meta.Platform = registration.Platform meta.Language = registration.Language meta.Timezone = registration.Timezone password = strings.TrimSpace(password) if password == "" { return authdomain.Token{}, xerr.New(xerr.InvalidArgument, "password is required") } if registration.DeviceID == "" || registration.Platform == "" { // refresh session 必须绑定设备,平台也会进入注册来源和风控审计。 return authdomain.Token{}, xerr.New(xerr.InvalidArgument, "device_id and platform are required") } if registration.Username == "" || registration.Avatar == "" || registration.Gender == "" || registration.Country == "" { // 快捷账号直接跳过 onboarding,因此必须一次性写齐客户端准入所需的最小资料。 return authdomain.Token{}, xerr.New(xerr.InvalidArgument, "username, avatar, gender and country are required") } if err := validateThirdPartyRegistration(registration); err != nil { return authdomain.Token{}, err } if s.authRepository == nil { return authdomain.Token{}, xerr.New(xerr.Unavailable, "auth repository is not configured") } registration, err := s.resolveRegistrationCountry(ctx, registration) if err != nil { return authdomain.Token{}, err } passwordHash, err := hashPassword(password) if err != nil { return authdomain.Token{}, err } var lastErr error for attempt := 0; attempt < s.allocateMaxAttempts; attempt++ { user, displayIdentity := s.newUserWithIdentity(attempt) user = applyThirdPartyRegistration(user, registration) user.ProfileCompleted = true user.ProfileCompletedAtMs = user.CreatedAtMs user.OnboardingStatus = userdomain.OnboardingStatusCompleted displayUserID, err := s.authRepository.AllocateDisplayUserID(ctx, registration.AppCode) if err != nil { return authdomain.Token{}, err } user.DefaultDisplayUserID = displayUserID user.CurrentDisplayUserID = displayUserID displayIdentity.AppCode = registration.AppCode displayIdentity.DisplayUserID = displayUserID displayIdentity.DefaultDisplayUserID = displayUserID if !userdomain.ValidDisplayUserID(displayIdentity.DisplayUserID) { // 分配器或历史种子异常时不写库,直接尝试下一个候选。 continue } session, refreshToken, err := s.newSession(registration.AppCode, user.UserID, registration.DeviceID) if err != nil { return authdomain.Token{}, err } account := authdomain.PasswordAccount{ AppCode: registration.AppCode, UserID: user.UserID, DisplayUserID: displayUserID, PasswordHash: passwordHash, HashAlg: hashAlgBcrypt, CreatedAtMs: user.CreatedAtMs, UpdatedAtMs: user.UpdatedAtMs, } inviteBind := invitedomain.BindCommand{ AppCode: registration.AppCode, InvitedUserID: user.UserID, InvitedRegionID: registration.RegionID, InviteCode: registration.InviteCode, Source: "quick_account_creation", RequestID: meta.RequestID, BoundAtMs: user.CreatedAtMs, } err = s.authRepository.CreatePasswordUser(ctx, user, displayIdentity, account, session, inviteBind) if err == nil { s.audit(ctx, meta, user.UserID, loginPassword, "", resultSuccess, "") token, tokenErr := s.issueToken(user, session.SessionID, refreshToken) if tokenErr != nil { return authdomain.Token{}, tokenErr } s.importIMAccountAfterCommit(ctx, user, meta) s.enqueueLoginIPRiskJob(ctx, meta, token, "account", registration.Platform, registration.Language, registration.Timezone) return token, nil } if xerr.IsCode(err, xerr.DisplayUserIDExists) { // 默认短号冲突可重试,避免少量并发建号把快捷入口打成失败。 lastErr = err continue } s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.CodeOf(err))) return authdomain.Token{}, err } if lastErr != nil { s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.DisplayUserIDAllocateFailed)) } return authdomain.Token{}, xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id allocation failed") }