Spaces:
Running
Running
Upload router_users.py
Browse files- router_users.py +73 -20
router_users.py
CHANGED
|
@@ -6,6 +6,8 @@ import random
|
|
| 6 |
import os
|
| 7 |
import json
|
| 8 |
import urllib.request
|
|
|
|
|
|
|
| 9 |
import 数据库连接 as db
|
| 10 |
from notifications import add_notification
|
| 11 |
from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowToggle, PrivacySettings, SendCodeRequest
|
|
@@ -40,14 +42,12 @@ def send_email_code(to_email: str, code: str, action: str):
|
|
| 40 |
</div>
|
| 41 |
"""
|
| 42 |
|
| 43 |
-
# 组装要发送给 Make.com 的数据
|
| 44 |
data = {
|
| 45 |
"to": to_email,
|
| 46 |
"subject": subject,
|
| 47 |
"html": html_content
|
| 48 |
}
|
| 49 |
|
| 50 |
-
# 发送普通的 HTTPS POST 请求,绝对不会被拦截
|
| 51 |
req = urllib.request.Request(
|
| 52 |
webhook_url,
|
| 53 |
data=json.dumps(data).encode('utf-8'),
|
|
@@ -60,14 +60,76 @@ def send_email_code(to_email: str, code: str, action: str):
|
|
| 60 |
print(f"❌ Webhook 触发失败: {e}")
|
| 61 |
|
| 62 |
def send_sms_code(phone: str, code: str, action: str):
|
| 63 |
-
"""短信发送
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
@router.post("/api/users/send-code")
|
| 69 |
async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
| 70 |
-
# 【新增核心逻辑】:如果是找回密码,先去数据库里核对联系方式
|
| 71 |
if req.action_type == "reset":
|
| 72 |
if not req.account:
|
| 73 |
raise HTTPException(status_code=400, detail="找回密码需先填写当前账号")
|
|
@@ -82,17 +144,14 @@ async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
|
| 82 |
if req.contact_type == "phone" and user.get("phone") != req.contact:
|
| 83 |
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不一���")
|
| 84 |
|
| 85 |
-
# 原有的生成验证码和发送逻辑保持不变
|
| 86 |
code = str(random.randint(100000, 999999))
|
| 87 |
cache_key = f"{req.contact}_{req.action_type}"
|
| 88 |
|
| 89 |
-
# 存入缓存,设置 10 分钟过期
|
| 90 |
VERIFY_CODES[cache_key] = {
|
| 91 |
"code": code,
|
| 92 |
"expires_at": int(time.time()) + 600
|
| 93 |
}
|
| 94 |
|
| 95 |
-
# 放入后台队列执行,立刻给前端返回成功响应
|
| 96 |
if req.contact_type == "email":
|
| 97 |
bg_tasks.add_task(send_email_code, req.contact, code, req.action_type)
|
| 98 |
elif req.contact_type == "phone":
|
|
@@ -102,17 +161,11 @@ async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
|
| 102 |
|
| 103 |
return {"status": "success", "message": "验证码发送请求已提交"}
|
| 104 |
|
| 105 |
-
|
| 106 |
-
# ==========================================
|
| 107 |
-
# 原有业务接口
|
| 108 |
-
# ==========================================
|
| 109 |
-
|
| 110 |
@router.post("/api/users/register")
|
| 111 |
async def register_user(user: UserRegister):
|
| 112 |
users_db = db.load_data("users.json", default_data={})
|
| 113 |
|
| 114 |
-
|
| 115 |
-
cache_key = f"{user.email}_register"
|
| 116 |
cached = VERIFY_CODES.get(cache_key)
|
| 117 |
if not cached or cached["code"] != user.code or int(time.time()) > cached["expires_at"]:
|
| 118 |
raise HTTPException(status_code=400, detail="验证码不正确或已过期")
|
|
@@ -124,10 +177,10 @@ async def register_user(user: UserRegister):
|
|
| 124 |
if user.intro and len(user.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
|
| 125 |
if user.account in users_db: raise HTTPException(status_code=400, detail="该账号已被注册")
|
| 126 |
for existing_user in users_db.values():
|
| 127 |
-
if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
|
| 128 |
-
if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
|
| 129 |
|
| 130 |
-
VERIFY_CODES.pop(cache_key, None)
|
| 131 |
|
| 132 |
new_user = user.dict()
|
| 133 |
new_user.pop("code", None)
|
|
|
|
| 6 |
import os
|
| 7 |
import json
|
| 8 |
import urllib.request
|
| 9 |
+
import urllib.parse
|
| 10 |
+
import base64
|
| 11 |
import 数据库连接 as db
|
| 12 |
from notifications import add_notification
|
| 13 |
from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowToggle, PrivacySettings, SendCodeRequest
|
|
|
|
| 42 |
</div>
|
| 43 |
"""
|
| 44 |
|
|
|
|
| 45 |
data = {
|
| 46 |
"to": to_email,
|
| 47 |
"subject": subject,
|
| 48 |
"html": html_content
|
| 49 |
}
|
| 50 |
|
|
|
|
| 51 |
req = urllib.request.Request(
|
| 52 |
webhook_url,
|
| 53 |
data=json.dumps(data).encode('utf-8'),
|
|
|
|
| 60 |
print(f"❌ Webhook 触发失败: {e}")
|
| 61 |
|
| 62 |
def send_sms_code(phone: str, code: str, action: str):
|
| 63 |
+
"""支持 阿里云 与 Twilio 的双引擎短信发送路由"""
|
| 64 |
+
action_str = "注册账号" if action == "register" else "修改/找回密码"
|
| 65 |
+
|
| 66 |
+
# ==========================================
|
| 67 |
+
# 引擎 A:Twilio (无需装 SDK,走纯 HTTP 接口,适合海外或免审核测试)
|
| 68 |
+
# ==========================================
|
| 69 |
+
twilio_sid = os.environ.get("TWILIO_SID")
|
| 70 |
+
if twilio_sid:
|
| 71 |
+
token = os.environ.get("TWILIO_TOKEN")
|
| 72 |
+
from_phone = os.environ.get("TWILIO_FROM")
|
| 73 |
+
|
| 74 |
+
body = f"【ComfyUI社区】您正在请求{action_str},验证码是:{code},10分钟内有效。"
|
| 75 |
+
url = f"https://api.twilio.com/2010-04-01/Accounts/{twilio_sid}/Messages.json"
|
| 76 |
+
auth = base64.b64encode(f"{twilio_sid}:{token}".encode('utf-8')).decode('utf-8')
|
| 77 |
+
data = urllib.parse.urlencode({'To': phone, 'From': from_phone, 'Body': body}).encode('utf-8')
|
| 78 |
+
|
| 79 |
+
req = urllib.request.Request(url, data=data)
|
| 80 |
+
req.add_header("Authorization", f"Basic {auth}")
|
| 81 |
+
try:
|
| 82 |
+
with urllib.request.urlopen(req, timeout=10) as response:
|
| 83 |
+
print(f"✅ Twilio 短信已成功下发至 {phone}")
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f"❌ Twilio 发送失败: {e}")
|
| 86 |
+
return
|
| 87 |
|
| 88 |
+
# ==========================================
|
| 89 |
+
# 引擎 B:阿里云 (国内首选,到达率最高)
|
| 90 |
+
# ==========================================
|
| 91 |
+
aliyun_ak = os.environ.get("ALIYUN_AK")
|
| 92 |
+
if aliyun_ak:
|
| 93 |
+
try:
|
| 94 |
+
from alibabacloud_dysmsapi20170525.client import Client as DysmsapiClient
|
| 95 |
+
from alibabacloud_tea_openapi import models as open_api_models
|
| 96 |
+
from alibabacloud_dysmsapi20170525 import models as dysmsapi_models
|
| 97 |
+
except ImportError:
|
| 98 |
+
print("❌ 缺少阿里云 SDK,请在 requirements.txt 中添加 alibabacloud_dysmsapi20170525")
|
| 99 |
+
return
|
| 100 |
+
|
| 101 |
+
sk = os.environ.get("ALIYUN_SK")
|
| 102 |
+
sign_name = os.environ.get("ALIYUN_SIGN_NAME") # 短信签名,例如 "阿里云"
|
| 103 |
+
tpl_code = os.environ.get("ALIYUN_TPL_REGISTER") if action == "register" else os.environ.get("ALIYUN_TPL_RESET")
|
| 104 |
+
|
| 105 |
+
config = open_api_models.Config(access_key_id=aliyun_ak, access_key_secret=sk)
|
| 106 |
+
config.endpoint = 'dysmsapi.aliyuncs.com'
|
| 107 |
+
client = DysmsapiClient(config)
|
| 108 |
+
|
| 109 |
+
send_req = dysmsapi_models.SendSmsRequest(
|
| 110 |
+
phone_numbers=phone,
|
| 111 |
+
sign_name=sign_name,
|
| 112 |
+
template_code=tpl_code,
|
| 113 |
+
template_param=json.dumps({"code": code})
|
| 114 |
+
)
|
| 115 |
+
try:
|
| 116 |
+
response = client.send_sms(send_req)
|
| 117 |
+
if response.body.code == "OK":
|
| 118 |
+
print(f"✅ 阿里云短信已成功下发至 {phone}")
|
| 119 |
+
else:
|
| 120 |
+
print(f"❌ 阿里云下发失败: {response.body.message}")
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"❌ 阿里云请求异常: {e}")
|
| 123 |
+
return
|
| 124 |
+
|
| 125 |
+
# 如果都没有配置,则降级为控制台打印模拟
|
| 126 |
+
print(f"⚠️ 未配置短信秘钥,模拟下发 -> 手机号: {phone}, 验证码: {code}")
|
| 127 |
+
|
| 128 |
+
# ==========================================
|
| 129 |
+
# 接口路由
|
| 130 |
+
# ==========================================
|
| 131 |
@router.post("/api/users/send-code")
|
| 132 |
async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
|
|
|
| 133 |
if req.action_type == "reset":
|
| 134 |
if not req.account:
|
| 135 |
raise HTTPException(status_code=400, detail="找回密码需先填写当前账号")
|
|
|
|
| 144 |
if req.contact_type == "phone" and user.get("phone") != req.contact:
|
| 145 |
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不一���")
|
| 146 |
|
|
|
|
| 147 |
code = str(random.randint(100000, 999999))
|
| 148 |
cache_key = f"{req.contact}_{req.action_type}"
|
| 149 |
|
|
|
|
| 150 |
VERIFY_CODES[cache_key] = {
|
| 151 |
"code": code,
|
| 152 |
"expires_at": int(time.time()) + 600
|
| 153 |
}
|
| 154 |
|
|
|
|
| 155 |
if req.contact_type == "email":
|
| 156 |
bg_tasks.add_task(send_email_code, req.contact, code, req.action_type)
|
| 157 |
elif req.contact_type == "phone":
|
|
|
|
| 161 |
|
| 162 |
return {"status": "success", "message": "验证码发送请求已提交"}
|
| 163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
@router.post("/api/users/register")
|
| 165 |
async def register_user(user: UserRegister):
|
| 166 |
users_db = db.load_data("users.json", default_data={})
|
| 167 |
|
| 168 |
+
cache_key = f"{user.email}_register" if user.email else f"{user.phone}_register"
|
|
|
|
| 169 |
cached = VERIFY_CODES.get(cache_key)
|
| 170 |
if not cached or cached["code"] != user.code or int(time.time()) > cached["expires_at"]:
|
| 171 |
raise HTTPException(status_code=400, detail="验证码不正确或已过期")
|
|
|
|
| 177 |
if user.intro and len(user.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
|
| 178 |
if user.account in users_db: raise HTTPException(status_code=400, detail="该账号已被注册")
|
| 179 |
for existing_user in users_db.values():
|
| 180 |
+
if user.email and existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
|
| 181 |
+
if user.phone and existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
|
| 182 |
|
| 183 |
+
VERIFY_CODES.pop(cache_key, None)
|
| 184 |
|
| 185 |
new_user = user.dict()
|
| 186 |
new_user.pop("code", None)
|