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状态码和具体信息,如果是返回的错误信息可以用于对前端的有误区域进行标亮显示,以便于用户重新输入。

相关文章
|
22天前
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
65 0
|
26天前
|
负载均衡 应用服务中间件 网络安全
Django后端架构开发:Nginx服务优化实践
Django后端架构开发:Nginx服务优化实践
36 2
|
21天前
|
开发者 Android开发 iOS开发
Xamarin开发者的神器!揭秘你绝不能错过的插件和工具,让你的开发效率飞跃式提升
【8月更文挑战第31天】Xamarin.Forms 是一个强大的框架,让开发者通过单一共享代码库构建跨平台移动应用,支持 iOS、Android 和 Windows。使用 C# 和 XAML,它简化了多平台开发流程,保持一致的用户体验。本指南通过创建一个简单的 “HelloXamarin” 应用介绍 Xamarin.Forms 的基本功能和工作原理。首先配置 Visual Studio 开发环境,然后创建并运行一个包含标题、按钮和消息标签的示例应用,展示如何定义界面布局及处理按钮点击事件。这帮助开发者快速入门 Xamarin.Forms,提高跨平台应用开发效率。
31 0
|
21天前
|
C++ Python
Django视图函数VS类视图:如何选择最适合你的开发方式?
【8月更文挑战第31天】本文对比了Django中的函数视图和类视图。函数视图直接处理HTTP请求和响应,灵活且易于维护,适用于简单业务逻辑;类视图基于Python类,提供更丰富的功能和更高的灵活性,适合处理复杂业务逻辑。选择哪种视图取决于具体需求,合理使用两者可帮助你构建高效且易维护的Django应用。
12 0
|
22天前
|
JavaScript 前端开发 开发者
Vue.js的未来已来:掌握最新功能,驾驭前端开发的新浪潮!
【8月更文挑战第30天】Vue.js作为前端开发领域的明星框架,凭借其轻量级、响应式及组件化特性,深受全球开发者喜爱。它持续进化,引入新功能并优化性能,如提升渲染速度和减小打包体积,增强TypeScript支持,简化组件编写流程,进一步提升应用响应性和加载速度,改善开发者体验。活跃的社区和丰富的开源项目如“vuejs-challenges”推动其不断发展。未来,Vue.js将探索更多新特性,如宏和非虚拟DOM模式,巩固其在现代前端框架中的领先地位。
34 0
|
22天前
|
JavaScript 开发者 UED
Vue.js 错误处理与调试:跟上技术潮流,摆脱开发困扰,成为代码大神不是梦!
【8月更文挑战第30天】在 Vue.js 开发中,错误处理与调试至关重要。本文将对比 Vue 的全局错误捕获机制 `Vue.config.errorHandler` 和组件内 `watch` 监听数据变化的方式,并介绍 Vue 开发者工具、控制台打印 (`console.log`) 以及代码断点 (`debugger`) 等调试方法。此外,还将探讨如何通过自定义错误页面提升用户体验。通过这些技巧的对比,帮助开发者灵活选择适合的策略,确保应用稳定性和开发效率。
39 0
|
22天前
|
JavaScript 前端开发 API
全栈开发革命来临!Vue.js与Node.js联手,打造前后端无缝对接的奇迹之作!
【8月更文挑战第30天】在Web开发领域,前后端分离与协作至关重要。Vue.js以其轻量级和易用性深受前端开发者喜爱,而Node.js则以高性能和事件驱动特性在后端领域崭露头角。二者结合开启了全栈开发新篇章,通过RESTful API或GraphQL实现高效稳定通信。本文以示例说明如何使用Vue.js和Node.js构建全栈应用,从前端Vue组件到后端Express服务器,展示了数据获取与展示的全过程。这种组合提供了一种高效简洁的全栈开发方案,使开发者能更专注于业务逻辑实现。
45 0
|
22天前
|
JavaScript 前端开发 UED
揭秘Vue.js高效开发:Vue Router如何让单页面应用路由管理变得如此简单?
【8月更文挑战第30天】随着Web应用复杂性的增加,单页面应用(SPA)因出色的用户体验和高效的页面加载性能而备受青睐。Vue.js凭借简洁的语法和灵活的组件系统成为构建SPA的热门选择,其官方路由管理器Vue Router则简化了路由管理。本文通过实战示例介绍如何利用Vue Router实现高效的SPA路由管理,包括命名路由、动态路由及其核心优势。
12 0
|
22天前
|
存储 JavaScript 前端开发
【Vue.js的神秘力量】一键解锁:如何让Bootstrap和Vuex成为你的开发超能力?
【8月更文挑战第30天】Vue.js是一个轻量且灵活的JavaScript框架,易于上手且功能强大。为提高开发效率和应用交互性,常需集成第三方库。本文介绍如何在Vue.js项目中集成Bootstrap和Vuex,及其它常见第三方库。Bootstrap提供响应式设计和预制组件,通过安装插件和引入CSS/JS即可集成;Vuex作为官方状态管理库,通过安装并创建store来管理组件状态。此外,Vue.js还可轻松集成Axios和Vue Router等库,提升HTTP请求和页面路由功能。合理选择和集成第三方库能显著提升开发效率,但需保持代码可维护性和可读性。
16 0
|
1月前
|
机器学习/深度学习 数据采集 数据可视化
基于爬虫和机器学习的招聘数据分析与可视化系统,python django框架,前端bootstrap,机器学习有八种带有可视化大屏和后台
本文介绍了一个基于Python Django框架和Bootstrap前端技术,集成了机器学习算法和数据可视化的招聘数据分析与可视化系统,该系统通过爬虫技术获取职位信息,并使用多种机器学习模型进行薪资预测、职位匹配和趋势分析,提供了一个直观的可视化大屏和后台管理系统,以优化招聘策略并提升决策质量。