Django 前後端分離實戰項目 生鮮超市(八)之用戶登錄和手機註冊

用戶登錄和手機註冊

前言

所有vue接口全部在src/api/api.js文件下

需要在雲片網完成登記,實現短信服務

代碼已上傳至github:https://github.com/kalipoison/fresh-market

此項目僅學習用途

要求

Package Version


certifi 2020.4.5.1
chardet 3.0.4
coreapi 2.3.1
coreschema 0.0.4
Django 1.11.3
django-cors-headers 2.1.0
django-crispy-forms 1.6.1
django-filter 1.0.4
django-formtools 2.0
django-guardian 1.4.9
django-reversion 2.0.9
djangorestframework 3.6.3
djangorestframework-jwt 1.11.0
future 0.16.0
httplib2 0.9.2
idna 2.9
itypes 1.2.0
Jinja2 2.11.2
Markdown 2.6.8
MarkupSafe 1.1.1
mysqlclient 1.3.10
olefile 0.46
Pillow 4.2.1
pip 20.0.2
PyJWT 1.7.1
pytz 2019.3
requests 2.23.0
setuptools 46.1.3
six 1.10.0
uritemplate 3.0.1
urllib3 1.25.9
wheel 0.34.2
XlsxWriter 0.9.8
xlwt 1.2.0

流程

在settings.py中添加如下內容

settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
}

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'DjangoUeditor',
    'goods.apps.GoodsConfig',
    'trade.apps.TradeConfig',
    'user_operation.apps.UserOperationConfig',
    'crispy_forms',
    'django_filters',
    'rest_framework',
    'xadmin',
    'corsheaders',
    'rest_framework.authtoken',
]
AUTHENTICATION_BACKENDS = (
    'users.views.CustomBackend',
)
import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}
#手機號碼正則表達式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
#雲片網設置,寫自己的APIKEY
APIKEY = ""

在終端輸入

python manage.py makemigrations
python manage.py migrate

在users文件夾下views.py代碼如下:

views.py

from django.shortcuts import render

# Create your views here.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from random import choice

from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler

from .serializers import SmsSerializer, UserRegSerializer
from Mxshop.settings import APIKEY
from utils.yunpian import YunPian
from .models import VerifyCode

User = get_user_model()


class CustomBackend(ModelBackend):
    """
    自定義用戶驗證
    """
    def authenticate(self, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username)|Q(mobile=username))
            if user.check_password(password):
                return user
        except Exception as e:
            return None

class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
    """
    發送短信驗證碼
    """
    serializer_class = SmsSerializer

    def generate_code(self):
        """
        生成四位數字的驗證碼
        :return:
        """
        seeds = "1234567890"
        random_str = []
        for i in range(4):
            random_str.append(choice(seeds))

        return "".join(random_str)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        mobile = serializer.validated_data["mobile"]

        yun_pian = YunPian(APIKEY)

        code = self.generate_code()

        sms_status = yun_pian.send_sms(code=code, mobile=mobile)

        if sms_status["code"] != 0:
            return Response({
                "mobile":sms_status["msg"]
            }, status=status.HTTP_400_BAD_REQUEST)
        else:
            code_record = VerifyCode(code=code, mobile=mobile)
            code_record.save()
            return Response({
                "mobile":mobile
            }, status=status.HTTP_201_CREATED)

class UserViewset(CreateModelMixin, viewsets.GenericViewSet):
    """
    用戶
    """
    serializer_class = UserRegSerializer
    queryset = User.objects.all()

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)

        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        re_dict["token"] = jwt_encode_handler(payload)
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)


在users文件夾下serializers.py代碼如下:

serializers.py


import re
from rest_framework import serializers
from django.contrib.auth import get_user_model
from datetime import datetime
from datetime import timedelta
from rest_framework.validators import UniqueValidator

from .models import VerifyCode

from Mxshop.settings import REGEX_MOBILE

User = get_user_model()


class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    def validate_mobile(self, mobile):
        """
        驗證手機號碼
        :param data:
        :return:
        """

        # 手機是否註冊
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("用戶已經存在")

        # 驗證手機號碼是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("手機號碼非法")

        # 驗證碼發送頻率
        one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
            raise serializers.ValidationError("距離上一次發送未超過60s")

        return mobile


class UserDetailSerializer(serializers.ModelSerializer):
    """
    用戶詳情序列化類
    """
    class Meta:
        model = User
        fields = ("name", "gender", "birthday", "email", "mobile")

class UserRegSerializer(serializers.ModelSerializer):
    code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,label="驗證碼",
                                 error_messages={
                                     "blank": "請輸入驗證碼",
                                     "required": "請輸入驗證碼",
                                     "max_length": "驗證碼格式錯誤",
                                     "min_length": "驗證碼格式錯誤"
                                 },
                                 help_text="驗證碼")
    username = serializers.CharField(label="用戶名", help_text="用戶名", required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")])

    password = serializers.CharField(
        style={'input_type': 'password'},help_text="密碼", label="密碼", write_only=True,
    )

    # def create(self, validated_data):
    #     user = super(UserRegSerializer, self).create(validated_data=validated_data)
    #     user.set_password(validated_data["password"])
    #     user.save()
    #     return user

    def validate_code(self, code):
        # try:
        #     verify_records = VerifyCode.objects.get(mobile=self.initial_data["username"], code=code)
        # except VerifyCode.DoesNotExist as e:
        #     pass
        # except VerifyCode.MultipleObjectsReturned as e:
        #     pass
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_record = verify_records[0]

            five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            if five_mintes_ago > last_record.add_time:
                raise serializers.ValidationError("驗證碼過期")

            if last_record.code != code:
                raise serializers.ValidationError("驗證碼錯誤")

        else:
            raise serializers.ValidationError("驗證碼錯誤")

    def validate(self, attrs):
        attrs["mobile"] = attrs["username"]
        del attrs["code"]
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile", "password")

在users文件夾下signals.py代碼如下:

signals.py


from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model

User = get_user_model()

@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
    if created:
        password = instance.password
        instance.set_password(password)
        instance.save()

url.py代碼如下:

url.py

from django.conf.urls import url,include
# from django.contrib import admin
import xadmin
from Mxshop.settings import MEDIA_ROOT
from django.views.static import serve
from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from rest_framework_jwt.views import obtain_jwt_token
from goods.views import GoodsListViewSet,CategoryViewset,HotSearchsViewset
from users.views import SmsCodeViewset,UserViewset

router = DefaultRouter()

#配置goods的url
router.register(r'goods', GoodsListViewSet, base_name="goods")

#配置category的url
router.register(r'categorys', CategoryViewset, base_name="categorys")

router.register(r'codes', SmsCodeViewset, base_name="codes")

router.register(r'hotsearchs', HotSearchsViewset, base_name="hotsearchs")

router.register(r'users', UserViewset, base_name="users")

goods_list = GoodsListViewSet.as_view({
    'get': 'list',
})

urlpatterns = [
    url(r'^xadmin/', xadmin.site.urls),
    url(r'^media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),

    url(r'^', include(router.urls)),

    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

    url(r'docs/',include_docs_urls(title='GOHB生鮮')),

    # drf自帶的token認證模式
    url(r'^api-token-auth/', views.obtain_auth_token),

    # jwt的認證接口
    url(r'^login/', obtain_jwt_token),
]

在utils文件夾下添加yunpian.py

yunpian.py


import json
import requests


class YunPian(object):

    def __init__(self, api_key):
        self.api_key = api_key
        self.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json"

    def send_sms(self, code, mobile):
        parmas = {
            "apikey": self.api_key,
            "mobile": mobile,
            "text": "【Gohb平臺】您的驗證碼是{code}。如非本人操作,請忽略本短信".format(code=code)
        }

        response = requests.post(self.single_send_url, data=parmas)
        re_dict = json.loads(response.text)
        return re_dict


if __name__ == "__main__":
    yun_pian = YunPian("")
    yun_pian.send_sms("2020", "")


修改api.js內容,修改註冊、登陸、短信獲取的ip地址
在這裏插入圖片描述
後臺測試驗證碼是否正確,嘗試無效驗證碼
在這裏插入圖片描述
進入xadmin後臺管理,添加驗證碼
在這裏插入圖片描述
後臺測試註冊功能成功
在這裏插入圖片描述
使用手機號進行註冊
在這裏插入圖片描述
登陸成功
在這裏插入圖片描述

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