Files
xiaoxia-saas/packages/application/auth/register_user_use_case.py
T
CI Bot 9c6c477f55
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m22s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m24s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 37s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 4m3s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
fix(backend): Phase 1 后端代码清理与修复
P0 关键修复:
- P0-1: 注册接口添加 RateLimitMiddleware 限流保护
- P0-3: /metrics 端点添加 JWT 认证(移除匿名访问)
- P0-4: 修复 Celery 任务名冲突(generation_task vs generate_video)
- P1-5: JWT logout token 黑名单机制

P1 修复:
- P1-1: forgot_password 硬编码 localhost → 使用 settings.APP_BASE_URL
- P1-2: generation.py 直接创建 DB 连接 → 使用依赖注入
- P1-6: Image.open() 未关闭 → 统一使用 with 语句
- P1-7: 订阅续费事务修复

P2 代码质量:
- P2-1: 修复 EditingMode 枚举重复定义 → 统一引用 shared 包
- P2-2: 修复 SMTP_FRON_NAME → SMTP_FROM_NAME 拼写
- P2-3: UserModel subscription_quota 类型统一为 float
- P2-4: .env.production DATABASE_MAX_OVERFLOW 30 → 10
- 清理 15 处 except:pass(保留 2 处有注释说明的)
- 禁用 SVG 上传(XSS 风险)
- 删除 decode_token_unsafe() 不安全函数
- 简化 /ready 端点
- 删除 8 处死代码、10 个空文件/模块
- 合并 3 对 100% 重复函数
- 对齐 6 个废弃环境变量

v2 修复(代码审查后):
- 修复密码重置路由路径: /password/forgot → /forgot-password,
  /password/reset → /reset-password(与前端 API 对齐)
- 合并 _check_project_access: asset_libraries.py 和 edit_plans.py
  中的重复函数统一到 _helpers.py(含空字符串守卫 + 中文错误信息)
- 顺手修复: HTTPException 统一从 fastapi 导入(替换 starlette 导入)
- OSS_ENDPOINT 拼写修复拆分为单独 PR,本 PR 不包含
2026-07-13 13:50:52 +08:00

205 lines
5.8 KiB
Python
Executable File

"""
用户注册 Use Case
"""
import secrets
from datetime import datetime, timezone
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
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(timezone.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:
print(f"Failed to send verification email: {error}")
except Exception as e:
print(f"Email service error: {e}")
# 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)}"