RESTful API
什么是RESTful:
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer 的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
RESTful API设计:
1,API与用户的通信协议:
总是使用HTTPs协议。
2,域名
https://api.example.com 尽量将API部署在专用域名
https://example.org/api/ API很简单
3,版本
1. 将版本信息放在URL中,如:https://api.example.com/v1/
2. 将版本信息放在请求头中。
4,路径
网络上任何东西都是资源,均使用名字表示(可复数)
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
5,method
GET:从服务器取出资源(一项或多项)
POST :在服务器新建一个资源
PUT :在服务器更新资源(客户端提供改变后的完整资源)
PATCH :在服务器更新资源(客户端提供改变的属性)
DELETE :从服务器删除资源
6,过滤:
通过在url上传参的形式传递搜索条件
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,状态码:
OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
NO CONTENT - [DELETE]:用户删除数据成功。
INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
错误处理:
状态码是4xx时,应返回错误信息,error当做key。
{ error: "Invalid API key" }
返回结果:
针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
Hypermedia API:
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
基于Django REST Framework实现RESTful API:
models:
from django.db import models # Create your models here. class Article(models.Model): # title = models.CharField(max_length=32,unique=True,error_messages={"unique":"文章标题不能重复"}) title = models.CharField(max_length=32,unique=True) # 文章发布时间 create_time = models.DateField(auto_now_add=True) # auto_now_add = True 把第一次创建的时间保存 # auto_now = True 每次更新的时候就会把该字段值保存为当前时间 type = models.SmallIntegerField( choices=((1,"原创"),(2,"转载")), default=1 ) # school 关联的外键 school = models.ForeignKey(to="School",on_delete=models.CASCADE) # tag 关联标签外键 tag = models.ManyToManyField(to="Tag") class School(models.Model): name = models.CharField(max_length=32) class Tag(models.Model): name = models.CharField(max_length=32) class Comment(models.Model): content = models.CharField(max_length=128) article = models.ForeignKey(to="Article",on_delete=models.CASCADE)
url:
from django.conf.urls import url,include from django.contrib import admin from app01 import app01_urls urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/', include(app01_urls)), ]
app01_urls:
from django.conf.urls import url from app01 import views urlpatterns = [ url(r"comment/",views.Comment.as_view()), url(r"article/$",views.Article.as_view()), url(r"school/(?P<id>\d+)",views.SchoolDetail.as_view(),name="school-detail"), url(r"article/(?P<pk>\d+)",views.ArticleDetail.as_view(),name="article-detail"), ]
views:
from django.shortcuts import render,HttpResponse from django.http import JsonResponse from app01 import models from rest_framework import serializers from django import views from rest_framework.views import APIView from rest_framework.response import Response from app01 import serializers as app01_serializers # Create your views here. # 评论CBV class Comment(APIView): def get(self, request): # print(request) res = {"code":1} print(self.request) # rest framework 封装好的request print(self.request.data) # 此时的request则是封装好后的 print(self.request._request.GET.get("age")) # self.request._request 则是Django原来的request对象 # 数据 all_comment = models.Comment.objects.all() # 序列化 ser_obj = app01_serializers.CommentSerializer(all_comment,many=True) res["data"] = ser_obj.data return Response(res) def post(self,request): res = {"code":0} # 去提交的数据 comment_data = self.request.data print(comment_data) # <QueryDict: {'age': ['17']}> 获取的数据是QueryDict类型 # 对用户提交的的数据做校验 ser_obj = app01_serializers.CommentSerializer(data=comment_data) if ser_obj.is_valid(): # 表示数据没有问题 ser_obj.save() # 创建数据 else: # 表示数据有问题 res["code"] = 0 res["data"] = ser_obj.errors # 将错误的提示信息传给res return Response(res) def put(self,request): print(self.request) print(self.request.data) return HttpResponse("修改评论") def delete(self,request): print(self.request) print(self.request.data) return HttpResponse("删除评论") # 文章CBV class Article(APIView): def get(self,request): res = {"code":1} all_article = models.Article.objects.all() ser_obj = app01_serializers.ArticleHyperLinkedSerializer(all_article,many=True,context={"request":request}) # ser_obj = app01_serializers.ArticleHyperLinkedSerializer(all_article,many=True) res["data"] = ser_obj.data print(ser_obj.data) return Response(res) def post(self,request): res = {"code":1} ser_obj = app01_serializers.ArticleModelSerializer(data=self.request.data) # 通过文章序列化数据得到 对象 if ser_obj.is_valid(): ser_obj.save() # 确认数据符合要求保存 else: res["code"] = 0 res["data"] = ser_obj.errors return Response(res) # 文章详情CBV class ArticleDetail(APIView): def get(self,request,pk): print(request) print(pk) res = {"code":1} article_obj = models.Article.objects.filter(pk=pk).first() # 序列化 ser_obj = app01_serializers.ArticleHyperLinkedSerializer(article_obj,context={"request":request}) # ser_obj = app01_serializers.ArticleHyperLinkedSerializer(article_obj) print(ser_obj.data) res["data"] = ser_obj.data return Response(res) class SchoolDetail(APIView): def get(self,request,id): res = {"code":1} school_obj = models.School.objects.filter(pk=id).first() print(111) ser_obj = app01_serializers.SchoolSerializer(school_obj,context={"request":request}) # ser_obj = app01_serializers.SchoolSerializer(school_obj) res["data"] = ser_obj.data return Response(res)
serializers:
from app01.models import Comment,Article,School from rest_framework import serializers from rest_framework.validators import ValidationError # 序列化评论的类 class CommentSerializer(serializers.ModelSerializer): # content = serializers.CharField(error_messages={"required":"内容不能为空"}) # article_detail = serializers.SerializerMethodField(read_only=True) # 在序列化类中自定义的字段:SerializerMethodField,在类中自定义字段。get_自定义字段名(self,obj)方法。 # read_only : 只在查询时才会显示相应的字段。 # def get_article_detail(self,obj): # print(obj.article.title) # 返回的值当作新字段的值 # return obj.article.title # 与forms组件的钩子函数一致,使用 validata_字段名 构成钩子函数。 def validate_content(self,value): if "somethings" in value: # 内容不符合要求,则直接终止程序抛出错误。 raise ValidationError("内容不合法") else: # 内容符合要求,则需直接返回原值即刻 return value # 全局钩子 def validate(self,value): # somethins code pass class Meta: model = Comment fields = "__all__" depth = 1 # 定义额外的参数 extra_kwargs = { "content": { "error_messages": { "required": '内容不能为空', } }, "article": { "error_messages": { "required": '文章不能为空' } } } # 文章的序列化类 class ArticleModelSerializer(serializers.ModelSerializer): # type = serializers.CharField(source="get_type_display") class Meta: model = Article # 绑定ORM类是哪一个 fields = "__all__" # depth = 1 # 定义额外的参数 extra_kwargs = { "type": { "error_messages": { "required": "类型不能为空" } }, "title": { "error_messages": { "required": "标题不能为空" } }, } # 文章超链接序列化 class ArticleHyperLinkedSerializer(serializers.HyperlinkedModelSerializer): school = serializers.HyperlinkedIdentityField(view_name="school-detail",lookup_url_kwarg="id") type = serializers.CharField(source="get_type_display") # lookup_url_kwarg : 期望寻找的url参数是 id(路径传过来的参数值). # view_name:若是不写这个属性,则默认寻找的是url对应的别名:school-detail # 若是写则需要值对应别名的值。 class Meta: model = Article fields = ["id","title","type","school",] # 学校的序列化 class SchoolSerializer(serializers.ModelSerializer): class Meta: model = School fields = "__all__"
超链接的序列化:
1,超链接字段的三个参数:
view_name: 默认使用 表名-detail ,可以设置为路径的别名。
lookup_url_kwarg: 默认使用pk,指的是反向生成URL时,路由中分组命名匹配的key.
lookpu_field : 默认使用的pk,指的是反向生成URL的时候,路由中分组命名匹配的value.
2,想要生成完整的超链接API,在序列化的时候要传入参数:context:{"request":request}.
上面生成的是绝对路径,若是想生成相对路径:context:{"request":None }