c9fad9bca8
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
"""短信 provider 共享基座:业务异常 + provider 无关的 mock 校验。
|
||
|
||
各 provider(jiguang / aliyun)都 `from .base import SmsError`,api 层也从包入口拿到同一个
|
||
`SmsError` —— 保证无论用哪个 provider,异常类型与 HTTP 码映射语义一致。
|
||
`SendResult` 是分派层 `send_code` 的返回类型,携带 cooldown、实际 provider 及是否走了 fallback。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from app.core.config import settings
|
||
|
||
|
||
class SmsError(Exception):
|
||
"""业务异常。`status_code` 决定 api 层翻成哪个 HTTP 码:
|
||
过频/每日超限 = 429(客户端等会再来),供应商不可用 = 503,手机号无效 = 400。
|
||
"""
|
||
|
||
def __init__(self, message: str, status_code: int = 429) -> None:
|
||
super().__init__(message)
|
||
self.status_code = status_code
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class SendResult:
|
||
"""发码结果:距下次可发秒数 + 实际发码的 provider 名 + 是否走了备(fallback)。
|
||
|
||
分派层 send_code 的返回类型(provider 各自的 send_code 仍返回 int cooldown,
|
||
由分派层包装)。auth 层据此把渠道记入风控流水 details。
|
||
"""
|
||
|
||
cooldown_sec: int
|
||
provider: str
|
||
fallback: bool = False
|
||
|
||
|
||
def mock_verify(code: str) -> bool:
|
||
"""mock 模式校验:放行任意 SMS_CODE_LENGTH 位数字(provider 无关,测试/开发便利,不真校验)。"""
|
||
return len(code) == settings.SMS_CODE_LENGTH and code.isdigit()
|