邮件验证码搞定

This commit is contained in:
2025-11-21 09:53:16 +08:00
parent 3e598fe0a1
commit d755f4710f
3 changed files with 104 additions and 4 deletions

View File

@@ -24,6 +24,10 @@
<input id="code" name="code" type="text" required /> <input id="code" name="code" type="text" required />
<label for="email">邮箱</label> <label for="email">邮箱</label>
<input id="email" name="email" type="email" required /> <input id="email" name="email" type="email" required />
<button id="sendCodeBtn" type="button">发送验证码</button>
<div id="sendMsg" class="hint"></div>
<label for="email_code">邮箱验证码</label>
<input id="email_code" name="email_code" type="text" required />
<label for="username">用户名</label> <label for="username">用户名</label>
<input id="username" name="username" type="text" required /> <input id="username" name="username" type="text" required />
<label for="password">密码</label> <label for="password">密码</label>
@@ -43,20 +47,36 @@
const code=(document.getElementById('code').value||'').trim(); const code=(document.getElementById('code').value||'').trim();
const email=(document.getElementById('email').value||'').trim(); const email=(document.getElementById('email').value||'').trim();
const username=(document.getElementById('username').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 password=document.getElementById('password').value||'';
const confirm=document.getElementById('confirm').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;} if(password!==confirm){err.textContent='两次密码不一致';return;}
const btn=document.getElementById('regBtn'); btn.disabled=true; const btn=document.getElementById('regBtn'); btn.disabled=true;
try{ try{
const csrftoken=getCookie('csrftoken'); 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(); const data=await resp.json();
if(!resp.ok||!data.ok){throw new Error(data.message||'注册失败');} if(!resp.ok||!data.ok){throw new Error(data.message||'注册失败');}
window.location.href=data.redirect_url; window.location.href=data.redirect_url;
}catch(e){err.textContent=e.message||'发生错误';} }catch(e){err.textContent=e.message||'发生错误';}
finally{btn.disabled=false;} 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;}
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -11,4 +11,5 @@ urlpatterns = [
path("logout/", views.logout, name="logout"), path("logout/", views.logout, name="logout"),
path("register/", views.register_page, name="register"), path("register/", views.register_page, name="register"),
path("register/submit/", views.register_submit, name="register_submit"), path("register/submit/", views.register_submit, name="register_submit"),
path("email/send-code/", views.send_email_code, name="send_email_code"),
] ]

View File

@@ -4,6 +4,8 @@ import os
import io import io
import random import random
import string import string
import time
import smtplib
from django.http import JsonResponse, HttpResponseBadRequest from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
@@ -183,10 +185,22 @@ def register_submit(request):
return HttpResponseBadRequest("Invalid JSON") return HttpResponseBadRequest("Invalid JSON")
code = (payload.get("code") or "").strip() code = (payload.get("code") or "").strip()
email = (payload.get("email") or "").strip() email = (payload.get("email") or "").strip()
email_code = (payload.get("email_code") or "").strip()
username = (payload.get("username") or "").strip() username = (payload.get("username") or "").strip()
password = (payload.get("password") or "") 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") 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) rc = get_registration_code(code)
if not rc: if not rc:
return JsonResponse({"ok": False, "message": "注册码无效"}, status=400) return JsonResponse({"ok": False, "message": "注册码无效"}, status=400)
@@ -217,4 +231,69 @@ def register_submit(request):
}) })
if not ok: if not ok:
return JsonResponse({"ok": False, "message": "注册失败"}, status=500) return JsonResponse({"ok": False, "message": "注册失败"}, status=500)
return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"}) 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)