一.Web应用两种开发模式
1、前后端不分离模式
也叫前后端混合开发模式, 需要后端写模板语言(DTL), 返回的是HTML页面,比如有BBS项目,图书管理系统。
在前后端不分离的项目中,模板渲染通常是在后端完成的。这种项目结构中,后端负责处理业务逻辑、与数据库交互,并最终生成 HTML 页面,将数据填充到模板中,然后将完整的 HTML 页面发送给浏览器进行渲染。
2、前后端不分离
从程序角度来看:
- 1个Django程序,接收请求+处理+HTML模版+用户返回
- 2个程序:
- 前端:vue.js/react.js/angular.js
- 后端:Django + DjangoRestFramework
从专业度角度来看:
- 前端,专门写前端代码 + 部署 + 版本管理 + ajax请求获取
- 后端,API接口文档,只专注于写后端接口, 返回 json, xml格式数据
在前后端分离的项目中,模板渲染通常是在前端完成。在这种项目结构下,后端主要负责提供数据接口(通常是 JSON 格式),而前端则负责将这些数据获取并渲染到页面上。
二、什么是drf?作用是什么
1、简介
**DRF ( Django REST framework)**是一个用于构建 Web API 的强大且灵活的工具包,基于 Django Web 框架。DRF 提供了一组用于快速开发 Web API 的工具和功能,使得构建 RESTful API 变得简单和高效。也使后端开发人员更方便的编写API接口。
DRF 的设计理念是使构建 Web API 变得简单、快速且易于维护。它与 Django 框架紧密集成,为开发者提供了丰富的功能和工具,使得构建强大的 RESTful API 成为可能。
2、作用
-
构建 RESTful API:DRF 提供了丰富的工具和功能,使开发者能够轻松地构建符合 REST 架构风格的 Web API。通过使用 DRF,开发者可以快速地创建支持标准 HTTP 方法(如 GET、POST、PUT、DELETE 等)的 API。
-
序列化和反序列化:DRF 提供了序列化器(Serializers),用于处理数据的序列化和反序列化操作。这使得在 API 请求和响应中处理复杂的数据结构变得更加简单和高效。
-
认证和权限控制:DRF 提供了灵活的认证和权限控制系统,开发者可以轻松地实现基于 token、session、OAuth 等各种认证方式,并定义细粒度的权限控制规则。
-
视图类:DRF 提供了多种视图类,包括基于类的视图(Class-based Views)和函数视图(Function-based Views),以及通用视图类(Generic Views),帮助开发者快速构建 API 视图。
-
路由:DRF 提供了简单而强大的路由系统,可以方便地映射 URL 到视图函数或类,从而定义 API 的端点。
-
文档生成:DRF 集成了强大的 API 文档生成工具,可以自动生成交互式的 API 文档,帮助开发者和用户了解 API 的结构、参数和用法。
三、API接口
1、什么是API接口?
API(Application Programming Interface)接口是一组定义和描述不同软件系统之间如何互相通信的规则集合。在软件开发中,API 接口允许不同的应用程序之间共享数据和功能,从而实现更高层次的集成和互操作性。
API 接口可以是不同形式的,其中最常见的是 Web API,它通过网络(通常是通过 HTTP 协议)暴露服务,并允许其他应用程序通过发送请求和接收响应的方式与之交互。
通俗一点来说,web后端提供给前端可以调用的访问拿到数据的东西接口可以称之为API接口。API接口是前后端交互的媒介。
2、API接口应该有哪些东西?
Web API接口和一般的url链接还是有区别的,Web API接口简单概括有下面四大特点:
- url地址:长得像返回数据的url链接
- https://api.map.baidu.com/place/v2/search
- 请求方式:get、post、put、patch、delete
- 采用get方式请求上方接口
- 请求参数:Json格式或者XML格式的key-value类型数据
- GET请求数据:地址栏中的数据----->request.GET
- 请求体中数据:reqeust.POST’
- 早些年 前后端交互使用XML格式,ajax其实就是异步JavaScript和XML
- 后来 随着Json格式的出现,乃至今日都是主流
- 未来 可能会有更高效、安全的交互格式会替代目前的Json格式
- 响应结果:Json格式或者XML格式的数据
3、补充 Json格式与XML格式的区别
JSON(JavaScript Object Notation)和 XML(eXtensible Markup Language)都是用于在不同系统之间交换数据的常见格式,它们有以下几点区别:
-
语法:
- JSON:JSON 是一种轻量级的数据交换格式,采用键值对的方式表示数据。数据以键值对的形式存储,使用大括号
{}
表示对象,使用方括号[]
表示数组。 - XML:XML 是一种标记语言,使用标签(tag)来描述数据结构。XML 使用尖括号
<>
包围标签,标签可以嵌套表示复杂的数据结构。
- JSON:JSON 是一种轻量级的数据交换格式,采用键值对的方式表示数据。数据以键值对的形式存储,使用大括号
-
可读性:
- JSON:JSON 的语法相对简洁,易于阅读和编写。对于人类来说更易于理解。
- XML:XML 的语法相对繁琐,标签较多,使得文件相对冗长。但由于标签的结构清晰,有时也更易于理解复杂的数据结构。
-
数据体积:
- JSON:由于 JSON 的语法相对简洁,通常比 XML 更紧凑,数据体积更小,传输效率更高。
- XML:XML 的冗长标签和结构使得数据文件相对较大,传输效率相对较低。
-
数据类型:
- JSON:JSON 支持基本数据类型(字符串、数字、布尔值、数组、对象)以及 null 值。对于简单的数据结构和前后端交互,JSON 更为常用。
- XML:XML 可以描述更复杂的数据结构,并支持自定义数据类型和命名空间。在某些情况下,如配置文件或复杂的数据交换,XML 可能更适用。
-
解析和处理:
- JSON:JSON 的解析速度比 XML 更快,因为 JSON 的结构更简单。
- XML:XML 的解析相对复杂,需要更多的处理步骤,解析速度比 JSON 慢。
总的来说,JSON 更适用于简单数据结构和前后端交互,而 XML 更适用于描述复杂数据结构和传输带有元数据的文档。在实际应用中,可以根据具体需求选择合适的格式。
四、API接口测试工具:Postman
1、安装
2、使用
**① 😗*可以切换请求方法:post,put,get等
**② params:参数:**设置一些请求的参数的地方,通常适用于一些get的没有请求体的请求
**③ authorization:**鉴权:有些请求需要验证身份,采取不同的鉴权方式,携带内容,验证你的身份(和token的作用类似)
④ headers:请求头:携带请求的一些必要信息例如:Content-Type和token等
**⑤ body:**请求体:
-
none:没有请求体
-
form-data:表单格式的内容,即可上传键值对,又可以上传文件,选择不同的内容请求头的Content-Type对应值不同 x-www-form-urlencoded:只可以上传键值对
-
raw:原始数据(有text,javascript,json,html,xml)等不同格式的数据,按接口要求上传数据 选择text,则请求头是: text/plain
-
选择javascript,则请求头是: application/javascript
-
选择json,则请求头是: application/json (如果想以json格式传参,就用raw+json就行了)
-
选择html,则请求头是: text/html
-
选择application/xml,则请求头是: application/xml
-
binary:相当于Content-Type:application/octet-stream,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件
-
GraphQL:数据类型对参数具体的类型会有一定的标准
-
Cookies:验证身份的一种方式
⑥ Pre-request Script:预请求脚本也叫做前置脚本,在请求发生之前先发生的脚本,请求中需要用的一些内容,可太勇敢前置脚本获得
⑦ tests:后置脚本,请求之后发生的一些脚本,此接口的响应有一些其他接口用的内容,可通过tests传递出去
可以设置获取变量也可以设置断言方法
⑧ settings:设置:设置这个请求需要的一些条件,比如证书,请求头设置,等,根据需要设置
- body:响应体
-
Pretty:响应展示的视图格式化显示:JSON,XML,HTML,Text,Auto这几种类型的响应
-
Raw:响应的原始文本,未格式化的
-
Preview:视图:一些html的响应方便查看
-
Visualize:可视化:一些图片验证码的响应就可以在此处查看
-
- cookies:当响应中有cookies时会在这里显示
- headers:响应头:展示响应头的信息
- test results:测试结果:当在请求中有断言的时候,测试的结果会在此处展示
3、补充 body的编码格式
- get 请求,可以在地址栏中带数据—》能不能在body体中带数据—》能!
- -注意:可以提交到后端,但是django框架没有放在request.POST中,放在request.body中
- urlencoded 编码格式—》请求体中–》name=%E5%BD%AD%E4%BA%8E%E6%99%8F&age=38
- form-data格式:携带文件和数据
- 文件:request.FILES.get(‘myfile’)
- 数据:request.POST.get()
- request.body 能不能用,取决于文件大小,一般不用打印body(会报错)
- 因为http是基于tcp的协议,而tcp是流式协议,一次性发达文件会报错
- json格式
- request.POST 是没有数据的
- request.body 中有 —> 需要自己在后端转成自己需要的格式数据
4、postman创建文件,导入和导出
- 这些操作需要我们先登录才能操作,所以记得注册一个账号,反正免费
- 创建完请求之后一定要记得保存
五、RESTful API规范
RESTful API 是一种基于 REST 架构风格设计的 API,它遵循一组约定和规范,以提供统一的接口,使得不同系统之间能够更轻松地进行通信和交互。以下是设计 RESTful API 时应遵循的一些规范:
1、数据的安全保障
- url链接一般都采用https协议进行传输
- 注:采用https协议,可以提高数据交互过程中的安全性
2、url地址中带接口标识
- https://api.baidu.com
- https://www.baidu.com/api/
3、url中带版本标识
- 接口有版本
- https://api.baidu.com/v1/login
- https://api.baidu.com/v2/login
4、数据即是资源,均使用名词(可复数)
- 以后所有接口路径中,尽量不出现动词
- 接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
- https://api.baidu.com/v1/users
- https://api.baidu.com/v1/books
- https://api.baidu.com/v1/books/3
5、请求方式决定如何操作资源【增,删,改,查:curd】
-
https://api.baidu.com/books - get请求:获取所有书
-
https://api.baidu.com/books/1 - get请求:获取主键为1的书
-
https://api.baidu.com/books - post请求:新增一本书
-
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
-
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6、url中带搜索条件
-
当我们发送请求需要对应参数时直接在对应请求url后携带即可。只针对于查询所有
-
https://api.baidu.com/v1/user?username=bbs123&user_id=1
7、响应中带状态码
- http响应状态码
- 1xx :请求正在处理 --> 客户端看不到
- 2xx :正常响应—> 经常看到的有
- 200:请求成功
- 201:创建成功
- 3xx :重定向—》偶尔看到 301 和 302
- 4xx :客户端异常—》403: 404
- 5xx: 服务端错误 500
- 有趣的状态码记忆方法:
- https://www.sohu.com/a/278045231_120014184
- 自定制状态码
- 例如:mysql–> 连接连不上–> 有个错误提示信息:错误码,错误描述
- 我们也可以自己定制,但是一定要把错误提示码对应的错误消息公布出来让别人知道
8、响应中带信息描述(错误,正常)
- 例如:code,message
- { code: 1000, message: 成功}
9、针对不同操作符合以下规范
-
统一规范的错误响应格式,包括错误码、错误信息和可能的解决方案。客户端可以根据错误信息快速定位问题所在。
-
比如:
-
GET /collection:返回资源对象的列表(数组)
- {code:100,msg:成功,results:[{},{},{}]}
-
GET /collection/resource:返回单个资源对象 {}
- {code:100,msg:成功,result:{}}
-
POST /collection:返回新生成的资源对象 {}
-
PUT /collection/resource:返回完整的资源对象 {}
-
DELETE /collection/resource:返回一个空文档
- {code:100,msg:删除成功}
10、返回数据中带链接
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
六、序列化与反序列化
api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,序列化可以分两个阶段:【序列化值的是转换数据格式:序列化,反序列化】
1、序列化
序列化: 把我们识别的数据转换成指定的格式提供给别人
站在python后端来看:把python的对象【字典,列表,对象】—> 转成json/xml格式字符串过程称之为序列化。
从数据库取出来—> 给前端 也可以叫序列化
例如:我们在django中获取到的数据默认是模型对象(qs/单个对象),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
2、反序列化
反序列化:把别人提供的数据转换/还原成我们需要的格式。
前端给我们的数据----> 我们保存到数据库中 也可以叫反序列化
例如:前端js提供过来的json/xml数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
3、js中的序列化和反序列化
js 如何把对象序列化成字符串:【JSON.stringify()】,把字符串反序列化成对象:【JSON.parse()】
4、小结
- 序列化:
- drf称为 read(读取数据)
- queryset — > json
- 返给前端
- 反序列化:
- drf称为 write(写入数据)
- 字符串 — > json
- 接收前端的数据
七、初步编写API
1、基于drf实现
(1)安装
pip install djangorestframework
(2)注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework'
]
(3)返回数据界面
返回的是数据 + 嵌套好看的页面
路由
path('login/',views.login)
path('info/',views.InfoView.as_view())
视图
- FBV
from rest_framework.response import Response
from rest_framework.decorators import api_view
@api_view(["GET"])
def login(request):
return Response({'status':True,'message':"success"})
- CBV
from rest_framework.response import Response
from rest_framework.views import APIView
class InfoView(APIView):
def get(self,request):
Response({'status': True, 'message': "success"})
八、DRF快速使用
1、安装DRF
- 使用pip包管理器,在终端中运行以下命令来安装DRF
pip install djangorestframework
- 最新的drf,必须django 3.x以上,配置MySQL3.8版本以上
2、注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework'
]
3、创建表
import uuid
from django.db import models
class Task(models.Model):
task_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
task_name = models.CharField(max_length=255, db_index=True)
task_time = models.DateTimeField(auto_now_add=True)
task_desc = models.TextField()
4、创建序列化器(Serializer):
- 序列化器是DRF中一个重要的概念,它将Python对象转换为JSON等可被传输的格式,并可以反序列化接收到的数据。
- 在你的应用程序中创建一个名为
serializers.py
的文件,并定义你的序列化器类。 - 一个示例:
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
- 在这个示例中,我们使用
ModelSerializer
来自动创建序列化器类。
5、创建视图(View)
- 在你的应用程序中创建一个名为
views.py
的文件,并定义视图类。 - 一个示例:
from .models import Task
from .serializer import TaskSerializer
from rest_framework.viewsets import ModelViewSet
class TaskView(ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
6、配置URL路由
- 在你的应用程序的
urls.py
文件中,定义DRF的URL路由。 - 一个示例:
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('task', TaskView, 'task')
urlpatterns = [
]
urlpatterns += router.urls
- 通过上述配置,当访问
127.0.0.1:8000/task/
时,将会调用TaskView
视图。
7、测试代码
import requests
# res = requests.get('http://127.0.0.1:8000/app01/task/')
# print(res.text)
from threading import Thread
# 多线程新增数据
def task(task_id, task_name):
data = {
"task_id": task_id,
"task_name": task_name,
"task_time": "2024-04-10 11:54:56",
"task_desc": "很重要,不能忘记"
}
res = requests.post('http://127.0.0.1:8000/app01/task/', json=data)
print(res.text)
if __name__ == '__main__':
l = []
for i in range(30):
t = Thread(target=task, args=['asdfads-33-as-%s' % i, '任务00%s' % i])
t.start()
l.append(t)
for i in l:
i.join()
九、CBV源码剖析
(1)cbv写法:
- 视图中写视图类,继承View,写跟请求方式同名的方法
class TaskView(View):
def get(self,request):
return 四件套
- 在路径用写
path('task/', TaskView.as_view())
- 如上写法,为什么能够执行
(2)前置条件
- 前端请求,一旦路径匹配成功,就会执行
- TaskView.as_view()(request传入,)
- 入口在
- TaskView.as_view()—>执行结果—》View中有个as_view类的绑定方法
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
res=self.dispatch(request, *args, **kwargs)
return res
return view
- 执行结果是view 的内存地址: 请求来了,执行view(request)
path('books/', view)
- 执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
self.dispatch(request, *args, **kwargs)
- self是谁的对象?
- BookView的对象
- 去BookView中dispatch,找不到
- 去父类,View中找到了
- View这个类的dispatch
def dispatch(self, request, *args, **kwargs):
# 取出请求方式,转成小写,判断在不在列表中 get请求在
if request.method.lower() in self.http_method_names:
# handler=getattr(BookView的对象,'get')
# handler就是BookView类中的get方法
handler = getattr(self, request.method.lower())
else:
handler = self.http_method_not_allowed
# 执行 BookView类中的get方法 (request)
return handler(request, *args, **kwargs)
- 最终本质跟写fbv的执行流程一样
(3)最终结论
- 什么请求方式,就会执行视图类中的什么方法
# 1 在路由中:path('index/',IndexView.as_view())
# 2 请求来了---》路由匹配成功--》执行 IndexView.as_view()(request)
-看View类的as_view的返回结果[可以加括号执行]---》猜:函数内存地址
# 3 View类的as_view
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # cls--》IndexView
return self.dispatch(request, *args, **kwargs)
return view
# 4 IndexView.as_view() 本质就是 --》view--》内存函数view
# 5 IndexView.as_view()(request)---》view(request)
# 6 本质 执行 self.dispatch--》self 是对象--》谁的对象?视图类的对象IndexView类的对象
return self.dispatch(request, *args, **kwargs)
# 7 IndexView没有dispatch--》View类中的dispatch
# request 当次请求的requets
def dispatch(self, request, *args, **kwargs):
#1 取出请求方式,转成小写,判断在不在列表中 get请求在
#2 self.http_method_names
if request.method.lower() in self.http_method_names:
# 3 反射:去 self IndexView类的对象中通过字符串get找属性或方法
# 找到了get方法,赋值给了handler
handler = getattr(self, request.method.lower())
else:
handler = self.http_method_not_allowed
# 4 执行handler--》本质是get(request)
# 执行了IndexView类中的get方法--》把request传进去了
return handler(request, *args, **kwargs)
十、基于APIView的5个接口使用
1、视图类
from rest_framework.response import Response
from rest_framework.views import APIView # # APIView继承了djagno原来的View
from app01.serializer import TaskSerializer
from app01.models import Task
class TaskView(APIView):
def get(self, request):
tasks = Task.objects.all()
# drf提供了序列化类
serialized_tasks = TaskSerializer(instance=tasks, many=True) # 序列化
return Response({'code': 100, 'msg': '查询所有成功', 'results': serialized_tasks.data})
def post(self, request):
serialized_task = TaskSerializer(data=request.data) # 反序列化
if serialized_task.is_valid(): # 数据校验---》有些不合法的禁止
serialized_task.save() # 保存到数据库中
return Response({'code': 100, 'msg': '创建成功'})
class TaskDetailView(APIView):
def get(self, request, *args, **kwargs):
try:
task = Task.objects.filter(id=kwargs['id']).first()
serialized_task = TaskSerializer(instance=task, many=False) # 序列化
return Response({'code': 100, 'msg': '查询单个成功', 'result': serialized_task.data})
except Exception:
return Response({'code': 200, 'msg': '任务不存在'})
def put(self, request, *args, **kwargs):
try:
task = Task.objects.filter(id=kwargs['id']).first()
serialized_task = TaskSerializer(instance=task, data=request.data) # 反序列化
if serialized_task.is_valid(): # 数据校验---》有些不合法的禁止
task.save() # 重保存到数据库中
return Response({'code': 100, 'msg': '更新成功'})
except Exception:
return Response({'code': 200, 'msg': '任务不存在'})
def delete(self, request, *args, **kwargs):
try:
Task.objects.filter(id=kwargs['id']).delete()
return Response({'code': 100, 'msg': '删除成功'})
except Task.DoesNotExist:
return Response({'code': 200, 'msg': '任务不存在'})
2、序列化类
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
3、模型类
import uuid
from django.db import models
class UserLog(models.Model):
ip = models.CharField(max_length=255)
visit_time = models.DateTimeField(auto_now=True)
method = models.CharField(max_length=32)
path = models.CharField(max_length=64)
user_agent = models.CharField(max_length=255)
class Task(models.Model):
task_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
task_name = models.CharField(max_length=255, db_index=True)
task_time = models.DateTimeField(auto_now_add=True)
task_desc = models.TextField()
4、路由
from django.urls import path
from app01 import views
urlpatterns = [
path('task/', views.TaskView.as_view()),
path('task/<int:id>', views.TaskDetailView.as_view()),
]