121 lines
4.8 KiB
Go
121 lines
4.8 KiB
Go
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")
|
||
}
|