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 }

posted @ 2018-08-01 21:59  Qingqiu_Gu  阅读(241)  评论(0编辑  收藏  举报