- Backend: workspace_id isolation for 14 model tables + safe migration/backfill - Backend: RBAC system with 4 roles and 23 permissions, seeded on startup - Backend: workspace admin endpoints (list/manage all workspaces) - Backend: admin user management API (CRUD, reset password) - Backend: billing API with subscription plans, usage tracking, rate limiting - Backend: fix system_logs.py UNION query and wrong column references - Backend: WebSocket JWT auth and workspace enforcement - Frontend: sidebar navigation replacing top dropdown menu - Frontend: user management page (Users.vue) for admins - Frontend: enhanced Workspaces.vue with admin table view - Frontend: workspace RBAC computed properties in user store - Android: agent marketplace, billing/subscription UI, onboarding wizard - Android: phone login, analytics tracker, crash handler, network diagnostics - Android: splash screen, encrypted token storage, app update enhancements - Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""
|
||
SMS 服务抽象层
|
||
Mock 模式:验证码打印到后端日志
|
||
后续可接入阿里云短信 / 腾讯云 SMS,只需替换 Provider 实现
|
||
"""
|
||
import logging
|
||
from abc import ABC, abstractmethod
|
||
from app.core.config import settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class SmsProvider(ABC):
|
||
"""SMS 发送提供者基类"""
|
||
|
||
@abstractmethod
|
||
async def send(self, phone: str, code: str) -> bool:
|
||
"""发送验证码短信,成功返回 True"""
|
||
...
|
||
|
||
|
||
class MockSmsProvider(SmsProvider):
|
||
"""Mock 提供者 —— 验证码打印到后端日志(开发/测试环境用)"""
|
||
|
||
async def send(self, phone: str, code: str) -> bool:
|
||
logger.info("=" * 50)
|
||
logger.info("📱 [MockSMS] 验证码: %s → %s", code, phone)
|
||
logger.info("=" * 50)
|
||
return True
|
||
|
||
|
||
class TencentSmsProvider(SmsProvider):
|
||
"""腾讯云 SMS 提供者(待接入)"""
|
||
|
||
def __init__(self, secret_id: str, secret_key: str, app_id: str, sign_name: str, template_id: str):
|
||
self.secret_id = secret_id
|
||
self.secret_key = secret_key
|
||
self.app_id = app_id
|
||
self.sign_name = sign_name
|
||
self.template_id = template_id
|
||
|
||
async def send(self, phone: str, code: str) -> bool:
|
||
# TODO: 接入腾讯云 SMS SDK
|
||
logger.warning("TencentSmsProvider 尚未实现,验证码: %s → %s", code, phone)
|
||
return False
|
||
|
||
|
||
class AliyunSmsProvider(SmsProvider):
|
||
"""阿里云短信提供者(待接入)"""
|
||
|
||
def __init__(self, access_key_id: str, access_key_secret: str, sign_name: str, template_code: str):
|
||
self.access_key_id = access_key_id
|
||
self.access_key_secret = access_key_secret
|
||
self.sign_name = sign_name
|
||
self.template_code = template_code
|
||
|
||
async def send(self, phone: str, code: str) -> bool:
|
||
# TODO: 接入阿里云短信 SDK
|
||
logger.warning("AliyunSmsProvider 尚未实现,验证码: %s → %s", code, phone)
|
||
return False
|
||
|
||
|
||
def get_sms_provider() -> SmsProvider:
|
||
"""根据配置返回 SMS 提供者实例"""
|
||
provider_name = getattr(settings, "SMS_PROVIDER", "mock") or "mock"
|
||
|
||
if provider_name == "tencent":
|
||
return TencentSmsProvider(
|
||
secret_id=getattr(settings, "TENCENT_SMS_SECRET_ID", ""),
|
||
secret_key=getattr(settings, "TENCENT_SMS_SECRET_KEY", ""),
|
||
app_id=getattr(settings, "TENCENT_SMS_APP_ID", ""),
|
||
sign_name=getattr(settings, "TENCENT_SMS_SIGN_NAME", ""),
|
||
template_id=getattr(settings, "TENCENT_SMS_TEMPLATE_ID", ""),
|
||
)
|
||
elif provider_name == "aliyun":
|
||
return AliyunSmsProvider(
|
||
access_key_id=getattr(settings, "ALIYUN_SMS_ACCESS_KEY_ID", ""),
|
||
access_key_secret=getattr(settings, "ALIYUN_SMS_ACCESS_KEY_SECRET", ""),
|
||
sign_name=getattr(settings, "ALIYUN_SMS_SIGN_NAME", ""),
|
||
template_code=getattr(settings, "ALIYUN_SMS_TEMPLATE_CODE", ""),
|
||
)
|
||
else:
|
||
return MockSmsProvider()
|