refactor(#777): 修复Domain层依赖Adapters的DDD违规(4处) #789
@@ -10,6 +10,8 @@ from typing import Optional
|
||||
import redis
|
||||
from redis import Redis
|
||||
|
||||
from packages.domain.auth.session_store import SessionStorePort
|
||||
|
||||
|
||||
class RedisConfig:
|
||||
"""Redis 配置"""
|
||||
@@ -50,7 +52,7 @@ class NoopSessionStore:
|
||||
return False
|
||||
|
||||
|
||||
class SessionStore:
|
||||
class SessionStore(SessionStorePort):
|
||||
"""Session 存储服务"""
|
||||
|
||||
def __init__(self, redis_client: Optional[Redis] = None, config: Optional[RedisConfig] = None):
|
||||
|
||||
Regular → Executable
+3
-15
@@ -4,11 +4,12 @@
|
||||
"""
|
||||
|
||||
import smtplib
|
||||
from dataclasses import dataclass
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from typing import List, Optional
|
||||
|
||||
from packages.domain.auth.email_service import EmailConfig, EmailServicePort
|
||||
|
||||
|
||||
class NoopEmailService:
|
||||
def send_verification_email(self, **kwargs):
|
||||
@@ -18,20 +19,7 @@ class NoopEmailService:
|
||||
return False, "Email delivery is disabled"
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmailConfig:
|
||||
"""邮件配置"""
|
||||
|
||||
smtp_host: str = "smtp.gmail.com"
|
||||
smtp_port: int = 587
|
||||
smtp_user: str = ""
|
||||
smtp_password: str = ""
|
||||
from_email: str = ""
|
||||
from_name: str = "小虾 SaaS"
|
||||
use_tls: bool = True
|
||||
|
||||
|
||||
class EmailService:
|
||||
class EmailService(EmailServicePort):
|
||||
"""邮件服务类"""
|
||||
|
||||
def __init__(self, config: Optional[EmailConfig] = None):
|
||||
|
||||
Regular → Executable
+3
-1
@@ -6,6 +6,8 @@ from typing import Any, Dict, Optional
|
||||
import jwt
|
||||
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
|
||||
|
||||
from packages.domain.auth.jwt_service import JWTServicePort
|
||||
|
||||
|
||||
class JWTConfig:
|
||||
"""JWT 配置"""
|
||||
@@ -57,7 +59,7 @@ class TokenType:
|
||||
REFRESH = "refresh"
|
||||
|
||||
|
||||
class JWTService:
|
||||
class JWTService(JWTServicePort):
|
||||
"""JWT 服务类"""
|
||||
|
||||
def __init__(self, config: JWTConfig = None):
|
||||
|
||||
Regular → Executable
+5
-3
@@ -3,12 +3,14 @@
|
||||
使用 bcrypt 安全存储密码
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import bcrypt
|
||||
|
||||
from packages.domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
|
||||
|
||||
class PasswordHasher:
|
||||
|
||||
class PasswordHasher(PasswordHasherPort):
|
||||
"""密码哈希服务"""
|
||||
|
||||
def __init__(self, rounds: int = 12):
|
||||
@@ -98,7 +100,7 @@ class PasswordHasher:
|
||||
return False
|
||||
|
||||
|
||||
class PasswordValidator:
|
||||
class PasswordValidator(PasswordValidatorPort):
|
||||
"""密码强度验证器"""
|
||||
|
||||
def __init__(
|
||||
|
||||
Regular → Executable
+23
-29
@@ -1,36 +1,30 @@
|
||||
"""Authentication domain services.
|
||||
"""领域层认证相关端口接口.
|
||||
|
||||
Only pure domain authentication helpers are exported here. Infrastructure-backed
|
||||
services such as Redis session storage and SMTP email delivery live under
|
||||
`packages.adapters` and should be injected into use cases.
|
||||
遵循 DDD 依赖倒置原则:领域层定义端口(抽象接口),
|
||||
外层(应用层、基础设施层)实现端口。
|
||||
|
||||
使用方式:
|
||||
from packages.domain.auth import EmailServicePort, SessionStorePort
|
||||
from packages.domain.auth import JWTServicePort, PasswordHasherPort
|
||||
from packages.domain.auth.sms_service import SmsService
|
||||
|
||||
注意:具体实现请从对应的适配器层导入:
|
||||
- 邮件服务: from packages.adapters.smtp import EmailService
|
||||
- Session存储: from packages.adapters.redis import SessionStore
|
||||
- JWT服务: from packages.application.auth.jwt_service import JWTService
|
||||
- 密码哈希: from packages.application.auth.password_hasher import PasswordHasher
|
||||
"""
|
||||
|
||||
from packages.application.auth.jwt_service import (
|
||||
JWTConfig,
|
||||
JWTService,
|
||||
TokenType,
|
||||
jwt_service,
|
||||
)
|
||||
from packages.application.auth.password_hasher import (
|
||||
PasswordHasher,
|
||||
PasswordValidator,
|
||||
password_hasher,
|
||||
password_validator,
|
||||
)
|
||||
from packages.domain.auth.email_service import EmailConfig, EmailService
|
||||
from packages.domain.auth.session_store import RedisConfig, SessionStore
|
||||
from packages.domain.auth.email_service import EmailConfig, EmailServicePort
|
||||
from packages.domain.auth.jwt_service import JWTServicePort
|
||||
from packages.domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
|
||||
from packages.domain.auth.session_store import SessionStorePort
|
||||
|
||||
__all__ = [
|
||||
"JWTService",
|
||||
"JWTConfig",
|
||||
"TokenType",
|
||||
"jwt_service",
|
||||
"PasswordHasher",
|
||||
"PasswordValidator",
|
||||
"password_hasher",
|
||||
"password_validator",
|
||||
"SessionStore",
|
||||
"RedisConfig",
|
||||
"EmailService",
|
||||
"EmailConfig",
|
||||
"EmailServicePort",
|
||||
"JWTServicePort",
|
||||
"PasswordHasherPort",
|
||||
"PasswordValidatorPort",
|
||||
"SessionStorePort",
|
||||
]
|
||||
|
||||
Regular → Executable
+58
-6
@@ -1,10 +1,62 @@
|
||||
"""Compatibility import for SMTP email delivery.
|
||||
"""邮件服务端口(领域层接口).
|
||||
|
||||
Infrastructure implementations live under `packages.adapters`. New code should
|
||||
import `packages.adapters.smtp.email_service` directly or inject an email-sender
|
||||
port into the use case.
|
||||
定义邮件发送服务的抽象接口,具体实现由基础设施层(adapters)提供。
|
||||
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
|
||||
"""
|
||||
|
||||
from packages.adapters.smtp.email_service import EmailConfig, EmailService
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["EmailConfig", "EmailService"]
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmailConfig:
|
||||
"""邮件配置(领域层值对象)"""
|
||||
|
||||
smtp_host: str = "smtp.gmail.com"
|
||||
smtp_port: int = 587
|
||||
smtp_user: str = ""
|
||||
smtp_password: str = ""
|
||||
from_email: str = ""
|
||||
from_name: str = "小虾 SaaS"
|
||||
use_tls: bool = True
|
||||
|
||||
|
||||
class EmailServicePort(ABC):
|
||||
"""邮件服务端口(抽象接口)"""
|
||||
|
||||
@abstractmethod
|
||||
def send_email(
|
||||
self,
|
||||
to_email: str,
|
||||
subject: str,
|
||||
html_body: str,
|
||||
text_body: Optional[str] = None,
|
||||
cc: Optional[List[str]] = None,
|
||||
bcc: Optional[List[str]] = None,
|
||||
) -> tuple[bool, Optional[str]]:
|
||||
"""发送邮件"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def send_verification_email(
|
||||
self,
|
||||
to_email: str,
|
||||
verification_code: str,
|
||||
username: str = "",
|
||||
) -> tuple[bool, Optional[str]]:
|
||||
"""发送验证邮件"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def send_password_reset_email(
|
||||
self,
|
||||
to_email: str,
|
||||
reset_token: str,
|
||||
reset_url: str = "",
|
||||
username: str = "",
|
||||
) -> tuple[bool, Optional[str]]:
|
||||
"""发送密码重置邮件"""
|
||||
...
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
"""JWT 服务端口(领域层接口).
|
||||
|
||||
定义 JWT Token 生成、验证、解析的抽象接口,具体实现由应用层或基础设施层提供。
|
||||
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
class JWTServicePort(ABC):
|
||||
"""JWT 服务端口(抽象接口)"""
|
||||
|
||||
@abstractmethod
|
||||
def create_access_token(
|
||||
self,
|
||||
user_id: str,
|
||||
role: str = "",
|
||||
additional_claims: Dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""创建 access_token"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def create_refresh_token(self, user_id: str, session_id: str) -> str:
|
||||
"""创建 refresh_token"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def verify_token(self, token: str) -> Dict[str, Any]:
|
||||
"""验证任意 Token"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def verify_access_token(self, token: str) -> Dict[str, Any]:
|
||||
"""验证 access_token"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def verify_refresh_token(self, token: str) -> Dict[str, Any]:
|
||||
"""验证 refresh_token"""
|
||||
...
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
"""密码哈希服务端口(领域层接口).
|
||||
|
||||
定义密码哈希与验证的抽象接口,具体实现由应用层或基础设施层提供。
|
||||
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
class PasswordHasherPort(ABC):
|
||||
"""密码哈希服务端口(抽象接口)"""
|
||||
|
||||
@abstractmethod
|
||||
def hash_password(self, password: str) -> str:
|
||||
"""哈希密码"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def verify_password(self, password: str, hashed_password: str) -> bool:
|
||||
"""验证密码"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def needs_rehash(self, hashed_password: str) -> bool:
|
||||
"""检查哈希是否需要重新计算"""
|
||||
...
|
||||
|
||||
|
||||
class PasswordValidatorPort(ABC):
|
||||
"""密码强度验证端口(抽象接口)"""
|
||||
|
||||
@abstractmethod
|
||||
def validate(self, password: str) -> Tuple[bool, Optional[str]]:
|
||||
"""验证密码强度"""
|
||||
...
|
||||
Regular → Executable
+63
-6
@@ -1,10 +1,67 @@
|
||||
"""Compatibility import for Redis-backed session storage.
|
||||
"""Session 存储端口(领域层接口).
|
||||
|
||||
Infrastructure implementations live under `packages.adapters`. New code should
|
||||
import `packages.adapters.redis.session_store` directly or inject a session-store
|
||||
port into the use case.
|
||||
定义 Session 存储服务的抽象接口,具体实现由基础设施层(adapters)提供。
|
||||
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
|
||||
"""
|
||||
|
||||
from packages.adapters.redis.session_store import RedisConfig, SessionStore
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["RedisConfig", "SessionStore"]
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SessionStorePort(ABC):
|
||||
"""Session 存储端口(抽象接口)"""
|
||||
|
||||
@abstractmethod
|
||||
def save_session(
|
||||
self,
|
||||
session_id: str,
|
||||
user_id: str,
|
||||
refresh_token: str,
|
||||
user_agent: str = "",
|
||||
ip_address: str = "",
|
||||
expires_in_days: int = 7,
|
||||
) -> bool:
|
||||
"""保存 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_session(self, session_id: str) -> Optional[dict]:
|
||||
"""获取 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_session_by_refresh_token(self, refresh_token: str) -> Optional[dict]:
|
||||
"""通过 refresh_token 查找 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_refresh_token(self, session_id: str) -> Optional[str]:
|
||||
"""获取 refresh_token"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def update_last_active(self, session_id: str) -> bool:
|
||||
"""更新最后活跃时间"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def delete_session(self, session_id: str) -> bool:
|
||||
"""删除 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_user_sessions(self, user_id: str) -> list[dict]:
|
||||
"""获取用户所有 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def delete_all_user_sessions(self, user_id: str) -> int:
|
||||
"""删除用户所有 Session"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def session_exists(self, session_id: str) -> bool:
|
||||
"""检查 Session 是否存在"""
|
||||
...
|
||||
|
||||
Regular → Executable
+1
-1
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.auth.email_service import EmailConfig, EmailService
|
||||
from packages.adapters.smtp.email_service import EmailConfig, EmailService
|
||||
|
||||
|
||||
class TestEmailService:
|
||||
|
||||
Regular → Executable
+1
-1
@@ -7,7 +7,7 @@ from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.auth.session_store import SessionStore
|
||||
from packages.adapters.redis.session_store import SessionStore
|
||||
|
||||
|
||||
class TestSessionStore:
|
||||
|
||||
Reference in New Issue
Block a user