0. 准备环境
* 1. 新建一个项目, 不创建模板层
* 2. 将rest_framework注册到app应用列表中.
INSTALLED_APPS = [
...
'rest_framework'
]
* 3. 自定义正常响应类与异常响应类
from rest_framework.response import Response
from rest_framework import status
class NormalResponse(Response):
def __init__(self, code=200, msg='访问成功!', data=None, status=status.HTTP_200_OK, **kwargs):
back_info = {'code': code, 'msg': msg}
if data:
back_info.update(data=data)
back_info.update(kwargs)
super().__init__(data=back_info, status=status)
from rest_framework.views import exception_handler
def exception_response(exc, context):
response = exception_handler(exc, context)
if not response:
if isinstance(exc, Exception):
error_info = f'遇到异常>>>:{exc}'
return NormalResponse(500, '访问失败', error_info, status.HTTP_500_INTERNAL_SERVER_ERROR)
error_info = response.data.get('detail')
if error_info:
error_info = response.data
return NormalResponse(500, '访问失败!!', error_info, status.HTTP_500_INTERNAL_SERVER_ERROR)
项目配置文件settings.py中, 全局配置dispatch异常响应.
只能全局配置, 自定义的异常响应, 值就是一个字符串, 不能是列表['自定义的异常响应']
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'utils.response.exception_response',
}
* 4. 建立一个测试路由(采用路由分发)
from django.conf.urls import url, include
from django.contrib import admin
from app01 import urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/api/', include(urls))
]
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^test1/', views.Test1.as_view())
]
* 5. 测试视图类
from rest_framework.views import APIView
from utils.reponse import NormalResponse
class Test1(APIView):
def get(self, request):
return NormalResponse(data='Test1')
* 6. 测试
get请求: 127.0.0.1:8000/app01/api/test1/
1. 自定义频率限制
1.1 SimpleRateThrottle源码
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
def get_cache_key(self, request, view):
raise NotImplementedError('.get_cache_key() must be overridden')
def get_rate(self):
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
def allow_request(self, request, view):
if self.rate is None:
return True
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
self.history = self.cache.get(self.key, [])
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
def throttle_success(self):
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
def throttle_failure(self):
return False
def wait(self):
if self.history:
remaining_duration = self.duration - (self.now - self.history[-1])
else:
remaining_duration = self.duration
available_requests = self.num_requests - len(self.history) + 1
if available_requests <= 0:
return None
return remaining_duration / float(available_requests)
1.2 SimpleRateThrottle使用
限制用户请求的ip每分钟只能访问三次.
请求ip在request.META中. request.META.get('REMOTE_ADDR') 获取.
* 1. 定义限制类
from rest_framework.throttling import SimpleRateThrottle
class Limit(SimpleRateThrottle):
scope = 'ip'
def get_cache_key(self, request, view):
ip_addr = request.META.get('REMOTE_ADDR')
return ip_addr
从配置文件DEFAULT_THROTTLE_RATES中根据scope得到频率配置(次数/时间单位)
类的scope数据作为频率认证类的数据值的key
* 2. 全局配置限制类
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'utils.response.exception_response',
'DEFAULT_THROTTLE_CLASSES': ['utils.throttling.Limit',],
'DEFAULT_THROTTLE_RATES': {'ip': '3/m', },
}
* 3. 测试
1.3 自定义模块
自定义频率限制类, 需要有写两个方法:
1. allow_request(self, request, view)
如果应该允许请求,则返回True(不限次),否则返回False (限次)。
2. wait(seif) (可选)返回在下一个请求之前等待的建议秒数。
继承BaseThrottle, 该类中规范了子类的行为, 也可以不继承, 倒是类中必须有
源码中必须要执行allow_request方法.
BaseThrottle源码
代码逻辑:
0. 建立一个空字段 -->访问字典. {}
1. 从request.META.get('REMOTE_ADDR')中取出访问的ip.
2. 判断当前的ip在不在访问字典中, 若不在则以ip为键, 时间为值添加到字典中, 并且放回True, 表示第一次访问.
{'xxxip': [访问时间1, ]}
3. ip存在, 循环将列表取出来, 循环判断列表中每个值是否超过了60秒, 超过60秒的pop掉. 保证列表中只存60秒内
访问的时间信息, 退出循环.
{'xxxip': [访问时间3, 访问时间2, 访问时间1,]}
[1650897168.0505562, 1650897167.4371457, 1650897166.5577233] 时间戳
4. 统计列表所有的元素, 如果超过三次直接返回False, 访问失败
没有超过三次, 将当时时间插入到列表中, 访问成功
import time
class Limit2():
access_info = {}
ip = None
def allow_request(self, request, view):
ip = request.META.get('REMOTE_ADDR')
self.ip = ip
if ip not in self.access_info:
self.access_info[ip] = []
self.access_info[ip].append(time.time())
return True
print(self.access_info[ip])
while True:
if time.time() - self.access_info[ip][-1] > 60:
self.access_info[ip].pop()
break
print(self.access_info[ip])
if len(self.access_info[ip]) > 3:
return False
self.access_info[ip].insert(0, time.time())
return True
def wait(self):
"""
当前时间 - 列表第一次访问的时间 = 0 1 2 ↑
60 - (当前时间 - 列表第一次访问的时间 0 1 2) = 60 59 58 ↓
"""
return 60 - (time.time() - self.access_info[self.ip][-1])
2. API接口文档
rest_framework 可以自动生成接口文档, 文档以网页方式呈现.
1. 安装依赖库coreapi
2. 继承自APIView及其子类的视图.
* 1. 安装依赖库
pip install coreapi
* 2. 在总路由中设置接口文档访问路径.
from rest_framework.documentation import include_docs_urls
url('^docs/', include_docs_urls(title='站点页面标题'))
* 3. 继承APIView类及其子类
1. 继承APIView在, 在视图类的请求方法的文档字符串中写帮助信息.
class BookAPIView(APIView):
def get(...):
"""
获取所有书籍信息
"""
2. 单一方法的视图, 可以直接在视图类的文档字符串中写帮助信息.
class BookListView(ListAPIView):
"""
返回所有的图书信息
"""
3. 多方法的图书, 在视图类的文档字符串中, 为每个方法定义帮助信息.
class BookListCreateView(ListCreateAPIView):
"""
get: 返回所有图书信息
post: 新建图书
"""
4. 对于数图集ViewSet, 在视图类的文档字符串中定义, 使用action的名称区分.
class BookViewSet(ListModelMinxin, ...):
"""
list: 返回图书列表数据
retrieve: 返回图书详情数据
latest: 放回最新的图书数据
...
"""
* 4. 在配置文文件这添加core接口
AutoSchema' object has no attribute 'get_link
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}
* 5. 需要session的信息, 执行数据库迁移命令, 否则会报错 no such table:django_session
python manage.py makemigrations
python manaage.py migrate
* 6. 浏览器中访问 127.0.0.1:8000/docs/ 便可以看见自动生成的接口文档.
视图集的retrieve名称, 在接口文档网站叫做read
参数的Description需要在模型类或序列化器的字段中以help_text中定义
3. JWT
jwt: Json Web token
3.1 原理
1. jwt分三段式: 头.体.签名 (head.payload.sgin)
2. 头个体的可逆加密, 让服务器可以反解析出user对象, 前签名是不可逆加密, 保证整个token的安全性.
3. 头体签名三部分, 都是采用json格式的字符串进行加密, 可逆加密一般采用base64算法,
不可逆加密一般采用hash(例: md5)算法.
4. 头中的内容是基本信息: 公司信息, 项目信息. token采用的加密方式信息
{
"company": "公司信息"
...
}
5. 体中的内容是关键信息: 用户主键, 用户名, 签发时客户端信息(设备号, 地址), 过期时间
{
"uese_id": 1,
...
}
6. 签名中内容安全信息: 头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密结果",
"payload": "体的加密结果",
"secret_key": "安全码"
}
3.2 base64
base64是一种编码格式.
导入模块:
import base64
方法: (针对二进制数据)
.b64encode 编码
.b64decode 解码
import base64
import json
dic = {'name': 'kid', 'age': 18}
json_dic = json.dumps(dic)
print(json_dic, type(json_dic))
b_dic = json_dic.encode('utf8')
print(b_dic)
base_dic = base64.b64encode(b_dic)
print(base_dic)
b_dic = base64.b64decode(base_dic)
print(b_dic)
json_dic = b_dic.decode('utf8')
print(json_dic)
dic = json.loads(json_dic)
print(dic)
3.3 签发/校验
签发: 根据登入请求提交的账户+密码+设备信息 签发token
用基本信息存储json字典, 采用base64算法加密得到 头字符串
用关键信息存储json字段, 采用base64算法加密得到 体字符串
用头, 体的加密字符串再加安全存储到json字典, 采用hash md5算法加密得到 签名字符串
账户密码能够根据User表得到user对象, 形成三段字段串用.拼接成token返回给前端
校验:根据客户端带token的请求反解出user对象
将token按.拆分成三端
第一段 头的加密信息, 一般不需要做任何处理
第二段 体的加密字符串, 要反解析出用户主键, 通过主键从User表中得到登入的用户, 过期时间和设备信息都是安全信息, 确保token没过期, 且是同一个设备发送的请求
再用 第一段 + 第二段 + 服务器安全密码 通过不可逆md5加密 与第三端签名字符串进程碰撞校验, 校验成功
后才能第二段校验得到user对象就是合法的登入用户
3.4 自动签发
1. 用账户/密码访问登入接口, 登入接口逻辑中调用签发token算法, 得到token, 返回给客户端, 并保存到cookie中
2. 校验token算法因该写在认证类中, 反解析出数据去数据库中校验得到user对象, 将对象放回即可,
源码中保存到requesr.user中, 全局配置给认证组件, 所有视图类请求都进行校验, 所有请求携带token访问.
* 登入接口的认证与权限局部禁用
1. 安装模块
* 1. rest_framework 有对应的jwt模块.
安装: pip install djangorestframework_jwt
2.继承内置模型表
* 2. 继承AbstractUser内置用户表表, 拓展两个字段
项目中一开始没继承AbstractUser表, 之后再继承执行生成表记录命令就会报错!
1. 新建项目
2. 解决templates路径问题, 将正常正常响应与异常响应代码复制过来. 将rest_framework注册.
修改语言/时区
INSTALLED_APPS = [
...
'rest_framework',
]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
3. 继承AbstractUser写表模型
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
phone = models.CharField(max_length=11, verbose_name='手机号')
在settings.py中,添加使用UserInfo的配置选项:
AUTH_USER_MODEL = 'app名.扩展表的表名'
AUTH_USER_MODEL = 'app01.UserInfo'
4. 新建媒体文件夹, 并设置媒体文件存放路径
MEDIA_URL = '/media/'
MEDIA_ROOT = Path(BASE_DIR, 'media')
5. 创建表的命令:
python manage.py makemigrations
python manage.py migrate
3. 创建超级用户
PS F:\synchro\Project\DRF_JWT> python manage.py createsuperuser
用户名: root
电子邮件地址: 136@qq.com
Password: zxc123456
Password (again): zxc123456
这个密码太常见了。
Bypass password validation and create user anyway? [y/N]: y
4. 测试路由
使用rest_framework_jwt提供的视图类
三个可用的视图类:
ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken -继承-> JSONWebTokenAPIView
一个基类:
JSONWebTokenAPIView -继承-> APIView
由于在模块中, 视图类执行了.as_view()方法, 得到一个函数的内存地址给变量名, 在路由中直接使用函数名即可.
obtain_jwt_token方法中有登入校验, 登入成功之后, 返回token.
from django.contrib import admin
from django.urls import path, re_path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^login/', obtain_jwt_token)
]
5.登入
使用POST请求提交账户/密码进行登入: 127.0.0.1:8000/login
登入成功之后, 返回token.
第一次登入...
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2NTA5NzM2NTYsImVtYWlsIjoiMTM2QHFxLmNvbSJ9.EcLM6P9PhEYgxHA4VmNh4zvKoU2ITIuFrsvv9ZyTMwk"
}
第二次登入...
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2NTA5NzM5MjQsImVtYWlsIjoiMTM2QHFxLmNvbSJ9.caseIhIg7VWNiSRhrJOPmzc0gTJ_ilo_b4sgGwoJoKk"
}
obtain_jwt_token方法中将登入时间作为第二端的一个参数, 每次登入第二段的信息一定会表.
第二段的信息发生了变化, 第三段的签名信息一定会变.
3.4 JSONWebTokenAuthentication源码
在访问的时候携带的token值要以JWT空格开头...
如果不按这个要求不做校验, 直接可以访问...
JSONWebTokenAuthentication 继承 BaseJSONWebTokenAuthentication
BaseJSONWebTokenAuthentication有两个方法:
authenticate 认证方法
authenticate_credentials 数据token校验方法
JSONWebTokenAuthentication有主要方法:
get_jwt_value 获取token的值
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
jwt_value = self.get_jwt_value(request)
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
user = self.authenticate_credentials(payload)
return (user, jwt_value)
def authenticate_credentials(self, payload):
"""
Returns an active user that matches the payload's user id and email.
"""
User = get_user_model()
username = jwt_get_username_from_payload(payload)
if not username:
msg = _('Invalid payload.')
raise exceptions.AuthenticationFailed(msg)
try:
user = User.objects.get_by_natural_key(username)
except User.DoesNotExist:
msg = _('Invalid signature.')
raise exceptions.AuthenticationFailed(msg)
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.AuthenticationFailed(msg)
return user
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
规定authorization携带token
内置的校验程序
设置token前缀 JWT
3.5 携带token访问
* 1. 测试路由
from django.contrib import admin
from django.urls import path, re_path
from app01 import views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^login/', obtain_jwt_token),
re_path(r'^test1/', views.Test1.as_view())
]
* 2. 测试视图类
from rest_framework.views import APIView
from utils.response import NormalResponse
class Test1(APIView):
def get(self, request):
return NormalResponse('Test1')
* 3. 添加认证, 局部配置
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication, ]
from rest_framework.views import APIView
from utils.response import NormalResponse
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class Test1(APIView):
authentication_classes = [JSONWebTokenAuthentication, ]
def get(self, request):
return NormalResponse('Test1')
* 4. 添加认证, 全局配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication',]
}
4. J WT认证+权限认证
单独使用rest_framework_jwt.authentication.JSONWebTokenAuthentication的话所有游客都可以访问,
在不提供authorization参数携带token, 或不会要求提交都能正常访问到接口的信息. 这样认证之后,
request.user 是AnonymousUser 匿名用户, 使用rest_framework内置权限类, 对登入用户进行校验,
匿名用户禁止访问!
以后在使用rest_framework_jwt.authentication.JSONWebTokenAuthentication全局配置的时候,
需要限制登入用户才能访问的接口为该类添加上权限类认证, 不需要限制的就什么都不做.
from rest_framework.views import APIView
from utils.response import NormalResponse
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from utils.JWT import JsonWenToken
from rest_framework.permissions import IsAuthenticated
class Test1(APIView):
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated, ]
def get(self, request):
print(request.user)
return NormalResponse('Test1')
5. 自定义JWT认证类
# 导入内置的校验程序
from rest_framework_jwt.utils import jwt_decode_handler
BaseJSONWebTokenAuthentication有两个方法:
authenticate 认证方法
authenticate_credentials 数据token校验方法
继承 BaseJSONWebTokenAuthentication, 重写authenticate认证方法,
自己获取请求头的token值, 使用jwt_decode_handler方法对值进行校验, 解析出字第二端的用户信息-->dict
获取用户数据对象authenticate_credentials
Pythonn内置jwt模块, 其中收录了rest_framework_jwt校验出现的异常
import jwt
class InvalidTokenError(PyJWTError):
class DecodeError(InvalidTokenError):
class InvalidSignatureError(DecodeError):
class ExpiredSignatureError(InvalidTokenError):
class InvalidAudienceError(InvalidTokenError):
class InvalidIssuerError(InvalidTokenError):
class InvalidIssuedAtError(InvalidTokenError):
class ImmatureSignatureError(InvalidTokenError):
class InvalidKeyError(PyJWTError):
class InvalidAlgorithmError(InvalidTokenError):
class MissingRequiredClaimError(InvalidTokenError):
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
from app01 import models
class JsonWenToken(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value = request.META.get('HTTP_AUTHORIZATION')
print(jwt_value)
if not jwt_value:
raise AuthenticationFailed('没有携带token')
try:
payload = jwt_decode_handler(jwt_value)
print(payload)
except jwt.ExpiredSignatureError:
raise AuthenticationFailed('签名过期!')
except jwt.DecodeError:
raise AuthenticationFailed('无效的负载字符串!')
except Exception:
raise AuthenticationFailed('token无效!')
user_obj = models.UserInfo.objects.get(username=payload.get('username'))
user_obj2 = models.UserInfo(username=payload.get('username'))
print(user_obj2)
return user_obj, jwt_value
内置的获取用户数据对象模块
6. 自定义返回数据格式
第一种方案: JWT模块预留返回格式的配置, 重新定义登入接口返回的数据格式, 更改配置即可. jwt的配置文件:
'JWT_RESPONSE_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_response_payload_handler'
* 1. 自定义返回数据的格式
def customize_jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 200,
'msg': '登入成功!',
'用户名': user.username,
'token': token,
}
* 2. 修改配置文件
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'utils.JWT.customize_jwt_response_payload_handler',
}
7. 多种登入自动签发token
可以通过用户名, 邮箱, 手机号登入
登入签发过程:
1. post提交(用户名/邮箱/手机号)与密码登入
2. 使用序列化器对数据进行校验
3. 在序列化器中按照 用户名/邮箱/手机号 获取到用户的数据对象
4. 密文校验用户密码
5. 使用用户对象生成token, 并将token返回
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
from rest_framework import serializers
from app01 import models
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
class UserInfoModelSerializer(serializers.ModelSerializer):
user = serializers.CharField()
class Meta:
model = models.UserInfo
fields = ['user', 'password']
def validate(self, attrs):
user = attrs.get('user')
password = attrs.get('password')
print(user)
if re.match('^1[3-9][0-9]{9}$', user):
user_obj = models.UserInfo.objects.filter(phone=user).first()
elif re.match('^.*@.*$', user):
user_obj = models.UserInfo.objects.filter(email=user).first()
else:
user_obj = models.UserInfo.objects.filter(username=user).first()
if not user_obj:
raise ValidationError('提供的用户名/邮箱/手机号不正确')
if not user_obj.check_password(password):
raise ValidationError('密码不正确')
payload = jwt_payload_handler(user_obj)
token = jwt_encode_handler(payload)
"""
token 存放在
序列化器有一个参数context 可以用于视图与序列化器的信息交互
视图类中 序列化器(序列化器有一个参数context={'request': request}), 在序列化器中便可以拿到request
class BaseSerializer(Field):
def __init__(..., **kwargs)
self._context = kwargs.pop('context', {}) 自己不提供值则是一个空字典
"""
self._context['token'] = token
return attrs
用户名登入
邮箱登入
手机号码登入
访问测试
8. 过期时间配置
datetime.timedelta(days=0, seconds=0, microseconds=0,
milliseconds=0, minutes=0, hours=0, weeks=0)
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7)}
9. RBAC权限
RBAC权限: 是基于角色的访问控制,
Django的Auth组件, 才有的认证规则就是RBAC
适用于人员权限管理CRM系统, 公司内部使用数据量在10w以下, 效率要求不高.
用户量大的项目, 会分两种用户. 前台用户(三大认证) 和后台用户(RBAC管理)
没有特殊要求的Django项目可以直接采用Auth组件的权限六表.
9.1 前后台权限控制
后台用户对各表操作, 可以直接借助admin后台控制.
后期也可以使用xadmin框架来做后台用户权限管理'
前台用户的权限, 定义一堆数据接口的视图类, 不同的登入用户是否能访问这些视图类, 能则代表有权限
不能则代表无权限, 前台用户权限用DRF框架的三大认证
9.2 Django内置RBAC六表
Django依据六张表做后台的权限的控制
userinfo 用户表
auth_group 分组表
auth_permission 权限表
userinfo_groups 用户与分组的中间表
userinfo_user_permissions 用户与权限的中间表
auth_group_permissions 分组与权限的中间表
1.权限三表
User 用户表 | |
---|
id | name 用户名 | 1 | kid | 2 | qq | 3 | qaq | 4 | root |
Group 分组表 | |
---|
id | dep_name 部门名字 | 1 | 经理 | 2 | 人事 | 3 | 财务 |
Permission 权限表 | |
---|
id | function 能操作功能 | 1 | 管理 | 2 | 招人 | 3 | 发工资 | 4 | 外挂 |
2. 关系表
表与表之间不直接关联, 而是使用第三张表关联.
U_G 关系表 | | |
---|
id | u_id | g_id | 1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 |
G_P 关系表 | | |
---|
id | g_id | p_id | 1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 |
U_P 用户没有部门, 直接拥有权限...
9.3 后台管理
* 1. 添加几张表
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=11, verbose_name='手机号')
icon = models.ImageField(upload_to='icon')
class Dep(models.Model):
dep_name = models.CharField(max_length=32, verbose_name='部门名称')
dep_num = models.IntegerField(verbose_name='部门人数')
class Work(models.Model):
work_name = models.CharField(max_length=32, verbose_name='工作的名称')
desc = models.CharField(max_length=64, verbose_name='工作的描述')
生成表操作记录, 数据库迁移
python manage.py makemigrations
python manage.py migrate
* 2. 在admim表中注册展示的表
from django.contrib import admin
from app01 import models
admin.site.register(models.UserInfo)
admin.site.register(models.Dep)
admin.site.register(models.Work)
* 3. 登入到后台管理, 超级用户拥有所有的操作权限
可以为用户设置分组, 再为分组设置权限,
可以直接为用户设置权限
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5hPCBC8z-1651160504530)(D:/%E6%88%AA%E5%9B%BE/2022-04-28_00392.png)]
* 4. 添加用户, 设置工作人员状态才能登入后台
* 5. 使用新建的用户登入站点, 仅有对部分的操作权限
10. Django缓存机制
10.1 缓存介绍
在动态网站中, 用户所有的请求, 服务器都会去数据库中进行相应的增, 删, 改, 查渲染模板, 执行业务逻辑,
最后生成用户看到的页面.
当一个网站的用户访问量很大的时候, 每一个的后台操作, 都会消耗很多的服务端资源, 所有必须使用缓存来减轻后端
服务端的压力.
缓存是将一些常用的数据保存到内存中或memcache中, 在一定时间内有人来访问这些数据则不在去执行数据库及渲染
等操作, 而是慧姐从内存或memcache的缓存中去取到数据, 然后返回给用户.
10.2 Django的6种缓存方式
1. 开发调试缓存
2. 内存缓存
3. 文件缓存
4. 数据库缓存
5. Memcache缓存 使用 python-mamcached模块
6. Memcache缓存 使用 pylibmc模块
10.3 测试
1. 测试环境
* 1. 路由
re_path(r'^index/', views.index),
* 2. 视图函数
import time
def index(request):
new_time = time.ctime
return render(request, 'index.html', context={'new_time': new_time})
* 3. 模型层
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
页面:
{{ new_time }}
</body>
</html>
* 4. 在settings.py 配置文件中配置信息
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/D/XXX/',
'TIMEOUT': 300,
'OPTIONS': {
'MAX_ENTRIES': 300,
'CULL_FREQUENCY': 3,
}
}
}
2. 单页面缓存
* 1. 使用缓存装饰器
from django.views.decorators.cache import cache_page
import time
@cache_page(5)
def index(request):
new_time = time.ctime
return render(request, 'index.html', context={'new_time': new_time})
缓存之后会生成一些文件, 如果缓存没有过去, 删除文件之后, 缓存也不就不存在了
3. 页面局部缓存
导入cache
{% load cache %}
超时时间 '唯一标识的键'
{% cache 5 'key_name' %}
局部缓存的内容
{% endcache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
页面:
{{ new_time }}
<hr>
{% load cache %}
局部缓存:
{% cache 5 'key_name' %}
{{ new_time }}
{% endcache %}
</body>
</html>
4. 全栈缓存
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
...
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_SECONDS=5
1. 请求来的时候, 经过所有中间的 process_request到最后一个django.middleware.cache.FetchFromCacheMiddleware, 该中间件中重写了process_request请求方法
该方法实现了从缓存中获取数据
2. 请求走的时候经过所有中间的 process_response到最后一个
django.middleware.cache.UpdateCacheMiddleware, 该中间件中重写了process_response响应方法
该方法实现了数据缓存
5. 前后端分离缓存
使用模块 caches
# 导入模块 caches对象
from django.core.cache import caches
.set('key': 'value') value可以是任意类型数据, 将数据序列化之后再存.
.get('key') 通过键取值
* 1. 路由
re_path(r'^books/', views.BookAPI.as_view())
* 2. 视图类
from django.core.cache import cache
class BookAPI(APIView):
def get(self, request):
data = cache.get('book_dic')
if data:
data.update({'remark': '缓存的信息!'})
return NormalResponse(data=data)
data = {'name': 'kid'}
cache.set('book_dic', data)
return NormalResponse(data=data)
第一次访问, 获取不到数据则执行获取数据, 存数据的操作.
第一次访问, 从缓存中获取到值, 直接返回.
|