Flask項目實戰——4—(郵箱的驗證碼發送、Redis數據庫存儲驗證碼、郵箱修改功能、權限管理)

1、郵箱的驗證碼發送

郵箱的url_for綁定

重定向郵箱修改的url,綁定url和html、樣式文件
在這裏插入圖片描述
添加修改郵箱的html頁面文件:
修改密碼文件:templates/cms/cms_resetemail.html

<!--  繼承模板文件cms/cms_base.html  簡化代碼 -->
{% extends 'cms/cms_base.html' %}

<!-- 頁面標題 -->
{% block title %}
    修改郵箱
{% endblock %}

<!--  標題  -->
{% block page_title %}
    {{self.title()}}
{% endblock %}

<!--  寫修改密碼的form表單格式  -->
{% block head %}
<style>
    .form-container{
        width:300px;
    }
</style>

<!--  關聯本地的resetemail.js樣式 -->
<script src="{{ url_for('static', filename='cms/js/resetemail.js') }}"></script>

{% endblock %}

<!-- 只有form表單信息不一樣 -->
{% block content %}
    <form action="" method="post">
        <div class="form-container">
            <div class="form-group">
                <div class="input-group">
                    <input type="email" class="form-control" name="email" placeholder="新郵箱">
                    <span class="input-group-addon" id="captcha-btn" style="cursor:pointer;">獲取驗證碼</span>
                </div>
            </div>
            <div class="form-group">
                <input type="text" class="form-control" name="captcha" placeholder="郵箱驗證碼">
            </div>
            <div class="form-group">
                <button class="btn btn-primary" id="submit">立即修改</button>
            </div>
        </div>
    </form>
{% endblock %}

修改密碼的樣式文件:static/cms/js/resetemail.js

var lgajax = {
    'get':function(args) {
        args['method'] = 'get';
        this.ajax(args);
    },
    'post':function(args) {
        args['method'] = 'post';
        this.ajax(args);
    },
    'ajax':function(args) {
        // 設置csrftoken
        this._ajaxSetup();
        $.ajax(args);
    },
    '_ajaxSetup': function() {
        $.ajaxSetup({
            'beforeSend':function(xhr,settings) {
                if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                    var csrftoken = $('meta[name=csrf-token]').attr('content');
                    xhr.setRequestHeader("X-CSRFToken", csrftoken)
                }
            }
        });
    }
};
$(function () {
    $("#captcha-btn").click(function (event) {
        event.preventDefault();
        var email = $("input[name='email']").val();
        if(!email){
            lgalert.alertInfoToast('請輸入郵箱');
            return;
        }
        var lgajax = {
            'get':function(args) {
                args['method'] = 'get';
                this.ajax(args);
            },
            'post':function(args) {
                args['method'] = 'post';
                this.ajax(args);
            },
            'ajax':function(args) {
                // 設置csrftoken
                this._ajaxSetup();
                $.ajax(args);
            },
            '_ajaxSetup': function() {
                $.ajaxSetup({
                    'beforeSend':function(xhr,settings) {
                        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                            var csrftoken = $('meta[name=csrf-token]').attr('content');
                            xhr.setRequestHeader("X-CSRFToken", csrftoken)
                        }
                    }
                });
            }
        };
        lgajax.get({                         // 這裏ajax的方法是get方法
            'url': '/cms/email_captcha/',         // 該url需要與視圖函數中的路由相同
            'data': {
                'email': email
            },
            'success': function (data) {
                if(data['code'] == 200){
                    lgalert.alertSuccessToast('郵件發送成功!請注意查收!');
                }else{
                    lgalert.alertInfo(data['message']);
                }
            },
            'fail': function (error) {
                lgalert.alertNetworkError();
            }
        });
    });
});

$(function () {
    $("#submit").click(function (event) {
        event.preventDefault();
        var emailE = $("input[name='email']");
        var captchaE = $("input[name='captcha']");

        var email = emailE.val();
        var captcha = captchaE.val();

        lgajax.post({
            'url': '/cms/resetemail/',
            'data': {
                'email': email,
                'captcha': captcha
            },
            'success': function (data) {
                if(data['code'] == 200){
                    emailE.val("");
                    captchaE.val("");
                    lgalert.alertSuccessToast('恭喜!郵箱修改成功!');
                }else{
                    lgalert.alertInfo(data['message']);
                }
            },
            'fail': function (error) {
                lgalert.alertNetworkError();
            }
        });
    });
});

修改模板文件中的url_for重定向到修改密碼的url:
基礎模板文件cms_base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--  在頭文件中接收csrf信息  -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{% block title %}

    {% endblock %}</title>
    <script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

<!--  關聯本地的cms_base.css樣式 後臺管理界面CMS的樣式 -->
    <link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<!--  關聯本地的cms_base.js樣式 後臺管理界面CMS的樣式 -->
    <script src="{{ url_for('static', filename='cms/js/cms_base.js') }}"></script>

<!--  提示框的靜態資源文件  -->
    <link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
<!-- 關聯提示框的js樣式  -->
    <script src="{{ url_for('static', filename='common/sweetalert/lgalert.js') }}"></script>
    <script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}"></script>

<!--  預留空間,給之後的html文件進行修改調整  -->
    {% block head %}

    {% endblock %}

</head>
<body>
     <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">論壇CMS管理系統</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav navbar-right">
        <!--       從數據庫中調用用戶名,g對象全局調用g.cms_user對象  .username是該對象的一個字段屬性      -->
            <li><a href="#">{{ g.cms_user.username }}<span>[超級管理員]</span></a></li>

        <!--  用戶註銷,關聯到views.py中的@cms_bp.route("/logout/")路由,重定向到該路由      -->
            <li><a href="{{ url_for('cms.logout') }}">註銷</a></li>
          </ul>
          <form class="navbar-form navbar-right">
            <input type="text" class="form-control" placeholder="查找...">
          </form>
        </div>
      </div>
    </nav>

    <div class="container-fluid">
      <div class="row">
          <div class="col-sm-3 col-md-2 sidebar">
              <ul class="nav-sidebar">
                <li class="unfold"><a href="#">首頁</a></li>
                <li class="profile-li">
                    <a href="#">個人中心<span></span></a>
                    <ul class="subnav">

                        <!--          url重定向到/cms/profile/下   路由在views.py中定義了       -->
                        <li><a href="{{ url_for('cms.profile') }}">個人信息</a></li>
                        <!--         密碼修改的url_for 重定向到/cms/resetpwd/  路由在views.py中定義了           -->
                        <li><a href="{{ url_for('cms.resetpwd') }}">修改密碼</a></li>
                        <!--         重定向到修改郵箱的url_for=/cms/resetemail/        -->
                        <li><a href="{{ url_for('cms.resetemail') }}">修改郵箱</a></li>
                    </ul>
                </li>

                <li class="nav-group post-manage"><a href="#">帖子管理</a></li>
                <li class="comments-manage"><a href="#">評論管理</a></li>
                <li class="board-manage"><a href="#">板塊管理</a></li>

                <li class="nav-group user-manage"><a href="#">用戶管理</a></li>
                <li class="role-manage"><a href="#">組管理</a></li>

                <li class="nav-group cmsuser-manage"><a href="#">CMS用戶管理</a></li>
                <li class="cmsrole-manage"><a href="#">CMS組管理</a></li>
            </ul>
          </div>
          <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
            <h1>{% block page_title %}

            {% endblock %}</h1>
            <div class="main_content">
                {% block content %}

                {% endblock %}
            </div>
          </div>
      </div>
    </div>
</body>
</html>

通過視圖文件apps/cms/views.py文件定義修改郵箱的類視圖函數進行驗證
視圖文件apps/cms/views.py文件

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen

"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                  # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,   # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db                               # 數據庫中更新密碼、郵箱等使用

# 導入form表單   .forms代表同級目錄下的forms.py       ResetPwdForm修改密碼的form信息
from .forms import LoginForm, ResetPwdForm

# 導入模型  .models代表同級目錄下的models.py
from .models import CMS_User

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數
from utils import restful

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')       # 登陸之後進入CMS後臺管理界面,路徑寫全cms/cms_index.html


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        pass


# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

實現效果如圖:
在這裏插入圖片描述

Flask-Mail發送郵件

Flask虛擬環境安裝 Flask-Mail

pip install Flask-Mail

在這裏插入圖片描述
根據:Flask-Mail網頁介紹
添加配置文件中的配置:config.py

# -*- encoding: utf-8 -*-
"""
@File    : config.py
@Time    : 2020/5/11 10:08
@Author  : chen

"""
import os    # 導入隨機字符串用於加密session

# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo_bbs"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

'''
# 創建引擎並生成Base類
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
SQLALCHEMY_DATABASE_URI = DB_URL           # 數據庫連接成功

# FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.
# Set it to True or False to suppress this warning.'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
# 這裏是爲了解決上面的警告
SQLALCHEMY_TRACK_MODIFICATIONS = False


SECRET_KEY = os.urandom(15)        # 產生隨機15位字符串

# flask-mail配置信息
MAIL_SERVER = 'smtp.qq.com'       # 發送郵箱的服務地址  這裏設置爲QQ郵箱服務器地址
MAIL_PORT = '587'                 # 發送端口爲465或者587
MAIL_USE_TLS = True               # 端口爲587對應的服務
# MAIL_USE_SSL = True             # 端口爲465對應的服務  二選一即可

MAIL_USERNAME = '[email protected]'   # 使用者的郵箱
MAIL_PASSWORD = 'lzpihkhrsbhqbajd'   # 不是QQ郵箱登錄密碼,是QQ郵箱授權碼獲取,用於第三方登錄驗證
MAIL_DEFAULT_SENDER = '[email protected]'   # 默認發送者,暫時先設置爲自己

QQ郵箱授權碼獲取方式
在這裏插入圖片描述
生成QQ郵箱授權碼
在這裏插入圖片描述
修改主程序入口文件:bbs.py

# -*- encoding: utf-8 -*-
"""
@File    : bbs.py
@Time    : 2020/5/11 9:46
@Author  : chen

"""
# 項目主文件,啓動入口

# 前臺  front    管理前端界面的邏輯
# 後臺  cms      管理後端的操作
# 公有的文件 common

from flask import Flask
import config                              # 配置文件庫
from exts import db, mail                  # 第三方庫導入db,mail
from apps.cms.views import cms_bp          # 導入後端藍圖文件
from apps.front.views import front_bp      # 導入前端藍圖文件
from flask_wtf import CSRFProtect          # CSRF表單保護驗證

app = Flask(__name__)

CSRFProtect(app)                           # CSRF保護app

app.config.from_object(config)             # 添加配置

db.init_app(app)                           # 綁定app
mail.init_app(app)                         # mail綁定app

app.register_blueprint(cms_bp)             # 後端藍圖文件註冊
app.register_blueprint(front_bp)           # 前端藍圖文件註冊


if __name__ == '__main__':
    app.run(debug=True, port=9999)

修改視圖文件apps/cms/views.py文件

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen

"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                  # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,   # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db, mail                            # 數據庫中更新密碼、郵箱等使用

# 導入form表單   .forms代表同級目錄下的forms.py       ResetPwdForm修改密碼的form信息
from .forms import LoginForm, ResetPwdForm

# 導入模型  .models代表同級目錄下的models.py
from .models import CMS_User

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數
from utils import restful

# 導入flask-mail中的Message
from flask_mail import Message

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')       # 登陸之後進入CMS後臺管理界面,路徑寫全cms/cms_index.html


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        pass


# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail():
    message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送')   # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
    mail.send(message)                   # 發送郵件
    return "郵件已發送"


# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

第三方引用文件 exts.py
導入flask-mail模塊

# -*- encoding: utf-8 -*-
"""
@File    : exts.py
@Time    : 2020/5/11 9:51
@Author  : chen

"""
# 第三方引用文件,防止互相引用報錯
from flask_sqlalchemy import SQLAlchemy

# 引用flask-mail
from flask_mail import Mail

db = SQLAlchemy()
# 引入Mail()
mail = Mail()

實現效果如下:
在這裏插入圖片描述

郵箱驗證碼

創建隨機生成驗證碼的函數文件:utils/random_captcha.py

# -*- encoding: utf-8 -*-
"""
@File    : random_captcha.py
@Time    : 2020/5/17 21:05
@Author  : chen

"""
"""隨機生成驗證碼(字母數字隨機組合,包含大小寫)"""
import random


def get_random_captcha(num):
    code_list = []
    # 每一位驗證碼都有三種可能(大寫字母,小寫字母,數字)
    for i in range(num):
        statu = random.randint(1, 3)
        if statu == 1:
            a = random.randint(65, 90)
            random_uppercase = chr(a)
            code_list.append(random_uppercase)
        
        elif statu == 2:
            b = random.randint(97, 122)
            random_lowercase = chr(b)
            code_list.append(random_lowercase)
        
        elif statu == 3:
            random_num = random.randint(0, 9)
            code_list.append(str(random_num))
    
    verification_code = "".join(code_list)               # 生成隨機驗證碼
    return verification_code

配置文件configs.py

# -*- encoding: utf-8 -*-
"""
@File    : config.py
@Time    : 2020/5/11 10:08
@Author  : chen

"""
import os    # 導入隨機字符串用於加密session

# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo_bbs"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

'''
# 創建引擎並生成Base類
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
SQLALCHEMY_DATABASE_URI = DB_URL           # 數據庫連接成功

# FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.
# Set it to True or False to suppress this warning.'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
# 這裏是爲了解決上面的警告
SQLALCHEMY_TRACK_MODIFICATIONS = False


SECRET_KEY = os.urandom(15)        # 產生隨機15位字符串

# flask-mail配置信息
MAIL_SERVER = 'smtp.qq.com'       # 發送郵箱的服務地址  這裏設置爲QQ郵箱服務器地址
MAIL_PORT = '587'                 # 發送端口爲465或者587
MAIL_USE_TLS = True               # 端口爲587對應的服務
# MAIL_USE_SSL = True             # 端口爲465對應的服務  二選一即可

MAIL_USERNAME = '[email protected]'   # 使用者的郵箱
MAIL_PASSWORD = 'lzpihkhrsbhqbajd'   # 不是QQ郵箱登錄密碼,是QQ郵箱授權碼獲取,用於第三方登錄驗證
MAIL_DEFAULT_SENDER = '[email protected]'   # 默認發送者,暫時先設置爲自己

TEMPLATES_AUTO_RELOAD = True         # 修改模板,ajax自動啓動

視圖文件apps/cms/views.py文件

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen

"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                  # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,   # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db, mail                            # 數據庫中更新密碼、郵箱等使用

# 導入form表單   .forms代表同級目錄下的forms.py       ResetPwdForm修改密碼的form信息
from .forms import LoginForm, ResetPwdForm

# 導入模型  .models代表同級目錄下的models.py
from .models import CMS_User

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數
from utils import restful, random_captcha           # 隨機生成驗證碼函數random_captcha()

# 導入flask-mail中的Message
from flask_mail import Message

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')       # 登陸之後進入CMS後臺管理界面,路徑寫全cms/cms_index.html


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現  form是收集到的信息
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        pass


# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail():
    message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送')   # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
    mail.send(message)                   # 發送郵件
    return "郵件已發送"


# 郵件發送
class EmailCaptcha(views.MethodView):
    def get(self):                                  # 根據resetemail.js中的ajax方法來寫函數,不需要post請求
        email = request.args.get('email')           # 查詢email參數是否存在
        if not email:
            return restful.params_error('請傳遞郵箱參數')
        
        # 發送郵件,內容爲一個驗證碼:4、6位數字英文組合
        captcha = random_captcha.get_random_captcha(4)            # 生成4位驗證碼
        message = Message('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)
        
        # 異常處理
        try:
            mail.send(message)
        except:
            return restful.server_error(message="服務器錯誤,郵件驗證碼未發送!")   # 發送異常,服務器錯誤
        
        # 驗證碼保存,一般有時效性,且頻繁請求變化,所以保存在Redis中
        
        return restful.success("郵件驗證碼發送成功!")
    

# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

# 綁定路由,路由的命名和cms_base.js中的命名要相同,必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))

2、Redis數據庫存儲驗證碼

開啓Redis服務端和客戶端,並清除客戶端所有數據:
在這裏插入圖片描述
虛擬環境中導入redis庫
在這裏插入圖片描述
創建redis存儲、獲取、刪除驗證碼:
工具文件utils/redis_captcha.py

# -*- encoding: utf-8 -*-
"""
@File    : redis_captcha.py
@Time    : 2020/5/17 21:46
@Author  : chen

"""
# Redis中保存驗證碼,並讀取進行驗證,再刪除驗證碼
import redis

# 連接Redis,相當於redis.Redis()    暫時是本地連接,decode_responses=True是將redis讀取出來的驗證碼轉換成字符串類型,原本是二進制字節
r = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True)


# 存儲驗證碼
def redis_set(key, value, timeout=60):         # timeout=60過期時間60s
    return r.set(key, value, timeout)


# 提取驗證碼
def redis_get(key):
    return r.get(key)          # 這裏輸出的原本是二進制字節類型數據,decode_responses=True自動轉換成字符串


# 刪除驗證碼
def redis_delete(key):
    return r.delete(key)

創建redis存儲驗證碼文件
修改視圖文件通過Redis保存驗證碼:
視圖文件apps/cms/views.py文件

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen

"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                  # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,   # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db, mail                            # 數據庫中更新密碼、郵箱等使用

# 導入form表單   .forms代表同級目錄下的forms.py       ResetPwdForm修改密碼的form信息
from .forms import LoginForm, ResetPwdForm

# 導入模型  .models代表同級目錄下的models.py
from .models import CMS_User

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數          redis_captcha:redis存儲、提取、刪除驗證碼功能
from utils import restful, random_captcha, redis_captcha           # 隨機生成驗證碼函數random_captcha()

# 導入flask-mail中的Message
from flask_mail import Message

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')       # 登陸之後進入CMS後臺管理界面,路徑寫全cms/cms_index.html


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現  form是收集到的信息
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        pass


# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail():
    message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送')   # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
    mail.send(message)                   # 發送郵件
    return "郵件已發送"


# 郵件發送
class EmailCaptcha(views.MethodView):
    def get(self):                                  # 根據resetemail.js中的ajax方法來寫函數,不需要post請求
        email = request.args.get('email')           # 查詢email參數是否存在
        if not email:
            return restful.params_error('請傳遞郵箱參數')
        
        # 發送郵件,內容爲一個驗證碼:4、6位數字英文組合
        captcha = random_captcha.get_random_captcha(4)            # 生成4位驗證碼
        message = Message('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)
        
        # 異常處理
        try:
            mail.send(message)
        except:
            return restful.server_error(message="服務器錯誤,郵件驗證碼未發送!")   # 發送異常,服務器錯誤
        
        # 驗證碼保存,一般有時效性,且頻繁請求變化,所以保存在Redis中
        redis_captcha.redis_set(key=email, value=captcha)        # redis中都是鍵值對類型,存儲驗證碼
        return restful.success("郵件驗證碼發送成功!")
    

# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

# 綁定路由,路由的命名和cms_base.js中的命名要相同,必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))

實現效果如下:
在這裏插入圖片描述

3、郵箱修改功能

需要定義設置郵箱時用戶輸入的表單信息,進行提交驗證時候使用
修改Form表單文件:apps/cms/forms.py文件

# -*- encoding: utf-8 -*-
"""
@File    : forms.py
@Time    : 2020/5/11 10:00
@Author  : chen

"""
# forms表單信息
from wtforms import Form, StringField, IntegerField, ValidationError
from wtforms.validators import Email, InputRequired, Length, EqualTo  # EqualTo驗證新密碼是否相同
from utils.redis_captcha import redis_get                                       # 導入驗證碼模塊


# 創父類form表單,用於輸出錯誤信息
class BaseForm(Form):
    def get_error(self):
        message = self.errors.popitem()[1][0]          # 錯誤信息的收集,字典類型數據信息提取
        return message


# 登錄頁面中的Form表單      繼承父類form
class LoginForm(BaseForm):
    email = StringField(validators=[Email(message="請輸入正確的郵箱"),  InputRequired(message="請輸入郵箱")])
    password = StringField(validators=[Length(3, 15, message='請輸入正確長度的密碼')])   # 長度可以先設置短的,方便項目測試
    remember = IntegerField()                # 記住cookie操作  賦值爲0或1


# 修改密碼頁面中的form表單信息    繼承父類form
class ResetPwdForm(BaseForm):
    oldpwd = StringField(validators=[Length(3, 15, message="密碼長度有誤")])
    newpwd = StringField(validators=[Length(3, 15, message="密碼長度有誤")])
    newpwd2 = StringField(validators=[EqualTo("newpwd", message="兩次輸入密碼不一致")])
    

# 定義設置郵箱的表單信息,進行提交時候使用
class ResetEmailForm(BaseForm):
    email = StringField(validators=[Email(message="請輸入正確格式的郵箱")])                    # 名稱email與cms_resetemail.html中的要相同
    captcha = StringField(validators=[Length(min=4, max=4, message="請輸入正確長度的驗證碼")]) # 名稱captcha與cms_resetemail.html中的要相同
    
    # 驗證redis中的字段與數據庫中的字段是否相同
    def validate_captcha(self, field):           # 方法命名規則是:validate_字段名()
        # 表單提交上來的驗證碼
        email = self.email.data
        captcha = self.captcha.data
        
        # 取redis中保存的驗證碼             第一個redis_captcha是新對象,第二個redis_captcha是redis_captcha.py文件
        redis_captcha = redis_get(email)
        if not redis_captcha or captcha.lower() != redis_captcha.lower():    # 不區分大小寫
            raise ValidationError('郵箱驗證碼錯誤')

修改視圖文件進行驗證郵箱驗證碼和數據庫信息:
視圖文件apps/cms/views.py文件

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen

"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                  # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,   # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db, mail                            # 數據庫中更新密碼、郵箱等使用

# 導入form表單   .forms代表同級目錄下的forms.py       ResetPwdForm修改密碼的form信息
from .forms import LoginForm, ResetPwdForm
# 導入forms.py文件中的郵箱驗證的表單信息類
from apps.cms.forms import ResetEmailForm

# 導入模型  .models代表同級目錄下的models.py
from .models import CMS_User

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數          redis_captcha:redis存儲、提取、刪除驗證碼功能
from utils import restful, random_captcha, redis_captcha           # 隨機生成驗證碼函數random_captcha()

# 導入flask-mail中的Message
from flask_mail import Message

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')       # 登陸之後進入CMS後臺管理界面,路徑寫全cms/cms_index.html


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現  form是收集到的信息
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        form = ResetEmailForm(request.form)                    # 接收郵箱驗證的form表單信息
        if form.validate():                                    # 驗證表單信息是否通過
            email = form.email.data                            # 獲取form表單中填寫的郵箱地址
            
            # 查詢數據庫
            # CMS_User.query.filter_by(email=email).first()
            # CMS_User.query.filter(CMS_User.email == email).first()
            g.cms_user.email = email                           # 數據庫中的查詢在apps/cms/hooks.py文件中確定了該用戶的數據庫信息,用全局對象g.cms_user修改郵箱
            db.session.commit()
            return restful.success()                           # 郵箱修改成功
        else:
            return restful.params_error(form.get_error())      # form是這個類中的所有表單信息
        
        
# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail():
    message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送')   # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
    mail.send(message)                   # 發送郵件
    return "郵件已發送"


# 郵件發送
class EmailCaptcha(views.MethodView):
    def get(self):                                  # 根據resetemail.js中的ajax方法來寫函數,不需要post請求
        email = request.args.get('email')           # 查詢email參數是否存在
        if not email:
            return restful.params_error('請傳遞郵箱參數')
        
        # 發送郵件,內容爲一個驗證碼:4、6位數字英文組合
        captcha = random_captcha.get_random_captcha(4)            # 生成4位驗證碼
        message = Message('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)
        
        # 異常處理
        try:
            mail.send(message)
        except:
            return restful.server_error(message="服務器錯誤,郵件驗證碼未發送!")   # 發送異常,服務器錯誤
        
        # 驗證碼保存,一般有時效性,且頻繁請求變化,所以保存在Redis中
        redis_captcha.redis_set(key=email, value=captcha)        # redis中都是鍵值對類型,存儲驗證碼
        return restful.success("郵件驗證碼發送成功!")
    

# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

# 綁定路由,路由的命名和cms_base.js中的命名要相同,必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))

4、權限管理

權限管理功能是使用二進制的與、或運算來實現

在這裏插入圖片描述
用戶、角色和權限的關係:
在這裏插入圖片描述
創建角色模型定義:
模型文件models.py

# -*- encoding: utf-8 -*-
"""
@File    : models.py
@Time    : 2020/5/11 10:00
@Author  : chen

"""
# 定義後端用戶模型
from exts import db                                                               # 數據庫
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash         # 導入密碼加密,解密方法的庫


# 權限定義,不是模型,沒有繼承db.Model
class CMSPersmission(object):
    # 255 二進制表示所有的權限
    ALL_PERMISSION = 0b11111111          # 每一位數代表一個權限,共7個權限,8位1個字節
    
    # 訪問權限
    VISITOR        = 0b00000001
    
    # 管理帖子
    POSTER         = 0b00000010
    
    # 管理評論
    COMMENTER      = 0b00000100
    
    # 管理板塊
    BOARDER        = 0b00001000
    
    # 管理後臺用戶
    CMSUSER        = 0b00010000
    # 管理前臺用戶
    FRONTUSER      = 0b00100000
    # 管理管理員用戶
    ADMINER        = 0b01000000


# 權限與角色是多對多的關係,創建他們的中間表
cms_role_user = db.Table(
    "cms_role_user",
    db.Column("cms_role_id", db.Integer, db.ForeignKey('cms_role.id'), primary_key=True),
    db.Column("cms_user_id", db.Integer, db.ForeignKey('cms_user.id'), primary_key=True),
)


# 角色模型定義   繼承了db.Model
class CMSRole(db.Model):
    __tablename__ = 'cms_role'
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)      # 主鍵  自增
    name = db.Column(db.String(50), nullable=False)                       # 非空
    desc = db.Column(db.String(250), nullable=False)                      # 非空
    creat_time = db.Column(db.DateTime, default=datetime.now)
    permission = db.Column(db.Integer, default=CMSPersmission.VISITOR)    # 默認先給遊客權限

    # 反向查詢屬性,關聯中間表secondary=cms_role_user,對應了CMS_User模型,建立模型聯繫,不映射到數據庫中
    users = db.relationship('CMS_User', secondary=cms_role_user, backref="roles")
    
    
# 後臺用戶模型定義
class CMS_User(db.Model):
    __tablename__ = 'cms_user'
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)           # 主鍵  自增
    username = db.Column(db.String(150), nullable=False)                       # 非空
    # password = db.Column(db.String(150), nullable=False)
    _password = db.Column(db.String(150), nullable=False)                      # 密碼加密操作修改字段
    email = db.Column(db.String(50), nullable=False, unique=True)              # 非空、唯一
    join_time = db.Column(db.DateTime, default=datetime.now)                   # 默認當前時間
    
    # 修改密碼加密操作中的字段,在manage.py映射數據庫時候,使用字段還是保持相同
    def __init__(self, username, password, email):
        self.username = username
        self.password = password         # 調用該方法 返回下面的self._password數值,
        self.email = email
    
    # 密碼加密操作
    @property
    def password(self):                   # 密碼取值
        return self._password

    @password.setter                      # 密碼加密
    def password(self, raw_password):
        self._password = generate_password_hash(raw_password)

    # 用於驗證後臺登錄密碼是否和數據庫一致,raw_password是後臺登錄輸入的密碼
    def check_password(self, raw_password):
        result = check_password_hash(self.password, raw_password)   # 相當於用相同的hash加密算法加密raw_password,檢測與數據庫中是否一致
        return result
    

映射數據庫信息文件manage.py

# -*- encoding: utf-8 -*-
"""
@File    : manage.py
@Time    : 2020/5/10 17:36
@Author  : chen

"""
from flask_script import Manager
from bbs import app     # 需要將當前文件夾設置爲當前根目錄,纔不會報錯
from flask_migrate import Migrate, MigrateCommand
from exts import db

# 導入模型 才能映射到數據庫     導入後端的模型
from apps.cms.models import CMS_User

# 導入角色模型,映射到數據庫
from apps.cms.models import CMSRole

manage = Manager(app)

Migrate(app, db)
manage.add_command('db', MigrateCommand)


# 命令行添加後端用戶
@manage.option('-u', '--username', dest='username')
@manage.option('-p', '--password', dest='password')
@manage.option('-e', '--email', dest='email')
def create_cms_user(username, password, email):
    user = CMS_User(username=username, password=password, email=email)
    # 添加映射到數據庫,提交至數據庫
    db.session.add(user)
    db.session.commit()
    print("cms後端用戶添加成功")


if __name__ == '__main__':
    manage.run()

命令行映射到數據庫

映射到數據庫中
python manage.py db migrate

python manage.py db upgrade

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章