登录注册
# 接口分析 1 校验手机号是否存在接口 2 多方式登录接口:用户名/手机号/邮箱 + 密码都可以登录 3 发送手机验证码接口(借助于第三方短信平台) 4 短信登录接口 5 注册接口
校验手机号是否存在接口
from .models import User from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from utils.common_response import APIResponse class UserView(GenericViewSet): @action(methods=['post'],detail=False) def check_mobile(self,request,*args,**kwargs): mobile = request.query_params.get('mobile') if mobile: user=User.objects.filter(mobile=mobile).first() if user: return APIResponse(code=100,msg='手机号存在',exist=True) else: return APIResponse(code=100, msg='手机号不存在',exist=False) else: return APIResponse(code=999,msg='手机号必填')
因为已经做了全局异常处理,主动抛异常也可以,抛异常不是return,而是raise
from .models import User from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from utils.common_response import APIResponse from rest_framework.exceptions import APIException from .serilizer import UserLoginSerializer class UserView(GenericViewSet): serializer_class = UserLoginSerializer queryset = User.objects.filter(is_active=True).all() @action(methods=['get'],detail=False) def check_mobile(self,request,*args,**kwargs): try: mobile = request.GET.get('mobile') if mobile: # 用get方法来查询一个数据库里不存在的记录,程序会报错,我们做了全局异常处理,这里会被捕获 User.objects.get(mobile=mobile) else: raise APIException('手机号必填') except Exception as e: return APIResponse(code=777,msg=str(e)) return APIResponse(code=100, msg='手机号存在')
class UserView(GenericViewSet): serializer_class = UserLoginSerializer queryset = User.objects.filter(is_active=True).all() @action(methods=['get'], detail=False) def check_mobile(self, request, *args, **kwargs): try: mobile = request.GET.get('mobile') User.objects.get(mobile=mobile) except Exception as e: return APIResponse(code=777, msg=str(e)) return APIResponse(code=100, msg='手机号存在')
路由
总路由
from django.urls import path,include path('api/v1/user/',include('home.urls'))
子路由
from rest_framework.routers import SimpleRouter from . import views router = SimpleRouter() # 访问 http://127.0.0.1:8000/api/v1/user/userinfo/check_mobile/ router.register('userinfo', views.UserView, 'userinfo') urlpatterns = [ ] urlpatterns += router.urls
多方式登录接口
逻辑分析
1.取出前端传入的用户名和密码 2.通过用户名和密码去数据库查询用户 3.如果查询到,签发token 4.返回给前端登录成功
序列化类
import re from .models import User from rest_framework import serializers from rest_framework.exceptions import APIException from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER # 这个序列化类用来校验字段,不用来做序列化和反序列化 class UserLoginSerializer(serializers.ModelSerializer): # 重写username字段,把原来的校验规则去掉 username = serializers.CharField() class Meta: model = User # username映射过来,是唯一的,字段自己的校验就过不了 fields = ['username','password'] def validate(self, attrs): """ 1.取出前端传入的用户名和密码 2.通过用户名和密码去数据库查询用户 3.如果查询到,签发token 4.返回给前端登录成功 """ # attrs是前端传入的数据,经过字段自己校验和局部钩子校验过后的数据 user=self._get_user(attrs) token=self._get_token(user) # 把用户名和token放到ser的context中 self.context['token']=token self.context['username']=user.username return attrs def _get_user(self,attrs): username=attrs.get('username') password=attrs.get('password') if re.match(r'^1[3-9][0-9]{9}$',username): user = User.objects.filter(mobile=username).first() elif re.match(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$',username): user=User.objects.filter(email=username).first() else: user = User.objects.filter(username=username).first() if user and user.check_password(password): return user else: # 用户不存在或密码错误 这里的代码,还是在全局钩子中执行,全局钩子校验失败,要抛异常,所以在这抛异常 raise APIException('用户不存在或密码错误') def _get_token(self,user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token
视图类
class UserView(GenericViewSet): queryset = User.objects.filter(is_active=True).all() serializer_class = UserLoginSerializer @action(methods=['POST'],detail=False) def login_mul(self,request): # 序列化类对象时,可以传入context字典 # context是视图类和序列化类沟通的桥梁 # 有了序列化类对象,通过对象.context就可以拿到值 ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) # 执行这句话,会执行字段自己校验,局部钩子、全局钩子 token=ser.context.get('token') username = ser.context.get('username') return APIResponse(token=token,username=username)
# sdk:https://cloud.tencent.com/document/product/382/43196# # 使用步骤: -下载模块:pip3 install tencentcloud-sdk-python
# -*- coding: utf-8 -*- from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException # 导入对应产品模块的client models。 from tencentcloud.sms.v20210111 import sms_client, models # 导入可选配置类 from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile try: cred = credential.Credential("AKIDTXcdXG6u5C9ycAd7WyFex9ED5VwPpBXp", "LZgaKxTOI0VowVs22qTvDKDtLcfWCiqm") httpProfile = HttpProfile() httpProfile.reqMethod = "POST" # post请求(默认为post请求) httpProfile.reqTimeout = 30 # 请求超时时间,单位为秒(默认60秒) httpProfile.endpoint = "sms.tencentcloudapi.com" # 指定接入地域域名(默认就近接入) # 非必要步骤: # 实例化一个客户端配置对象,可以指定超时时间等配置 clientProfile = ClientProfile() clientProfile.signMethod = "TC3-HMAC-SHA256" # 指定签名算法 clientProfile.language = "en-US" clientProfile.httpProfile = httpProfile client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile) req = models.SendSmsRequest() req.SmsSdkAppId = "1400763090" # 腾讯短信创建app把app的id号复制过来https://console.cloud.tencent.com/smsv2/app-manage # 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 # 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 req.SignName = "关于金鹏公众号" # 模板 ID: 必须填写已审核通过的模板 ID # 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 req.TemplateId = "1603526" # 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,,若无模板参数,则设置为空 req.TemplateParamSet = ["8888",'100'] # 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] # 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 req.PhoneNumberSet = ["+8615386800417"] # 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 req.SessionContext = "" req.ExtendCode = "" req.SenderId = "" resp = client.SendSms(req) # 输出json格式的字符串回包 print(resp.to_json_string(indent=2)) except TencentCloudSDKException as err: print(err)
# libs目录下建一个包send_sms_v3,在此包内建setttings.py和sms.py文件 -libs下: send_sms_v3文件夹 __init__.py settings.py sms.py
__init__.py
from .sms import get_code,send_sms
settings.py
SECRET_ID = 'AKIDfDnFkBVinhQ0PiwyoV6WjldKXbT6Yryu' SECRET_KEY = 'veCZigT5X8e1EHXcR30nNjvwnVDvbIsI' APP_ID = '1400799929' SIGN_NAME='新注册公众号' TEMPLATE_ID='1720550'
sms.py
# -*- coding: utf-8 -*- from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException # 导入对应产品模块的client models。 from tencentcloud.sms.v20210111 import sms_client, models # 导入可选配置类 from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile import json # 生成n位验证码的函数 import random from libs.send_sms_v3 import settings def get_code(number=4): code='' for i in range(number): code+=str(random.randint(0,9)) return code def send_sms(code,mobile): try: # 必要步骤: # 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 cred = credential.Credential(settings.SECRET_ID, settings.SECRET_KEY) # cred = credential.Credential( # os.environ.get(""), # os.environ.get("") # ) # 实例化一个http选项,可选的,没有特殊需求可以跳过。 httpProfile = HttpProfile() # 如果需要指定proxy访问接口,可以按照如下方式初始化hp(无需要直接忽略) # httpProfile = HttpProfile(proxy="http://用户名:密码@代理IP:代理端口") httpProfile.reqMethod = "POST" # post请求(默认为post请求) httpProfile.reqTimeout = 60 # 请求超时时间,单位为秒(默认60秒) httpProfile.endpoint = "sms.tencentcloudapi.com" # 指定接入地域域名(默认就近接入) # 非必要步骤: # 实例化一个客户端配置对象,可以指定超时时间等配置 clientProfile = ClientProfile() clientProfile.signMethod = "TC3-HMAC-SHA256" # 指定签名算法 clientProfile.language = "en-US" clientProfile.httpProfile = httpProfile client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile) req = models.SendSmsRequest() req.SmsSdkAppId = settings.APP_ID req.SignName = settings.SIGN_NAME req.TemplateId = settings.TEMPLATE_ID req.TemplateParamSet = [code, '1'] req.PhoneNumberSet = ["+86176XXXX7381"] req.SessionContext = "" req.ExtendCode = "" req.SenderId = "" resp = client.SendSms(req) # 输出json格式的字符串回包 # print(resp.to_json_string(indent=2)) res=json.loads(resp.to_json_string(indent=2)) # 从json格式的字符串回包中获取Code信息,返回 if res.get('SendStatusSet')[0].get('Code')=='Ok': return True except TencentCloudSDKException as err: print(err)
from .models import User from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from utils.common_response import APIResponse from rest_framework.exceptions import APIException from .serilizer import UserLoginSerializer from libs.send_sms_v3 import get_code,send_sms class UserView(GenericViewSet): queryset = User.objects.filter(is_active=True).all() serializer_class = UserLoginSerializer @action(methods=['POST'],detail=False) def send_msg(self,request): try: mobile = request.data['mobile'] # 生成验证码 code=get_code() # 使用异步发送短信 t = Thread(target=send_sms,args=[code,mobile]) t.start() return APIResponse(msg='短信已发送') # 同步发送短信 # res = send_sms(code,mobile) # if res: # return APIResponse(msg='发送成功') # else: # return APIResponse(code=101,msg='发送失败') except Exception as e: raise APIException(str(e))
from rest_framework.routers import SimpleRouter from . import views router = SimpleRouter() # 访问 http://127.0.0.1:8000/api/v1/user/userinfo/send_msg/ router.register('userinfo', views.UserView, 'userinfo') urlpatterns = [ ] urlpatterns += router.urls
1 取出手机号和验证码
2 校验验证码是否正确(发送验证码接口,存储验证码)
-session:根本不用
-全局变量:不好,可能会取不到,集群环境中
-缓存:django自带缓存
-导入:from django.core.cache import cache
-cache.set()
-cache.get()
3 根据手机号查询用户,如果能查到--->
4 签发token
5 返回给前端
# views.py class UserView(GenericViewSet): queryset = User.objects.filter(is_active=True).all() serializer_class = UserLoginSerializer @action(methods=['POST'],detail=False) def login_mul(self,request,*args,**kwargs): # 可以把login_mul与login_smg中相同的代码抽取出来,放到_login方法中,直接调用即可 ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) # 执行这句话,会执行字段自己校验,局部钩子、全局钩子 token=ser.context.get('token') username = ser.context.get('username') return APIResponse(token=token,username=username) @action(methods=['POST'],detail=False) def send_msg(self,request): try: mobile = request.data['mobile'] # 生成验证码 code=get_code() # 一定要找个地方存一下,存到缓存中,key值唯一 # 目前没学redis,暂时先存在内存中【项目部署会有坑】,后期要使用redis作为缓存 cache.set('sms_code_%s'%mobile,code) # 使用异步发送短信 t = Thread(target=send_sms,args=[code,mobile]) t.start() return APIResponse(msg='短信已发送') except Exception as e: raise APIException(str(e)) @action(methods=['POST'], detail=False) def login_smg(self, request,*args,**kwargs): # 此处代码后面进行封装 ser = UserMobileSerializer(data=request.data) ser.is_valid(raise_exception=True) # 执行这句话,会执行字段自己校验,局部钩子、全局钩子 token=ser.context.get('token') username = ser.context.get('username') return APIResponse(token=token,username=username) def _login(self,request,*args,**kwargs): ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) token = ser.context.get('token') username = ser.context.get('username') return APIResponse(token=token,username=username)
# views.py class UserView(GenericViewSet): queryset = User.objects.filter(is_active=True).all() serializer_class = UserLoginSerializer
# 多方式登录 @action(methods=['POST'],detail=False) def login_mul(self,request,*args,**kwargs): return self._login(request,*args,**kwargs)
# 发送短信 @action(methods=['POST'],detail=False) def send_msg(self,request): try: mobile = request.data['mobile'] # 生成验证码 code=get_code() # 一定要找个地方存一下,存到缓存中,key值唯一 # 目前没学redis,暂时先存在内存中【项目部署会有坑】,后期要使用redis作为缓存 cache.set('sms_code_%s'%mobile,code) # 使用异步发送短信 t = Thread(target=send_sms,args=[code,mobile]) t.start() return APIResponse(msg='短信已发送') except Exception as e: raise APIException(str(e)) # login_mul与login_smg的唯一区别就是使用的序列化类不一样,可以重写get_serializer_class方法 # 如果访问login_smg方法,使用的序列化类是UserMobileSerializer # 如果是其他,还使用之前的序列化类get_serializer_class def get_serializer_class(self): if self.action == 'login_msg': return UserMobileSerializer else: return super().get_serializer_class() # 短信验证码登录 @action(methods=['POST'], detail=False) def login_msg(self, request,*args,**kwargs): return self._login(request,*args,**kwargs) # 封装一个_login方法 def _login(self,request,*args,**kwargs): ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) token = ser.context.get('token') username = ser.context.get('username') return APIResponse(token=token,username=username)
# serializer.py 1 多方式登录序列化类 # 这个序列化类用来校验字段,不用来做序列化和反序列化 class UserLoginSerializer(serializers.ModelSerializer): # 重写username字段,把原来的校验规则去掉 username = serializers.CharField() class Meta: model = User # username映射过来,是唯一的,字段自己的校验就过不了 fields = ['username','password'] def validate(self, attrs): """ 1.取出前端传入的用户名和密码 2.通过用户名和密码去数据库查询用户 3.如果查询到,签发token 4.返回给前端登录成功 """ # attrs是前端传入的数据,经过字段自己校验和局部钩子校验过后的数据 user=self._get_user(attrs) token=self._get_token(user) # 把用户名和token放到ser的context中 self.context['token']=token self.context['username']=user.username return attrs def _get_user(self,attrs): username=attrs.get('username') password=attrs.get('password') if re.match(r'^1[3-9][0-9]{9}$',username): user = User.objects.filter(mobile=username).first() elif re.match(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$',username): user=User.objects.filter(email=username).first() else: user = User.objects.filter(username=username).first() if user and user.check_password(password): return user else: # 用户不存在或密码错误 这里的代码,还是在全局钩子中执行,全局钩子校验失败,要抛异常,所以在这抛异常 raise APIException('用户不存在或密码错误') def _get_token(self,user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token 2 验证码短信发送与登录校验序列化类 class UserMobileSerializer(serializers.ModelSerializer): mobile=serializers.CharField() code=serializers.CharField() class Meta: model = User # code不是表字段,要重写,mobile有唯一约束,需要重写 fields = ['mobile','code'] def validate(self, attrs): """ 1.取出前端传入的用户名和密码 2.通过用户名和密码去数据库查询用户 3.如果查询到,签发token 4.返回给前端登录成功 """ # attrs是前端传入的数据,经过字段自己校验和局部钩子校验过后的数据 user=self._get_user(attrs) token=self._get_token(user) # 把用户名和token放到ser的context中 self.context['token']=token self.context['username']=user.username return attrs def _get_user(self,attrs): code =attrs.get('code') mobile=attrs.get('mobile') # 从缓存中取出 old_code=cache.get('sms_code_%s'%mobile) if old_code and old_code==code: # 根据手机号,查到用户 user=User.objects.filter(mobile=mobile).first() if user: return user else: raise APIException('用户不存在') else: raise APIException('验证码错误') def _get_token(self,user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token
import re from .models import User from rest_framework import serializers from rest_framework.exceptions import APIException from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER from django.core.cache import cache # 写一个父类,两个序列化类都继承这个父类,只要在各自的类中重写_get_user方法即可 class BaseUserSerializer: def validate(self, attrs): """ 1.取出前端传入的用户名和密码 2.通过用户名和密码去数据库查询用户 3.如果查询到,签发token 4.返回给前端登录成功 """ # attrs是前端传入的数据,经过字段自己校验和局部钩子校验过后的数据 user=self._get_user(attrs) token=self._get_token(user) # 把用户名和token放到ser的context中 self.context['token']=token self.context['username']=user.username return attrs def _get_user(self,attrs): raise Exception('你必须重写它') def _get_token(self,user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token # 这个序列化类用来校验字段,不用来做序列化和反序列化 class UserLoginSerializer(BaseUserSerializer,serializers.ModelSerializer,): # 重写username字段,把原来的校验规则去掉 username = serializers.CharField() class Meta: model = User # username映射过来,是唯一的,字段自己的校验就过不了 fields = ['username','password'] def _get_user(self,attrs): username=attrs.get('username') password=attrs.get('password') if re.match(r'^1[3-9][0-9]{9}$',username): user = User.objects.filter(mobile=username).first() elif re.match(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$',username): user=User.objects.filter(email=username).first() else: user = User.objects.filter(username=username).first() if user and user.check_password(password): return user else: # 用户不存在或密码错误 这里的代码,还是在全局钩子中执行,全局钩子校验失败,要抛异常,所以在这抛异常 raise APIException('用户不存在或密码错误') class UserMobileSerializer(BaseUserSerializer,serializers.ModelSerializer): mobile=serializers.CharField() code=serializers.CharField() class Meta: model = User # code不是表字段,要重写,mobile有唯一约束,需要重写 fields = ['mobile','code'] def _get_user(self,attrs): code =attrs.get('code') mobile=attrs.get('mobile') # 从缓存中取出 old_code=cache.get('sms_code_%s'%mobile) if old_code and old_code==code: # 根据手机号,查到用户 user=User.objects.filter(mobile=mobile).first() if user: return user else: raise APIException('用户不存在') else: raise APIException('验证码错误')
from rest_framework.mixins import CreateModelMixin class RegisterUserView(GenericViewSet,CreateModelMixin): queryset = User.objects.all() serializer_class = RegisterSerializer def create(self, request, *args, **kwargs): # 两种写法: # 方法一:调用父类的create方法,但是父类的create方法中有serializer.data,只要调用它,就会触发序列化,必须在序列化类中指明字段write_only或read_only # super().create(request,*args,**kwargs) # 方法二:把父类中的create方法拿过来进行修改 serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) return APIResponse(msg='注册成功')
# 数据校验,反序列化 class RegisterSerializer(serializers.ModelSerializer): code=serializers.CharField(max_length=4) class Meta: model=User fields=['mobile','code','password'] extra_kwargs={ 'code':{'write_only':True}, 'password':{'write_only':True} } def validate(self, attrs): # 全局钩子 ''' 1.取出前端传入的code,校验code是否正确 2.把username设置成手机号(你可以随机生成) 3.code不是数据库的字段,从attr中剔除 ''' mobile=attrs.get('mobile') code=attrs.get('code') old_code=cache.get('sms_code_%s'%mobile) if old_code and old_code==code: attrs['username']=mobile attrs.pop('code') else: raise APIException('验证码校验失败') return attrs # 一定要重写create,因为密码是明文,如果不重写,存入到数据库也是明文 def create(self, validated_data): # 创建user用户用户 user = User.objects.create_user(**validated_data) return user
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人