欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

(项目)生鲜超市(六)

程序员文章站 2022-04-09 18:16:58
七、用户登录与手机注册 1、drf的token 在INSTALLED_APPS中注册: 然后迁移数据库,会生成一张表authtoken_token,存放用户的token信息: 配置token的url: 然后现在测试发起post请求登录,我们使用postman工具来发起请求: drf返回的token值 ......

七、用户登录与手机注册

1、drf的token

  在installed_apps中注册:

1 installed_apps = (
2     'rest_framework.authtoken'
3 )

  然后迁移数据库,会生成一张表authtoken_token,存放用户的token信息:

(项目)生鲜超市(六)

  配置token的url:

1 from rest_framework.authtoken import views
2 
3 
4 urlpatterns = [
5     path('api-token-auth/', views.obtain_auth_token),  # drf-token
6 ]

  然后现在测试发起post请求登录,我们使用postman工具来发起请求:

(项目)生鲜超市(六)

  drf返回的token值会保存到数据库中并与用户进行关联:

(项目)生鲜超市(六)

  然后客户端需要进行身份验证,令牌密钥包含在 authorization http header 中。关键字应以字符串文字 “token” 为前缀,用空格分隔两个字符串。例如:

authorization: token 30fc1a3cab2d97a6ab3431d603a0bfc40145785b

  通过验证tokenauthentication 将提供以下凭据:

  • request.user
  • request.auth

  要想获取这两个实例,还要在settings.py中添加以下设置:

1 rest_framework = {
2     'default_authentication_classes': (
3         'rest_framework.authentication.basicauthentication',
4         'rest_framework.authentication.sessionauthentication',
5         'rest_framework.authentication.tokenauthentication'
6     )
7 }

  drf的token也有很大的缺点:

  • token信息是保存在数据库中的,如果是一个分布式的系统,就比较麻烦
  • token永久有效,没有过期时间

2、json web token方式完成用户认证(jwt)

  在虚拟环境中pip install djangorestframework-jwt

  将settings中的rest_framework的tokenauthentication改成jsonwebtokenauthentication:

1 rest_framework = {
2     'default_authentication_classes': (
3         'rest_framework.authentication.basicauthentication',
4         'rest_framework.authentication.sessionauthentication',
5         # 'rest_framework.authentication.tokenauthentication'
6         'rest_framework_jwt.authentication.jsonwebtokenauthentication',
7     )
8 }

  然后修改jwt的url:

1 from rest_framework_jwt.views import obtain_jwt_token
2 
3 urlpatterns = [
4     path('jwt-auth/', obtain_jwt_token )
5 ]

  通过postman发起请求:

(项目)生鲜超市(六)

3、vue和jwt接口调试

  vue中登录接口是login:

1 //登录
2 export const login = params => {
3   return axios.post(`${host}/login/`, params)
4 }

  后台的接口要与前端保持一致:

1 urlpatterns = [
2     path('login/', obtain_jwt_token ),  # jwt-token
3 ]

(项目)生鲜超市(六)

  jwt接口默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,所以我们需要自定义一个用户验证,在users/view.py中编写:

 1 from django.shortcuts import render
 2 from django.contrib.auth.backends import modelbackend
 3 from django.contrib.auth import get_user_model
 4 from django.db.models import q
 5 
 6 # create your views here.
 7 
 8 
 9 user = get_user_model()
10 
11 
12 class custombackend(modelbackend):
13     """jwt自定义用户验证"""
14 
15     def authenticate(self, request, username=none, password=none, **kwargs):
16         try:
17             user = user.objects.get(q(username=username) | q(mobile=username))
18             if user.check_password(password):
19                 return user
20         except exception as e:
21             return none

  然后在setting中配置定义好的类:

1 authentication_backends = (
2     'users.views.custombackend',
3 )

  jwt过期时间的设置,在setting中配置:

# jwt过期时间
jwt_auth = {
    'jwt_expiration_delta': datetime.timedelta(days=7),  # 也可以设置seconds=20
    'jwt_auth_header_prefix': 'jwt',  # jwt跟前端保持一致,比如“token”这里设置成jwt
}

4、云片网发送短信验证码

  在云片网进行注册,完善开发者信息,然后新增签名和模板,审核通过之后,添加ip白名单,测试的时候使用本地ip,线上部署的时候一定要换成服务器的ip。

  然后编写发送验证码的逻辑,在apps下新建utils文件夹,新建yunpian.py文件:

 1 import requests
 2 import json
 3 
 4 
 5 class yunpian(object):
 6     def __init__(self, api_key):
 7         self.api_key = api_key
 8         self.single_send_url = 'https://sms.yunpian.com/v2/sms/single_send.json'
 9 
10     def send_sms(self, code, mobile):
11         # 向云片网发起请求的参数
12         parmas = {
13             "apikey": self.api_key,
14             "mobile": mobile,
15             "text": "【倍思乐】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
16         }
17 
18         # 发起请求
19         response = requests.post(self, self.single_send_url, data=parmas)
20         re_dict = json.loads(response.text)
21         return re_dict
22 
23 
24 # 测试
25 if __name__ == '__main__':
26     yun_pian = yunpian('9b11127a9701975c734b8aee81ee3526')
27     yun_pian.send_sms('2018', '13993601652')

  现在开始编写发送短信验证码的接口,首先在settings中配置手机号码的正则表达式:

1 # 手机号码正则表达式
2 regex_mobile = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"

  然后对手机号码进行序列化,在users下新建serializers.py:

 1 import re
 2 from datetime import datetime, timedelta
 3 
 4 from rest_framework import serializers
 5 from django.contrib.auth import get_user_model
 6 
 7 from mxshop.settings import regex_mobile
 8 from .models import verifycode
 9 
10 user = get_user_model()
11 
12 
13 class smsserializer(serializers.serializer):
14     mobile = serializers.charfield(max_length=11)
15 
16     # 函数名必须是validate + 验证的字段名
17     def validate_mobile(self, mobile):
18         """手机号验证"""
19 
20         # 查询手机号是否已注册
21         if user.objects.filter(mobile=mobile).count():
22             raise serializers.validationerror('用户已存在')
23 
24         # 验证手机号码是否合法
25         if not re.match(regex_mobile, mobile):
26             raise serializers.validationerror('手机号码非法')
27 
28         # 限制验证码的发送频率,60秒发送一次
29         one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
30         if verifycode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
31             raise serializers.validationerror('距离上一次发送未超过60秒')
32 
33         return mobile

  将云片网的apikey配置到settings中:

1 # 云片网的apikey
2 apikey = "xxxxx327d4be01608xxxxxxxxxx"

  现在开始完善发送短信验证码的接口:

 1 class smscodeviewset(mixins.createmodelmixin, viewsets.genericviewset):
 2     """手机验证码"""
 3 
 4     serializer_class = smsserializer
 5 
 6     # 随机生成code
 7     def generate_code(self):
 8         seeds = "1234567890"
 9         random_str = []
10         for i in range(4):
11             random_str.append(choice(seeds))
12 
13         return "".join(random_str)
14 
15     # 重写createmodelmixin的create方法,加入发送验证码的逻辑
16     def create(self, request, *args, **kwargs):
17         # 验证手机号码
18         serializer = self.get_serializer(data=request.data)
19         serializer.is_valid(raise_exception=true)
20 
21         # 发送验证码
22         mobile = serializer.validated_data["mobile"]
23         yun_pian = yunpian(apikey)
24         code = self.generate_code()
25         sms_status = yun_pian.send_sms(code=code, mobile=mobile)
26         if sms_status["code"] != 0:  # 发送失败
27             return response({
28                 "mobile": sms_status["msg"]
29             }, status=status.http_400_bad_request)
30         else:
31             code_record = verifycode(code=code, mobile=mobile)
32             code_record.save()
33             return response({
34                 "mobile": mobile
35             }, status=status.http_201_created)

  然后注册url:

1 router.register(r'code', smscodeviewset, base_name='code')  # 短信验证码

  现在开是在接口中进行验证,输入不合法的手机号:

(项目)生鲜超市(六)

  输入合法的手机号后,会发送短信验证码到你的手机。

5、注册接口编写

  在编写注册接口之前,需要修改userprofile中的mobile字段为可以为空,因为前端只有一个值,是username,所以mobile可以为空:

 1 class userprofile(abstractuser):
 2     """用户信息"""
 3 
 4     gender_choices = (
 5         ("male", u"男"),
 6         ("female", u"女")
 7     )
 8     name = models.charfield("姓名", max_length=30, null=true, blank=true)
 9     birthday = models.datefield("出生年月", null=true, blank=true)
10     gender = models.charfield("性别", max_length=6, choices=gender_choices, default="female")
11     mobile = models.charfield("电话", max_length=11, null=true, blank=true)
12     email = models.emailfield("邮箱", max_length=100, null=true, blank=true)
13 
14     class meta:
15         verbose_name = "用户信息"
16         verbose_name_plural = verbose_name
17 
18     def __str__(self):
19         return self.username

  然后编写用户注册的serializer:

 1 class userregserializer(serializers.modelserializer):
 2     # userprofile中没有code字段,这里需要自定义一个code序列化字段
 3     code = serializers.charfield(required=true, write_only=true, max_length=4, min_length=4,
 4                                  error_messages={
 5                                      "blank": "请输入验证码",
 6                                      "required": "请输入验证码",
 7                                      "max_length": "验证码格式错误",
 8                                      "min_length": "验证码格式错误"
 9                                  },
10                                  help_text="验证码")
11     # 验证用户名是否存在
12     username = serializers.charfield(label="用户名", help_text="用户名", required=true, allow_blank=false,
13                                      validators=[uniquevalidator(queryset=user.objects.all(), message="用户已经存在")])
14 
15     # 验证code
16     def validate_code(self, code):
17         # 用户注册,post方式提交注册信息,post的数据都保存在initial_data里面
18         # username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
19         verify_records = verifycode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
20 
21         if verify_records:
22             # 最近的一个验证码
23             last_record = verify_records[0]
24             # 有效期为五分钟
25             five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
26             if five_mintes_ago > last_record.add_time:
27                 raise serializers.validationerror("验证码过期")
28 
29             if last_record.code != code:
30                 raise serializers.validationerror("验证码错误")
31 
32         else:
33             raise serializers.validationerror("验证码错误")
34 
35     # 所有字段。attrs是字段验证合法之后返回的总的dict
36     def validate(self, attrs):
37         # 前端没有传mobile值到后端,这里添加进来
38         attrs["mobile"] = attrs["username"]
39         # code是自己添加得,数据库中并没有这个字段,验证完就删除掉
40         del attrs["code"]
41         return attrs
42 
43     class meta:
44         model = user
45         fields = ('username', 'code', 'mobile')

  然后在views.py中编写用户注册的接口:

1 class userviewset(mixins.createmodelmixin, viewsets.genericviewset):
2     """用户注册"""
3 
4     serializer_class = userregserializer

  注册url:

1 router.register(r'users', userviewset, base_name='users')  # 用户注册

  然后在接口中进行测试:

(项目)生鲜超市(六)

6、django信号量实现用户密码修改

  完善用户注册接口:

1 class userviewset(mixins.createmodelmixin, viewsets.genericviewset):
2     """用户注册"""
3 
4     serializer_class = userregserializer
5     queryset = user.objects.all()

  然后在serializers.py中添加密码字段:

1 fields = ('username', 'code', 'mobile', 'password')

  需要注意的是密码不能明文显示,需要加密保存, 这是重载create方法:

 1 class userregserializer(serializers.modelserializer):
 2     # userprofile中没有code字段,这里需要自定义一个code序列化字段
 3     code = serializers.charfield(required=true, write_only=true, max_length=4, min_length=4,
 4                                  error_messages={
 5                                      "blank": "请输入验证码",
 6                                      "required": "请输入验证码",
 7                                      "max_length": "验证码格式错误",
 8                                      "min_length": "验证码格式错误"
 9                                  },
10                                  help_text="验证码")
11     # 验证用户名是否存在
12     username = serializers.charfield(label="用户名", help_text="用户名", required=true, allow_blank=false,
13                                      validators=[uniquevalidator(queryset=user.objects.all(), message="用户已经存在")])
14 
15     # 输入密码的时候不显示明文
16     password = serializers.charfield(
17         style={'input_type': 'password'}, label=true, write_only=true
18     )
19 
20     # 密码加密保存
21     def create(self, validated_data):
22         user = super(userregserializer, self).create(validated_data=validated_data)
23         user.set_password(validated_data["password"])
24         user.save()
25         return user
26 
27     # 验证code
28     def validate_code(self, code):
29         # 用户注册,post方式提交注册信息,post的数据都保存在initial_data里面
30         # username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
31         verify_records = verifycode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
32 
33         if verify_records:
34             # 最近的一个验证码
35             last_record = verify_records[0]
36             # 有效期为五分钟
37             five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
38             if five_mintes_ago > last_record.add_time:
39                 raise serializers.validationerror("验证码过期")
40 
41             if last_record.code != code:
42                 raise serializers.validationerror("验证码错误")
43 
44         else:
45             raise serializers.validationerror("验证码错误")
46 
47     # 所有字段。attrs是字段验证合法之后返回的总的dict
48     def validate(self, attrs):
49         # 前端没有传mobile值到后端,这里添加进来
50         attrs["mobile"] = attrs["username"]
51         # code是自己添加得,数据库中并没有这个字段,验证完就删除掉
52         del attrs["code"]
53         return attrs
54 
55     class meta:
56         model = user
57         fields = ('username', 'code', 'mobile', 'password')

  下面通过信号量的方式来保存密码,在users下新建signals.py文件:

 1 from django.dispatch import receiver
 2 from django.db.models.signals import post_save
 3 from django.contrib.auth import get_user_model
 4 
 5 
 6 user = get_user_model()
 7 
 8 
 9 # post_save接收信号的方法, sender接收信号的model
10 @receiver(post_save, sender=user)
11 def create_user(sender, instance=none, created=false, **kwargs):
12     # 是否新建,因为update的时候也会进行post_save
13     if created:
14         # instance相当于user
15         password = instance.password
16         instance.set_password(password)
17         instance.save()

  然后在users/apps.py中重载配置:

1 from django.apps import appconfig
2 
3 
4 class usersconfig(appconfig):
5     name = 'users'
6     verbose_name = "用户管理"
7 
8     def ready(self):
9         import users.signals

  appconfig自定义的函数,会在django启动时被运行,现在添加用户的时候,密码就会自动加密存储了。

7、vue和注册接口联调

  完善注册接口:

 1 class userviewset(mixins.createmodelmixin, viewsets.genericviewset):
 2     """用户注册"""
 3 
 4     serializer_class = userregserializer
 5     queryset = user.objects.all()
 6 
 7     def create(self, request, *args, **kwargs):
 8         serializer = self.get_serializer(data=request.data)
 9         serializer.is_valid(raise_exception=true)
10 
11         user = self.perform_create(serializer)
12         re_dict = serializer.data
13         payload = jwt_payload_handler(user)
14         re_dict["token"] = jwt_encode_handler(payload)
15         re_dict["name"] = user.name if user.name else user.username
16 
17         headers = self.get_success_headers(serializer.data)
18         return response(re_dict, status=status.http_201_created, headers=headers)
19 
20     def perform_create(self, serializer):
21         return serializer.save()

  然后将vue中register的接口的host修改:

1 //注册
2 
3 export const register = parmas => { return axios.post(`${host}/users/`, parmas) }

  然后在注册页面进行测试,发送短信注册成功跳转到首页:

(项目)生鲜超市(六)

  如果没有在云片网审核通过的童靴想要测试接口是否正确,可以先暂时修改发送短信的接口,将随机生成的验证码打印出来,暂时不同云片网发送短信,修改发送短信的接口:

 1 class smscodeviewset(mixins.createmodelmixin, viewsets.genericviewset):
 2     """手机验证码"""
 3 
 4     serializer_class = smsserializer
 5 
 6     # 随机生成code
 7     def generate_code(self):
 8         seeds = "1234567890"
 9         random_str = []
10         for i in range(4):
11             random_str.append(choice(seeds))
12 
13         print("".join(random_str))
14 
15         return "".join(random_str)
16 
17     # 重写createmodelmixin的create方法,加入发送验证码的逻辑
18     # def create(self, request, *args, **kwargs):
19     #     # 验证手机号码
20     #     serializer = self.get_serializer(data=request.data)
21     #     serializer.is_valid(raise_exception=true)
22     #
23     #     # 发送验证码
24     #     mobile = serializer.validated_data["mobile"]
25     #     yun_pian = yunpian(apikey)
26     #     code = self.generate_code()
27     #     sms_status = yun_pian.send_sms(code=code, mobile=mobile)
28     #     if sms_status["code"] != 0:  # 发送失败
29     #         return response({
30     #             "mobile": sms_status["msg"]
31     #         }, status=status.http_400_bad_request)
32     #     else:
33     #         code_record = verifycode(code=code, mobile=mobile)
34     #         code_record.save()
35     #         return response({
36     #             "mobile": mobile
37     #         }, status=status.http_201_created)
38 
39     # 以下为没有使用云片网
40     def create(self, request, *args, **kwargs):
41         # 验证手机号码
42         serializer = self.get_serializer(data=request.data)
43         serializer.is_valid(raise_exception=true)
44 
45         # 获取打印验证码
46         mobile = serializer.validated_data["mobile"]
47         code = self.generate_code()
48 
49         code_record = verifycode(code=code, mobile=mobile)
50         code_record.save()
51         return response({
52             "mobile": mobile
53         }, status=status.http_201_created)