昨日回顧
1 分頁功能
-三個(gè)類:普通分頁,偏移分頁,游標(biāo)分頁
-每個(gè)類中都有幾個(gè)屬性:查詢的字段捆昏,每頁顯示的條數(shù),每頁最多顯示的條數(shù)吹艇,游標(biāo)分頁中有個(gè)排序
-定義一個(gè)類诱建,繼承上面3個(gè)其中一個(gè),重寫字段
-繼承了APIView:實(shí)例化得到分頁對象仅颇,把要分頁的數(shù)據(jù)傳入单默,返回分頁后的數(shù)據(jù),序列化忘瓦,可以按照自己定制的規(guī)則返回搁廓,也可也使用page.get_paginated_response(ser.data)
-如果繼承了ListModelMixin和GenericAPIView,直接配置就可以了
pagination_class = MyCursorPagination
2 全局異常
-寫一個(gè)方法
def common_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is None:
response = Response({'code': 999, 'detail': str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return response
-setting中配置一下
'EXCEPTION_HANDLER':'app01.utils.common_exception_handler'
-通常情況咱們會(huì)記錄日志
-使用django日志記錄 xx.log文件中
-使用sentry(公司自己寫)日志記錄耕皮, 平臺(tái)(django)境蜕,查詢,統(tǒng)計(jì)凌停,告警
3 自己寫的Response
class APIResponse(Response):
def __init__(self, code=100, msg='成功', data=None, status=None, headers=None, content_type=None, **kwargs):
dic = {'code': code, 'msg': msg}
if data:
dic['data'] = data
dic.update(kwargs) # 這里使用update
super().__init__(data=dic, status=status,
template_name=None, headers=headers,
exception=False, content_type=content_type)
#我們期望這種格式
{
code:100
msg:成功粱年,失敗信息
data:[]
count:
}
# 使用
APIResponse(data=[],code=101,heades={})
4 自動(dòng)生成接口文檔
-手寫
-自動(dòng)生成(drf:crorapi)罚拟,swagger(java台诗,go完箩,python)
-安裝:pip3 install coreapi
-路由中配置:path('docs/', include_docs_urls(title='圖書管理系統(tǒng)api')),
-在配置文件中配置:'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
-寫視圖類,在里面要加注釋拉队,就在接口平臺(tái)就能看到
1弊知、作業(yè)講解之用戶注冊/查詢用戶/修改頭像
詳見當(dāng)天代碼,補(bǔ)上來
作業(yè):
1 自定義User表擴(kuò)展auth的User粱快,新增mobile唯一約束字段秩彤;新增icon圖片字段
2 在自定義User表基礎(chǔ)上,用 GenericViewSet + CreateModelMixin + serializer 完成User表新增接口(就是注冊接口)(重要提示:序列化類要重寫create方法皆尔,不然密碼就是明文了)
3 在自定義User表基礎(chǔ)上呐舔,用 GenericViewSet + RetrieveModelMixin + serializer 完成User表單查(就是用戶中心)
4 在自定義User表基礎(chǔ)上,用 GenericViewSet + UpdateModelMixin + serializer 完成用戶頭像的修改
代碼:
models.py 擴(kuò)寫了user表
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
mobile=models.CharField(max_length=32,unique=True)
icon=models.ImageField(upload_to='head/',default='head/default.png')
serializer.py 可以寫多個(gè)序列化器來對一個(gè)視圖類
from rest_framework import serializers
from homework import models
from rest_framework.exceptions import ValidationError
class UserModelSerializer(serializers.ModelSerializer):
# 注冊功能慷蠕,需要什么字段
# username,password,re_password,mobile
# 這個(gè)字段在表中沒有珊拼,需要寫成write_only=True
re_password = serializers.CharField(max_length=18, min_length=3, write_only=True)
class Meta:
model = models.UserInfo
fields = ['username', 'password', 'mobile', 're_password', 'icon']
extra_kwargs = {
'username': {'max_length': 12, 'min_length': 3},
'password': {'write_only': True},
'icon': {'read_only': True}
}
# 寫mobile的局部鉤子
def validate_mobile(self, data):
if len(data) == 11:
return data
else:
raise ValidationError('手機(jī)號(hào)不合法')
# 全局鉤子校驗(yàn)兩次密碼是否一致
def validate(self, attrs):
password = attrs.get('password')
re_password = attrs.get('re_password')
if password == re_password:
return attrs
else:
raise ValidationError('兩次密碼不一致')
# 重寫create方法,實(shí)現(xiàn)密碼的加密
def create(self, validated_data):
# re_password不在表字段里流炕,在這里移除
validated_data.pop('re_password')
# models.UserInfo.objects.create(**validated_data) # 密碼是明文
user = models.UserInfo.objects.create_user(**validated_data)
return user # 不要忘記了這句話
# 序列化的時(shí)候澎现,一個(gè)模型類,不一定對著一個(gè)序列化類
# 這個(gè)序列化類只做序列化用
class UserReadOnlyModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ['username', 'mobile', 'icon', 'email', 'date_joined']
# 這個(gè)序列化類每辟,只做修改頭像用
class UserIconModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ['icon']
views.py 一個(gè)視圖類可以對應(yīng)不同的序列化器類剑辫,只要重寫get_serializer_class(self):方法
from homework import models
from homework.serializer import UserModelSerializer, UserReadOnlyModelSerializer, UserIconModelSerializer
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import GenericViewSet, ViewSetMixin
# ViewSetMicin:路由寫法變了,自動(dòng)生成路由
# GenericAPIView :必須指定queryset渠欺,和serializer
class UserView(GenericViewSet, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin):
# class UserView(ViewSetMixin,GenericAPIView,CreateModelMixin):
queryset = models.UserInfo.objects.all()
serializer_class = UserModelSerializer
# 重寫get_serializer_class(self):方法妹蔽,實(shí)現(xiàn)不同的請求,返回的序列化類不一樣挠将,這個(gè)方法返回的是哪個(gè)序列化器就回用哪個(gè)序列化器來執(zhí)行
def get_serializer_class(self):
# 可以根據(jù)請求方式來選擇哪個(gè)序列化器來執(zhí)行
if self.action == 'create':
return UserModelSerializer
elif self.action == 'retrieve':
return UserReadOnlyModelSerializer
elif self.action == 'update':
return UserIconModelSerializer
# 是再寫一個(gè)視圖類胳岂,還是繼續(xù)用上面的?繼續(xù)用上面的
class UserReadOnlyView(GenericViewSet, RetrieveModelMixin):
queryset = models.UserInfo.objects.all()
serializer_class = UserReadOnlyModelSerializer
jwt的使用舔稀,登錄以后才能訪問該視圖類
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
#登錄后才能訪問乳丰,內(nèi)置jwt的認(rèn)證類
class OrderView(APIView):
# 只配它不行,不管是否登錄内贮,都能訪問产园,需要搭配一個(gè)內(nèi)置權(quán)限類
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated] # 必須要帶一個(gè)自帶的權(quán)限認(rèn)證,否則并沒有通過認(rèn)證夜郁,但是不會(huì)報(bào)錯(cuò)什燕,但是request.user里沒有數(shù)據(jù)
def get(self, request):
print(request.user.username)
return Response('訂單數(shù)據(jù)')
#自定義基于jwt的認(rèn)證類
from homework.auth import JwtAuthentication
class OrderView(APIView):
# 登錄以后才能訪問
authentication_classes = [JwtAuthentication]
def get(self, request):
print(request.user.username)
return Response('訂單數(shù)據(jù)')
2、jwt認(rèn)證介紹
-1竞端、不在使用Session認(rèn)證機(jī)制秋冰,而使用Json Web Token(本質(zhì)就是token)認(rèn)證機(jī)制,用戶登錄認(rèn)證
-2婶熬、用戶只要登錄了剑勾,返回用戶一個(gè)token串(隨機(jī)字符串)埃撵,每次用戶發(fā)請求,需要攜帶這個(gè)串過來虽另,驗(yàn)證通過暂刘,我們認(rèn)為用戶登錄了
-3、JWT的構(gòu)成(字符串)
-三部分(每一部分中間通過.分割):header payload signature
-header:明類型捂刺,這里是jwt,聲明加密算法谣拣,頭里加入公司信息...,base64轉(zhuǎn)碼
{
'typ': 'JWT',
'alg': 'HS256'
}
-payload:荷載(有用),當(dāng)前用戶的信息(用戶名族展,id森缠,這個(gè)token的過期時(shí)間,手機(jī)號(hào))仪缸,base64轉(zhuǎn)碼
{
"sub": "1234567898",
"name": "egon",
"admin": true,
"userid":1,
'mobile':123444444
}
-signature:簽名
-把前面兩部分的內(nèi)容通過加密算法+密鑰加密后得到的一個(gè)字符串
-jwt總的構(gòu)成樣子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-4贵涵、JWT認(rèn)證原理
-用戶攜帶用戶名、密碼登錄我的系統(tǒng)恰画,校驗(yàn)通過宾茂,生成一個(gè)token(三部分),返回給用戶---》登錄功能完成
-訪問需要登錄的接口(用戶中心)拴还,必須攜帶token過來跨晴,后端拿到token后,把header和payload截取出來片林,再通過一樣的加密方式和密碼得到一個(gè)signature端盆,
和該token的signature比較,如果一樣费封,表示是正常的token爱谁,就可以繼續(xù)往后訪問
3、base64價(jià)紹和使用
-1孝偎、任何語言都有base64的加碼和解碼,轉(zhuǎn)碼方式(加密方式)
-2凉敲、 python中base64的加密與解密
import base64
import json
dic_info={
"name": "lqz",
"age": 18
}
# 轉(zhuǎn)成json格式字符串
dic_str=json.dumps(dic_info)
print(dic_str)
#eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=
#eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=
# 需要用bytes格式
# 加碼
base64_str=base64.b64encode(dic_str.encode('utf-8'))
print(base64_str)
# 解碼
res_bytes=base64.b64decode('eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=')
print(res_bytes)
4衣盾、jwt基本使用(jwt內(nèi)置,控制用戶登錄后能訪問和未登陸能訪問)
-1爷抓、drf中使用jwt势决,借助第三方模塊:https://github.com/jpadilla/django-rest-framework-jwt
-2、pip3 install djangorestframework-jwt
-3蓝撇、快速使用(默認(rèn)使用auth的user表)
-1果复、在默認(rèn)auth的user表中創(chuàng)建一個(gè)用戶
-2、在路由中配置
# jwt自帶的登錄視圖渤昌,最簡單的使用
from rest_framework_jwt.views import obtain_jwt_token
path('login/', obtain_jwt_token)虽抄。這個(gè)是jwt自帶的登錄視圖
-3走搁、用postman向這個(gè)地址發(fā)送post請求,攜帶用戶名迈窟,密碼私植,登錄成功就會(huì)返回token
-4、obtain_jwt_token本質(zhì)也是一個(gè)視圖類车酣,繼承了APIView
-通過前端傳入的用戶名密碼曲稼,校驗(yàn)用戶,如果校驗(yàn)通過湖员,生成token贫悄,返回
-如果檢驗(yàn)失敗,返回錯(cuò)誤信息
-4娘摔、用戶登錄以后才能訪問某個(gè)接口
-jwt模塊內(nèi)置了認(rèn)證類窄坦,拿過來局部配置就可以
-class OrderView(APIView):
# 只配它不行,不管是否登錄,都能范圍,需要搭配一個(gè)內(nèi)置權(quán)限類
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated,]
def get(self, request):
print(request.user.username)
return Response('訂單的數(shù)據(jù)')
-5、用戶未登錄晰筛,就能訪問嫡丙,區(qū)別看第六條
-class OrderView(APIView):
# 只配它不行,不管是否登錄,都能范圍,需要搭配一個(gè)內(nèi)置權(quán)限類
authentication_classes = [JSONWebTokenAuthentication, ]
def get(self, request):
print(request.user.username)
return Response('訂單的數(shù)據(jù)')
-6、如果用戶攜帶了token读第,并且配置了JSONWebTokenAuthentication曙博,從request.user就能拿到當(dāng)前登錄用戶,如果沒有攜帶怜瞒,當(dāng)前登錄用戶就是匿名用戶父泳,
request.user里沒有東西,這個(gè)jwt的認(rèn)證不是我們傳統(tǒng)的認(rèn)證吴汪,只是request.user里有無用戶信息惠窄。
-7、前端要發(fā)送請求漾橙,攜帶jwt杆融,格式必須如下
-把token放到請求頭中,key為:Authorization
jwt加空格
-value必須為:jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImVnb24xIiwiZXhwIjoxNjA1MjQxMDQzLCJlbWFpbCI6IiJ9.7Y3PQM0imuSBc8CUe_h-Oj-2stdyzXb_U-TEw-F82WE
5霜运、控制登錄接口返回的數(shù)據(jù)格式
-1 控制登錄接口返回的數(shù)據(jù)格式如下
{
code:100
msg:登錄成功
token:asdfasfd
username:egon
}
-2 寫一個(gè)函數(shù)
from homework.serializer import UserReadOnlyModelSerializer
def jwt_response_payload_handler(token, user=None, request=None):
return {'code': 100,
'msg': '登錄成功',
'token': token,
'user': UserReadOnlyModelSerializer(instance=user).data
}
-3 在setting.py中配置
import datetime
JWT_AUTH = {
# 過期時(shí)間
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=365*7),
# 自定義認(rèn)證結(jié)果:見函數(shù)字典中序列化user和自定義response
# 如果不自定義择镇,返回的格式是固定的报破,只有token字段
'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler',
}
6立宜、自定義基于jwt的認(rèn)證類
-1舆瘪、自己實(shí)現(xiàn)基于jwt的認(rèn)證類,通過認(rèn)證焦除,才能訪問激况,通不過認(rèn)證就返回錯(cuò)誤信息
-2、代碼如下
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 這是jwt里內(nèi)置的認(rèn)證的認(rèn)證父類
class JwtAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
# 認(rèn)證邏輯()
# token信息可以放在請求頭中,請求地址中
# key值可以隨意叫
# token=request.GET.get('token')
token=request.META.get('HTTP_Authorization'.upper()) # 放在Header頭里
# 校驗(yàn)token是否合法
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('過期了')
except jwt.DecodeError:
raise AuthenticationFailed('解碼錯(cuò)誤')
except jwt.InvalidTokenError:
raise AuthenticationFailed('不合法的token')
user=self.authenticate_credentials(payload)
return (user, token)
-3 在視圖類中配置
authentication_classes = [JwtAuthentication, ]