Spaces:
Running
Running
Upload router_users.py
Browse files- router_users.py +21 -27
router_users.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# router_users.py
|
| 2 |
-
from fastapi import APIRouter, HTTPException
|
| 3 |
import time
|
| 4 |
import re
|
| 5 |
import random
|
|
@@ -13,20 +13,19 @@ from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowTog
|
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
# ==========================================
|
| 16 |
-
#
|
| 17 |
# ==========================================
|
| 18 |
-
# 结构: {"contact_action": {"code": "123456", "expires_at": 1690000000}}
|
| 19 |
VERIFY_CODES = {}
|
| 20 |
|
| 21 |
def send_email_code(to_email: str, code: str, action: str):
|
| 22 |
-
"""底层邮件发送引擎
|
| 23 |
sender = os.environ.get("SMTP_USER")
|
| 24 |
pwd = os.environ.get("SMTP_PASS")
|
| 25 |
-
server = os.environ.get("SMTP_SERVER", "smtp.qq.com")
|
| 26 |
port = int(os.environ.get("SMTP_PORT", 465))
|
| 27 |
|
| 28 |
if not sender or not pwd:
|
| 29 |
-
print("警告: 未配置 SMTP_USER 或 SMTP_PASS
|
| 30 |
return
|
| 31 |
|
| 32 |
action_str = "注册账号" if action == "register" else "修改/找回密码"
|
|
@@ -36,28 +35,28 @@ def send_email_code(to_email: str, code: str, action: str):
|
|
| 36 |
msg['To'] = to_email
|
| 37 |
|
| 38 |
try:
|
|
|
|
| 39 |
if port == 465:
|
| 40 |
-
with smtplib.SMTP_SSL(server, port) as smtp:
|
| 41 |
smtp.login(sender, pwd)
|
| 42 |
smtp.send_message(msg)
|
| 43 |
else:
|
| 44 |
-
with smtplib.SMTP(server, port) as smtp:
|
| 45 |
smtp.starttls()
|
| 46 |
smtp.login(sender, pwd)
|
| 47 |
smtp.send_message(msg)
|
|
|
|
| 48 |
except Exception as e:
|
| 49 |
-
print(f"邮件发送失败: {e}")
|
| 50 |
-
raise HTTPException(status_code=500, detail="邮件下发失败,请检查云端 SMTP 配置")
|
| 51 |
|
| 52 |
def send_sms_code(phone: str, code: str, action: str):
|
| 53 |
"""短信发送接口挂载点"""
|
| 54 |
-
|
| 55 |
-
print(f"[模拟短信发送] 手机号: {phone}, 验证码: {code}, 动作: {action}")
|
| 56 |
pass
|
| 57 |
|
|
|
|
| 58 |
@router.post("/api/users/send-code")
|
| 59 |
-
async def send_verify_code(req: SendCodeRequest):
|
| 60 |
-
# 生成 6 位纯数字验证码
|
| 61 |
code = str(random.randint(100000, 999999))
|
| 62 |
cache_key = f"{req.contact}_{req.action_type}"
|
| 63 |
|
|
@@ -67,25 +66,26 @@ async def send_verify_code(req: SendCodeRequest):
|
|
| 67 |
"expires_at": int(time.time()) + 600
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
if req.contact_type == "email":
|
| 71 |
-
|
| 72 |
elif req.contact_type == "phone":
|
| 73 |
-
|
| 74 |
else:
|
| 75 |
raise HTTPException(status_code=400, detail="不支持的验证方式")
|
| 76 |
|
| 77 |
-
return {"status": "success", "message": "验证码
|
| 78 |
|
| 79 |
|
| 80 |
# ==========================================
|
| 81 |
-
# 原有业务接口
|
| 82 |
# ==========================================
|
| 83 |
|
| 84 |
@router.post("/api/users/register")
|
| 85 |
async def register_user(user: UserRegister):
|
| 86 |
users_db = db.load_data("users.json", default_data={})
|
| 87 |
|
| 88 |
-
#
|
| 89 |
cache_key = f"{user.email}_register"
|
| 90 |
cached = VERIFY_CODES.get(cache_key)
|
| 91 |
if not cached or cached["code"] != user.code or int(time.time()) > cached["expires_at"]:
|
|
@@ -101,11 +101,10 @@ async def register_user(user: UserRegister):
|
|
| 101 |
if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
|
| 102 |
if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
|
| 103 |
|
| 104 |
-
# 验证通过,销毁验证码
|
| 105 |
-
VERIFY_CODES.pop(cache_key, None)
|
| 106 |
|
| 107 |
new_user = user.dict()
|
| 108 |
-
new_user.pop("code", None)
|
| 109 |
new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
|
| 110 |
users_db[user.account] = new_user
|
| 111 |
db.save_data("users.json", users_db)
|
|
@@ -146,16 +145,13 @@ async def update_user_profile(account: str, update_data: UserUpdate):
|
|
| 146 |
async def reset_password(account: str, pwd_data: PasswordReset):
|
| 147 |
users_db = db.load_data("users.json", default_data={})
|
| 148 |
if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
|
| 149 |
-
|
| 150 |
user = users_db[account]
|
| 151 |
|
| 152 |
-
# 【新增】:校验账号与提供的联系方式是否匹配,防止通过自己的邮箱改别人的密码
|
| 153 |
if pwd_data.verify_type == "email" and user.get("email") != pwd_data.verify_contact:
|
| 154 |
raise HTTPException(status_code=400, detail="填写的邮箱与该账号绑定的邮箱不匹配")
|
| 155 |
if pwd_data.verify_type == "phone" and user.get("phone") != pwd_data.verify_contact:
|
| 156 |
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不匹配")
|
| 157 |
|
| 158 |
-
# 【新增】:校验重置验证码
|
| 159 |
cache_key = f"{pwd_data.verify_contact}_reset"
|
| 160 |
cached = VERIFY_CODES.get(cache_key)
|
| 161 |
if not cached or cached["code"] != pwd_data.code or int(time.time()) > cached["expires_at"]:
|
|
@@ -164,9 +160,7 @@ async def reset_password(account: str, pwd_data: PasswordReset):
|
|
| 164 |
if len(pwd_data.new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
|
| 165 |
if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', pwd_data.new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
|
| 166 |
|
| 167 |
-
# 验证通过,销毁验证码
|
| 168 |
VERIFY_CODES.pop(cache_key, None)
|
| 169 |
-
|
| 170 |
user["password"] = pwd_data.new_password
|
| 171 |
db.save_data("users.json", users_db)
|
| 172 |
return {"status": "success"}
|
|
|
|
| 1 |
# router_users.py
|
| 2 |
+
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
| 3 |
import time
|
| 4 |
import re
|
| 5 |
import random
|
|
|
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
# ==========================================
|
| 16 |
+
# 验证码内存缓存与发送引擎
|
| 17 |
# ==========================================
|
|
|
|
| 18 |
VERIFY_CODES = {}
|
| 19 |
|
| 20 |
def send_email_code(to_email: str, code: str, action: str):
|
| 21 |
+
"""底层邮件发送引擎"""
|
| 22 |
sender = os.environ.get("SMTP_USER")
|
| 23 |
pwd = os.environ.get("SMTP_PASS")
|
| 24 |
+
server = os.environ.get("SMTP_SERVER", "smtp.qq.com")
|
| 25 |
port = int(os.environ.get("SMTP_PORT", 465))
|
| 26 |
|
| 27 |
if not sender or not pwd:
|
| 28 |
+
print("警告: 未配置 SMTP_USER 或 SMTP_PASS,跳过邮件发送")
|
| 29 |
return
|
| 30 |
|
| 31 |
action_str = "注册账号" if action == "register" else "修改/找回密码"
|
|
|
|
| 35 |
msg['To'] = to_email
|
| 36 |
|
| 37 |
try:
|
| 38 |
+
# 【核心优化】:加入 timeout=10 防卡死机制
|
| 39 |
if port == 465:
|
| 40 |
+
with smtplib.SMTP_SSL(server, port, timeout=10) as smtp:
|
| 41 |
smtp.login(sender, pwd)
|
| 42 |
smtp.send_message(msg)
|
| 43 |
else:
|
| 44 |
+
with smtplib.SMTP(server, port, timeout=10) as smtp:
|
| 45 |
smtp.starttls()
|
| 46 |
smtp.login(sender, pwd)
|
| 47 |
smtp.send_message(msg)
|
| 48 |
+
print(f"✅ 成功发送验证码 {code} 至 {to_email}")
|
| 49 |
except Exception as e:
|
| 50 |
+
print(f"❌ 邮件发送失败: {e}")
|
|
|
|
| 51 |
|
| 52 |
def send_sms_code(phone: str, code: str, action: str):
|
| 53 |
"""短信发送接口挂载点"""
|
| 54 |
+
print(f"[模拟短信] 手机号: {phone}, 验证码: {code}")
|
|
|
|
| 55 |
pass
|
| 56 |
|
| 57 |
+
# 【核心优化】:引入 BackgroundTasks
|
| 58 |
@router.post("/api/users/send-code")
|
| 59 |
+
async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
|
|
|
| 60 |
code = str(random.randint(100000, 999999))
|
| 61 |
cache_key = f"{req.contact}_{req.action_type}"
|
| 62 |
|
|
|
|
| 66 |
"expires_at": int(time.time()) + 600
|
| 67 |
}
|
| 68 |
|
| 69 |
+
# 放入后台队列执行,立刻给前端返回成功响应
|
| 70 |
if req.contact_type == "email":
|
| 71 |
+
bg_tasks.add_task(send_email_code, req.contact, code, req.action_type)
|
| 72 |
elif req.contact_type == "phone":
|
| 73 |
+
bg_tasks.add_task(send_sms_code, req.contact, code, req.action_type)
|
| 74 |
else:
|
| 75 |
raise HTTPException(status_code=400, detail="不支持的验证方式")
|
| 76 |
|
| 77 |
+
return {"status": "success", "message": "验证码发送请求已提交"}
|
| 78 |
|
| 79 |
|
| 80 |
# ==========================================
|
| 81 |
+
# 原有业务接口
|
| 82 |
# ==========================================
|
| 83 |
|
| 84 |
@router.post("/api/users/register")
|
| 85 |
async def register_user(user: UserRegister):
|
| 86 |
users_db = db.load_data("users.json", default_data={})
|
| 87 |
|
| 88 |
+
# 校验注册验证码
|
| 89 |
cache_key = f"{user.email}_register"
|
| 90 |
cached = VERIFY_CODES.get(cache_key)
|
| 91 |
if not cached or cached["code"] != user.code or int(time.time()) > cached["expires_at"]:
|
|
|
|
| 101 |
if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
|
| 102 |
if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
|
| 103 |
|
| 104 |
+
VERIFY_CODES.pop(cache_key, None) # 验证通过,销毁验证码
|
|
|
|
| 105 |
|
| 106 |
new_user = user.dict()
|
| 107 |
+
new_user.pop("code", None)
|
| 108 |
new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
|
| 109 |
users_db[user.account] = new_user
|
| 110 |
db.save_data("users.json", users_db)
|
|
|
|
| 145 |
async def reset_password(account: str, pwd_data: PasswordReset):
|
| 146 |
users_db = db.load_data("users.json", default_data={})
|
| 147 |
if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
|
|
|
|
| 148 |
user = users_db[account]
|
| 149 |
|
|
|
|
| 150 |
if pwd_data.verify_type == "email" and user.get("email") != pwd_data.verify_contact:
|
| 151 |
raise HTTPException(status_code=400, detail="填写的邮箱与该账号绑定的邮箱不匹配")
|
| 152 |
if pwd_data.verify_type == "phone" and user.get("phone") != pwd_data.verify_contact:
|
| 153 |
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不匹配")
|
| 154 |
|
|
|
|
| 155 |
cache_key = f"{pwd_data.verify_contact}_reset"
|
| 156 |
cached = VERIFY_CODES.get(cache_key)
|
| 157 |
if not cached or cached["code"] != pwd_data.code or int(time.time()) > cached["expires_at"]:
|
|
|
|
| 160 |
if len(pwd_data.new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
|
| 161 |
if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', pwd_data.new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
|
| 162 |
|
|
|
|
| 163 |
VERIFY_CODES.pop(cache_key, None)
|
|
|
|
| 164 |
user["password"] = pwd_data.new_password
|
| 165 |
db.save_data("users.json", users_db)
|
| 166 |
return {"status": "success"}
|