ZHIWEI666 commited on
Commit
d3ae963
·
verified ·
1 Parent(s): 7b74f04

新增验证码功能

Browse files
Files changed (2) hide show
  1. models.py +11 -1
  2. router_users.py +104 -3
models.py CHANGED
@@ -1,6 +1,12 @@
1
  from pydantic import BaseModel
2
  from typing import Optional
3
 
 
 
 
 
 
 
4
  class UserRegister(BaseModel):
5
  account: str
6
  password: str
@@ -8,6 +14,7 @@ class UserRegister(BaseModel):
8
  phone: str
9
  name: str
10
  gender: str
 
11
  avatarDataUrl: Optional[str] = None
12
  age: Optional[int] = None
13
  country: Optional[str] = None
@@ -28,8 +35,11 @@ class UserUpdate(BaseModel):
28
  avatarDataUrl: Optional[str] = None
29
 
30
  class PasswordReset(BaseModel):
31
- old_password: str
32
  new_password: str
 
 
 
33
 
34
  class InteractionToggle(BaseModel):
35
  item_id: str
 
1
  from pydantic import BaseModel
2
  from typing import Optional
3
 
4
+ # 【新增】:发送验证码的请求模型
5
+ class SendCodeRequest(BaseModel):
6
+ contact: str
7
+ contact_type: str # "email" 或 "phone"
8
+ action_type: str # "register" 或 "reset"
9
+
10
  class UserRegister(BaseModel):
11
  account: str
12
  password: str
 
14
  phone: str
15
  name: str
16
  gender: str
17
+ code: str # 【新增】:注册验证码
18
  avatarDataUrl: Optional[str] = None
19
  age: Optional[int] = None
20
  country: Optional[str] = None
 
35
  avatarDataUrl: Optional[str] = None
36
 
37
  class PasswordReset(BaseModel):
38
+ old_password: Optional[str] = None # 找回密码时此项可为空
39
  new_password: str
40
+ verify_contact: str # 【新增】:接收验证码的邮箱或手机号
41
+ verify_type: str # 【新增】:"email" 或 "phone"
42
+ code: str # 【新增】:重置验证码
43
 
44
  class InteractionToggle(BaseModel):
45
  item_id: str
router_users.py CHANGED
@@ -2,15 +2,95 @@
2
  from fastapi import APIRouter, HTTPException
3
  import time
4
  import re
 
 
 
 
5
  import 数据库连接 as db
6
  from notifications import add_notification
7
- from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowToggle, PrivacySettings
8
 
9
  router = APIRouter()
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  @router.post("/api/users/register")
12
  async def register_user(user: UserRegister):
13
  users_db = db.load_data("users.json", default_data={})
 
 
 
 
 
 
 
14
  if len(user.account) <= 5: raise HTTPException(status_code=400, detail="账号必须大于5个字符")
15
  if not re.match(r'^[a-zA-Z0-9_]{6,20}$', user.account): raise HTTPException(status_code=400, detail="账号仅支持大小写英文字母、数字及下划线")
16
  if len(user.password) < 6: raise HTTPException(status_code=400, detail="密码必须大于等于6个字符")
@@ -21,7 +101,11 @@ async def register_user(user: UserRegister):
21
  if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
22
  if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
23
 
 
 
 
24
  new_user = user.dict()
 
25
  new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
26
  users_db[user.account] = new_user
27
  db.save_data("users.json", users_db)
@@ -62,10 +146,27 @@ async def update_user_profile(account: str, update_data: UserUpdate):
62
  async def reset_password(account: str, pwd_data: PasswordReset):
63
  users_db = db.load_data("users.json", default_data={})
64
  if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  if len(pwd_data.new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
66
  if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', pwd_data.new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
67
- user = users_db[account]
68
- if user.get("password") != pwd_data.old_password: raise HTTPException(status_code=401, detail="原密错误")
 
 
69
  user["password"] = pwd_data.new_password
70
  db.save_data("users.json", users_db)
71
  return {"status": "success"}
 
2
  from fastapi import APIRouter, HTTPException
3
  import time
4
  import re
5
+ import random
6
+ import os
7
+ import smtplib
8
+ from email.mime.text import MIMEText
9
  import 数据库连接 as db
10
  from notifications import add_notification
11
+ from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowToggle, PrivacySettings, SendCodeRequest
12
 
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
+ """底层邮件发送引擎 (需在 HF Spaces 设置环境变量)"""
23
+ sender = os.environ.get("SMTP_USER")
24
+ pwd = os.environ.get("SMTP_PASS")
25
+ server = os.environ.get("SMTP_SERVER", "smtp.qq.com") # 默认使用 QQ 邮箱 SMTP
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 "修改/找回密码"
33
+ msg = MIMEText(f"【ComfyUI 社区精选】您的验证码是:{code},用于{action_str}。有效期 10 分钟。如非本人操作,请忽略此邮件。")
34
+ msg['Subject'] = f"ComfyUI 社区 - {action_str}验证码"
35
+ msg['From'] = sender
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
+ # TODO: 这里接入阿里云/腾讯云短信 SDK
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
+
64
+ # 存入缓存,设置 10 分钟过期
65
+ VERIFY_CODES[cache_key] = {
66
+ "code": code,
67
+ "expires_at": int(time.time()) + 600
68
+ }
69
+
70
+ if req.contact_type == "email":
71
+ send_email_code(req.contact, code, req.action_type)
72
+ elif req.contact_type == "phone":
73
+ 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"]:
92
+ raise HTTPException(status_code=400, detail="验证码不正确或已过期")
93
+
94
  if len(user.account) <= 5: raise HTTPException(status_code=400, detail="账号必须大于5个字符")
95
  if not re.match(r'^[a-zA-Z0-9_]{6,20}$', user.account): raise HTTPException(status_code=400, detail="账号仅支持大小写英文字母、数字及下划线")
96
  if len(user.password) < 6: raise HTTPException(status_code=400, detail="密码必须大于等于6个字符")
 
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
  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"]:
162
+ raise HTTPException(status_code=400, detail="验证码不正确或已过期")
163
+
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"}