一上午憋出一个注册功能。其实这算是练习自己的架构之类的软实力。在看文章之前,需要声明:你要拥有flask的基础知识,会一点点(真的一点点,不过不是很必要)的sql语句,额我想想..没了。那就开始吧!
首先分析要实现的功能:俩路由,一个路由支持get和post,get是返回注册的表单,post是接受表单。接受之后在后端向提交的表单(包含用户名,邮箱和密码)中的邮箱发送邮件,发的是一个正常人看不懂的URL,点进去之后告诉你欸注册好啦!
邮件发送
首先搞点简单的,发邮件的函数:
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
import smtplib
def send_email(text, receiver):
sender = 'xxx@163.com'
sender_title = "xxx网站机器人"
recipient = receiver
msg = MIMEText(text, 'plain', 'utf-8')
msg['Subject'] = Header("嗨!这里是xxx网站的机器人呀~", 'utf-8')
msg['From'] = formataddr((str(Header(sender_title, 'utf-8')), sender))
msg['To'] = recipient
server = smtplib.SMTP_SSL('smtp.163.com', 465)
server.login('xxx@163.com', 'SMTP token')
server.sendmail(sender, [recipient], msg.as_string())
server.quit()
其中俩xxx@163.com填的都是你网站机器人的邮箱,SMTP token需要你去网易邮箱(网页版)开SMTP服务,然后会告诉你这个token,别弄丢了。其他邮件服务都一样的流程,记得smtp.163.com要改(大部分邮箱都是smtp.domain.name)
正式开始
这些是我们要用到的库
from flask import render_template, Blueprint, request, redirect
由于代码比较长,所以我用蓝图隔离开
sign_blueprint = Blueprint("sign", __name__, template_folder="templates", url_prefix="/signin")
表单设置
首先来配置一下表单(如果使用的是前端自己写的话,可以跳过,不过flask_wtf还是比较推荐,具体可看我之前的一篇文章:juejin.cn/post/714582…
from flask_wtf import FlaskForm
from wtforms import StringField, EmailField, PasswordField
from wtforms.validators import DataRequired
class SignInForm(FlaskForm):
username = StringField(validators=[DataRequired()])
email = EmailField(validators=[DataRequired()])
pwd = PasswordField(validators=[DataRequired()])
SignInForm是我们自己定义的表单,其中的username是字符串,email是邮箱,pwd是密码。DataRequired是指必填字段(我发现如果不加DataRequired,单独使用EmailField,浏览器不会提示你要填邮箱,就很无语) (我写完才想到sign in是登录,不是注册hhh)
第一个路由的GET请求
python代码:
@sign_blueprint.route("/", methods=['GET', 'POST'])
def first_page():
#引入表单
form = SignInForm()
if request.method == "GET":
return render_template("sign.html", form=form)
HTML代码(仅展示表单部分)
<!--action后填写自己的路由,是蓝图路由+刚才那个路由-->
<!--我是直接找的模板,所以div和都可删,但是hutton和type="submit"一定不能动!-->
<!--如果你也找的模板,而input给了样式(比如class之类的)就在转义的form.xx后加括号,补上去(比如我这里的placeholder和class)-->
<form method="post" action="/signin">
<div>
<label>
{{ form.username(placeholder="用户名", class="form-element") }}
</label>
</div>
<div>
<label>
{{ form.email(class="form-element", placeholder="邮箱")}}
</label>
</div>
<div>
<label>
{{ form.pwd(class="form-element", placeholder="密码")}}
</label>
</div>
<div class="flex">
<button type="submit">注册</button>
<a href="mailto:peterrabbit2009@163.com">联系站长</a>
</div>
</form>
第一个路由的POST请求
注意:这里需要用到数据库了。cur是数据库的cursor,conn是连接数据库的连接(?)。register表格之后也会用到,负责存储提交了表单但是没去邮箱确认的人的信息。login表格是已经注册成功的人的信息,要在两个表格中查重。(两个表格均由用户名,邮箱和密码组成,但是这里不需要获取密码)
# else接的是上面flask的代码,处理POST请求
else:
# 获取表格内容
usn = form.username.data
email = form.email.data
pwd = form.pwd.data
# 开始查重
cur.execute("select username, email from login")
login_table = cur.fetchall()
# login_table示例:[(usn1, eml1), usn2, eml2)]
for tup in login_table:
if usn in tup:
return "用户名被占用"
elif email in tup:
return "邮箱被占用"
del login_table, tup
# 鬼知道为什么我要删login_table和tup
# 然后是注册表的查重
cur.execute("select username, email from register")
register_table = cur.fetchall()
print(register_table)
for tup in register_table:
if usn in tup:
return "用户名被占用"
elif email in tup:
return "邮箱被占用"
如何处理发至邮箱的链接?
然后是一些奇奇怪怪的玩意:我们实现的是要发给邮箱一个链接。这个链接一定要有唯一性。那么这里就取决于大家想怎么搞了。然后介绍一下我的方法(非常的低级甚至很拉):我将用户名,邮箱和密码弄成一个字典,字符串化然后转成bytes,bytes贴到一个路由下面。这样处理第二个路由的时候再把bytes转成字典然后处理。这部分代码直接不看也行。不过建议收藏,说不定以后就需要用了呢。先给出两个字符串和bytes转换的函数吧。
import base64
def bencode(encode_str):
"""
input str, output byte
"""
return base64.b64encode(encode_str.encode())
def bdecode(decode_str):
"""
input byte, output str
"""
return base64.b64decode(decode_str).decode()
然后是调用函数
dict={"username": usn, "email": email, "password": pwd}
str_dict=str(dict)
byte_dict=bencode(str_dict)
# bt是字符串,比如 "b'xxx'"
received_dict = eval((bdecode(eval(bt))))
usn = received_dict["username"]
email = received_dict["email"]
pwd = received_dict["password"]
简单吧,简单。但是搞了我半小时...
完成POST路由
dict={"username": usn, "email": email, "password": pwd}
str_dict=str(dict)
byte_dict=bencode(str_dict)
# 插入到register表格,之后进行验证
cur.execute("insert into register (username, email, password) values ('%s', '%s', '%s')" % (usn, email, pwd))
conn.commit()
# 给邮箱发邮件
link = "https://panwy.tech/signin/finish/"+str(byte_dict)
send_email是开头写的那个发邮件的函数
send_email("刚才去panwy.tech注册了是吧,去链接%s完成注册" % link, email)
return "请看邮件收件箱"
那么第一个路由写好了,接下来是第二个路由
第二个路由
从刚才的代码中看出验证邮箱的路由是在 /signin/finish/ 下面,所以就在这个路由下面写函数
# 这个<bt>的传参应该都知道,flask官方文档开头应该就有,bt是bytes,但是字符串
@sign_blueprint.route("/finish/<bt>")
def finish(bt):
# 如果发现bt无法转义,就到404
try:
received_dict = eval((bdecode(eval(bt))))
except:
return redirect("/404")
usn = received_dict["username"]
email = received_dict["email"]
pwd = received_dict["password"]
cur.execute("select * from register where username='%s' and email='%s' and password='%s'"%(usn, email, pwd))
# 如果没有这个用户,那么也到404
# cur.fetchall()是空列表,就是False,加一个not就是True
if not cur.fetchall():
return redirect("/404")
# 添加到登录表之中
cur.execute("insert into login (username, email, password) VALUES ('%s', '%s', '%s')" % (usn, email, pwd))
conn.commit()
# 从注册表之中删除
cur.execute("delete from register where email='%s'" % email)
conn.commit()
return "ok"
结束语
那么到这里就结束了,这种算是有一点大的工程还是比较有挑战性的(仅供参考!)