-后端人员,写后端,也要写【模板语法】---》xx.html的python代码 -全栈开发-->前后端混合时代,比较多
-后端人员,一点前端都不需要动,只需要写接口即可 -全栈开发---》web后端,前端框架(vue,react)
在开发Web应用中,有两种应用模式:
1.前后端不分离
客户端看到的内容和所有界面效果都是由服务端提供出来的
2.前后端分离【把前端的界面效果(html
,css
,js
分离到另一个服务端,python服务端只需要返回数据即可)】
前端形成一个独立的网站,服务端构成一个独立的网站
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本【前后端合作】
API概念
API:应用程序接口(API:Application Program Interface
)
应用程序接口(API:application programming interface)是一组定义、程序及协议的集合,通过 API 接口实现计算机软件之间的相互通信。API 的一个主要功能是提供通用功能集。程序员通过使用 API 函数开发应用程序,从而可以避免编写无用程序,以减轻编程任务
简单来说就是通过网络,规定了前后端信息交互规定的URL链接,也就是前后端信息交互的媒介
http://127.0.0.1/books/ 点赞,点踩 就是API接口
URL:长得像返回数据的URL链接
https://api.map.baidu.com/place/v2/search
请求方式:get
、post
、put
、patch
、delete
采用get
方式请求上方接口
请求参数:json
或xml
格式的key-value类型数据ak
:6E823f587c95f0148c19993539b99295
region:上海
query:肯德基
output:json
-早些年 前后端交互使用xml格式----》ajax:异步JavaScript和XML -后来,随着json格式的出现,成了主流,直到现在 -以后:一定会出现,比json更高效的交互格式,更安全
# xml格式 https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=xml '''中文会变成上面的样式,上下两个url是一样的意思''' https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=上海&query=肯德基&output=xml #json格式 https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=json https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=上海&query=肯德基&output=json '''下方是返回的值''' { "status":0, "message":"ok", "results":[ { "name":"肯德基(罗餐厅)", "location":{ "lat":31.415354, "lng":121.357339 }, "address":"月罗路2380号", "province":"上海市", "city":"上海市", "area":"宝山区", "street_id":"339ed41ae1d6dc320a5cb37c", "telephone":"(021)56761006", "detail":1, "uid":"339ed41ae1d6dc320a5cb37c" } ... ] }
作为后端,写好了接口,我们自己要测试通过,再给别人用。
而后端测试人员需要测试API接口的时候,总不可能在浏览器里面试用吧,所以有了专业的接口测试工具Postman
-浏览器只能发送get请求,不能自动发送post,delete请求 -postman---》开源软件,只是谷歌浏览器的插件,越做好好,后来可以按装到操作系统上,再后来,收费 -postwoman -很多很多其他的,不同公司用的也可能不一样,你只需要明白一个点,这个工具只是用来发送http请求
1、官方下载
https://www.postman.com/downloads/?utm_source=postman-home----》Postman-win64-Setup.exe
2、安装时,双击即可,马上就会出现下图界面
3、使用讲解
打开一个新的测试页面后界面如下,返回数据的窗口在没有发送请求之前是空的。
点击界面中间的Body,可以选择发送请求的方式,需要注意在使用form-data请求方式,即用form表单传输文件的时候,需要手动选择传输的数据类型
这里返回的结果
我们需要再settings.py
文件中注册rest_framework
才能在浏览器中进行接口测试
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', ]
概念
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中
RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口
十条规范
-1 数据的安全保障,通常使用https(http+ssl/tsl)协议 -url链接一般都采用https协议进行传输 -采用https协议,可以提高数据交互过程中的安全性 -2 接口中带api标识 -https://api.lqz.com/books(可以写在域名中) -https://www.lqz.com/api/books(也可以写在路由中) 咱们用这个 -3 多版本共存,路径中带版本信息 -https://api.lqz.com/v1/login -https://api.lqz.com/v2/login -https://www.lqz.com/api/v1/login -https://www.lqz.com/api/v2/login -4 数据即是资源,均使用名词,尽量不出现动词(最核心的) -接口一般都是用于完成前后台数据的交互,交互的数据我们称之为资源 -接口形式如下 https://api.baidu.com/users https://api.baidu.com/books -特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义、 https://api.baidu.com/login -5 资源操作由请求方式决定(method) -操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作 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 - patch请求:修改主键为1的书(局部修改一条) 'ps:patch用的不多,一般用put即可' https://api.baidu.com/books/1 - delete请求:删除主键为1的书(删除一条) -6 在请求地址中带过滤条件 https://api.example.com/v1/zoos?limit=10:指定返回记录的数量 https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置 https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数 https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序 https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件 -7 响应中状态码:两套 -http响应状态码:1xx:请求正在处理,2xx:成功响应,3xx:重定向,4xx:客户端错误,5xx:服务端错误 '常见的需要记住' 响应状态码2XX 200:常规请求 201:创建成功 响应状态码3XX 301:永久重定向 302:暂时重定向 响应状态码4XX 403:请求无权限 404:请求路径不存在 405:请求方法不存在 响应状态码5XX 500:服务器异常 -https://blog.csdn.net/li_chunlong/article/details/120787872 -公司内部规定的响应状态码,放在响应体中 {code:0} 咱们后期一般使用100 101 102这种 -8 返回数据中带错误信息 { code:0 msg: "ok/用户名错误" } -9 返回的结果应该符合以下规范---》好多公司不遵循这个 GET 获取所有数据:返回资源对象的列表(数组)[{name:红楼梦,price:99},{name:红楼梦,price:99},{name:红楼梦,price:99}] GET 单个对象:返回单个资源对象:{name:红楼梦,price:99} POST 新增对象:返回新生成的资源对象:{name:西游记,price:99} PUT 修改对象:返回完整的资源对象 :{name:西游记,price:100} DELETE 删除:返回一个空文档 -10 响应数据中带连接 # Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么 { "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "img": "https://image.baidu.com/kfc/001.png" } ... ] } 比较好的接口返回 # 响应数据要有状态码、状态信息以及数据本身 { "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "location":{ "lat":31.415354, "lng":121.357339 }, "address":"月罗路2380号", "province":"上海市", "city":"上海市", "area":"宝山区", "street_id":"339ed41ae1d6dc320a5cb37c", "telephone":"(021)56761006", "detail":1, "uid":"339ed41ae1d6dc320a5cb37c" } ... ] }
api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把【数据转换格式】,序列化可以分两个阶段:
序列化: 把我们识别的数据转换成指定的格式提供给别人
-字典,列表------》json格式存到文件中了 -例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人 -read
反序列化:把别人提供的数据转换/还原成我们需要的格式
-例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中。 -write
# 以后写的接口,基本上都是五个接口及其变形 -查询所有 -查询单个 -新增一个 -修改一个 -删除一个 # 基于books单表为例,写五个接口 -创建book表 -表迁移 -录入假数据:直接录,后台管理录 -写查询所有接口----》遵循restful规范,使用cbv -新增一条数据-----》 -查询一条 -修改一条:put提交的数据,不能从request.POST中取 -删除一条
models.py
form django.db import models class Book(models.Model): name = models.CharField(max_length=32) price = models.CharField(max_length=32) publish = models.CharField(max_length=32)
urls.py
urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/books/', views.BookView.as_view()), path('api/v1/books/<int:pk>/', views.BookDetailView.as_view()), ]
views.py
from django.shortcuts import render,HttpResponse,redirect from django.http import JsonResponse from .models import Book # 相对导入 from django.views import View import json class BookView(View): # 查询所有 def get(self, request): # 查询出所有的图书,queryset对象,不能直接给前端 books = Book.objects.all() # 转成json格式,给前端 # 把quer对象转成列表,然后再使用JsonResponse book_list = [] for book in books: book_list.append({'name': book.name, 'price': book.price, 'publish': book.publish}) # 扩展做了解 # return HtpResponse(json.dumps(book_list, ensure_ascii=False)) # 指定ensure_ascii为false,前端就显示中文了 return JsonResponse(book_list, safe=False, json_dumps_params={'ensure_ascii': False}) # JsonRsponse只能放字典或列表 # 薪增一个(只能使用urlencoded或form-data编码,使用json形式编码不行,因为json格式编码提交的数据,不能从request.POST中取,从body中) def post(self, request): # 取出前端传入的数据 name = request.POST.get('name') price = reqeust.POST.get('price') publish = request.POST.get('publish') # 存到数据库中 book = Book.objects.create(name=name, price=price, publish=publish) # 返回新增的对象字典 return JsonResponse({'name': book.name, 'price': book.price, 'publish': book.publish}) class BookDetailView(View): def get(self, request, pk): book = Book.objectsfilter(pk=pk).first() # book模型对象转成字典,使用JsonResponse返回 return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish}) def put(self, request, pk): # request.POST只能取post提交的urlencoded或form-data编码数据,put提交的取不到 # 查到要改的 book = Book.objects.filter(pk=pk).first() # 取出前端传入的数据,修改完,保存----》存在问题,因为put提交的取不到 # book.name = request.POST.get('name') # book.price = request.POST.get('price') # book.publish = request.POST.get('publish') # book.save() # 前端使用json格式提交,自己保存 print(request.body) book_dict=json.loads(request.body) book.name = book_dict.get('name') book.price = book_dict.get('price') book_publish = book_dict.get('publish') book.save() return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish}) def delete(self, request, pk): Book.objects.filter(pk=pk).delete() return JsonResponse(data={})
核心思想: 缩减编写api接口的代码
Django REST framework
是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。
官方文档:https://www.django-rest-framework.org/
github: https://github.com/encode/django-rest-framework/tree/master
# djangorestframework: drf 帮助我们快速的实现符合restful规范的接口 # django 最新 4.x ,一般都会用最新版的上一版3.x # drf最新支持到djagno 3.x ,最新不支持2.x # 安装drf pip3 install djangorestframework -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com/simple/ # 由于你是django2.x 它发现它不支持,它会自动写在dajgno,安装最新的django 4.x
注意:url的末尾必须要写斜杠符号,不然会报错
urls.py
from app01.views import BookView from rest_framework.routers import SimpleRouter router = SimpleRouter router = SimpleRouter() router.register('books', BookView, 'books') urlpatterns = [ path('admin/', admin.site.urls), ] # 两个列表相加 [1,2,4] + [6,7,8]= urlpatterns += router.urls
views.py
form .serializer import BookSerializer from rest_framework.viewsets import ModelViewSet class BookView(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
serializer.py
from rest_framework import serializers form .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = '__all__'
urls.py
urlpatterns = [ path('books/', BookView.as_view()), path('books/<int:pk>/', BookDetailView.as_view()), ]
views.py
form rest_framework.views import APIView # APIView继承了Django原来的View from .serializer import BookSerializer from rest_framework.response import Response class BookView(APIView): # 查询所有 def get(self, request): book_list = Book.objects.all() # drf提供了序列化类 ser = BookSerializer(instance=book_list, many=True) # 序列化 return Response({'code': 100, 'msg': '成功', 'result': ser.data}) def post(self, request): ser = BookSerializer(data=request.data) # 反序列化 if ser.is_valid(): # 数据校验---》有些不合法禁止 ser.save() # 保存到数据库中 return Response({'code': 100, 'msg': '成功'}) class BookDetailView(APIView): # # 查询单条 def get(self, request, pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer(instance=book,many=False) # 序列化 return Response({'code': 100, 'msg': '成功', 'result': ser.data}) # 修改一条 def put(self, request, pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer(instance=book,data=request.data) # 反序列化 if ser.is_valid(): # 数据校验---》有些不合法禁止 ser.save() # 保存到数据库中 return Response({'code': 100, 'msg': '成功'}) def delete(self, request, pk): Book.objects.filter(pk=pk).delete() return Response({'code': 100, 'msg': '删除成功'})
serializer.py
from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = '__all__'
# cbv写法: 1 视图中写视图类,继承View,写跟请求方式同名的方法 class BookView(View): def get(self,request): return 四件套 2 在路径用写 path('books/', BookView.as_view()) # 如上写法,为什么能够执行 # 前置条件:前端请求,一旦路径匹配成功,就会执行 BookView.as_view()(request传入,) # 入口在 BookView.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): # request.method.lower() 如果是get请求, ‘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的执行流程一样 # 最终结论:什么请求方式,就会执行视图类中的什么方法 -本身请求来了,匹配成功,会执行view(request) def view(request, *args, **kwargs): return self.dispatch(request, *args, **kwargs) -self.dispatch View类的方法 def dispatch(self, request, *args, **kwargs): # request.method请求方式转成小写,必须在列表中才能往下走 if request.method.lower() in self.http_method_names: # 反射,去self【视图类的对象:BookView】,去通过get字符串,反射出属性或方法 # BookView的get方法 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed # BookView的get方法,加括号,传入request return hander(request, *args, **kwargs)
urls.py
path('books/', views.BookView.as_view()),---》请求来了,执行views.BookView.as_view()()--->现在as_view是APIView的as_view(因为我们把父类从View换成了APIView)
我们点进源码查看可以发现APIView其实继承了View
class APIView(View):
往下查找as_view()的代码
# APIView的as_view方法:view还是原来的view,但是以后再也没有csrf认证了 @classmethod def as_view(cls, **initkwargs): # 调用父类的as_view,父类是Django原生view # 把Django原生view的as_view方法中的闭包函数view拿出来了 view = super().as_view(**initkwargs) # csrf_exempt 排除所有csrf的认证 # 相当于再所有的方法上面加了这个装饰器 return csrf_exempt(view) '从他内部的view我们可以看到他是用派生方法调用父类View中的as_view因此我们说view还是原来的view,但是返回的结果是csrf_exempt(view),回顾装饰器的知识,我们可以知道虽然把装饰器写成了语法糖,但是在运行的时候其实就是把被装饰的函数名称传进来当参数运行,因此这里就是取消了csrf认证的作用' # 路由匹配成功,执行的其实就是csrf_exempt(view)(request)--->而这个view就是View的as_view中的闭包函数view---》然后执行self.dispatch--->self是视图类的对象---》BookView产生的对象---》这时候会执行self.dispatch,根据名称的查找顺序他会查找到APIView的dispatch: def dispatch(self, request, *args, **kwargs): # 这里的参数中的request是Django原生request,老的request # 把老的request包装成了新的request,这个是drf提供的Request类的对象 request = self.initialize_request(request, *args, **kwargs): # 到此以后,这个request就是新的了,老的request在request._request这是老的 # 把新的request放到了self对象【BookView的对象】 self.request = reqeust try: # 执行了三大认证【认证、频率、权限】,使用新的request,之后会将 self.initial(request, *args, **kwargs) # 跟之前一模一样用反射获取执行结果,如果报错就用异常捕获处理 if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed # 把新的request传入了,视图类的方法中get的request也是新的 response = handler(request, *atgs, **kwargs) except Exception as exc: # 在执行3大认证和视图类中方法的过程中,如果出了异常,都能捕获到---》全局异常捕获 response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response '上方的request调用了initialize_request方法,这个方法的返回结果产生了一个Request生成的对象,它的源码如下:' def initialize_request(self, request, *args, **kwargs): parser_context = self.get_parser_context(request) return Request( reqeust, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context ) '接着我们点进这个Request类的源码,我们会发现老的request变成了_request属性' def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): self.request = request self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty # 总结:APIView的执行流程 1.去除了所以的csrf 2.包装了新的request,以后在视图类中用的request Request类的对象,不是原生的了 -原生的在:新的request._request 3.在执行视图类的方法之前,执行了三大认证 4.如果三大认证或视图函数方法执行过程中出了错,会有异常捕获---》全局异常捕获 5.以后视图类方法中的request都是新的 # 补充:装饰器的基本原理 def auth() # 装饰器 def add() # 函数 # 使用auth装饰add函数 @auth # 本质是 add=auth(add) def add() # 以后再使用add,其实就是在使用 auth(add) 的返回结果