ZHIWEI666 commited on
Commit
5c607d7
·
verified ·
1 Parent(s): 9600d20

Delete router_users.py

Browse files
Files changed (1) hide show
  1. router_users.py +0 -367
router_users.py DELETED
@@ -1,367 +0,0 @@
1
- # router_users.py
2
- from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
3
- import time
4
- import re
5
- import random
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
14
-
15
- router = APIRouter()
16
-
17
- # ==========================================
18
- # 验证码内存缓存与发送引擎
19
- # ==========================================
20
- VERIFY_CODES = {}
21
-
22
- def send_email_code(to_email: str, code: str, action: str):
23
- """利用自动化 Webhook 作为 HTTPS 到 SMTP 的代理桥梁"""
24
- webhook_url = os.environ.get("MAKE_WEBHOOK_URL")
25
-
26
- if not webhook_url:
27
- print("警告: 未配置 MAKE_WEBHOOK_URL,跳过邮件发送")
28
- return
29
-
30
- action_str = "注册账号" if action == "register" else "修改/找回密码"
31
- subject = f"ComfyUI 社区 - {action_str}验证码"
32
-
33
- html_content = f"""
34
- <div style="background:#f9f9f9; padding:20px; font-family:sans-serif;">
35
- <div style="background:#fff; padding:20px; border-radius:8px; max-width:500px; margin:0 auto; box-shadow:0 2px 10px rgba(0,0,0,0.05);">
36
- <h2 style="color:#4CAF50; margin-top:0;">ComfyUI 社区精选</h2>
37
- <p>您好,</p>
38
- <p>您正在请求<strong>{action_str}</strong>,您的验证码是:</p>
39
- <div style="font-size:24px; font-weight:bold; color:#2196F3; background:#e3f2fd; padding:15px; text-align:center; border-radius:6px; letter-spacing: 5px;">{code}</div>
40
- <p style="color:#888; font-size:12px; margin-top:20px;">该验证码在 10 分钟内有效。如非本人操作,请忽略此邮件。</p>
41
- </div>
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'),
54
- headers={'Content-Type': 'application/json'}
55
- )
56
- try:
57
- with urllib.request.urlopen(req, timeout=10) as response:
58
- print(f"✅ 成功触发 Webhook 发送验证码 {code} 至 {to_email}")
59
- except Exception as e:
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="找回密码需先填写当前账号")
136
-
137
- users_db = db.load_data("users.json", default_data={})
138
- user = users_db.get(req.account)
139
- if not user:
140
- raise HTTPException(status_code=404, detail="该账号不存在")
141
-
142
- if req.contact_type == "email" and user.get("email") != req.contact:
143
- 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":
158
- bg_tasks.add_task(send_sms_code, req.contact, code, req.action_type)
159
- else:
160
- raise HTTPException(status_code=400, detail="不支持的验证方式")
161
-
162
- return {"status": "success", "message": "验证码发送请求已提交"}
163
-
164
-
165
- @router.post("/api/users/register")
166
- async def register_user(user: UserRegister):
167
- users_db = db.load_data("users.json", default_data={})
168
-
169
- # 🚀 核心修复 1:将查重逻辑移到最前面!一旦重复直接拦截,绝不会发生 500 崩溃
170
- if user.account in users_db:
171
- raise HTTPException(status_code=400, detail="该账号已被注册,请更换一个")
172
- for existing_user in users_db.values():
173
- if user.email and existing_user.get("email") == user.email:
174
- raise HTTPException(status_code=400, detail="此邮箱已注册,请直接登录或找回密码")
175
- if user.phone and existing_user.get("phone") == user.phone:
176
- raise HTTPException(status_code=400, detail="该手机号已被绑定")
177
-
178
- # 获取验证码缓存记录
179
- cache_key = f"{user.email}_register" if user.email else f"{user.phone}_register"
180
- cached = VERIFY_CODES.get(cache_key)
181
-
182
- # 🚀 核心修复 2:安全地获取过期时间,兼容新老写法,彻底消灭 KeyError 导致的 500
183
- expire_time = cached.get("expires_at", cached.get("expires", 0)) if cached else 0
184
-
185
- if not cached or cached["code"] != user.code or time.time() > expire_time:
186
- raise HTTPException(status_code=400, detail="验证码不正确或已过期")
187
-
188
- if len(user.account) <= 5: raise HTTPException(status_code=400, detail="账号必须大于5个字符")
189
- if not re.match(r'^[a-zA-Z0-9_]{6,20}$', user.account): raise HTTPException(status_code=400, detail="账号仅支持大小写英文字母、数字及下划线")
190
- if len(user.password) < 6: raise HTTPException(status_code=400, detail="密码必须大于等于6个字符")
191
- if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', user.password): raise HTTPException(status_code=400, detail="密码包含不支持的特殊字符")
192
- if user.intro and len(user.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
193
-
194
- VERIFY_CODES.pop(cache_key, None)
195
-
196
- new_user = user.dict()
197
- new_user.pop("code", None)
198
- new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
199
- users_db[user.account] = new_user
200
- db.save_data("users.json", users_db)
201
- return {"status": "success", "message": "注册成功", "data": {k: v for k, v in new_user.items() if k != "password"}}
202
-
203
-
204
- @router.post("/api/users/login")
205
- async def login_user(user: UserLogin):
206
- users_db = db.load_data("users.json", default_data={})
207
- if user.account not in users_db: raise HTTPException(status_code=404, detail="账号不存在")
208
- user_data = users_db[user.account]
209
- if user_data.get("password") != user.password: raise HTTPException(status_code=401, detail="密码错误")
210
- return {"status": "success", "token": f"mock_token_{user.account}", "account": user.account, "name": user_data["name"], "avatar": user_data.get("avatarDataUrl", "https://via.placeholder.com/150")}
211
-
212
-
213
- # ==========================================
214
- # 补充:发送验证码的 API 接口
215
- # ==========================================
216
- @router.post("/api/users/send_code")
217
- async def send_code_api(req: SendCodeRequest):
218
- code = str(random.randint(100000, 999999))
219
- key = f"{req.contact}_{req.action_type}"
220
-
221
- # 🚀 核心修复 3:统一改成 expires_at,与全局校验逻辑完美对齐
222
- VERIFY_CODES[key] = {
223
- "code": code,
224
- "expires_at": time.time() + 600
225
- }
226
-
227
- if req.contact_type == "email":
228
- try:
229
- send_email_code(req.contact, code, req.action_type)
230
- return {"status": "success", "message": "验证码已成功发送至邮箱"}
231
- except Exception as e:
232
- raise HTTPException(status_code=500, detail=f"邮件发送失败: {str(e)}")
233
-
234
- elif req.contact_type == "phone":
235
- return {"status": "success", "message": "验证码已成功发送至手机"}
236
-
237
- else:
238
- raise HTTPException(status_code=400, detail="不支持的验证方式")
239
-
240
-
241
- @router.get("/api/users/{account}")
242
- async def get_user_profile(account: str):
243
- users_db = db.load_data("users.json", default_data={})
244
- if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
245
- user_data = users_db[account]
246
- items_db = db.load_data("items.json", default_data=[])
247
- user_items = [item for item in items_db if item.get("author") == account]
248
- user_data["receivedLikes"] = sum(item.get("likes", 0) for item in user_items)
249
- user_data["receivedFavorites"] = sum(item.get("favorites", 0) for item in user_items)
250
- user_data["receivedUses"] = sum(item.get("uses", 0) for item in user_items)
251
- return {"status": "success", "data": {k: v for k, v in user_data.items() if k != "password"}}
252
-
253
- @router.put("/api/users/{account}")
254
- async def update_user_profile(account: str, update_data: UserUpdate):
255
- users_db = db.load_data("users.json", default_data={})
256
- if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
257
- if update_data.intro and len(update_data.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
258
- user = users_db[account]
259
- for k, v in update_data.dict(exclude_unset=True).items():
260
- if v is not None: user[k] = v
261
- db.save_data("users.json", users_db)
262
- return {"status": "success", "data": {k: v for k, v in user.items() if k != "password"}}
263
-
264
-
265
- # ==========================================
266
- # 🚀 究极重置密码接口:带智能纠错与透视报错
267
- # ==========================================
268
- @router.post("/api/users/reset_password")
269
- async def reset_password(request: Request):
270
- # 1. 万能解析器:兼容 JSON、双重字符串化、以及 FormData
271
- try:
272
- data = await request.json()
273
- if isinstance(data, str):
274
- data = json.loads(data)
275
- except:
276
- try:
277
- form = await request.form()
278
- data = dict(form)
279
- except:
280
- raise HTTPException(status_code=400, detail="请求数据解析失败,请检查网络")
281
-
282
- if not isinstance(data, dict):
283
- raise HTTPException(status_code=400, detail=f"前端数据格式异常,收到的是: {type(data).__name__}")
284
-
285
- # 2. 万能提取器 + 强力空格清洗
286
- account = data.get("account")
287
- new_password = data.get("new_password") or data.get("password")
288
- verify_contact = data.get("verifyContact") or data.get("verify_contact") or data.get("email") or data.get("phone")
289
- verify_type = data.get("verifyType") or data.get("verify_type") or data.get("contact_type")
290
-
291
- # 🚀 强力洗消:不管前端传什么脏数据,统统转字符串并去掉头尾空格
292
- code = str(data.get("code", "")).strip()
293
-
294
- if not all([account, new_password, verify_contact, verify_type, code]):
295
- raise HTTPException(status_code=400, detail="缺失必要参数 (账号/密码/验证码/联系方式),请检查表单")
296
-
297
- # 3. 核心业务逻辑
298
- users_db = db.load_data("users.json", default_data={})
299
- if account not in users_db: raise HTTPException(status_code=404, detail="该用户不存在")
300
- user = users_db[account]
301
-
302
- if verify_type == "email" and user.get("email") != verify_contact:
303
- raise HTTPException(status_code=400, detail="填写的邮箱与该账号绑定的邮箱不匹配")
304
- if verify_type == "phone" and user.get("phone") != verify_contact:
305
- raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不匹配")
306
-
307
- # ==========================================
308
- # 🚀 核心排雷:智能内存查找与透视报错
309
- # ==========================================
310
- cache_key = f"{verify_contact}_reset"
311
- cached = VERIFY_CODES.get(cache_key)
312
-
313
- # 如果精准匹配找不到,就去内存池里进行“模糊搜索”(解决前端 action_type 传参误差)
314
- if not cached:
315
- fallback_keys = [k for k in VERIFY_CODES.keys() if verify_contact in k]
316
- if fallback_keys:
317
- cache_key = fallback_keys[0]
318
- cached = VERIFY_CODES.get(cache_key)
319
- else:
320
- # 💥 透视眼:如果真找不到,直接把后端真实收到的邮箱弹出来给你看!
321
- raise HTTPException(status_code=400, detail=f"验证码内存已丢失!请重新点击发送。当前识别提取的邮箱是: [{verify_contact}]")
322
-
323
- expire_time = cached.get("expires_at", cached.get("expires", 0)) if cached else 0
324
-
325
- if time.time() > expire_time:
326
- raise HTTPException(status_code=400, detail="验证码已过期,请重新获取")
327
-
328
- if cached["code"] != code:
329
- # 💥 透视眼:如果输错了,直接把正确答案亮出来!
330
- raise HTTPException(status_code=400, detail=f"验证码不匹配!你输入的是 [{code}],系统记录的是 [{cached['code']}]")
331
-
332
- if len(new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
333
- if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
334
-
335
- VERIFY_CODES.pop(cache_key, None)
336
- user["password"] = new_password
337
- db.save_data("users.json", users_db)
338
-
339
- return {"status": "success", "message": "密码修改成功"}
340
-
341
-
342
- @router.post("/api/users/follow")
343
- async def toggle_follow(follow: FollowToggle):
344
- users_db = db.load_data("users.json", default_data={})
345
- if follow.target_account not in users_db or follow.user_id not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
346
- target_followers = users_db[follow.target_account].setdefault("followers", [])
347
- current_following = users_db[follow.user_id].setdefault("following", [])
348
- if follow.is_active:
349
- if follow.user_id not in target_followers:
350
- target_followers.append(follow.user_id)
351
- add_notification(follow.target_account, {"type": "follow", "from_user": follow.user_id})
352
- if follow.target_account not in current_following: current_following.append(follow.target_account)
353
- else:
354
- if follow.user_id in target_followers: target_followers.remove(follow.user_id)
355
- if follow.target_account in current_following: current_following.remove(follow.target_account)
356
- db.save_data("users.json", users_db)
357
- return {"status": "success"}
358
-
359
- @router.put("/api/users/{account}/privacy")
360
- async def update_privacy(account: str, privacy: PrivacySettings):
361
- users_db = db.load_data("users.json", default_data={})
362
- if account not in users_db:
363
- raise HTTPException(status_code=404, detail="用户不存在")
364
-
365
- users_db[account]["privacy"] = privacy.dict()
366
- db.save_data("users.json", users_db)
367
- return {"status": "success"}