Django+Vue开发生鲜电商平台之7.用户登录和注册功能(中)

简介: 基于DRF的前后端分离登录与单独使用Django登录的原理不同,不再需要CSRF验证,DRF提供了许多开箱即用的身份验证方案,并且还允许实现自定义方案。

3.Vue和JWT接口调试

在Vue中登录的接口为/login/,域名需要修改为local_host,如下:

//登录
export const login = params => {
  return axios.post(`${local_host}/login/`, params)
}

定义登录的Vue组件为src/views/login/login.vue,如下:

methods:{
    login(){
    var that = this;
    login({
        username:this.userName, //当前页码
        password:this.parseWord
    }).then((response)=> {
        console.log(response);
        //本地存储用户信息
        cookie.setCookie('name',this.userName,7);
        cookie.setCookie('token',response.data.token,7)
        //存储在store
        // 更新store数据
        that.$store.dispatch('setInfo');
        //跳转到首页页面
        this.$router.push({ name: 'index'})
        })
        .catch(function (error) {
        if("non_field_errors" in error){
            that.error = error.non_field_errors[0];
        }
        if("username" in error){
            that.userNameError = error.username[0];
        }
        if("password" in error){
            that.parseWordError = error.password[0];
        }
        });
    },
    errorUnshow(){
    this.error = false;
    }
},

在获取到username和password之后,即可保存到cookie中,并设置有效期为7天。通过setInfo更新store数据,再根据src/store/actions.js中export const setInfo = makeAction(types.SET_INFO);找到

src/store/mutations.js,如下:

[types.SET_INFO] (state) {
    state.userInfo = {
        name:cookie.getCookie('name'),
        token:cookie.getCookie('token')
    }
    console.log(state.userInfo);
},

用于将登录信息保存到状态中,进行测试如下:

2345_image_file_copy_32.jpg

可以看到,在登录之前,state中name和token均为空,登录之后即变为当前用户的用户名和JWT。

在用户进行登录提交后,通过对用户名和密码进行比对,但是如果通过手机号码登录,就可能失败,因为登录时obtain_jwt_token查询数据库默认查询的是用户名和密码,而未查询手机号码,因此需要自定义用户认证方法,settings.py中配置如下:

# DRF配置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}
# 自定义用户认证配置
AUTHENTICATION_BACKENDS = [
    'users.views.CustomBackend',
]

apps/users/views.py中定义自定义验证类如下:

from django.db.models import Q
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
User = get_user_model()
# Create your views here.
class CustomBackend(ModelBackend):
    '''自定义用户验证'''
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username)|Q(mobile=username))
            if user.check_password(password) and user.is_delete != True:
                return user
        except Exception as e:
            return None

urls.py中配置路由如下:

from django.conf.urls import url, include
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
import xadmin
from .settings import MEDIA_ROOT
from goods.views import GoodsListViewSet, CategoryViewSet
# Create a router and register our viewsets with it.
router = DefaultRouter()
# 配置goods的路由
router.register(r'goods', GoodsListViewSet, basename='goods')
# 配置categories的路由
router.register(r'categorys', CategoryViewSet, basename='categorys')
urlpatterns = [
       url(r'^xadmin/', xadmin.site.urls),
       url(r'^media/(?P<path>.*)$', serve, {'document_root':MEDIA_ROOT}),
       # url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
       # 商品列表页
       url(r'^', include(router.urls)),
       # 文档路由
       url(r'docs/', include_docs_urls(title='生鲜电商')),
       # DRF自带认证路由
       url(r'^api-token-auth/', views.obtain_auth_token, name='api_token_auth'),
       # JWT认证路由
       url(r'^login/', obtain_jwt_token),
]

显示:

image.jpeg

显然,获取到了token并成功进行了验证。

JWT还有很多设置,包括过期时间等,可以根据需要进行配置,如下:

# JWT配置
JWT_AUTH = {
    # 过期时间
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    # 请求头前缀
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

三、用户注册功能实现

1.云片网发送短信验证码

在注册页面输入手机号发送验证码,后端需要有相应的接口来发送验证码,在成功和失败后需要进行相应的操作。

发送短信验证码需要使用第三方服务,可以使用云片网、阿里妈妈等平台的短信验证码服务,这里选择云片网。

在使用之前需要新增签名和模板,具体操作可参考https://blog.csdn.net/CUFEECR/article/details/106941804

其中用于发送验证码的单条发送接口文档为https://www.yunpian.com/official/document/sms/zh_cn/domestic_single_send,可以看到接口为https://sms.yunpian.com/v2/sms/single_send.json,请求参数中必传参数为apikey、mobile和text。

在apps下新建一个Python Package为utils作为工具目录,下新建yunpian.py用于短信发送测试如下:

import requests
import json
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):
        params = {
            'apikey': self.api_key,
            'mobile': mobile,
            'text': '【Python进化讲堂】欢迎您注册Fresh_Ecommerce ,验证码:{}(5分钟内有效,如非本人操作,请忽略)'.format(code)
        }
        response = requests.post(self.single_send_url, data=params)
        re_dict = json.loads(response.text)
        print(re_dict)
if __name__ == '__main__':
    yunpian = YunPian('edf71361381f31b3957beda37f20xxxx')  # 改为你自己的apikey
    yunpian.send_sms('1234', '13312345678')  # 改为你自己的手机号

运行该文件,打印:

{'code': 0, 'msg': '发送成功', 'count': 1, 'fee': 0.05, 'unit': 'RMB', 'mobile': '13312345678', 'sid': 56592475448}

则发送短信成功。

除此之外,还可以使用云片的Python SDK进行短信发送,可参考http://oss-standard.oss-cn-hangzhou.aliyuncs.com/yunpian/app/apiweb/pythonSDK.mp4https://github.com/yunpian/yunpian-python-sdk进行使用。

2.DRF实现发送短信验证码接口

需要在DRF中接入短信验证码发送。

在发送短信验证码前需要进行验证,包括手机号是否合法、是否被注册过和注册频率等,在serializer中进行验证,apps/users下新建serializers.py如下:

import re
from datetime import datetime, timedelta
from rest_framework import serializers
from django.contrib.auth import get_user_model
from Fresh_Ecommerce.settings import REGEX_MOBILE
from .models import VerifyCode
User = get_user_model()
class SmsSerializer(serializers.Serializer):
    '''短信发送序列化'''
    mobile = serializers.CharField(max_length=11)
    def validate_mobile(self, mobile):
        '''验证手机号码'''
        # 验证手机号码是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError('手机号格式有误,请重新输入')
        # 验证手机是否注册
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError('手机号已经被注册过,请更换手机号重新注册或直接使用该手机号登录')
        # 验证短信发送频率
        one_minute_ago = datetime.now() - timedelta(minutes=1)
        if VerifyCode.objects.filter(add_time__gt=one_minute_ago, mobile=mobile).count():
            raise serializers.ValidationError('验证码发送频率过快,请稍后再试')
        return mobile

apps/users//views.py下创建发送短信的视图如下:

class SmsCodeViewSet(CreateModelMixin, viewsets.GenericViewSet):
    '''发送短信验证码'''
    serializer_class = SmsSerializer
    def generate_code(self):
        '''生成4位数验证码'''
        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']
        code = self.generate_code()
        sms_status = yunpian.send_sms(code, 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,
                'code': code
            }, status=status.HTTP_201_CREATED)

在定义发送短信验证码的View时,create()方法中调用serializer.is_valid()时需要加入参数raise_exception=True,这样在执行时如果is_valid()方法出错就会抛出异常,不会再向下执行,并且由DRF捕捉返回400状态码,便于在前端查看。

apps/utils/yunpian.py修改如下:

import requests
import json
from Fresh_Ecommerce.settings import APIKEY
class YunPian(object):
    def __init__(self):
        self.api_key = APIKEY
        self.single_send_url = 'https://sms.yunpian.com/v2/sms/single_send.json'
    def send_sms(self, code, mobile):
        params = {
            'apikey': self.api_key,
            'mobile': mobile,
            'text': '【Python进化讲堂】欢迎您注册Fresh_Ecommerce ,验证码:{}(5分钟内有效,如非本人操作,请忽略)'.format(code)
        }
        response = requests.post(self.single_send_url, data=params)
        re_dict = json.loads(response.text)
        return re_dict
yunpian = YunPian()
if __name__ == '__main__':
    yunpian.send_sms('1234', '13312345678')  # 改为你自己的手机号

settings.py配置如下:

# 手机号码验证正则表达式
REGEX_MOBILE = '^1[35789]\d{9}$|^147\d{8}$'
# 云片网APIKEY
APIKEY = 'edf71361381f31b3957beda37f20xxxx'

urls.py中配置路由如下:

# 配置短信验证码路由
router.register(r'codes', SmsCodeViewSet, basename='codes')

进行请求测试如下:

2345_image_file_copy_34.jpg

显然,已经可以对手机号进行验证,并且发送成功后会返回相应信息。

说明:

因为接口请求需要用POST方法,因此开始直接使用GET方法会失败,DRF提供了在页面直接用POST方法发送数据的功能,这对以后的测试提供了极大的方便。

此时查看数据库,可以看到刚刚保存的验证码如下:

+----+------+-------------+----------------------------+-----------+
| id | code | mobile      | add_time                   | is_delete |
+----+------+-------------+----------------------------+-----------+
|  1 | 4745 | 13311111111 | 2020-07-28 17:10:38.142213 |         0 |
+----+------+-------------+----------------------------+-----------+
1 row in set (0.01 sec)

3.用户序列化和验证器

注册页面需要传递3个数据,即手机号码、验证码和密码,对应3个字段,需要定义视图并验证。

serializers.py中定义用户注册的序列化如下:

class UserRegSerializer(serializers.ModelSerializer):
    '''用户序列化'''
    code = serializers.CharField(max_length=4, min_length=4,
                                 help_text='验证码',
                                 error_messages={
                                     'required': '请输入验证码',
                                     'blank': '请输入验证码',
                                     'max_length': '请输入4位验证码',
                                     'min_length': '请输入4位验证码'
                                 })
    username = serializers.CharField(required=True, allow_blank=False,  validators=[UniqueValidator(queryset=User.objects.all(), message='用户已经存在')])
    def validate_code(self, code):
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time')
        # 验证验证码是否存在
        if verify_records:
            last_record = verify_records[0]
            five_minute_ago = datetime.now() - timedelta(minutes=5)
            # 验证验证码是否过期
            if five_minute_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']

因为code字段只是为了验证临时生成的、并不需要保存到用户数据表中,因此在验证之后需要删除,在validate(attrs)方法中实现即可,同时因为人为设定前端传递回来的手机号数据变量名为username而非mobile,因此需要在validate(attrs)方法中为attrs变量增加键为mobile的数据,并且要修改UserProfile模型的mobile字段允许为空,修改如下:

class UserProfile(AbstractUser):
    '''用户'''
    name = models.CharField(max_length=30, null=True, blank=True, verbose_name='姓名')
    birthday = models.DateField(null=True, blank=True, verbose_name='出生日期')
    gender = models.CharField(max_length=6, choices=(('male', u'男'), ('female', u'女')), default='female',
                              verbose_name='性别')
    mobile = models.CharField(max_length=11, null=True, blank=True, verbose_name='电话')
    email = models.CharField(max_length=50, null=True, blank=True, verbose_name='邮箱')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')
    class Meta:
        verbose_name = '用户'
        verbose_name_plural = '用户'
    def __str__(self):
        return self.username

修改后需要将变化映射到数据库中。

对于字段的验证,除了默认的required、max_length、min_length等验证方式,DRF还提供了专业的验证器,包括UniqueValidator、UniqueTogetherValidator、UniqueForDateValidator和Advanced field defaults等。

views.py定义视图如下:

class UserViewSet(CreateModelMixin, viewsets.GenericViewSet):
    '''用户'''
    serializer_class = UserRegSerializer

urls.py中注册路由如下:

# 配置注册路由
router.register(r'users', UserViewSet, basename='users')

进行访问测试如下:

2345_image_file_copy_35.jpg

显然,对于多个字段的验证,如果某一个字段验证失败,则提示该字段的错误信息,如果多个字段验证失败,则将这些字段的错误信息都显示出来。

从之前的DRF的测试中可以总结出,DRF请求消息返回的规范为:

http_code
{
    field1: ['', ''],
    field2: [],
    ...
    'non_fields_error'
}

即包含HTTP状态码和具体信息,如果是返回的错误信息可以用于对前端的有误区域进行标亮显示,以便于用户重新输入。

相关文章
|
2天前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的校园二手交易平台的设计与开发附带文章和源代码设计说明文档ppt
基于ssm+vue.js+uniapp小程序的校园二手交易平台的设计与开发附带文章和源代码设计说明文档ppt
13 2
|
3天前
|
存储 JavaScript 前端开发
Vue.js表单开发宝藏工具集,让构建表单变得轻松又酷炫!
Vue Tiny Validate 是最小的验证库,如果你只需要最基本的功能,它可以帮你减轻负担。
11 3
|
4天前
|
JavaScript Java 测试技术
基于ssm+vue.js的小型企业办公自动化系统的设计和开发附带文章和源代码设计说明文档ppt
基于ssm+vue.js的小型企业办公自动化系统的设计和开发附带文章和源代码设计说明文档ppt
17 4
|
5天前
|
JavaScript Java 测试技术
基于springboot+vue.js的线上辅导班系统的开发与设计附带文章和源代码设计说明文档ppt
基于springboot+vue.js的线上辅导班系统的开发与设计附带文章和源代码设计说明文档ppt
13 1
|
6天前
|
JavaScript Java 测试技术
基于微信小程序校园订餐的设计与开发+ssm+vue.js附带文章和源代码设计说明文档ppt
基于微信小程序校园订餐的设计与开发+ssm+vue.js附带文章和源代码设计说明文档ppt
15 1
|
6天前
|
JavaScript 前端开发 Java
开发语言漫谈-Vue
Vue严格说来不是一门语言
|
7天前
|
JavaScript Java 测试技术
智慧旅游平台开发微信小程序+springboot+vue.js附带文章和源代码设计说明文档ppt
智慧旅游平台开发微信小程序+springboot+vue.js附带文章和源代码设计说明文档ppt
17 0
|
16天前
|
监控 安全 NoSQL
采用java+springboot+vue.js+uniapp开发的一整套云MES系统源码 MES制造管理系统源码
MES系统是一套具备实时管理能力,建立一个全面的、集成的、稳定的制造物流质量控制体系;对生产线、工艺、人员、品质、效率等多方位的监控、分析、改进,满足精细化、透明化、自动化、实时化、数据化、一体化管理,实现企业柔性化制造管理。
44 3
|
19天前
|
JavaScript 前端开发 BI
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
开发环境 技术架构:前后端分离 开发语言:C#.net6.0 开发工具:vs2022,vscode 前端框架:Vue,Ant-Design 后端框架:百小僧开源框架 数 据 库:sqlserver2019
31 4
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
|
19天前
|
JavaScript 前端开发 API
给网站添加一个邮件反馈(django+vue)
给网站添加一个邮件反馈(django+vue)
8 0