75%原生flask实现注册功能

1,072 阅读5分钟

一上午憋出一个注册功能。其实这算是练习自己的架构之类的软实力。在看文章之前,需要声明:你要拥有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"

结束语

那么到这里就结束了,这种算是有一点大的工程还是比较有挑战性的(仅供参考!)