Files
xiaoxia-saas/packages/application/auth/register_user_use_case.py
T
xiaoxia 53fb25efcf
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 41s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 48s
CI/CD Pipeline / Integration Tests (push) Successful in 3m10s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m30s
CI/CD Pipeline / Validate - Style (push) Successful in 4m17s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 5m33s
CI/CD Pipeline / Validate - Security (push) Successful in 7m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m38s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
CI/CD Pipeline / Unit Tests (push) Successful in 10m11s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Failing after 26h14m3s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 26h24m21s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 26h19m47s
CI/CD Pipeline / PR Build Web Image (push) Failing after 26h23m44s
CI/CD Pipeline / PR Build API Image (push) Failing after 26h23m44s
CI/CD Pipeline / Deploy Production (push) Failing after 26h13m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 26h13m26s
CI/CD Pipeline / CI Gate (push) Failing after 26h13m25s
CI/CD Pipeline / Build Production API Image (push) Failing after 26h13m26s
CI/CD Pipeline / Canary Release to Production (push) Failing after 26h13m23s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 26h19m46s
CI/CD Pipeline / Frontend Lint (push) Failing after 26h23m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 26h23m45s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 26h19m46s
fix(#1834): 批量修复 UP 系列静态分析警告(UP007/UP006/UP017/UP035) (#1928)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-15 12:59:17 +08:00

208 lines
5.9 KiB
Python

"""
用户注册 Use Case
"""
import logging
import secrets
from datetime import UTC, datetime
from typing import Optional
from uuid import uuid4
from packages.adapters.smtp import get_email_service
from packages.application.auth.password_hasher import password_hasher, password_validator
from packages.domain.entities import User
logger = logging.getLogger(__name__)
class RegisterUserRequest:
"""注册请求"""
def __init__(
self,
email: str,
password: str,
username: str,
display_name: str,
):
self.email = email.strip().lower()
self.password = password
self.username = username.strip()
self.display_name = display_name.strip()
class RegisterUserResponse:
"""注册响应"""
def __init__(
self,
user_id: str,
email: str,
username: str,
display_name: str,
email_verification_sent: bool,
):
self.user_id = user_id
self.email = email
self.username = username
self.display_name = display_name
self.email_verification_sent = email_verification_sent
class RegisterUserUseCase:
"""用户注册用例"""
def __init__(
self,
user_repository,
base_url: str,
email_service=None,
):
"""
初始化注册用例
Args:
user_repository: 用户仓储
base_url: 应用基础 URL(用于生成验证链接),必须由调用方传入
"""
self.user_repository = user_repository
self.base_url = base_url
self.email_service = email_service or get_email_service()
def execute(self, request: RegisterUserRequest) -> tuple[Optional[RegisterUserResponse], Optional[str]]:
"""
执行注册
Args:
request: 注册请求
Returns:
(注册响应, 错误信息)
"""
try:
# 1. 验证输入
if not request.email:
return None, "Email is required"
if not request.username:
return None, "Username is required"
if not request.display_name:
return None, "Display name is required"
# 2. 验证密码强度
valid, error = password_validator.validate(request.password)
if not valid:
return None, error
# 3. 检查邮箱是否已存在
existing_user = self.user_repository.find_by_email(request.email)
if existing_user:
return None, "Email already registered"
# 4. 检查用户名是否已存在
existing_username = self.user_repository.find_by_username(request.username)
if existing_username:
return None, "Username already taken"
# 5. 哈希密码
hashed_password = password_hasher.hash_password(request.password)
# 6. 生成邮箱验证令牌
verification_token = secrets.token_urlsafe(32)
# 7. 创建用户
user = User(
id=uuid4().hex,
email=request.email,
username=request.username,
display_name=request.display_name,
password_hash=hashed_password,
email_verified=False,
email_verification_token=verification_token,
created_at=datetime.now(UTC),
)
# 8. 保存用户
self.user_repository.save(user)
# 9. 发送验证邮件
verification_url = f"{self.base_url}/verify-email?token={verification_token}"
email_sent = False
try:
success, error = self.email_service.send_verification_email(
to_email=user.email,
username=user.username,
verification_url=verification_url,
)
email_sent = success
if not success:
logger.warning(f"Verification email failed: {error}")
except Exception as e:
logger.error(f"Email service error: {e}", exc_info=True)
# 10. 返回响应(即使邮件发送失败,用户也已创建)
return (
RegisterUserResponse(
user_id=user.id,
email=user.email,
username=user.username,
display_name=user.display_name,
email_verification_sent=email_sent,
),
None,
)
except Exception as e:
return None, f"Registration failed: {str(e)}"
class VerifyEmailRequest:
"""邮箱验证请求"""
def __init__(self, token: str):
self.token = token
class VerifyEmailUseCase:
"""邮箱验证用例"""
def __init__(self, user_repository):
self.user_repository = user_repository
def execute(self, request: VerifyEmailRequest) -> tuple[bool, Optional[str]]:
"""
执行邮箱验证
Args:
request: 验证请求
Returns:
(是否成功, 错误信息)
"""
try:
if not request.token:
return False, "Verification token is required"
# 查找用户
user = self.user_repository.find_by_verification_token(request.token)
if not user:
return False, "Invalid or expired verification token"
# 检查是否已验证
if user.email_verified:
return True, None # 已验证,返回成功
# 更新用户状态
user.email_verified = True
user.email_verification_token = None # 清空令牌
self.user_repository.save(user)
return True, None
except Exception as e:
return False, f"Email verification failed: {str(e)}"