From d755f4710ff771eefb2a0424ed6e754c8c198c69 Mon Sep 17 00:00:00 2001 From: spdis Date: Fri, 21 Nov 2025 09:53:16 +0800 Subject: [PATCH] =?UTF-8?q?=E9=82=AE=E4=BB=B6=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=90=9E=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/templates/accounts/register.html | 24 ++++++- accounts/urls.py | 1 + accounts/views.py | 83 ++++++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/accounts/templates/accounts/register.html b/accounts/templates/accounts/register.html index 791cbe9..263d2e8 100644 --- a/accounts/templates/accounts/register.html +++ b/accounts/templates/accounts/register.html @@ -24,6 +24,10 @@ + +
+ + @@ -43,20 +47,36 @@ const code=(document.getElementById('code').value||'').trim(); const email=(document.getElementById('email').value||'').trim(); const username=(document.getElementById('username').value||'').trim(); + const email_code=(document.getElementById('email_code').value||'').trim(); const password=document.getElementById('password').value||''; const confirm=document.getElementById('confirm').value||''; - if(!code||!email||!username||!password){err.textContent='请填写所有字段';return;} + if(!code||!email||!email_code||!username||!password){err.textContent='请填写所有字段';return;} if(password!==confirm){err.textContent='两次密码不一致';return;} const btn=document.getElementById('regBtn'); btn.disabled=true; try{ const csrftoken=getCookie('csrftoken'); - const resp=await fetch('/accounts/register/submit/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({code,email,username,password})}); + const resp=await fetch('/accounts/register/submit/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({code,email,email_code,username,password})}); const data=await resp.json(); if(!resp.ok||!data.ok){throw new Error(data.message||'注册失败');} window.location.href=data.redirect_url; }catch(e){err.textContent=e.message||'发生错误';} finally{btn.disabled=false;} }); + document.getElementById('sendCodeBtn').addEventListener('click',async()=>{ + const email=(document.getElementById('email').value||'').trim(); + const msg=document.getElementById('sendMsg'); + msg.textContent=''; + if(!email){msg.textContent='请输入邮箱';return;} + const btn=document.getElementById('sendCodeBtn'); btn.disabled=true; + try{ + const csrftoken=getCookie('csrftoken'); + const resp=await fetch('/accounts/email/send-code/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({email})}); + const data=await resp.json(); + if(!resp.ok||!data.ok){throw new Error(data.message||'发送失败');} + msg.textContent='验证码已发送,请查收邮件'; + }catch(e){msg.textContent=e.message||'发送失败';} + finally{btn.disabled=false;} + }); \ No newline at end of file diff --git a/accounts/urls.py b/accounts/urls.py index d28836f..2499fb5 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -11,4 +11,5 @@ urlpatterns = [ path("logout/", views.logout, name="logout"), path("register/", views.register_page, name="register"), path("register/submit/", views.register_submit, name="register_submit"), + path("email/send-code/", views.send_email_code, name="send_email_code"), ] \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index b98592f..ce2f0ff 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -4,6 +4,8 @@ import os import io import random import string +import time +import smtplib from django.http import JsonResponse, HttpResponseBadRequest from django.shortcuts import render, redirect @@ -183,10 +185,22 @@ def register_submit(request): return HttpResponseBadRequest("Invalid JSON") code = (payload.get("code") or "").strip() email = (payload.get("email") or "").strip() + email_code = (payload.get("email_code") or "").strip() username = (payload.get("username") or "").strip() password = (payload.get("password") or "") - if not code or not email or not username or not password: + if not code or not email or not email_code or not username or not password: return HttpResponseBadRequest("Missing fields") + v = request.session.get("email_verify") or {} + if (v.get("email") or "") != email: + return JsonResponse({"ok": False, "message": "请先验证邮箱"}, status=400) + try: + exp_ts = int(v.get("expires_at") or 0) + except Exception: + exp_ts = 0 + if exp_ts < int(time.time()): + return JsonResponse({"ok": False, "message": "验证码已过期"}, status=400) + if (v.get("code") or "") != email_code: + return JsonResponse({"ok": False, "message": "邮箱验证码错误"}, status=400) rc = get_registration_code(code) if not rc: return JsonResponse({"ok": False, "message": "注册码无效"}, status=400) @@ -217,4 +231,69 @@ def register_submit(request): }) if not ok: return JsonResponse({"ok": False, "message": "注册失败"}, status=500) - return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"}) \ No newline at end of file + try: + if "email_verify" in request.session: + del request.session["email_verify"] + except Exception: + pass + return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"}) + +@require_http_methods(["POST"]) +@csrf_protect +def send_email_code(request): + try: + payload = json.loads(request.body.decode("utf-8")) + except json.JSONDecodeError: + return HttpResponseBadRequest("Invalid JSON") + email = (payload.get("email") or "").strip() + if not email: + return HttpResponseBadRequest("Missing email") + if "@" not in email: + return JsonResponse({"ok": False, "message": "邮箱格式不正确"}, status=400) + verify_code = "".join(random.choice(string.digits) for _ in range(6)) + ttl = int(os.environ.get("SMTP_CODE_TTL", "600") or 600) + request.session["email_verify"] = {"email": email, "code": verify_code, "expires_at": int(time.time()) + max(60, ttl)} + ok, msg = _send_smtp_email(email, verify_code) + if not ok: + return JsonResponse({"ok": False, "message": msg or "验证码发送失败"}, status=500) + return JsonResponse({"ok": True}) + +def _send_smtp_email(to_email: str, code: str): + host = os.environ.get("SMTP_HOST", "") + port_raw = os.environ.get("SMTP_PORT", "") + try: + port = int(port_raw) if port_raw else 0 + except Exception: + port = 0 + user = os.environ.get("SMTP_USERNAME") or os.environ.get("SMTP_USER") or "" + password = os.environ.get("SMTP_PASSWORD", "") + use_tls = str(os.environ.get("SMTP_USE_TLS", "")).lower() in ("1", "true", "yes") + use_ssl = str(os.environ.get("SMTP_USE_SSL", "")).lower() in ("1", "true", "yes") + sender = os.environ.get("SMTP_FROM_EMAIL") or os.environ.get("SMTP_FROM") or user or "" + subject = os.environ.get("SMTP_SUBJECT") or "邮箱验证码" + if not host or not port or not sender: + return False, "缺少SMTP配置" + body = f"您的验证码是:{code},10分钟内有效。" + msg = f"From: {sender}\r\nTo: {to_email}\r\nSubject: {subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n{body}" + try: + if use_ssl: + server = smtplib.SMTP_SSL(host, port) + else: + server = smtplib.SMTP(host, port) + server.ehlo() + if use_tls and not use_ssl: + server.starttls() + server.ehlo() + if user and password: + server.login(user, password) + server.sendmail(sender, [to_email], msg.encode("utf-8")) + try: + server.quit() + except Exception: + try: + server.close() + except Exception: + pass + return True, "" + except Exception as e: + return False, str(e) \ No newline at end of file