04,认证、权限、频率

.wiz-editor-body .wiz-code-container { position: relative; padding: 8px 0; margin: 5px 25px 5px 5px; text-indent: 0; text-align: left }
.CodeMirror { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; color: black; font-size: 0.875rem }
.wiz-editor-body .wiz-code-container .CodeMirror div { margin-top: 0; margin-bottom: 0 }
.CodeMirror-lines { padding: 4px 0 }
.CodeMirror pre { padding: 0 4px }
.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler { background-color: white }
.CodeMirror-gutters { border-right: 1px solid #ddd; background-color: #f7f7f7; white-space: nowrap }
.CodeMirror-linenumbers { }
.CodeMirror-linenumber { padding: 0 3px 0 5px; min-width: 20px; text-align: right; color: #999; white-space: nowrap }
.CodeMirror-guttermarker { color: black }
.CodeMirror-guttermarker-subtle { color: #999 }
.CodeMirror-cursor { border-left: 1px solid black; border-right: none; width: 0 }
.CodeMirror div.CodeMirror-secondarycursor { border-left: 1px solid silver }
.cm-fat-cursor .CodeMirror-cursor { width: auto; border: 0 !important; background: #7e7 }
.cm-fat-cursor div.CodeMirror-cursors { z-index: 1 }
.cm-animate-fat-cursor { width: auto; border: 0; background-color: #7e7 }
.CodeMirror-overwrite .CodeMirror-cursor { }
.cm-tab { display: inline-block; text-decoration: inherit }
.CodeMirror-rulers { position: absolute; left: 0; right: 0; top: -50px; bottom: -20px; overflow: hidden }
.CodeMirror-ruler { border-left: 1px solid #ccc; top: 0; bottom: 0; position: absolute }
.cm-s-default .cm-header { color: blue }
.cm-s-default .cm-quote { color: #090 }
.cm-negative { color: #d44 }
.cm-positive { color: #292 }
.cm-header,.cm-strong { font-weight: bold }
.cm-em { font-style: italic }
.cm-link { text-decoration: underline }
.cm-strikethrough { text-decoration: line-through }
.cm-s-default .cm-keyword { color: #708 }
.cm-s-default .cm-atom { color: #219 }
.cm-s-default .cm-number { color: #164 }
.cm-s-default .cm-def { color: #00f }
.cm-s-default .cm-variable,.cm-s-default .cm-punctuation,.cm-s-default .cm-property,.cm-s-default .cm-operator { }
.cm-s-default .cm-variable-2 { color: #05a }
.cm-s-default .cm-variable-3 { color: #085 }
.cm-s-default .cm-comment { color: #a50 }
.cm-s-default .cm-string { color: #a11 }
.cm-s-default .cm-string-2 { color: #f50 }
.cm-s-default .cm-meta { color: #555 }
.cm-s-default .cm-qualifier { color: #555 }
.cm-s-default .cm-builtin { color: #30a }
.cm-s-default .cm-bracket { color: #997 }
.cm-s-default .cm-tag { color: #170 }
.cm-s-default .cm-attribute { color: #00c }
.cm-s-default .cm-hr { color: #999 }
.cm-s-default .cm-link { color: #00c }
.cm-s-default .cm-error { color: #f00 }
.cm-invalidchar { color: #f00 }
.CodeMirror-composing { border-bottom: 2px solid }
div.CodeMirror span.CodeMirror-matchingbracket { color: #0f0 }
div.CodeMirror span.CodeMirror-nonmatchingbracket { color: #f22 }
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3) }
.CodeMirror-activeline-background { background: #e8f2ff }
.CodeMirror { position: relative; background: #f5f5f5 }
.CodeMirror-scroll { overflow: hidden !important; margin-bottom: 0; margin-right: -30px; padding: 16px 30px 16px 0; outline: none; position: relative }
.CodeMirror-sizer { position: relative; border-right: 30px solid transparent }
.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none }
.CodeMirror-vscrollbar { right: 0; top: 0 }
.CodeMirror-hscrollbar { bottom: 0; left: 0 !important }
.CodeMirror-scrollbar-filler { right: 0; bottom: 0 }
.CodeMirror-gutter-filler { left: 0; bottom: 0 }
.CodeMirror-gutters { position: absolute; left: 0; top: -5px; min-height: 100%; z-index: 3 }
.CodeMirror-gutter { white-space: normal; height: inherit; display: inline-block; vertical-align: top; margin-bottom: -30px }
.CodeMirror-gutter-wrapper { position: absolute; z-index: 4; background: none !important; border: none !important }
.CodeMirror-gutter-background { position: absolute; top: 0; bottom: 0; z-index: 4 }
.CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; text-align: center }
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines { cursor: text; min-height: 1px }
.CodeMirror pre { border-width: 0; background: transparent; font-family: inherit; font-size: inherit; margin: 0; white-space: pre; line-height: inherit; color: inherit; z-index: 2; position: relative; overflow: visible }
.CodeMirror-wrap pre { white-space: pre-wrap }
.CodeMirror-linebackground { position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: 0 }
.CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto }
.CodeMirror-widget { }
.CodeMirror-rtl pre { direction: rtl }
.CodeMirror-code { outline: none }
.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber { }
.CodeMirror-measure { position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden }
.CodeMirror-cursor { position: absolute }
.CodeMirror-measure pre { position: static }
div.CodeMirror-cursors { visibility: hidden; position: relative; z-index: 3 }
div.CodeMirror-dragcursors { visibility: visible }
.CodeMirror-focused div.CodeMirror-cursors { visibility: visible }
.CodeMirror-selected { background: #d9d9d9 }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0 }
.CodeMirror-crosshair { cursor: crosshair }
.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection { background: #d7d4f0 }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0 }
.cm-searching { background: rgba(255, 255, 0, .4) }
.cm-force-border { padding-right: .1px }
.cm-tab-wrap-hack::after { content: "" }
span.CodeMirror-selectedtext { background: none }
.CodeMirror-activeline-background,.CodeMirror-selected { }
.CodeMirror-blur .CodeMirror-activeline-background,.CodeMirror-blur .CodeMirror-selected { visibility: hidden }
.CodeMirror-blur .CodeMirror-matchingbracket { color: inherit !important; outline: none !important; text-decoration: none !important }
.CodeMirror-sizer { min-height: auto !important }
html,.wiz-editor-body { font-size: 12pt }
.wiz-editor-body { font-family: Helvetica, "Hiragino Sans GB", "寰蒋闆呴粦", "Microsoft YaHei UI", SimSun, SimHei, arial, sans-serif; line-height: 1.7; margin: 0 auto; padding: 1.25rem 1rem }
.wiz-editor-body h1,.wiz-editor-body h2,.wiz-editor-body h3,.wiz-editor-body h4,.wiz-editor-body h5,.wiz-editor-body h6 { margin: 1.25rem 0 0.625rem }
.wiz-editor-body h1 { font-size: 1.67rem }
.wiz-editor-body h2 { font-size: 1.5rem }
.wiz-editor-body h3 { font-size: 1.25rem; background-color: #A3A9F5; font-weight: bold }
.wiz-editor-body h4 { font-size: 1.17rem; background-color: #8dd5f5; font-weight: bold }
.wiz-editor-body h5 { font-size: 1rem; background-color: #F5F183; font-weight: bold }
.wiz-editor-body h6 { font-size: 1rem; color: #777777; margin: 1rem 0 }
.wiz-editor-body div,.wiz-editor-body p,.wiz-editor-body ul,.wiz-editor-body ol,.wiz-editor-body dl,.wiz-editor-body li { margin: 8px 0 }
.wiz-editor-body blockquote,.wiz-editor-body table,.wiz-editor-body pre,.wiz-editor-body code { margin: 8px 0 }
.wiz-editor-body .CodeMirror pre { margin: 0 }
.wiz-editor-body ul,.wiz-editor-body ol { padding-left: 2rem }
.wiz-editor-body ol.wiz-list-level1>li { list-style-type: decimal }
.wiz-editor-body ol.wiz-list-level2>li { list-style-type: lower-latin }
.wiz-editor-body ol.wiz-list-level3>li { list-style-type: lower-roman }
.wiz-editor-body blockquote { padding: 0 12px }
.wiz-editor-body blockquote>:first-child { margin-top: 0 }
.wiz-editor-body blockquote>:last-child { margin-bottom: 0 }
.wiz-editor-body img { border: 0; max-width: 100%; height: auto !important; margin: 2px 0 }
.wiz-editor-body table { border-collapse: collapse; border: 1px solid #bbbbbb }
.wiz-editor-body td,.wiz-editor-body th { padding: 4px 8px; border-collapse: collapse; border: 1px solid #bbbbbb; min-height: 28px }
.wiz-hide { display: none !important }

认证组件

Django原生的authentic组件为我们的用户注册与登录提供了认证功能,十分的简介与强大。同样DRF也为我们提供了认证组件,一起来看看DRF里面的认证组件是怎么为我们工作的!

models.py

# 定义一个用户表和一个保存用户Token的表

class UserInfo(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=32)
type = models.SmallIntegerField(
choices=((1, ‘普通用户‘), (2, ‘VIP用户‘)),
default=1
)

class Token(models.Model):
user = models.OneToOneField(to=‘UserInfo‘)
token_code = models.CharField(max_length=128)<wiz_code_mirror>

15

1

# 定义一个用户表和一个保存用户Token的表

2


3


4

class UserInfo(models.Model):

5

    username = models.CharField(max_length=16)

6

    password = models.CharField(max_length=32)

7

    type = models.SmallIntegerField(

8

        choices=((1, ‘普通用户‘), (2, ‘VIP用户‘)),

9

        default=1

10

    )

11


12


13

class Token(models.Model):

14

    user = models.OneToOneField(to=‘UserInfo‘)

15

    token_code = models.CharField(max_length=128)

url

path(‘login/‘, views.LoginView.as_view()),<wiz_code_mirror>

1

1

    path(‘login/‘, views.LoginView.as_view()),

views.py

# 视图主要处理用户名、密码是否正确,用户每一次请求都要带着专有token来!
import hashlib, time
from rest_framework.response import Response
from rest_framework.views import APIView

def get_random_token(username):
"""
根据用户名和时间戳生成随机token
:param username:
:return:
"""
timestamp = str(time.time())
m = hashlib.md5(bytes(username, encoding="utf8"))
m.update(bytes(timestamp, encoding="utf8"))
return m.hexdigest()

class LoginView(APIView):
"""
校验用户名密码是否正确从而生成token的视图
"""
def post(self, request):
res = {"code": 0}
print(request.data)
username = request.data.get("username")
password = request.data.get("password")

user = models.UserInfo.objects.filter(username=username, password=password).first()
if user:
# 如果用户名密码正确
token = get_random_token(username)
models.Token.objects.update_or_create(defaults={"token_code": token}, user=user)
res["token"] = token
else:
res["code"] = 1
res["error"] = "用户名或密码错误"
return Response(res)<wiz_code_mirror>

38

1

# 视图主要处理用户名、密码是否正确,用户每一次请求都要带着专有token来!

2

import hashlib, time

3

from rest_framework.response import Response

4

from rest_framework.views import APIView

5


6


7

def get_random_token(username):

8

    """

9

    根据用户名和时间戳生成随机token

10

    :param username:

11

    :return:

12

    """

13

    timestamp = str(time.time())

14

    m = hashlib.md5(bytes(username, encoding="utf8"))

15

    m.update(bytes(timestamp, encoding="utf8"))

16

    return m.hexdigest()

17


18


19

class LoginView(APIView):

20

    """

21

    校验用户名密码是否正确从而生成token的视图

22

    """

23

    def post(self, request):

24

        res = {"code": 0}

25

        print(request.data)

26

        username = request.data.get("username")

27

        password = request.data.get("password")

28


29

        user = models.UserInfo.objects.filter(username=username, password=password).first()

30

        if user:

31

            # 如果用户名密码正确

32

            token = get_random_token(username)

33

            models.Token.objects.update_or_create(defaults={"token_code": token}, user=user)

34

            res["token"] = token

35

        else:

36

            res["code"] = 1

37

            res["error"] = "用户名或密码错误"

38

        return Response(res)

定义认证类model_serializer.py

# 这一步是要对着源码才能写出来
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class MyAuth(BaseAuthentication):
def authenticate(self, request):
if request.method in ["POST", "PUT", "DELETE"]:
request_token = request.data.get("token", None)
if not request_token:
raise AuthenticationFailed(‘缺少token‘)
token_obj = models.Token.objects.filter(token_code=request_token).first()
if not token_obj:
raise AuthenticationFailed(‘无效的token‘)
return token_obj.user.username, None
else:
return None, None
<wiz_code_mirror>

18

1

# 这一步是要对着源码才能写出来

2

from rest_framework.authentication import BaseAuthentication

3

from rest_framework.exceptions import AuthenticationFailed

4


5


6

class MyAuth(BaseAuthentication):

7

    def authenticate(self, request):

8

        if request.method in ["POST", "PUT", "DELETE"]:

9

            request_token = request.data.get("token", None)

10

            if not request_token:

11

                raise AuthenticationFailed(‘缺少token‘)

12

            token_obj = models.Token.objects.filter(token_code=request_token).first()

13

            if not token_obj:

14

                raise AuthenticationFailed(‘无效的token‘)

15

            return token_obj.user.username, None

16

        else:

17

            return None, None

18




全局配置

# 在settings.py中配置
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ]
}<wiz_code_mirror>

4

1

# 在settings.py中配置

2

REST_FRAMEWORK = {

3

    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ]

4

}

权限组件

只有vip才能看的内容

自定义权限类

# 自定义权限类
from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
message = ‘VIP用户才能访问‘

def has_permission(self, request, view):
"""
自定义权限只有VIP用户才能访问
"""
# 因为在进行权限判断之前已经做了认证判断,所以这里可以直接拿到request.user
if request.user and request.user.type == 2: # 如果是VIP用户
return True
else:
return False<wiz_code_mirror>

15

1

# 自定义权限类

2

from rest_framework.permissions import BasePermission

3


4

class MyPermission(BasePermission):

5

    message = ‘VIP用户才能访问‘

6


7

    def has_permission(self, request, view):

8

        """

9

        自定义权限只有VIP用户才能访问

10

        """

11

        # 因为在进行权限判断之前已经做了认证判断,所以这里可以直接拿到request.user

12

        if request.user and request.user.type == 2:  # 如果是VIP用户

13

            return True

14

        else:

15

            return False

视图级别配置

class CommentViewSet(ModelViewSet):

queryset = models.Comment.objects.all()
serializer_class = app01_serializers.CommentSerializer
authentication_classes = [MyAuth, ]
permission_classes = [MyPermission, ]<wiz_code_mirror>

6

1

class CommentViewSet(ModelViewSet):

2


3

    queryset = models.Comment.objects.all()

4

    serializer_class = app01_serializers.CommentSerializer

5

    authentication_classes = [MyAuth, ]

6

    permission_classes = [MyPermission, ]

全局配置

REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
"DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
}<wiz_code_mirror>

4

1

REST_FRAMEWORK = {

2

    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],

3

    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]

4

}

频率组件

频率:限制用户访问网站的频率

自定义限制类

VISIT_RECORD = {}
# 自定义限制
class MyThrottle(object):

def __init__(self):
self.history = None

def allow_request(self, request, view):
"""
自定义频率限制60秒内只能访问三次
"""
# 获取用户IP
ip = request.META.get("REMOTE_ADDR")
timestamp = time.time()
if ip not in VISIT_RECORD:
VISIT_RECORD[ip] = [timestamp, ]
return True
history = VISIT_RECORD[ip]
self.history = history
history.insert(0, timestamp)
while history and history[-1] < timestamp - 60:
history.pop()
if len(history) > 3:
return False
else:
return True

def wait(self):
"""
限制时间还剩多少
"""
timestamp = time.time()
return 60 - (timestamp - self.history[-1])<wiz_code_mirror>

33

1

VISIT_RECORD = {}

2

# 自定义限制

3

class MyThrottle(object):

4


5

    def __init__(self):

6

        self.history = None

7


8

    def allow_request(self, request, view):

9

        """

10

        自定义频率限制60秒内只能访问三次

11

        """

12

        # 获取用户IP

13

        ip = request.META.get("REMOTE_ADDR")

14

        timestamp = time.time()

15

        if ip not in VISIT_RECORD:

16

            VISIT_RECORD[ip] = [timestamp, ]

17

            return True

18

        history = VISIT_RECORD[ip]

19

        self.history = history

20

        history.insert(0, timestamp)

21

        while history and history[-1] < timestamp - 60:

22

            history.pop()

23

        if len(history) > 3:

24

            return False

25

        else:

26

            return True

27


28

    def wait(self):

29

        """

30

        限制时间还剩多少

31

        """

32

        timestamp = time.time()

33

        return 60 - (timestamp - self.history[-1])

视图级别配置

class CommentViewSet(ModelViewSet):

queryset = models.Comment.objects.all()
serializer_class = app01_serializers.CommentSerializer
throttle_classes = [MyThrottle, ]<wiz_code_mirror>

x

1

class CommentViewSet(ModelViewSet):

2


3

    queryset = models.Comment.objects.all()

4

    serializer_class = app01_serializers.CommentSerializer

5

    throttle_classes = [MyThrottle, ]

全局配置

# 在settings.py中设置rest framework相关配置项
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
"DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
"DEFAULT_THROTTLE_CLASSES": ["app01.utils.MyThrottle", ]
}<wiz_code_mirror>

1

# 在settings.py中设置rest framework相关配置项

2

REST_FRAMEWORK = {

3

    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],

4

    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]

5

    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.MyThrottle", ]

6

}


认证组件的源码阅读

源码、源码、源码!

新构建的request里面的源码的user方法

源码走到了这里其实就已经需要我们自己来进行认证了。就可以对前端的请求进行认证,到底该怎么认证,还得继续往下走!

权限组件的源码阅读

相比较与认证组件,权限组件就更加的简洁了

频率组件的注意部分

原文地址:https://www.cnblogs.com/pontoon/p/10217414.html

时间: 2024-07-31 16:08:24

04,认证、权限、频率的相关文章

认证权限频率自定义

from rest_framework.authentication import BaseAuthenticationfrom rest_framework.permissions import BasePermissionfrom rest_framework.throttling import BaseThrottle,SimpleRateThrottlefrom django_redis import get_redis_connectionfrom rest_framework imp

【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限

开发环境搭建参见<[原]无脑操作:IDEA + maven + SpringBoot + JPA + Thymeleaf实现CRUD及分页> 需求: ① 除了登录页面,在地址栏直接访问其他URL,均跳转至登录页面 ② 登录涉及帐号和密码,帐号错误提示帐号错误,密码错误提示密码错误 ③ 登录成功跳转至首页,首页显示登录者帐号信息,并有注销帐号功能,点击注销退出系统 ------------------------------------------------------------------

SAP云解决方案和企业本地部署(On-Premise)混合架构下的安全认证权限管理

SAP提供了用户认证.权限管理和单点登录等安全相关的解决方案.但是随着云平台的兴起,企业已经部署的安全解决方案如何与云平台的安全解决方案集成呢?这是摆在我们面前的一个问题,而且是一个至关重要.需要认真思考的问题. 本文将探讨SAP提供的本地部署和云平台的安全解决方案产品集:SAP Single Sign-On, SAP Cloud Platform Identity Authentication, SAP Identity Management, 和SAP Cloud Platform Iden

认证 权限 视图 频率

认证组件 使用:写一个认证类,继承BaseAuthentication 在类中写authenticate方法,把request对象传入 能从request对象中取出用户携带的token根据token判断是否登录过 如果登录过,返回两个值 user对象 ,token对象(或者其他自定义的对象) 如果没有登录过抛异常 from rest_framework.authentication import BaseAuthentication from app01 import models from r

drf框架 8 系统权限类使用 用户中心信息自查 token刷新机制 认证组件项目使用:多方式登录 权限组件项目使用:vip用户权限 频率组件 异常组件项目使用

系统权限类使用 图书接口:游客只读,用户可增删改查权限使用 from rest_framework.permissions import IsAuthenticatedOrReadOnly class BookViewSet(ModelViewSet): # 游客只读,用户可增删改查 permission_classes = [IsAuthenticatedOrReadOnly] queryset = models.Book.objects.all() serializer_class = se

DRF 权限 频率

DRF的权限 权限是什么 大家之前都应该听过权限~那么我们权限到底是做什么用的呢~~ 大家都有博客~或者去一些论坛~一定知道管理员这个角色~ 比如我们申请博客的时候~一定要向管理员申请~也就是说管理员会有一些特殊的权利~是我们没有的~~ 这些对某件事情决策的范围和程度~我们叫做权限~~权限是我们在项目开发中非常常用到的~~ 那我们看DRF框架给我们提供的权限组件都有哪些方法~~ 权限组件源码 我们之前说过了DRF的版本和认证~也知道了权限和频率跟版本认证都是在initial方法里初始化的~~ 其

基于asp.net MVC 的服务器和客户端的交互(二)之获取Oauth 2.0认证权限

基本Web API的ASP.NET的Oauth2认证 增加Token额外字段 增加Scope授权字段 持久化Token 设计Token的时间间隔 刷新Token后失效老的Token 自定义验证[重启IIS池Token失效,验证权限] Oauth2 认证的流程 客户端发送口令(grant_type,client_id,client_secret)到服务端请求,认证返回access token ,然后客户端跟据获得的access token,根据Access Token获得权限去访问Web API.

【Mongodb】用户和认证 权限总结

开启MongoDB服务时不添加任何参数时,默认是没有权限验证的,登录的用户可以对数据库任意操作而且可以远程访问数据库!   在刚安装完毕的时候MongoDB都默认有一个admin数据库,此时admin数据库是空的,没有记录权限相关的信息!当admin.system.users一个用户都没有时,即使mongod启动时添加了--auth参数,如果没有在admin数据库中添加用户,此时不进行任何认证还是可以做任何操作(不管是否是以--auth 参数启动),直到在admin.system.users中添

Django rest_framework----认证,权限,频率组件

认证 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api.models import * class AuthToken(BaseAuthentication): def authenticate(self, request): token=request.GET.get('token') t

版本,认证,权限

版本 DRF中版本 导入 from rest_framework.versioning import 全局配置版本控制系统 /v1/books/    是在 URL加查询参数 # DRF的配置 REST_FRAMEWORK = { # 配置默认使用的版本控制类 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 'DEFAULT_VERSION': 'v1', # 默认的版本 'ALLOWED_VE