1aafc28621
引入 JWT 认证、极光一键登录、短信 mock 登录与用户表,并补充技术实施文档与部署配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
2.3 KiB
Python
69 lines
2.3 KiB
Python
"""短信验证码服务。
|
|
|
|
当前实现:**mock 模式**。
|
|
- send_code(): 不真发短信,仅 log。记录 phone → 发送时间(防 60s 内重复发)
|
|
- verify_code(): 任意 6 位数字均通过(配合前台 demo 行为)
|
|
|
|
后续接真供应商(阿里云 / 腾讯云)时:
|
|
- send_code() 改为调供应商 API,把生成的 6 位码存到 cache(redis / sqlite)
|
|
- verify_code() 比对 cache 里的码,且验过即作废
|
|
|
|
进程内存方案占坑期够用;切真实供应商时一并切 redis。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from threading import Lock
|
|
|
|
from app.core.config import settings
|
|
|
|
logger = logging.getLogger("shagua.sms")
|
|
|
|
# 进程内最近发送时间表:{phone: epoch_seconds},用于 60s 内拒发
|
|
_last_sent: dict[str, float] = {}
|
|
_lock = Lock()
|
|
|
|
|
|
class SmsError(Exception):
|
|
"""业务异常:发送过频 / 验证码不对。api 层 catch 后翻成 4xx。"""
|
|
|
|
|
|
def send_code(phone: str) -> int:
|
|
"""发送(或 mock 发送)验证码。
|
|
|
|
Returns: 距下一次可发的秒数(=0 表示刚刚已发,需等 SMS_SEND_INTERVAL_SEC 秒)
|
|
Raises: SmsError 如果上次发送在 SMS_SEND_INTERVAL_SEC 内
|
|
"""
|
|
now = time.time()
|
|
with _lock:
|
|
last = _last_sent.get(phone, 0.0)
|
|
elapsed = now - last
|
|
if elapsed < settings.SMS_SEND_INTERVAL_SEC:
|
|
remain = int(settings.SMS_SEND_INTERVAL_SEC - elapsed)
|
|
raise SmsError(f"send too frequent, retry in {remain}s")
|
|
_last_sent[phone] = now
|
|
|
|
if settings.SMS_MOCK:
|
|
logger.info("[SMS-MOCK] send to %s****, code=any-6-digits", phone[:3])
|
|
else:
|
|
# TODO: 接真实短信供应商
|
|
logger.warning("[SMS] real provider not implemented yet, phone=%s****", phone[:3])
|
|
raise SmsError("sms provider not configured")
|
|
|
|
return settings.SMS_SEND_INTERVAL_SEC
|
|
|
|
|
|
def verify_code(phone: str, code: str) -> bool:
|
|
"""校验验证码。mock 模式下任意 6 位数字均通过。"""
|
|
if settings.SMS_MOCK:
|
|
if len(code) == 6 and code.isdigit():
|
|
logger.info("[SMS-MOCK] verify ok for %s****", phone[:3])
|
|
return True
|
|
logger.info("[SMS-MOCK] verify fail (need 6 digits) for %s****", phone[:3])
|
|
return False
|
|
|
|
# TODO: 比对供应商发出的真实验证码
|
|
logger.warning("[SMS] real provider not implemented yet, phone=%s****", phone[:3])
|
|
return False
|