当前位置: 首页 > news >正文

测试开发之Django实战示例 第十二章 创建API

第十二章 创建API

在上一章里,创建了一个学生注册系统和选课系统。然后创建了展示课程内容的视图,以及学习了如何使用Django缓存框架。在这一章里有如下内容:

  • 建立RESTful API

  • 管理API视图的认证与权限

  • 建立API视图集和路由

1创建RESTful API

你可能会想建立一个接口(API),让其他应用程序和我们的网站进行交互。通过建立一个API,就可以让第三方应用程序自动化的操作和消费我们网站生产的数据。

译者注:使用API而不是模板渲染与前端进行交互,这就是前后端分离的思路。仅使用Django来进行Web开发的话前后端分离并不明显。读者在未来的Web开发中接触到前端框架就会对此有更深的了解。

有很多种方式可以建立这样一个API,推荐根据REST原则来建立这样一个API。REST是Representational State Transfer的简称。RESTful API是基于资源的,即URL用于表示网站所有的资源,HTTP的请求种类比如GET,POST,PUT或DELETE表示对应的行为,即获取,创建,更新和删除数据。不同的HTTP响应码表示这次动作的完成结果,例如2XX表示该操作成功,4XX表示错误等。

RESTful API常用的数据交换格式是JSON或者XML,我们准备建立一个使用JSON进行数据交换的API。我们的API会提供以下功能:

  • 获取主题

  • 获取可用的课程

  • 获取课程内容

  • 在一个课程中注册

我们可以从0开始写视图来建立该API,也可以通过第三方应用简单的为项目建立API,在这方面最出名的第三方应用就是Django REST framework。

1.1安装Django REST framework

Django REST framework可以让你简单地创建符合REST风格的API,其官方网站是https://www.django-rest-framework.org/。

打开系统命令行输入如下命令:

Copypip install djangorestframework==3.8.2

然后编辑settings.py激活rest_framework应用:

CopyINSTALLED_APPS = [# ...'rest_framework',
]

再在settings.py中加入如下设置:

CopyREST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',]
}

原书代码这里少了一个左方括号。

REST_FRAMEWORK用于具体设置该模块。REST framework提供了很多设置:DEFAULT_PERMISSION_CLASSES提供了对于增删改查行为的默认权限。我们设置了DjangoModelPermissionsOrAnonReadOnly作为唯一默认的权限类。

这个权限类依赖于Django的权限系统,让用户可以增删改查数据对象,同时让未登录用户只能进行只读操作。在下边的向视图增加权限一节中还会详细学习这部分功能。

Django REST 框架的全部设置可以在https://www.django-rest-framework.org/api-guide/settings/找到。

1.2设置序列化器

在设置好框架后,还需要确定使用的序列化器。网站对外提供的数据应当是经过序列化的标准数据,同时还需要对外界输入的数据进行反序列化。REST框架提供了下列类用于对一个单独对象设置序列化器:

  • Serializer:为普通的Python类实例提供序列化

  • ModelSerializer:为数据模型的实例提供序列化

  • HyperlinkedModelSerializer:与ModelSerializer的功能相同,但可以通过超链接来表示对象之间的关系,而不是通过主键关联。

让我们来实际建立一个序列化器。在courses应用中建立如下文件结构:

Copyapi/__init__.pyserializers.py

我们创建了一个叫做api的包,然后打算在这个包里建立序列化器。编辑serializers.py文件,添加下列代码:

Copyfrom rest_framework import serializers
from ..models import SubjectclassSubjectSerializer(serializers.ModelSerializer):classMeta:model = Subjectfields = ['id', 'title', 'slug']

这是继承了ModelSerializer类的,专门用于Subject模型的序列化器。定义序列化器的类使用起来和Form以及ModelForm类很类似:Meta内的属性允许指定要序列化的类及字段。如果不设置具体的fields属性,则默认会包含该模型的全部字段。

来实验一下这个序列化器,进入带有Django环境的Python命令行模式:

Copypython manage.py shell

输入以下命令:

Copy>>> from courses.models import Subject
>>> from courses.api.serializers import SubjectSerializer
>>> subject = Subject.objects.latest('id')
>>> serializer = SubjectSerializer(subject)
>>> serializer.data
{'id': 4, 'title': 'Programming', 'slug': 'programming'}

在这个例子里,先获取了一个Subject实例,然后创建了一个序列化器的实例SubjectSerializer并访问序列化之后的数据,可以看到模型的数据被序列化成了Python原生的字典类型数据。

1.3理解解析器(parser)与渲染器(renderer)

序列化的结果在通过HTTP响应返回之前,必须被渲染成为一个特殊的格式。同样,在从HTTP请求中获取数据的时候,也必须解析数据然后反序列化。REST框架包含了渲染器和解析器用于处理这些过程。

先来看看如何解析数据,在Python命令行模式中输入下列命令:

Copy>>> from io import BytesIO
>>> from rest_framework.parsers import JSONParser
>>> data = b'{"id":4,"title":"Programming","slug":"programming"}'>>> JSONParser().parse(BytesIO(data))
{'id': 4, 'title': 'Programming', 'slug': 'programming'}

可以看到,给定一个二进制字节流形式的JSON字符串,使用JSONParser可以将其反序列化为Python的数据对象。

REST框架还包含渲染器Renderer类用于格式化API的响应。框架通过上下文内容协商机制来确定使用哪种渲染器,即渲染器会通过HTTP请求的Accept头部字段来确定这个请求所需要的内容类型来进行判断。还可以通过URL的格式化的前缀来判断,例如,一个请求返回JSON格式响应的访问可能会触发JSONRenderer渲染器。

再回到Python命令行模式中,在刚才的代码的基础上继续执行下列代码:

Copy>>> from rest_framework.renderers import JSONRenderer
>>> JSONRenderer().render(serializer.data)
b'{"id":4,"title":"Programming","slug":"programming"}'

使用JSONRenderer可以将Python数据对象渲染成JSON字符串。REST框架提供了两个不同的渲染器:JSONRenderer和BrowsableAPIRenderer。后者提供了一个浏览API返回数据的web界面。可以在settings.py的REST_FRAMEWORK设置中的DEFAULT_RENDERER_CLASSES选项中设置默认的渲染器。

关于渲染器和解析器的更多说明可以看https://www.django-rest-framework.org/api-guide/renderers/和https://www.django-rest-framework.org/api-guide/parsers/。

1.4创建列表和详情视图

REST框架包含一系列内置的通用视图和mixins用于建立API视图,提供了增删改查数据模型对象的功能。关于所有的通用视图和mixin可以看https://www.django-rest-framework.org/api-guide/generic-views/。

现在来建立一个获取Subject对象的视图,在courses/api/目录内新建views.py文件,在其中增加下列代码:

Copyfrom rest_framework import generics
from ..models import Subject
from .serializers import SubjectSerializerclassSubjectListView(generics.ListAPIView):queryset = Subject.objects.all()serializer_class = SubjectSerializerclassSubjectDetailView(generics.RetrieveAPIView):queryset = Subject.objects.all()serializer_class = SubjectSerializer

在这段代码中,使用了REST框架提供的ListAPIView和RetrieveAPIView两个内置视图,在URL中包含一个主键参数,用于获取具体的数据对象。两个视图都有下列属性:

  • queryset:基础的QuerySet,用于返回数据

  • serializer_class:序列化器对象,指定要使用的序列化器

接下来为新的视图配置URL,在courses/api/下新建urls.py文件,然后编辑其中的内容:

Copyfrom django.urls import path
from . import viewsapp_name = 'courses'urlpatterns = [path('subjects/', views.SubjectListView.as_view(), name='subject_list'),path('subjects/<pk>/', views.SubjectDetailView.as_view(), name='subject_detail'),
]

然后编辑educa项目的根urls.py,加上一行:

Copyurlpatterns = [# ......path('api/', include('courses.api.urls', namespace='api')),
]

我们为API视图使用了api路由命名空间。启动站点,使用curl命令访问http://127.0.0.1:8000/api/subjects/:

Copycurl http://127.0.0.1:8000/api/subjects/

会得到和下边很相似的响应:

Copy[{"id":1,"title":"Mathematics","slug":"mathematics"},{"id":2,"title":"Music","slug":"music"},{"id":3,"title":"Physics","slug":"physics"},{"id":4,"title":"Programming","slug":"programming"}
]

这个HTTP响应包含一系列JSON格式的字符串,其内容是序列化后的所有Subject模型中的数据,包含指定的三个字段。如果系统中没有安装curl,可以通过 https://curl.haxx.se/dlwiz/ 进行安装。也可以通过其他浏览器扩展比如Postman,在https://www.getpostman.com/进行安装。

现在不使用curl,而是直接在浏览器中打开http://127.0.0.1:8000/api/subjects/,会看到如下页面:

这个界面就是由之前提到的BrowsableAPIRenderer渲染器提供的。页面内显示了结果的头部信息及API的返回信息。还可以通过在URL中包含具体的ID来获取一个Subject对象,访问http://127.0.0.1:8000/api/subjects/1/,可以发现页面只展示了一个单独的JSON格式的对象数据。

1.5创建嵌套的序列化器

我们再为Course模型创建一个序列化器,打开courses/api/serializers.py继续编辑:

Copyfrom ..models import CourseclassCourseSerializer(serializers.ModelSerializer):classMeta:model = Coursefields = ['id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules']

之后看一下Course序列化器是如何工作的,进入Python命令行模式输入下列命令:

Copy>>> from rest_framework.renderers import JSONRenderer
>>> from courses.models import Course
>>> from courses.api.serializers import CourseSerializer
>>> course = Course.objects.latest('id')
>>> serializer = CourseSerializer(course)
>>> JSONRenderer().render(serializer.data)

这个时候可以看到查询结果里,该课程包含的模块是一个主键列表的形式,类似这样:

Copy"modules": [6, 7, 9, 10]

这样的数据意义不大,我们想在结果里包含每个Module的更多信息,所以还必须给Module模型也制作一个序列化器,编辑serializers.py,修改成如下:

Copyfrom rest_framework import serializers
from ..models import ModuleclassModuleSerializer(serializers.ModelSerializer):classMeta:model = Modulefields = ['order','title','description']classCourseSerializer(serializers.ModelSerializer):modules = ModuleSerializer(many=True, read_only=True)classMeta:model = Coursefields = ['id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules']

首先为Module模型制作了一个序列化器,然后给CourseSerializer增加了一个modules属性,设置为Module类的序列化器,many=True表示需要序列化多个对象,read_only参数表示这个字段是只读的,不应该被包含在任何需要进行增删改的字段中。

重新启动Python命令行模式,再执行一遍上边Python命令行代码,使用JSONRenderer渲染序列化器实例的data属性,可以看到结果中关于modules的部分被嵌套的ModuleSerializer序列化成下面这样:

Copy"modules": [{"order": 0,"title": "Introduction to overview","description": "A brief overview about the Web Framework."},{"order": 1,"title": "Configuring Django","description": "How to install Django."},...
]

这样就完成了嵌套序列化的工作,关于序列化器的更多信息可以看https://www.django-rest-framework.org/api-guide/serializers/。

1.6创建自定义API视图

REST框架提供了一个APIView视图,基于Django内置的View视图基础上增加了RESTful API的功能,但与View不同的是,APIView采用了REST框架自定义的处理Request和Response对象的方法,并且在返回HTTP响应的时候处理APIException错误,而且还包含内建的认证系统来管理对视图的访问。

下边通过APIView来创建一个视图供用户选课,编辑courses应用的api/views.py文件,增加如下代码:

Copyfrom django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from ..models import CourseclassCourseEnrollView(APIView):defpost(self, request, pk, format=None):course = get_object_or_404(Course, pk=pk)course.students.add(request.user)return Response({'enrolled': True})

这个CourseEnrollView视图管理用户选课的功能。代码解释如下:

  1. 创建一个视图继承APIView

  1. 在其中定义了post()方法用于处理POST请求,这个类不需要处理其他类型的HTTP请求。

  1. 这个类需要接收一个pk参数,为课程的主键id,用于取得该课程对象。如果找不到就返回404错误。

  1. 添加当前用户与Course对象的多对多关系,即选课。

编辑api/urls.py文件,为新的视图配置URL:

Copypath('courses/<pk>/enroll/', views.CourseEnrollView.as_view(), name='course_enroll'),

现在理论上我们就可以发送一个POST请求来选课,而无需在页面中点击按钮。然而这么做需要区分用户身份,避免未认证的用户也来发送POST请求。下一节来看看API认证与权限管理是如何工作的。

1.7处理身份认证

REST框架提供了一个认证类,用于鉴别提交HTTP请求的用户身份。如果认证通过,REST框架会在request.user中设置认证后的User对象,如果没有用户通过认证,则request被设置一个Django内置的AnonymousUser对象。

REST框架提供如下的认证后端:

  • BasicAuthentication:这是基础的HTTP认证(BA认证),用户和密码存放在HTTP请求头的Authorization头部数据中,以Base64格式发送。关于BA认证的具体内容看这里。

  • TokenAuthentication:这是基于token的认证,一个Token模型用于存放用户的token,HTTP请求头中的Authorization信息中存储token数据用于验证。

  • SessionAuthentication:使用Django的session后端进行验证,对于前端发来的AJAX请求一般使用该方式验证。

  • RemoteUserAuthentication:允许使用web服务器代理认证,会设置一个REMOTE_USER变量。

除此之外,还可以继承REST框架提供的BaseAuthentication类并且重写authenticate()方法来创建自定义的验证后端。

通过DEFAULT_AUTHENTICATION_CLASSES还可以设置认证是基于每个视图的,还是全局认证。

认证(Authentication)只解决用户身份的问题,即识别发请求的用户身份,但不会允许或阻止用户访问视图,必须通过设置用户权限来限制访问视图。

在https://www.django-rest-framework.org/api-guide/authentication/可以找到所有认证相关的文档。

在视图中增加BasicAuthentication类,编辑api/views.py文件,为CourseEnrollView添加一行:

Copyfrom rest_framework.authentication import BasicAuthenticationclassCourseEnrollView(APIView):authentication_classes = (BasicAuthentication,)# ......

现在视图就可以通过HTTP请求头的Authorization头部信息进行用户身份认证了。

1.8为视图增加权限控制

REST框架提供了一个权限系统用于控制对视图的访问。REST框架内建的部分权限有:

  • AllowAny:完全开放权限,不管用户认证与否,都不做任何限制

  • IsAuthenticated:仅允许通过认证的用户

  • IsAuthenticatedOrReadOnly:认证用户具有完整权限,匿名用户只读(只能使用GET,HEAD,OPTIONS三种HTTP请求种类)。

  • DjangoModelPermissions:使用django.contrib.auth的权限管理系统。视图需要一个queryset属性,只有认证的用户加上具备访问某个数据类的权限才能够进行操作

  • DjangoObjectPermissions:也使用Django权限,是基于每个对象的单独权限设置。

如果用户因为权限问题操作失败,则通常会得到下列HTTP响应码和错误信息:

  • HTTP 401: Unauthorized

  • HTTP 403:Permission denied

可以在https://www.django-rest-framework.org/api-guide/permissions/中找到更多关于权限的信息。

继续编辑api/views.py文件,为CourseEnrollView添加一个属性permission_classes:

Copyfrom rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticatedclassCourseEnrollView(APIView):authentication_classes = (BasicAuthentication,)permission_classes = (IsAuthenticated,)# ......

我们为视图加上了IsAuthenticated权限,意味着只有认证用户可以访问该视图。现在可以尝试给这个视图发一个POST请求。

启动站点,然后在系统命令行里输入下列命令:

Copycurl -i -X POST http://127.0.0.1:8000/api/courses/1/enroll/

应该会得到下列响应:

CopyHTTP/1.1401 Unauthorized
......
{"detail":"Authentication credentials were not provided."}

结果得到了401响应,因为我们没有认证过。现在我们为请求头增加一个已经注册的用户的认证信息,将下列代码中的student:password替换成你网站中的实际用户名和密码,然后执行命令:

Copycurl -i -X POST -u student:password http://127.0.0.1:8000/api/courses/1/enroll/

会得到如下响应:

CopyHTTP/1.1 200 OK
......
{"enrolled":true}

现在可以到管理后台查看数据库是否已经更新了该用户选课的数据。

1.9创建视图集和路由

视图集Viewsets允许对API定义一系列的交互动作,并允许REST框架使用一个Router对象动态的建立URL。通过使用视图集,可以避免重复编写视图逻辑。REST框架中的视图集涵盖的经典的增删改查动作,包括list(), create(), retrieve(), update(), partial_update(), 和 destroy()。

为Course模型创建一个视图集,编辑api/views.py文件,增加如下代码:

Copyfrom rest_framework import viewsets
from .serializers import CourseSerializerclassCourseViewSet(viewsets.ReadOnlyModelViewSet):queryset = Course.objects.all()serializer_class = CourseSerializer

创建了一个视图集并继承了ReadOnlyModelViewSet,ReadOnlyModelViewSet提供了只读的list()和retrieve()方法,可以返回一个对象集合或者单个对象。编辑api/urls.py,为视图集配置URL:

Copyfrom django.urls import path, include
from rest_framework import routers
from . import viewsrouter = routers.DefaultRouter()
router.register('courses', views.CourseViewSet)urlpatterns = [# ......path('', include(router.urls)),
]

建立了一个默认的路由对象DefaultRouter(),然后将CourseViewSet视图注册到路由中,使用了前缀courses,现在这个router对象就可以为视图集动态的生成URL。

打开http://127.0.0.1:8000/api/,可以看到如下页面:

这个时候可以访问http://127.0.0.1:8000/api/courses/,就可以得到JSON格式的课程列表。这个路径中的/courses/就是注册路由的时候使用的前缀courses。

视图集的详细使用可以看 https://www.django-rest-framework.org/api-guide/viewsets/,路由的使用方法可以参考https://www.django-rest-framework.org/api-guide/routers/。

1.10为视图集添加额外功能

可以为视图集添加额外功能。让我们来把CourseEnrollView变成一个自定义的视图集。编辑api/views.py文件,修改CourseViewSet为下面这样:

Copyfrom rest_framework.decorators import detail_routeclassCourseViewSet(viewsets.ReadOnlyModelViewSet):queryset = Course.objects.all()serializer_class = CourseSerializer@detail_route(methods=['post'], authentication_classes=[BasicAuthentication], permission_classes=[IsAuthenticated])defenroll(self, request, *args, kwargs):course = self.get_object()course.students.add(request.user)return Response({'enrolled': True})

为视图集增加了一个自定义的方法enroll(),代表为视图集增加的新功能,解释如下:

  1. 使用detail_router装饰器(该装饰器已经被Pycharm提醒要被删除,未来改用@action装饰器),定义了这是一个在单个对象上执行的功能。

  1. 这个装饰器同时还允许添加参数,methods设置为['post']表示该视图只接受POST请求,然后还设置了验证和权限。

  1. 使用self.get_object()获取当前的Course对象。

  1. 把当前的用户增加到多对多关系中(选课),返回一个自定义的成功响应

然后编辑api/urls.py文件,去掉下边这一行,因为通过@detail_route动态配置了新的路由,这一行无需再用:

Copy/enroll/', views.CourseEnrollView.as_view(), name='course_enroll'),

然后编辑api/views.py,删除CourseEnrollView,因为这个类的功能现在成为视图集的一部分。

现在选课功能的URL由router自动生成,实际的URL与刚才相同,因为使用了我们自定义的函数名称enroll。

1.11自定义权限

我们希望只有选了某课程的学生用户才可以访问该课程的全部内容。最好的方式就是自定义一个权限,REST框架(原书为Django,应该为REST)提供了一个BasePermission类允许重写下列方法:

  • has_permission():视图级别的权限检查

  • has_object_permission():对象级别的权限检查

这两个方法必须返回True表示具有权限或False表示不具有权限。在courses/api/目录下新建permissions.py文件,添加下列代码:

Copyfrom rest_framework.permissions import BasePermissionclassIsEnrolled(BasePermission):defhas_object_permission(self, request, view, obj):return obj.students.filter(id=request.user.id).exists()

这个IsEnrolled权限继承了BasePermission类然后重写了has_object_permission方法。由于这个方法是基于对象的,所以obj就是当前的课程。检查当前用户是否在已经选该课的所有用户里。之后就可以使用该权限类了。

1.12序列化课程内容

现在已经把主题,课程和章节都序列化了。还必须序列化内容。Content模型有一个通用外键关系,可以用于检索所有内容模型。而且我们还为所有内容模型添加了render()方法。可以使用这些关系和方法,来实现序列化。

编辑api/serializers.py文件,添加下列代码:

Copyfrom ..models import ContentclassItemRelatedField(serializers.RelatedField):defto_representation(self, value):return value.render()classContentSerializer(serializers.ModelSerializer):item = ItemRelatedField(read_only=True)classMeta:model = Contentfields = ['order', 'item']

在这段代码里,通过继承RelatedField定义了一个特别的字段ItemRelatedField,然后重写了to_representation()方法。然后定义了内容序列化器ContentSerializer并且指定与原来通用外键同名的item属性为刚定义的ItemRelatedField字段。

我们还需要另外一个用于Module模型的序列化器,其中嵌套这个Content序列化器;还需要改造Course序列化器以让其也包含内容输出,编辑api/serializers.py文件添加下列代码:

CopyclassModuleWithContentsSerializer(serializers.ModelSerializer):contents = ContentSerializer(many=True)classMeta:model = Modulefields = ['order', 'title', 'description', 'contents']classCourseWithContentsSerializer(serializers.ModelSerializer):modules = ModuleWithContentsSerializer(many=True)classMeta:model = Coursefields = ['id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules']

这其实就是一层一层从内到外嵌套序列化器,由于已经定义了Content的序列化器,就建立一个外层的ModuleWithContent序列化器,其中设置contents字段为Content序列化器,再往上一层的CourseWithContent序列化器也是类似来嵌套ModuleWithContent。

再建立一个视图,模仿刚才的retrieve()行为,但是采用新的序列化器,编辑api/views.py文件,给CourseViewSet视图集添加下列代码:

Copyfrom .permissions import IsEnrolled
from .serializers import CourseWithContentsSerializerclassCourseViewSet(viewsets.ReadOnlyModelViewSet):# ...    @detail_route(methods=['get'],serializer_class=CourseWithContentsSerializer,authentication_classes=[BasicAuthentication],permission_classes=[IsAuthenticated, IsEnrolled])defcontents(self, request, *args, kwargs):return self.retrieve(request, *args, kwargs)

这段代码解释如下:

  • 使用@detail_route装饰器来定义该方法是针对一个单独数据对象的

  • 该方法只接受GET请求

  • 使用了CourseWithContentsSerializer这个新的序列器,用于返回包含具体内容数据的序列化后输出。

  • 添加了用户认证IsAuthenticated和自定义权限IsEnrolled

  • 采用ReadOnlyModelViewSet提供的retrieve()方法来返回Course对象

然后打开http://127.0.0.1:8000/api/courses/1/contents/ 。如果你的当前用户选了对应的课程,就可以看到课程,章节和内容嵌套渲染后的字符串以JSON的形式显示出来,类似下边这样:

Copy{"order": 0,"title": "Introduction to Django","description": "Brief introduction to the Django Web Framework.","contents": [{"order": 0,"item": "<p>Meet Django. Django is a high-level Python Web framework...</p>"},{"order": 1,"item": "\n<iframe width=\"480\" height=\"360\" src=\"http://www.youtube.com/embed/bgV39DlmZ2U?wmode=opaque frameborder=\"0\" allowfullscreen></iframe>\n"}]
}

现在我们就建立了一个简单的符合RESTful风格的API,让网站自动化向外部提供数据。REST框架还可以使用ModelViewSet来创建和编辑数据对象。关于REST框架中的主要内容在本章都涉及到了,如果对于框架特性还需要详细了解,可以参考REST框架的官方文档:https://www.django-rest-framework.org/。

总结

在本章,为其他程序自动化使用本网站的程序,建立了一套API,方便与其他应用程序进行互动。

下一章将讨论如何通过uWSGI和NGINX配置生产环境。你还会学到如何实现一个自定义的中间件以及建立自定义的管理命令。

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!!

相关文章:

测试开发之Django实战示例 第十二章 创建API

第十二章 创建API在上一章里&#xff0c;创建了一个学生注册系统和选课系统。然后创建了展示课程内容的视图&#xff0c;以及学习了如何使用Django缓存框架。在这一章里有如下内容&#xff1a;建立RESTful API管理API视图的认证与权限建立API视图集和路由1创建RESTful API你可能…...

Yakit实战技巧:用MITM热加载任意修改流量

背景 用户在使用 Yakit MITM 功能的时候&#xff0c;经常会遇到一些特殊需求&#xff1a; 我的数据包需要携带一些特征变量才能访问&#xff0c;但是浏览器无法做到&#xff0c;我可以批量修改流量新增某一个 Header 吗&#xff1f; 我可以在代理层面在所有流量中新增一个参数…...

如何搭建自己的MQTT服务器?跟我来,一行代码搞定!

如何搭建自己的MQTT服务器&#xff1f;跟我来&#xff0c;一行代码搞定&#xff01;什么是mosquitto&#xff1f;如何使用mosquitto云服务器注意事项MQTT客户端软件下载在文章开始之前&#xff0c;你首先需要有一台服务器&#xff0c;我这里用的是阿里云的轻量级云服务器&#…...

遇到的问题

一、axios 请求 1、axios post 提交的请求的 content-type 为 json 默认情况下&#xff0c;axios将JavaScript对象序列化为JSON&#xff0c;再发送数据application/x-www-form-urlencoded格式相反&#xff0c;您可以使用URLSearchParamsAPI&#xff0c;也就是支持在绝大多数…...

线程没有被终止的异常的处理

process Runtime.getRuntime().exec(command); process.waitFor(); // 这个调用比较关键&#xff0c;就是等当前命令执行完成后再往下执行 if (!file.exists()) { Ulog.error("html转pdf执行失败"); } else { …...

RocketMQ 初步了解

RocketMQ 初步了解 前言&#xff1a; ​  近期&#xff0c;因公司使用 RocketMQ 作为消息队列中间件&#xff0c;特此了解。  RocketMQ 是阿里巴巴在 2012 年开发的分布式消息中间件&#xff0c;专为万亿级超大规模的消息处理而设计&#xff0c;具有高吞吐量、低延迟、海量…...

Mac下PyCharm快捷键

Mac键盘符号和修饰键说明 ⌘ Command⇧ Shift⌥ Option⌃ Control↩︎ Return/Enter⌫ Delete⌦ 向前删除键&#xff08;FnDelete&#xff09;↑ 上箭头↓ 下箭头← 左箭头→ 右箭头⇞ Page Up&#xff08;Fn↑&#xff09;⇟ Page Down&#xff08;Fn↓&#xff09;Home Fn …...

城市管网监测系统,保障城市血管生命线!

各种不同的管网线路组成了城市的供血管道&#xff0c;管网对于维持正常的社会生活、生产秩序和公共安全至关重要。我国城市平均漏损率达到38%&#xff0c;部分城市甚至超过50%&#xff0c;远超发达国家的平均水平&#xff08;10%&#xff09;。对于管道状态的监测&#xff0c;是…...

Web3中文|1月数据显示复苏迹象,涉及NFT、DeFi、Dapp、链游……

本期看点 1、Dapp行业概述 2、DeFi的TVL增长26.8%&#xff0c;有回暖迹象 3、NFT市场数据飙升&#xff0c;交易额达9.46亿美元 4、链游使用量占行业48% 5、与去年相比&#xff0c;1月份区块链漏洞损失最低 区块链领域正在多元化发展&#xff0c;2023年1月&#xff0c;从各…...

MySQL索引的介绍以及优缺点

1.索引简介 索引是一种用于快速查询和检索数据的数据结构&#xff0c;其本质可以看成是一种排序好的数据结构。 使用索引可以快速找出在某个或多个列中有一特定值的行&#xff0c;所有MySQL列类型都可以被索引&#xff0c;对相关列使用索引是提高查询操作速度的最佳途径。 索…...

Java_小项目书城

1.概述 书城的基本功能&#xff1a; 展示书籍上新书籍下架书籍退出应用 书城项目所涉及到的知识点&#xff1a; 用户交互–键盘录入分支结构&#xff0c;循环结构面向对象的思维&#xff0c;封装对象集合的使用 2.菜单的编写 这部分代码就是读取用户键盘的录入&#xff0…...

Unreal Engine08:Pawn的实现

写在前面 Pawn继承于Actor&#xff0c;增加了一些用于控制和提供玩家视角的功能&#xff0c;这里主要是介绍一下Pawn类的实现。 一、创建一个Pawn的C类 创建的C类也是放在Source文件夹中的Public和Private文件夹中&#xff1b;选择Pawn作为继承的父类&#xff1b;头文件中除…...

408强化(二)线性表纯享版

目录 一、顺序表&#xff08;数组&#xff09;和链表总览 二、考情分析 2.1 从历年考情可以看出&#xff0c;如果一个方法出现了第2次&#xff0c;一般是以下情况&#xff1a; 2.2 没有考过的地方 三、 共同操作或考法 3.1 多指针后移 3.2 逆置 3.3 空间换时间的操作 3.…...

ubuntu下如何使用wireshark抓包,保姆级教程

Wireshark&#xff08;前称Ethereal&#xff09;是一个网络封包分析软件。网络封包分析软件的功能是截取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。 一、安装wireshark 打开终端&…...

世界上最健康的程序员作息表!「值得一看」

昨晚看了一篇“传说中”的“世界上最健康的作息时间表”&#xff0c;开始纠结自己还要不要5点半起床。 都说程序员这一行&#xff0c;猝死概率极高&#xff0c;究其原因还是加班太狠、作息不规律、缺乏运动… 今天和大家分享一下这篇文章&#xff0c;还是非常值得参考的&#…...

Java中多继承的实现

1 问题Java是一种面向对象的只允许单继承的语言&#xff0c;那么怎样在Java中实现多继承呢&#xff1f;2 方法多层继承如果要直接继承类&#xff0c;子类是不可以直接多继承的&#xff0c;但是可以通过多层继承来实现多继承&#xff0c;但多层继承一般不建议超过三次。接口接口…...

蓝桥杯 stm32 USART 串口发送数据

文章代码使用 HAL 库。 文章目录 前言一、串口原理图二、CubeMX 创建工程。三、串口发送函数:四、串口助手 配置:五、详细代码:注意:连续发送数据六、printf 重定向问题代码示例:实验效果:总结前言 USART : ( Universal Synchronous/Asynchronous Receiver/Transmitter…...

Spring之AOP底层源码解析

Spring之AOP底层源码解析 1、动态代理 代理模式的解释&#xff1a;为其他对象提供一种代理以控制对这个对象的访问&#xff0c;增强一个类中的某个方法&#xff0c;对程序进行扩展。 举个例子 public class UserService {public void test() {System.out.println("test.…...

人脸识别——景联文科技提供3D头模数据采集业务!

“拿起手机刷脸解锁、上下班考勤、支付订单&#xff0c;刷脸已极大地便利了我们的生活。清华大学新闻学院教授沈阳表示&#xff0c;中国人平均每天要暴露在各种摄像头下超过500次。人脸识别已成了我们生活中重要的一部分。由于2D人脸识别容易受到姿态、表情、光照等因素影响&am…...

SpringBoot集成Flink-CDC 采集PostgreSQL变更数据发布到Kafka

最近做的一个项目&#xff0c;使用的是pg数据库&#xff0c;公司没有成熟的DCD组件&#xff0c;为了实现数据变更消息发布的功能&#xff0c;我使用SpringBoot集成Flink-CDC 采集PostgreSQL变更数据发布到Kafka。 一、业务价值 监听数据变化&#xff0c;进行异步通知&#xf…...

酷开系统壁纸模式,将氛围感死死拿捏!

古希腊哲学家柏拉图曾经说过&#xff1a;“美感是起于视觉、听觉产生的快感&#xff0c;以人的感官所能达到的范围为极限。”而电视则恰恰就是视觉听觉的完美融合体&#xff0c;当一台开启的电视可以给我们带来视听享受的时候&#xff0c;一台待机状态下的电视又如何取悦于我们…...

第0章 一些你可能正感到迷惑的问题

操作系统是什么 操作系统是控制管理计算机系统的硬软件&#xff0c;分配调度资源的系统软件。 由操作系统把资源获取到后台给用户进程&#xff0c;但为了保护计算机系统不被损坏&#xff0c;不允许用户进程直接访问硬件资源。 操作系统相当于是一个分配资源的机构&#xff0c;…...

MYSQL实战

SQL的处理 缓存解析查询优化&#xff08;查询优化器&#xff09; 重写查询&#xff1b;表的读取顺序&#xff1b;选择索引1.不要在索引上做任何操作 表达式函数 2.尽量全值匹配 联合索引中搜素条件后会根据最优条件排序进行查询&#xff0c;联合索引尽量都使用起来。搜索条…...

少儿户外拓展北斗定位解决方案

一、项目背景户外拓展训练是指通过专业的机构&#xff0c;对久居城市的人进行的一种野外生存训练。拓展训练通常利用崇山峻岭、翰海大川等自然环境&#xff0c;通过精心设计的活动达到“磨练意志、陶冶情操、完善人格、熔炼团队”的培训目的。针对户外拓展人员安全管理存在的实…...

更换ssl证书

更换ssl证书常用证书查看以及转换网址阿里云判断流量以及配置证书判断接入点阿里云控制台配置证书WAFAzure判断流量以及配置证书&#xff1a;判断接入点Azure配置证书CDNAPP GateWay常用证书查看以及转换网址 https://www.chinassl.net/ssltools/convert-ssl.htmlhttps://myss…...

线程池源码解析项目中如何配置线程池

目录 基础回顾 线程池执行任务流程 简单使用 构造函数 execute方法 execute中出现的ctl属性 execute中出现的addWorker方法 addWorker中出现的addWorkerFailed方法 addWorker中出现的Worker类 Worker类中run方法出现的runWorker方法 runWorker中出现的getTask runWo…...

Echarts 更改K线度颜色,解释K线图4个数字意义

第019个点击查看专栏目录本示例修改K线度的颜色&#xff0c;方法参考源代码。 这里面讲一下K线图的四个数字&#xff0c;如[20, 34, 10, 38], 第一位&#xff1a;20代表开盘价格&#xff0c; 第二位&#xff1a;34代表闭盘价格&#xff0c; 第三位&#xff1a;10代表最低价&…...

JavaScript和Java两种方法实现百度地图和高德、腾讯地图的相互转换

目录一、常见的经纬度标准二、百度地图和高德、腾讯地图经纬度的转换1、前端JavaScript转换2、后端Java实现转换一、常见的经纬度标准 高德、腾讯&#xff08;使用GCJ02&#xff09; GCJ-02坐标系&#xff0c;也称火星坐标系&#xff0c;由中国国家测绘局在02年发布&#xff0…...

Vue中常见的几种组件间通信方法

1.props&#xff08;父传子&#xff09; 父组件Parent.vue <template><child :msg"message"></child> </template>父组件通过:val"value"的形式定义要传给子组件的值value绑定到val上 子组件Child.vue export default {//写法一…...

Outcome VS. Output:研发效能提升中,谁会更胜一筹?

2007 年&#xff0c;网景通信公司&#xff08;Netscape&#xff09;的联合创始人 Marc Andreessen 在博客 The Pmarca Guide to Startups 中提出 「Product/Market Fit」 &#xff0c;他写道&#xff0c; 「这意味着在一个良好的市场中&#xff0c;拥有能够满足该市场的产品。」…...

网站集群建设方案/上海网站seo诊断

上一篇简单的讲了下Handler的使用&#xff0c;本篇就解析一下Andorid消息处理机制的内部实现&#xff0c;到底是如何为绑定的线程创建looper对象和消息队列的&#xff0c;又是怎么实现消息添加、消息遍历和消息分发的。 首先需要介绍几个非常重要的角色&#xff1a;Looper、Han…...

html5开发工程师/seo入门书籍推荐

IOS篇 一、尺寸及分辨率 iPhone界面尺寸&#xff1a;320*480、640*960、640*1136 iPhone6&#xff1a;4.7英寸(1334750)&#xff0c;iPhone6 Plus&#xff1a;5.5英寸(19201080) 设计图单位&#xff1a;像素72dpi。在设计的时候并不是每个尺寸都要做一套&#xff0c;尺寸按自己…...

湖北聚四方建设有限公司网站/南通企业网站制作

一、数组是什么&#xff1f; 数组就是用来存储 一批同类型数据 的 内存区域(可以理解成容器)。 例子&#xff1a; // 1.将整数&#xff1a;10&#xff0c;90&#xff0c;100&#xff0c;80&#xff0c;40 定义一个数组存起来 // 定义一个int类型的数组 arr int[] arr {10, 90…...

北京学校网站建设公司/百度关键词快排

PB ole insert object插入对象PB程序自动关闭解决方法如下&#xff1a;没错就是支付宝的问题32位操作系统:regsvr32 /u C:/windows/system32/aliedit/aliedit.dll64位操作系统regsvr32 /u C:/windows/SysWOW64/aliedit/aliedit.dllcmd后关闭PB再打开...

威海做网站whhl/最新新闻头条

在提交评论的时候&#xff0c;如何判断是一级评论还是二级评论&#xff08;因为都是通过一个文本域提交评论&#xff09;&#xff0c;思路&#xff1a;声明一个全局变量&#xff0c;如果是回复&#xff08;二级评论&#xff09;那么会触发点击回复事件&#xff0c;在这个事件的…...

网站编程技术有哪些/公司网站怎么建立

因果回路图&#xff08;也称为系统思维图&#xff09;用于从系统角度显示因果行为。鱼骨图可能引起影响问题的原因类别。因果循环显示了相互关系的原因及其影响。完成后&#xff0c;您将获得一个描述行为系统的正负增强图。因果循环的整洁之处在于它是去个性化。人们可以指向循…...