1. 程式人生 > >美多商場 - 使用者部分 - 1

美多商場 - 使用者部分 - 1

1 使用者模型類

Django提供了認證系統,文件資料可參考此連結https://yiyibooks.cn/xx/Django_1.11.6/topics/auth/index.html

Django認證系統同時處理認證和授權。簡單地講,認證驗證一個使用者是否它們聲稱的那個人,授權決定一個通過了認證的使用者被允許做什麼。 這裡的詞語“認證”同時指代這兩項任務,即Django的認證系統同時提供了認證機制和許可權機制。

Django的認證系統包含:

  1. 使用者
  2. 許可權:二元(是/否)標誌指示一個使用者是否可以做一個特定的任務。
  3. 組:對多個使用者運用標籤和許可權的一種通用的方式。
  4. 一個可配置的密碼雜湊系統
  5. 使用者登入或內容顯示的表單和檢視
  6. 一個可插拔的後臺系統

Django預設提供的認證系統中,使用者的認證機制依賴Session機制,我們在本專案中將引入JWT認證機制,將使用者的身份憑據存放在Token中,然後對接Django的認證系統,幫助我們來實現:

  • 使用者的資料模型
  • 使用者密碼的加密與驗證
  • 使用者的許可權系統

1.1 Django使用者模型類

Django認證系統中提供了使用者模型類User儲存使用者的資料,預設的User包含以下常見的基本欄位:

  • username

    必選。 150個字元以內。 使用者名稱可能包含字母數字,_@+ . 和-個字元。在Django更改1.10:max_length從30個字元增加到150個字元。

  • first_name

    可選(blank=True)。 少於等於30個字元。

  • last_name

    可選(blank=True)。 少於等於30個字元。

  • email

    可選(blank=True)。 郵箱地址。

  • password

    必選。 密碼的雜湊及元資料。 (Django 不儲存原始密碼)。 原始密碼可以無限長而且可以包含任意字元。

  • groups

    Group 之間的多對多關係。

  • user_permissions

    Permission 之間的多對多關係。

  • is_staff

    布林值。 指示使用者是否可以訪問Admin 站點。

  • is_active

    布林值。 指示使用者的賬號是否啟用。 我們建議您將此標誌設定為False而不是刪除帳戶;這樣,如果您的應用程式對使用者有任何外來鍵,則外來鍵不會中斷。它不是用來控制使用者是否能夠登入。 在Django更改1.10:在舊版本中,預設is_active為False不能進行登入。

  • is_superuser

    布林值。 指定這個使用者擁有所有的許可權而不需要給他們分配明確的許可權。

  • last_login

    使用者最後一次登入的時間。

  • date_joined

    賬戶建立的時間。 當賬號建立時,預設設定為當前的date/time。

1.1.1 常用方法

  • set_password(raw_password)

    設定使用者的密碼為給定的原始字串,並負責密碼的。 不會儲存User 物件。當Noneraw_password 時,密碼將設定為一個不可用的密碼。

  • check_password(raw_password)

    如果給定的raw_password是使用者的真實密碼,則返回True,可以在校驗使用者密碼時使用。

1.1.2 管理器方法

管理器方法即可以通過User.objects.進行呼叫的方法。

  • create_user(usernameemail=Nonepassword=None*\extra_fields*)

    建立、儲存並返回一個User物件。

  • create_superuser(usernameemailpassword*\extra_fields*)

    create_user() 相同,但是設定is_staff 和is_superuser 為True

1.2 建立自定義的使用者模型類

Django認證系統中提供的使用者模型類及方法很方便,我們可以使用這個模型類,但是欄位有些無法滿足專案需求,如本專案中需要儲存使用者的手機號,需要給模型類新增額外的欄位。

Django提供了django.contrib.auth.models.AbstractUser使用者抽象模型類允許我們繼承,擴充套件欄位來使用Django認證系統的使用者模型類。

我們現在在meiduo/meiduo_mall/apps中建立Django應用users,並在配置檔案中註冊users應用。

  • 在建立好的應用models.py中定義使用者的使用者模型類。
class User(AbstractUser):
    """使用者模型類"""
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手機號')

    class Meta:
        db_table = 'tb_users'
        verbose_name = '使用者'
        verbose_name_plural = verbose_name

我們自定義的使用者模型類還不能直接被Django的認證系統所識別,需要在配置檔案中告知Django認證系統使用我們自定義的模型類。

  • 在配置檔案中進行設定
AUTH_USER_MODEL = 'users.User'

AUTH_USER_MODEL 引數的設定以點.來分隔,表示應用名.模型類名

注意:Django建議我們對於AUTH_USER_MODEL引數的設定一定要在第一次資料庫遷移之前就設定好,否則後續使用可能出現未知錯誤。

  • 執行資料庫遷移
python manage.py makemigrations
python manage.py migrate

2 註冊

建立好使用者模型類後,我們開始來實現第一個業務邏輯——使用者註冊。

2.1 設計介面的思路

  • 分析要實現的業務邏輯,明確在這個業務中需要涉及到幾個相關子業務,將每個子業務當做一個介面來設計。
  • 分析介面的功能任務,明確介面的訪問方式與返回資料:
    • 介面的請求方式,如GET 、POST 、PUT等
    • 介面的URL路徑定義
    • 需要前端傳遞的資料及資料格式(如路徑引數、查詢字串、請求體表單、JSON等)
    • 返回給前端的資料及資料格式

在前後端分離的應用模式中,我們作為後端開發人員設計後端介面時,可以不用考慮返回給前端資料後,前端如何處理,這是前端開發人員的工作,我們只需明確我們要儲存的或者要返回的是什麼資料即可。

明確上述每一點後,即可開始編寫介面。

2.2 註冊業務介面分析

在使用者註冊中,需要實現以下介面:

  • 圖片驗證碼
  • 簡訊驗證碼
  • 使用者名稱判斷是否存在
  • 手機號判斷是否存在
  • 註冊儲存使用者資料

圖片驗證碼、簡訊驗證碼考慮到後續可能會在其他業務中也用到,因此我們將圖片驗證碼獨立,建立一個新應用verifications,在此應用中實現圖片驗證碼、簡訊驗證碼

3 圖片驗證碼

3.1 後端介面設計:

訪問方式: GET /image_codes/(?P<image_code_id>[\w-]+)/

請求引數: 路徑引數

引數 型別 是否必須 說明
image_code_id uuid字串 圖片驗證碼編號

返回資料:驗證碼圖片

檢視原型:

圖片驗證碼在一個新的應用中處理,新建應用verifications

# url('^image_codes/(?P<image_code_id>[\w-]+)/$', views.ImageCodeView.as_view()), 
class ImageCodeView(APIView):
    """
    圖片驗證碼
    """
    pass

這個ImageCodeView繼承APIView即可,原因分析如下

因為這個檢視邏輯,不需要校驗引數,因為這個引數由url路由中的正則就可以校驗,所以這裡壓根就不用考慮資料校驗,這裡也不需要查詢資料庫,所以也不需要做序列化操作。那麼其實說白了,就是不需要序列化器。

那麼DRF提供的類檢視中只有APIView,可不用序列化器,其他的類檢視都需要設定序列化器。 

註冊如下:

3.2 具體檢視實現

首先需要用到第三方的captcha,來生成圖片驗證碼,所以需要copy資料:

copy到meduo_mall的libs中:

在verifications/views.py中實現檢視:

class ImageCodeView(APIView):
    """
    圖片驗證碼
    """

    def get(self, request, image_code_id):
        """
        獲取圖片驗證碼
        """
        # 生成驗證碼圖片
        text, image = captcha.generate_captcha()

        redis_conn = get_redis_connection("verify_codes")
        redis_conn.setex("img_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)

        # 固定返回驗證碼圖片資料,不需要REST framework框架的Response幫助我們決定返回響應資料的格式
        # 所以此處直接使用Django原生的HttpResponse即可
        return HttpResponse(image, content_type="images/jpg")

說明:

django-redis提供了get_redis_connection的方法,通過呼叫get_redis_connection方法傳遞redis的配置名稱可獲取到redis的連線物件,通過redis連線物件可以執行redis命令。

 這裡有三點需要分析:

  • get_redis_connection是獲取redis連線物件的方法,引數為獲取的是哪個redis連線配置項,如下:(verify_code專門儲存圖片驗證碼)

我們需要在配置檔案中新增一個新的redis配置,用於存放驗證碼資料

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "verify_codes": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}
  • 過期時間: 在verifications應用中新建constants,然後增加圖片驗證碼過去時間

  • 最後是使用HttpResponse返回的響應

問題一,為啥還能返回HttpResponse,不是應該返回Response物件麼?

首先Response是間接繼承HttpResponse的,而檢視本身就應該返回的是HttpResponse。在DRF中可以返回Response,主要是因為Response繼承了HttpResponse。

問題二,為啥不返回Response?

這是因為Response會將內容交給json渲染器進行資料格式轉換,如下:

3.3 url配置

配置檢視的url:

配置應用的url:

3.4 設定域名

我們現在為前端和後端分別設定兩個不同的域名

位置 域名
前端 www.meiduo.site
後端 api.meiduo.site

但是這個域名要真想處理的話,我們需要購買域名,那麼我們又不想花錢,就是想用於測試怎麼辦?

我們可以配置本地域名,編輯/etc/hosts檔案,可以設定本地域名

Ubuntu中:

sudo vim /etc/hosts

在檔案中增加兩條資訊

127.0.0.1   api.meiduo.site
127.0.0.1   www.meiduo.site

windows系統中若設定本地域名,hosts檔案在如下目錄:

C:\Windows\System32\drivers\etc

還有一點需要配置,我們想通過api.meiduo.site訪問後端django專案,那就需要修改settings配置中的ALLOWED_HOSTS:

ALLOWED_HOSTS = ['api.meiduo.site', '127.0.0.1', 'localhost', 'www.meiduo.site']

既然後端限定要求api.meiduo.site訪問,那麼前端在訪問後端介面的時候,每次都寫api.meiduo.site會很麻煩,我們在前端front_end_pc/js目錄中,建立host.js檔案用以為前端儲存後端域名

var host = 'http://api.meiduo.site:8000';

在所有需要訪問後端介面的前端頁面中都引入host.js,使用host變數即可指代後端域名。

3.5 測試後端邏輯

接下來我們就可以測試一下圖片驗證碼的後端邏輯了,啟動程式,報錯:

缺少PIL,這是因為我們用到了captcha,這個captcha是生成圖片驗證碼的,要用到圖片相關的包PIL。

我們安裝Pillow即可:(為啥缺少的是PIL,但我們安裝的缺是Pillow,這是因為PIL安裝比較麻煩,會有各種問題,所以我們就安裝了可以完全替代PIL的Pillow包)

再次啟動:

訪問url:

還需要確定一件事情就是redis資料庫儲存是否成功:

3.6 啟動問題

大家在啟動的時候,會發現比以前多了很多紅色的Debug資訊,這是以為我們自己添加了日誌的配置。

3.7 前端Vue程式碼:

js/register.js

data: {
    ...
    image_code_id: '',  // 圖片驗證碼編號
    image_code_url: '',  // 驗證碼圖片路徑
},
mounted: function() {
    this.generate_image_code();
},
methods: {
    // 生成uuid
    generate_uuid: function(){
        var d = new Date().getTime();
        if(window.performance && typeof window.performance.now === "function"){
            d += performance.now(); //use high-precision timer if available
        }
        var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = (d + Math.random()*16)%16 | 0;
            d = Math.floor(d/16);
            return (c =='x' ? r : (r&0x3|0x8)).toString(16);
        });
        return uuid;
    },
    // 生成一個圖片驗證碼的編號,並設定頁面中圖片驗證碼img標籤的src屬性
    generate_image_code: function(){
        // 生成一個編號
        // 嚴格一點的使用uuid保證編號唯一, 不是很嚴謹的情況下,也可以使用時間戳
        this.image_code_id = this.generate_uuid();

        // 設定頁面中圖片驗證碼img標籤的src屬性
        this.image_code_url = this.host + "/image_codes/" + this.image_code_id + "/";
    },
    ...
}

3.8 對圖片驗證碼程式碼進行測試

4 簡訊驗證碼

4.1 業務處理流程

  1. 檢查圖片驗證碼
  2. 檢查是否在60s內有傳送記錄
  3. 生成簡訊驗證碼
  4. 儲存簡訊驗證碼與傳送記錄
  5. 傳送簡訊

4.2 後端介面設計:

訪問方式: GET /sms_codes/(?P<mobile>1[3-9]\d{9})/?image_code_id=xxx&text=xxx

請求引數: 路徑引數與查詢字串引數

引數 型別 是否必須 說明
mobile str 手機號
image_code_id uuid字串 圖片驗證碼編號
text str 使用者輸入的圖片驗證碼

返回資料: JSON

返回值 型別 是否必傳 說明
message str OK,傳送成功

檢視原型:在應用verifications中建立檢視SMSCodeView(GenericAPIView)

# url('^sms_codes/(?P<mobile>1[3-9]\d{9})/$', views.SMSCodeView.as_view()),
class SMSCodeView(GenericAPIView):
    """
    簡訊驗證碼
    傳入引數:
        mobile, image_code_id, text
    """
    pass

4.3 後端實現

在verifications/serializers.py中定義序列化器,用以對引數校驗

class ImageCodeCheckSerializer(serializers.Serializer):
    """
    圖片驗證碼校驗序列化器
    """
    image_code_id = serializers.UUIDField()
    text = serializers.CharField(max_length=4, min_length=4)

    def validate(self, attrs):
        """
        校驗
        """
        image_code_id = attrs['image_code_id']
        text = attrs['text']
        # 查詢真實圖片驗證碼
        redis_conn = get_redis_connection('verify_codes')
        real_image_code_text = redis_conn.get('img_%s' % image_code_id)
        if not real_image_code_text:
            raise serializers.ValidationError('圖片驗證碼無效')

        # 刪除圖片驗證碼
        redis_conn.delete('img_%s' % image_code_id)

        # 比較圖片驗證碼
        real_image_code_text = real_image_code_text.decode()
        if real_image_code_text.lower() != text.lower():
            raise serializers.ValidationError('圖片驗證碼錯誤')

        # 判斷是否在60s內
        mobile = self.context['view'].kwargs['mobile']
        send_flag = redis_conn.get("send_flag_%s" % mobile)
        if send_flag:
            raise serializers.ValidationError('請求次數過於頻繁')

        return attrs

 在verifications/views.py中定義實現檢視:

class SMSCodeView(GenericAPIView):
    """
    簡訊驗證碼
    傳入引數:
        mobile, image_code_id, text
    """
    serializer_class = ImageCodeCheckSerializer

    # GET /sms_codes/(?P<mobile>1[3-9]\d{9})/?image_code_id=xxx&text=xxx
    def get(self, request, mobile):
        # 校驗引數(由序列化器完成)
        serializer = self.get_serializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)

        # 生成簡訊驗證碼
        sms_code = "%06d" % random.randint(0, 999999)
        print("簡訊驗證碼:", sms_code)
        # 儲存簡訊驗證碼
        redis_conn = get_redis_connection("verify_codes")
        redis_conn.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # 儲存傳送記錄
        redis_conn.setex("send_flag_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, 1)

        # 傳送簡訊
        # 使用雲通訊第三方平臺
        try:
            ccp = CCP()
            expires = constants.SMS_CODE_REDIS_EXPIRES // 60
            result = ccp.send_template_sms(mobile, [sms_code, expires], constants.SMS_CODE_TEMP_ID)
        except Exception as e:
            logger.error("傳送驗證碼簡訊【異常】[ mobile: %s, message: %s]" % (mobile, e))
            return Response({"message": "failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            if result == 0:
                return Response({"message": "OK"})
            else:
                logger.error("傳送驗證碼簡訊【異常】[ mobile: %s]" % mobile)
                return Response({"message": "failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

因為這裡我們要用到序列化器,所以就繼承了GenericAPIView,這樣的話,可以指定serializer_class,然後通過get_serializer就可以例項化序列化器了。

注意: 有些人可能會問了,既然繼承了GenericAPIView,那為啥不設定queryset屬性,只設置了serializer_class屬性了呢?注意,這裡用到誰,就設定誰。GenericAPIView沒有要求必須都設定的。

傳送間隔常量如下:

4.3.1 send_flag標記問題

為啥要儲存一個send_flag_xx?

這個send_flag什麼時候用到了?這個是在傳送簡訊驗證碼序列化器中用到了。

send_flag標記的目的在於防止使用者1分鐘內多次請求,獲取簡訊驗證碼邏輯:

那有些同學說,這個邏輯在前端做處理了呀,1分鐘倒計時之後才能再次傳送簡訊驗證碼。是的,但是使用者是可以繞過前端,使用postman來發起請求,獲取簡訊驗證碼的。

還有個問題:那能不能不要這個標記,只儲存sms_xxx行不行,利用他的有效期來處理這個邏輯行不?

不行的,因為簡訊驗證碼的有效期是:5分鐘。

 4.3.2 雲通訊整合

傳送簡訊驗證碼,但是需要用到雲通訊的ccp,所以需要引入雲通訊工具:將資料中的yuntongxun資料夾,直接copy到utils下,copy結果:

然後修改一下配置資訊:

把上圖中的三個配置資訊,修改為自己雲通訊的三個配置,如下圖:

4.3.3 補充redis管道

我們來看一個問題,我們在儲存簡訊驗證碼和傳送記錄的時候,做了兩次redis的操作

這其實是比較耗費效能的,因為都是網路操作,Redis服務配置如下:

即使是本機的服務也是要進行網路通訊的。

那怎麼辦呢?使用redis管道:

        # 儲存簡訊驗證碼
        redis_conn = get_redis_connection("verify_codes")

        # redis_conn.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # # 儲存傳送記錄
        # redis_conn.setex("send_flag_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, 1)

        # redis管道
        pl = redis_conn.pipeline()
        pl.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # 儲存傳送記錄
        pl.setex("send_flag_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, 1)

        # 讓管道通知redis執行命令
        pl.execute()

注意: 最後一定要執行管道。 

4.3.4 補充刪除圖片驗證碼的邏輯

我們要做一件事情,就是避免使用者拿著同一個圖片驗證碼,獲取多次簡訊驗證碼。

如何避免呢?在獲取到圖片驗證碼之後,將redis中的圖片驗證碼刪除。

4.4 前端實現

修改register.html

<!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>
</head>
<body>
    <div class="register_con">
        <div class="l_con fl">
            <a class="reg_logo"><img src="images/logo.png"></a>
            <div class="reg_slogan">商品美 · 種類多 · 歡迎光臨</div>
            <div class="reg_banner"></div>
        </div>

        <div class="r_con fr">
            <div class="reg_title clearfix">
                <h1>使用者註冊</h1>
                <a href="/login.html">登入</a>
            </div>
            <div class="reg_form clearfix" id="app" v-cloak>
                <form id="reg_form" @submit.prevent="on_submit">
                <ul>
                    <li>
                        <label>使用者名稱:</label>
                        <input type="text" v-model="username" @blur="check_username" name="user_name" id="user_name">
                        <span v-show="error_name" class="error_tip">{{ error_name_message }}</span>
                    </li>                    
                    <li>
                        <label>密碼:</label>
                        <input type="password" v-model="password" @blur="check_pwd" name="pwd" id="pwd">
                        <span v-show="error_password" class="error_tip">密碼最少8位,最長20位</span>
                    </li>
                    <li>
                        <label>確認密碼:</label>
                        <input type="password" v-model="password2" @blur="check_cpwd" name="cpwd" id="cpwd">
                        <span v-show="error_check_password" class="error_tip">兩次輸入的密碼不一致</span>
                    </li>
                    <li>
                        <label>手機號:</label>
                        <input type="text" v-model="mobile" @blur="check_phone" name="phone" id="phone">
                        <span v-show="error_phone" class="error_tip">{{ error_phone_message }}</span>
                    </li>
                    <li>
                        <label>圖形驗證碼:</label>
                        <input type="text" v-model="image_code" @blur="check_image_code" name="pic_code" id="pic_code" class="msg_input">
                        <img :src="image_code_url" @click="generate_image_code" alt="圖形驗證碼" class="pic_code">
                        <span v-show="error_image_code" class="error_tip">{{ error_image_code_message }}</span>
                    </li>
                    <li>
                        <label>簡訊驗證碼:</label>
                        <input type="text" v-model="sms_code" @blur="check_sms_code" name="msg_code" id="msg_code" class="msg_input">
                        <a @click="send_sms_code" class="get_msg_code">{{ sms_code_tip }}</a>
                        <span v-show="error_sms_code" class="error_tip">{{ error_sms_code_message }}</span>
                    </li>
                    <li class="agreement">
                        <input type="checkbox" v-model="allow" @change="check_allow" name="allow" id="allow">
                        <label>同意”美多商城使用者使用協議“</label>
                        <span v-show="error_allow" class="error_tip2">請勾選同意</span>
                    </li>
                    <li class="reg_sub">
                        <input type="submit" value="注 冊" name="">
                    </li>
                </ul>                
                </form>
            </div>

        </div>

    </div>

    <div class="footer no-mp">
        <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>
    <script type="text/javascript" src="js/register.js"></script>
</body>
</html>

修改register.js

var vm = new Vue({
    el: '#app',
    data: {
        host: host,

        error_name: false,
        error_password: false,
        error_check_password: false,
        error_phone: false,
        error_allow: false,
        error_image_code: false,
        error_sms_code: false,
        error_name_message: '',
        error_image_code_message: '',
        error_phone_message: '',
        error_sms_code_message: '',

        image_code_id: '', // 圖片驗證碼id
        image_code_url: '',

        sms_code_tip: '獲取簡訊驗證碼',
        sending_flag: false, // 正在傳送簡訊標誌

        username: '',
        password: '',
        password2: '',
        mobile: '', 
        image_code: '',
        sms_code: '',
        allow: false
    },
    mounted: function(){
        this.generate_image_code();
    },
    methods: {
        // 生成uuid
        generate_uuid: function(){
            var d = new Date().getTime();
            if(window.performance && typeof window.performance.now === "function"){
                d += performance.now(); //use high-precision timer if available
            }
            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = (d + Math.random()*16)%16 | 0;
                d = Math.floor(d/16);
                return (c =='x' ? r : (r&0x3|0x8)).toString(16);
            });
            return uuid;
        },
        // 生成一個圖片驗證碼的編號,並設定頁面中圖片驗證碼img標籤的src屬性
        generate_image_code: function(){
            // 生成一個編號
            // 嚴格一點的使用uuid保證編號唯一, 不是很嚴謹的情況下,也可以使用時間戳
            this.image_code_id = this.generate_uuid();

            // 設定頁面中圖片驗證碼img標籤的src屬性
            this.image_code_url = this.host + "/image_codes/" + this.image_code_id + "/";
        },
        check_username: function (){
            var len = this.username.length;
            if(len<5||len>20) {
                this.error_name_message = '請輸入5-20個字元的使用者名稱';
                this.error_name = true;
            } else {
                this.error_name = false;
            }

        },
        check_pwd: function (){
            var len = this.password.length;
            if(len<8||len>20){
                this.error_password = true;
            } else {
                this.error_password = false;
            }        
        },
        check_cpwd: function (){
            if(this.password!=this.password2) {
                this.error_check_password = true;
            } else {
                this.error_check_password = false;
            }        
        },
        check_phone: function (){
            var re = /^1[345789]\d{9}$/;
            if(re.test(this.mobile)) {
                this.error_phone = false;
            } else {
                this.error_phone_message = '您輸入的手機號格式不正確';
                this.error_phone = true;
            }

        },
        check_image_code: function (){
            if(!this.image_code) {
                this.error_image_code_message = '請填寫圖片驗證碼';
                this.error_image_code = true;
            } else {
                this.error_image_code = false;
            }    
        },
        check_sms_code: function(){
            if(!this.sms_code){
                this.error_sms_code_message = '請填寫簡訊驗證碼';
                this.error_sms_code = true;
            } else {
                this.error_sms_code = false;
            }
        },
        check_allow: function(){
            if(!this.allow) {
                this.error_allow = true;
            } else {
                this.error_allow = false;
            }
        },
        // 傳送手機簡訊驗證碼
        send_sms_code: function(){
            if (this.sending_flag == true) {
                return;
            } 
            this.sending_flag = true;

            // 校驗引數,保證輸入框有資料填寫
            this.check_phone();
            this.check_image_code();

            if (this.error_phone == true || this.error_image_code == true) {
                this.sending_flag = false;
                return;
            }

            // 向後端介面傳送請求,讓後端傳送簡訊驗證碼
            axios.get(this.host + '/sms_codes/' + this.mobile + '/?text=' + this.image_code+'&image_code_id='+ this.image_code_id, {
                    responseType: 'json'
                })
                .then(response => {
                    // 表示後端傳送簡訊成功
                    // 倒計時60秒,60秒後允許使用者再次點擊發送簡訊驗證碼的按鈕
                    var num = 60;
                    // 設定一個計時器
                    var t = setInterval(() => {
                        if (num == 1) {
                            // 如果計時器到最後, 清除計時器物件
                            clearInterval(t);
                            // 將點選獲取驗證碼的按鈕展示的文本回覆成原始文字
                            this.sms_code_tip = '獲取簡訊驗證碼';
                            // 將點選按鈕的onclick事件函式恢復回去
                            this.sending_flag = false;
                        } else {
                            num -= 1;
                            // 展示倒計時資訊
                            this.sms_code_tip = num + '秒';
                        }
                    }, 1000, 60)
                })
                .catch(error => {
                    if (error.response.status == 400) {
                        this.error_image_code_message = '圖片驗證碼有誤';
                        this.error_image_code = true;
                        this.generate_image_code();
                    } else {
                        console.log(error.response.data);
                    }
                    this.sending_flag = false;
                })
        },
        // 註冊
        on_submit: function(){
            this.check_username();
            this.check_pwd();
            this.check_cpwd();
            this.check_phone();
            this.check_sms_code();
            this.check_allow();


        }
    }
});

5 跨域CORS

我們為前端和後端分別設定了兩個不同的域名

位置 域名
前端 www.meiduo.site
後端 api.meiduo.site

現在,前端與後端分處不同的域名,我們需要為後端新增跨域訪問的支援。

我們使用CORS來解決後端對跨域訪問的支援。

使用django-cors-headers擴充套件

參考文件https://github.com/ottoyiu/django-cors-headers/

  • 安裝
pip install django-cors-headers
  • 新增應用
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)
  • 中間層設定
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]
  • 新增白名單
# CORS
CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'localhost:8080',
    'www.meiduo.site:8080',
    'api.meiduo.site:8000'
)
CORS_ALLOW_CREDENTIALS = True  # 允許攜帶cookie
  • 凡是出現在白名單中的域名,都可以訪問後端介面
  • CORS_ALLOW_CREDENTIALS 指明在跨域訪問中,後端是否支援對cookie的操作。