This commit is contained in:
root
2026-10-16 13:51:00 +08:00
parent 6bf949949c
commit 8fc899d0a9
3830 changed files with 11228 additions and 188377 deletions
+82 -2
View File
@@ -1,10 +1,14 @@
"""认证服务:验证码 / 注册 / 登录 / 刷新 / 登出 / 找回密码"""
import asyncio
import logging
import random
import smtplib
import string
from datetime import datetime, timedelta
from email.mime.text import MIMEText
import redis.asyncio as aioredis
import httpx
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -66,14 +70,90 @@ async def send_code(
await redis.expire(ip_key, 3600)
data = {"expire_seconds": ttl, "retry_after": 60}
# TODO: 接入阿里云短信 / SMTP 实际发送;未配置通道时按开发模式处理
channel_configured = bool(settings.sms_access_key_id) if channel == "sms" else bool(settings.smtp_host)
channel_configured = (
bool(settings.smsbao_username and settings.smsbao_api_key)
if channel == "sms"
else bool(settings.smtp_host)
)
if not channel_configured:
logger.info("[dev] %s code for %s (%s): %s", channel, target, scene, code)
data["dev_code"] = code
elif channel == "sms":
await _send_sms_code(target, code)
else:
await _send_email_code(target, code, scene)
return data
_SCENES = {"register": "注册", "login": "登录", "reset": "找回密码"}
# ---------- 短信(短信宝)----------
SMSBAO_SEND_URL = "https://api.smsbao.com/sms"
# 验证码短信正文(签名前缀由 settings.sms_sign_name 提供)
_SMS_CONTENT = "您的验证码是{code}。如非本人操作,请忽略本短信"
# 接口文本返回码(非 0 均为失败,仅用于日志定位)
_SMS_ERRORS = {
"30": "密码/ApiKey 错误",
"40": "账号不存在",
"41": "余额不足",
"42": "账号已过期",
"43": "IP 地址限制",
"50": "内容含有敏感词",
"51": "手机号码不正确",
"60": "尚未充值,接口不可调用",
}
async def _send_sms_code(target: str, code: str) -> None:
"""短信宝发送验证码:返回 '0' 视为提交成功"""
content = f"{settings.sms_sign_name}{_SMS_CONTENT.format(code=code)}"
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(
SMSBAO_SEND_URL,
params={
"u": settings.smsbao_username,
"p": settings.smsbao_api_key,
"m": target,
"c": content, # httpx 自动按 UTF-8 URL Encode
},
)
result = resp.text.strip()
except Exception:
logger.exception("smsbao request failed: %s", target)
raise BizException(errors.PARAM_ERROR, "验证码发送失败,请稍后重试")
if result != "0":
logger.error(
"smsbao send failed: mobile=%s result=%s(%s)",
target, result, _SMS_ERRORS.get(result, "未知错误"),
)
raise BizException(errors.PARAM_ERROR, "验证码发送失败,请稍后重试")
def _smtp_send(target: str, code: str, scene: str) -> None:
"""SMTP 发送验证码(同步 smtplib,由调用方放线程池)"""
msg = MIMEText(
f"您的{_SCENES.get(scene, scene)}验证码:{code},10 分钟内有效。"
"如非本人操作请忽略本邮件。"
)
msg["Subject"] = f"短视频{_SCENES.get(scene, scene)}验证码"
msg["From"] = settings.smtp_user
msg["To"] = target
with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=15) as server:
server.starttls()
server.login(settings.smtp_user, settings.smtp_password)
server.send_message(msg)
async def _send_email_code(target: str, code: str, scene: str) -> None:
"""SMTP 异步包装:发送失败视为验证码错误前置(用户需重发)"""
try:
await asyncio.to_thread(_smtp_send, target, code, scene)
except Exception:
logger.exception("smtp send failed: %s", target)
raise BizException(errors.PARAM_ERROR, "验证码发送失败,请稍后重试")
async def _verify_code(redis: aioredis.Redis, channel: str, target: str, scene: str, code: str) -> None:
key = _code_key(scene, channel, target)
saved = await redis.get(key)