1. 程式人生 > >美多商場 - 使用者部分 - 4 使用者中心個人資訊

美多商場 - 使用者部分 - 4 使用者中心個人資訊

1 使用者個人中心說明與郵箱啟用欄位新增

1.1 個人中心介紹

前端訪問個人資訊頁面時,需要向後端請求個人資訊。

在本頁面中要顯示使用者的Email郵箱資訊,而對於郵箱資訊我們要實現對於郵箱的驗證功能,並在本頁面中顯示郵箱是否已驗證,如下所示,

個人資訊頁面

這裡有一個郵箱,而郵箱在註冊的時候,並沒有讓使用者輸入,所以會在這裡留一個輸入的入口,所以第一次訪問基本資訊,是這樣:

 使用者儲存之後,還要對郵箱進行校驗:

1.2 增加啟用欄位

這裡需要修改User模型類,增加郵箱是否驗證的欄位。

class User(AbstractUser):
    """
    使用者資訊
    """
    mobile = models.CharField(max_length=11, unique=True, verbose_name="手機號")
    email_active = models.BooleanField(default=False, verbose_name='郵箱驗證狀態')

進行資料庫遷移

python manage.py makemigrations
python manage.py migrate

2 返回使用者跟人資訊資料後端介面

2.1 後端邏輯

2.1.1 後端介面設計:

請求方式: GET /user/

請求引數: 無

返回資料: JSON

返回值 型別 是否必須 說明
id int 使用者id
username str 使用者名稱
mobile str 手機號
email str email郵箱
email_active bool 郵箱是否通過驗證

根據介面增加檢視邏輯:

而get中的邏輯,其實就是獲取詳情的邏輯,所以我們可以繼承RetrieveModelMixin

還可以直接繼承RetrieveAPIView:

繼承如下:

序列化器如下:

序列化器程式碼如下:

class UserDetailSerializer(serializers.ModelSerializer):
    """
    使用者詳細資訊序列化器
    """
    class Meta:
        model = User
        fields = ('id', 'username', 'mobile', 'email', 'email_active')

下來指定查詢集:

但是RetrieveAPI中獲取詳情資料的url是/users/<pk>/而我們設計的介面是/user/。

那隻能重寫get_object了:

這裡是如何獲取的user物件呢?

2.1.2 如何獲取user

而這裡我們壓根不用查詢資料庫,因為我們直接返回登入成功的使用者資訊即可,關鍵就是如何獲取登入成功的使用者。

如何獲取使用者呢?之前在講django的時候,說過HttpRequest物件中就有這個已經登入的使用者物件user:

但是現在是在類檢視的函式中,如何拿到request物件呢?

所以程式碼如下:

from rest_framework.permissions import IsAuthenticated

class UserDetailView(RetrieveAPIView):
    """
    使用者詳情
    """
    serializer_class = serializers.UserDetailSerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        return self.request.user

注意:訪問檢視必須要求使用者已通過認證(即登入之後)

2.1.3 認證授權

這個檢視我們應該是要求使用者必須登入之後,才能訪問,那怎麼辦?

來用DRF提供的認證授權機制:

APIView支援的屬性如下:

這裡就有一個permission_classes,可以知道檢測型別。

執行的許可權檢測型別為:

我們這裡選擇僅通過認證的使用者(也就是僅登入的使用者):

許可權使用方式如下:

我們選擇第二種即可,所以程式碼如下

2.2 前端請求獲取使用者個人資訊

修改user_center_info.html,增加Vue的變數

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>美多商城-使用者中心</title>
    <link rel="stylesheet" type="text/css" href="css/reset.css">
    <link rel="stylesheet" type="text/css" href="css/main.css">
    <script type="text/javascript" src="js/host.js"></script>
    <script type="text/javascript" src="js/vue-2.5.16.js"></script>
    <script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
    <script>
        var user_id = sessionStorage.user_id || localStorage.user_id;
        var token = sessionStorage.token || localStorage.token;
        if (!(user_id && token)) {
            location.href = '/login.html?next=/user_center_info.html';
        }
    </script>
</head>
<body>
    <div id="app" v-cloak>
    <div class="header_con">
        <div class="header">
            <div class="welcome fl">歡迎來到美多商城!</div>
            <div class="fr">
                <div class="login_btn fl">
                    歡迎您:<em>{{ username }}</em>
                    <span>|</span>
                    <a @click="logout">退出</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="user_center_info.html">使用者中心</a>
                    <span>|</span>
                    <a href="cart.html">我的購物車</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的訂單</a>
                </div>
            </div>
        </div>        
    </div>

    <div class="search_bar clearfix">
        <a href="index.html" class="logo fl"><img src="images/logo.png"></a>
        <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;使用者中心</div>
        <form method="get" action="/search.html" class="search_con fr mt40">
            <input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
            <input type="submit" class="input_btn fr" name="" value="搜尋">
        </form>
    </div>

    <div class="main_con clearfix">
        <div class="left_menu_con clearfix">
            <h3>使用者中心</h3>
            <ul>
                <li><a href="user_center_info.html" class="active">· 個人資訊</a></li>
                <li><a href="user_center_order.html">· 全部訂單</a></li>
                <li><a href="user_center_site.html">· 收貨地址</a></li>
                <li><a href="user_center_pass.html">· 修改密碼</a></li>
            </ul>
        </div>
        <div class="right_content clearfix">
                <div class="info_con clearfix">
                <h3 class="common_title2">基本資訊</h3>
                        <ul class="user_info_list">
                            <li><span>使用者名稱:</span>{{ username }}</li>
                            <li><span>手機號:</span>{{ mobile }}</li>
                            <li>
                                <span>Email:</span>
                                <div v-if="set_email">
                                    <input v-model="email" type="email" name="email">
                                    <input @click="save_email" type="button" name="" value="保 存">
                                    <input @click="set_email=false" type="reset" name="" value="取 消">
                                    <div v-if="email_error">郵箱格式錯誤</div>
                                </div>
                                <div v-else-if="email">
                                    {{ email }} 
                                    <div v-if="email_active">已驗證</div>
                                    <div v-else>
                                        待驗證<input @click="save_email" :disabled="send_email_btn_disabled" type="button" :value="send_email_tip">
                                    </div>
                                </div>
                                <div v-else>
                                    <input @click="set_email=true" type="button" name="" value="設 置">
                                </div>
                            </li>            
                        </ul>
                </div>

                <h3 class="common_title2">最近瀏覽</h3>
                <div class="has_view_list">
                    <ul class="goods_type_list clearfix">
                <li>
                    <a href="detail.html"><img src="images/goods/goods003.jpg"></a>
                    <h4><a href="detail.html">360手機 N6 Pro 全網通</a></h4>
                    <div class="operate">
                        <span class="prize">¥2699.00</span>
                        <span class="unit">臺</span>
                        <a href="#" class="add_goods" title="加入購物車"></a>
                    </div>
                </li>

                <li>
                    <a href="#"><img src="images/goods/goods004.jpg"></a>
                    <h4><a href="#">360手機 N6 Pro 全網通</a></h4>
                    <div class="operate">
                        <span class="prize">¥2699.00</span>
                        <span class="unit">臺</span>
                        <a href="#" class="add_goods" title="加入購物車"></a>
                    </div>
                </li>

                <li>
                    <a href="#"><img src="images/goods/goods005.jpg"></a>
                    <h4><a href="#">360手機 N6 Pro 全網通</a></h4>
                    <div class="operate">
                        <span class="prize">¥2699.00</span>
                        <span class="unit">臺</span>
                        <a href="#" class="add_goods" title="加入購物車"></a>
                    </div>
                </li>

                <li>
                    <a href="#"><img src="images/goods/goods006.jpg"></a>
                    <h4><a href="#">360手機 N6 Pro 全網通</a></h4>
                    <div class="operate">
                        <span class="prize">¥2699.00</span>
                        <span class="unit">臺</span>
                        <a href="#" class="add_goods" title="加入購物車"></a>
                    </div>
                </li>

                <li>
                    <a href="#"><img src="images/goods/goods007.jpg"></a>
                    <h4><a href="#">急速路由</a></h4>
                    <div class="operate">
                        <span class="prize">¥64.5</span>
                        <span class="unit">6.45/500g</span>
                        <a href="#" class="add_goods" title="加入購物車"></a>
                    </div>
                </li>
            </ul>
        </div>
            </div>
    </div>
    <div class="footer">
        <div class="foot_link">
            <a href="#">關於我們</a>
            <span>|</span>
            <a href="#">聯絡我們</a>
            <span>|</span>
            <a href="#">招聘人才</a>
            <span>|</span>
            <a href="#">友情連結</a>        
        </div>
        <p>CopyRight © 2016 北京美多商業股份有限公司 All Rights Reserved</p>
        <p>電話:010-****888    京ICP備*******8號</p>
    </div>
</div>
<script type="text/javascript" src="js/user_center_info.js"></script>
</body>
</html>

在js目錄中新建user_center_info.js

var vm = new Vue({
    el: '#app',
    data: {
        host,
        user_id: sessionStorage.user_id || localStorage.user_id,
        token: sessionStorage.token || localStorage.token,
        username: '',
        mobile: '',
        email: '',
        email_active: false,
        set_email: false,
        send_email_btn_disabled: false,
        send_email_tip: '重新發送驗證郵件',
        email_error: false,
        histories: []
    },
    mounted: function(){
        // 判斷使用者的登入狀態
        if (this.user_id && this.token) {
            axios.get(this.host + '/user/', {
                    // 向後端傳遞JWT token的方法
                    headers: {
                        'Authorization': 'JWT ' + this.token
                    },
                    responseType: 'json',
                })
                .then(response => {
                    // 載入使用者資料
                    this.user_id = response.data.id;
                    this.username = response.data.username;
                    this.mobile = response.data.mobile;
                    this.email = response.data.email;
                    this.email_active = response.data.email_active;
                })
                .catch(error => {
                    if (error.response.status==401 || error.response.status==403) {
                        location.href = '/login.html?next=/user_center_info.html';
                    }
                });
        } else {
            location.href = '/login.html?next=/user_center_info.html';
        }
    },
    methods: {
        // 退出
        logout: function(){
            sessionStorage.clear();
            localStorage.clear();
            location.href = '/login.html';
        },
        // 儲存email
        save_email: function(){

        }
    }
});

3 郵件與驗證

業務說明:

在使用者中心頁面中,我們允許使用者設定郵箱

設定郵箱

當用戶點選儲存後,我們會向用戶傳送郵件以驗證郵箱的有效性。

驗證郵件

為了避免使用者未收到驗證郵箱,我們提供“重新發送驗證郵件”按鈕允許使用者重新發送郵件。

重新發送驗證郵件

郵箱驗證成功,顯示已驗證。

郵箱已驗證

技術說明:

在郵件中提供的啟用連結地址,為了能區分是哪個使用者在進行郵箱驗證,需要在連結中包含使用者和郵箱的識別資訊,如user_id和email資料,但是基於安全性的考慮,不能將這兩個資料直接暴露在郵件連結中,而是需要進行隱藏和簽名處理(能夠檢測出是否修改過連結資料)。可以使用前面學過的itsdangerous對user_id和email資料進行處理,生成token作為連結的引數。

4 使用Django傳送郵件

我們需要在儲存郵箱的同時,傳送一封郵件,那我們需要先分析一下如何傳送郵件。

那我們如何給傳送郵件伺服器傳送一個smtp協議的請求,去傳送郵件呢?

我們需要找一個傳送郵件的伺服器,我們以163為例。

Django中內建了郵件傳送功能,被定義在django.core.mail模組中。傳送郵件需要使用SMTP伺服器,常用的免費伺服器有:163126QQ,下面以163郵件為例。

1)註冊163郵箱itcast88,登入後設置。

傳送郵件

2)在新頁面中點選“客戶端授權密碼”,勾選“開啟”,彈出新視窗填寫手機驗證碼。

傳送郵件

3)填寫授權碼。

傳送郵件

4)提示開啟成功。

傳送郵件

5) 在Django配置檔案中,設定郵箱的配置資訊

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
#傳送郵件的郵箱
EMAIL_HOST_USER = '[email protected]'
#在郵箱中設定的客戶端授權密碼
EMAIL_HOST_PASSWORD = 'python808'
#收件人看到的發件人
EMAIL_FROM = 'python<[email protected]>'

6) 使用Django提供的模組傳送郵件

django.core.mail模組提供了send_mail來發送郵件。

send_mail(subjectmessagefrom_emailrecipient_list,html_message=None)

  • subject 郵件標題
  • message 普通郵件正文, 普通字串
  • from_email 發件人
  • recipient_list 收件人列表
  • html_message 多媒體郵件正文,可以是html字串

例如:

msg='<a href="http://www.itcast.cn/subject/pythonzly/index.shtml" target="_blank">點選啟用</a>'
send_mail('註冊啟用','',settings.EMAIL_FROM, ['[email protected]'], html_message=msg)

5 儲存郵箱併發送驗證郵件

5.1 儲存郵箱後端介面實現

接下來處理儲存郵箱,介面如下:

後端介面設計:

請求方式:PUT /email/

請求引數: JSON 或 表單

引數 型別 是否必須 說明
email str Email郵箱

返回資料: JSON

返回值 型別 是否必須 說明
id int 使用者id
email str Email郵箱

具體邏輯分析如下:

上述的邏輯其實就是更新的一個邏輯,所以我們可以繼承UpdateModelMixin:

原始碼如下,大家可以看下update方法,做的事情與我們分析的是一致的:

當然我們會直接繼承UpdateApiView:

繼承之後程式碼如下:

注意:這裡為啥要重寫get_object,因為我們的url是不接受pk引數的,所以UpdateApiView無法確定我們要更新哪個模型類,所以我們要重寫get_object,告訴他更新哪個模型類。我們這裡更新的是user模型類。

序列化器如下:

序列化器中就兩個欄位id和email,關於這倆欄位的方向問題,都不用處理。

id,預設只能做序列化操作。

email,序列化器和反序列化都要做,也是預設。

注意:這裡咱們還沒有處理到更新郵箱的邏輯,我們在下邊傳送郵件的時候,處理。

5.2 定義傳送郵件的任務

配置Email:

celery_tasks新建任務email:

配置任務: