• django的DRF(三)


    一、基本概念

    DRF(Django Rest Framework)是可以快速基于Restful开发得Django应用得插件,功能非常多,被广泛应用。
    所谓Restful风格就是不在用户请求的URL当中说明 “操作动作(create,delete,put)”。而是直接请求资源,通过不同的http方法来做对应的操作。
    比如:

    GET 127.0.0.1:8000/app1/   获取app1应用的所有资源
    POST 127.0.0.1:8000/app1/  新增app1响应的数据
    
    GET 127.0.0.1:8000/app1/1     获取app1应用下主键为 1 的资源
    PUT 127.0.0.1:8000/app1/1     修改app1应用下主键为 1 的资源
    delete 127.0.0.1:8000/app1/1  删除app1应用下主键为 1 的资源
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、安装

    pip install djangorestframework
    
    • 1

    注册:
    在settings.py文件中进行注册.注册之后呢,在使用浏览器访问的时候,返回的结果会渲染成html页面

    INSTALLED_APPS = [
    	......
        'rest_framework',
    ]
    
    • 1
    • 2
    • 3
    • 4

    三、DRF的视图类

    django的视图类: View
    DRF的视图类: APIView
    View是APIView的父类,所以这两个视图类的用法是一样的

    1.APIView

    1.1 创建应用

    django-admin startapp app1
    
    • 1

    1.2 配置总路由

    编辑项目目录下的总urls.py文件

    urlpatterns = [
        path('app1/',include('app1.urls')),
    ]
    
    • 1
    • 2
    • 3

    1.3 新建分布式路文件

    在app1应用目录下新建urls.py文件,内容如下

    from django.urls import  path
    from .views import *
    
    urlpatterns = [
        path('',test.as_view()),
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.4 新建视图函数

    from rest_framework.views import  APIView,Response
    
    class test(APIView):
        def get(self,request):
            return  Response({"message": "访问GET方法成功"})
    
        def post(self,request):
            return  Response({"message": "访问POST方法成功"})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.5 验证

    分别使用GET方法和POST方法请求以下连接

    http://127.0.0.1:8000/app1/
    
    • 1

    2.APIView GET请求

    request.query_params: 获取GET请求所有参数
    request.query_params.get("name") :获取get请求中某个key的值
    
    • 1
    • 2

    视图函数:

    from rest_framework.views import  APIView,Response
    
    class test(APIView):
        def get(self,request):
            print(request.query_params)
            print(request.query_params.get("name"))
            return  Response({"message": "访问GET方法成功"})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    发送请求验证

    http://127.0.0.1:8000/app1/?name=zhangsan&password=123 
    
    • 1

    结果如下:

    <QueryDict: {'name': ['zhangsan'], 'password': ['123']}> 
    zhangsan 
    
    • 1
    • 2

    3.APIView POST请求

    request.data.get() : 获取post请求的数据
    
    • 1

    视图函数如下:

    from rest_framework.views import  APIView,Response
    
    class test(APIView):
        def post(self,request):
            print(f'POST的数据 = {request.data.get("user")}')
            print(f'POST的数据 = {request.data.get("password")}')
            return  Response({"message": "访问POST方法成功"})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    发送POST请求

    http://127.0.0.1:8000/app1/
    
    form-data 如下:
    user:admin
    password: 123123
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果如下:

    POST的数据 = admin 
    POST的数据 = 123123 
    
    • 1
    • 2

    4.DRF的Response

    DRF封装了自己的Response,对django的Httpsponse进行了封装和增强,可以直接对字典进行序列化。代码如下:

    from rest_framework.response import Response
    data = {
        "usernmae": "张三",
        "age": 100
    }
    
    
    class test(APIView):
        def get(self,request):
            return  Response(data)
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用get请求访问

    http://127.0.0.1:8000/app1/
    
    • 1

    返回结果如下:

    {
        "usernmae": "张三",
        "age": 100
    }
    
    • 1
    • 2
    • 3
    • 4

    四、DRF序列化

    1.概念

    1.1 序列化

    将内存中对象存储下来,把它变成一个个字节。
    简单来讲: 数据结构–>二进制
    举例:python中的json.dumps()就是序列化函数。将python中的字典转换成json格式的字符串的过程就是序列化

    1.2.反序列化

    将文件得一个个字节恢复成内存中得对象
    简单来讲: 二进制–>数据结构
    举例:python中的json.loads()就是反序列化,将json字符串转换成python的中字典的过程称为反序列化。

    1.3 DRF序列化

    在DRF中:model类–>字典–>JSON字符串(字符序列)
    django中得DRF主要是配置model类来使用得。他主要是将"model类得实例" 转化为 “字典”。再由json模块转换为json字符串.

    1.4 DRF反序列化

    在DRF中:JSON字符串–>字典–>model类(入库持久化)
    浏览器提交过来得数据,DRF做数据校验,在入库

    2.序列化器

    1.创建数据库model类

    !!! 注意 !!!:这里定义模型类的时候,不要定义id字段,django在迁移的时候,会自动生成id字段,并且是primary_key 和自增长的字段。
    如果写了,在后边写PUT方法的时候,会有一个报错:如果POST的数据中不传递id 会报错,说ID是必填字段。如果填写了又说ID是存在的。

    在应用app1的models.py中编写mode类

    from django.db import models
    
    class student(models.Model):
        class Meta:
            db_table = 'my_student'
            
        name = models.CharField(max_length=20)
        age = models.IntegerField()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    进行迁移

    python manage.py makemigrations
    python manage.py migrate
    
    • 1
    • 2

    查看生成的表结构

    mysql> desc my_student;
    +-------+-------------+------+-----+---------+----------------+
    | Field | Type        | Null | Key | Default | Extra          |
    +-------+-------------+------+-----+---------+----------------+
    | id    | bigint(20)  | NO   | PRI | NULL    | auto_increment |
    | name  | varchar(20) | NO   |     | NULL    |                |
    | age   | int(11)     | NO   |     | NULL    |                |
    +-------+-------------+------+-----+---------+----------------+
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.插入内容

    INSERT INTO my_student VALUES (0,'zhangsan',20),(0,'lisi',21),(0,'wangwu',20),(0,'xiaoqiang',26),(0,'xiaoming',22);
    
    mysql> select * from my_student;
    +----+-----------+-----+
    | id | name      | age |
    +----+-----------+-----+
    |  1 | zhangsan  |  20 |
    |  2 | lisi      |  21 |
    |  3 | wangwu    |  20 |
    |  4 | xiaoqiang |  26 |
    |  5 | xiaoming  |  22 |
    +----+-----------+-----+
    5 rows in set (0.00 sec
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.创建序列化器

    在app1的应用目录下创建serializers.py文件.文件名是可以自定义的。
    DRF提供了一个类:serializers.ModelSerializer。这个类就帮我们做了序列化器和model字段的一一对应
    这一步创建序列化器才是在序列化过程中最重要的的步骤。

    from .models import *
    from rest_framework import serializers
    
    class student_serializers(serializers.ModelSerializer):
        class Meta:
            # 这是对student 类进行序列化
            model = student
            # 序列化所有字段
            fields = '__all__'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.序列化

    注意:单行序列化 获取数据库数据时使用的是objects.get()
    多行序列化 获取数据库数据时使用的是objects.filter(),并且做数据转换的时候必须使用many=True参数。

    编写路由

    编辑app1应用下的路由文件urls.py。
    这里使用了两个函数test和testDetail

    from django.urls import  path
    from .views import *
    
    urlpatterns = [
    	# 这个路由是为了符合restful风格的接口,get所有资源准备的路由.
        path('',test.as_view()),
        
        # 这个路由是为了对单个资源进行操作的路由.
    	# 在路由中也定义了 用来定位资源的变量: id
        path('/',testDetail.as_view())
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1 单行序列化

    编写app1应用下视图函数文件views.py

    from rest_framework.views import  APIView,Response
    from .models import *
    from .serializers import *
    
    class testDetail(APIView):
        def get(self, request,id):
            # 获取单行数据库数据
            res = student.objects.get(pk=id)
    
            # 使用自定义序列化器 对结果进行序列化,并且使用.data返回序列化后的数据
            ser_data = student_serializers(instance=res).data
    
            # 将最后的结果序列化为json返回给用户
            return Response(ser_data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    用户使用GET请求

    http://127.0.0.1:8000/app1/2
    
    • 1

    结果如下:

    {
        "id": 2,
        "name": "lisi",
        "age": 21
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2 多行序列化

    依然是app1下视图函数文件views.py文件

    from rest_framework.views import  APIView,Response
    from .models import *
    from .serializers import *
    
    # 这个函数和上边不是一个函数
    class test(APIView):
        def get(self,request):
            # 获取数据库 符合条件的多行数据集。这里的参数应该是用户传递过来的,这里就直接写了
            res = student.objects.filter(age__gt=20)
            
            # 对多行数据集合进行序列化
            ser_data = student_serializers(instance=res,many=True).data
            
            # 将数据序列化后返回给用户
            return  Response(ser_data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果如下:

    [
        {
            "id": 2,
            "name": "lisi",
            "age": 21
        },
        {
            "id": 4,
            "name": "xiaoqiang",
            "age": 26
        },
        {
            "id": 5,
            "name": "xiaoming",
            "age": 22
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4、反序列化

    1.新增数据(POST)

    视图函数如下:

    from rest_framework.views import  APIView,Response
    from .models import *
    from .serializers import *
    
    class test(APIView):
    	def get()
            ......
        
        # 新增POST方法
        def post(self,request):
            # 将用户post的请求的数据传递给序列化器
            ser_data = student_serializers(data=request.data)
    
            # 序列化器对 用户提交的数据进行合法校验
            if ser_data.is_valid():
                ser_data.save()
                return  Response("数据写入成功")
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    用户访问路由,使用POST方法:

    http://127.0.0.1:8000/app1/
    
    用户POST数据为:
    {
        "name": "zhangwuji",
        "age": 20
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看数据库,数据已经添加成功

    mysql> select * from my_student;
    +----+-----------+-----+
    | id | name      | age |
    +----+-----------+-----+
    |  1 | zhangsan  |  20 |
    |  2 | lisi      |  21 |
    |  3 | wangwu    |  20 |
    |  4 | xiaoqiang |  26 |
    |  5 | xiaoming  |  22 |
    |  6 | zhangwuji |  20 |
    +----+-----------+-----+
    6 rows in set (0.00 sec)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.更新数据(PUT)

    class testDetail(APIView):
        def get(self,request,id):
            pass
        
        # 新增put方法
        def put(self,request,id):
            query_data = student.objects.get(pk=id)
            ser_save_data = student_serializers(instance=query_data, data=request.data)
            if ser_save_data.is_valid():
                ser_save_data.save()
                return  Response("数据修改成功")
            else:
                print(ser_save_data.errors)
                return  Response("数据修改失败")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用PUT方法访问:

    PUT   http://127.0.0.1:8000/app1/6/
    
    • 1

    将zhangwuji改为zhangsanfeng,提交数据为:

    {
        "name": "zhangsanfeng",
        "age": 20
    }
    
    • 1
    • 2
    • 3
    • 4

    查看结果修改成功

    mysql> select * from my_student;
    +----+--------------+-----+
    | id | name         | age |
    +----+--------------+-----+
    |  1 | zhangsan     |  20 |
    |  2 | lisi         |  21 |
    |  3 | wangwu       |  20 |
    |  4 | xiaoqiang    |  26 |
    |  5 | xiaoming     |  22 |
    |  6 | zhangsanfeng |  20 |
    +----+--------------+-----+
    6 rows in set (0.00 sec)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.删除数据

    class testDetail(APIView):
        def get(self,request,id):
            pass
        def put(self,request,id):
        	pass
        
        # 新增delete方法
    	def delete(self,request,id):
            try:
                query_data = student.objects.get(pk=id)
                query_data.delete()
            except Exception:
                return Response("数据删除失败")
            else:
                return Response("数据删除成功")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用delete方法请求.无需post数据。
    删除id为 6 的数据

    http://127.0.0.1:8000/app1/6/
    
    • 1

    5.总结

    这里一共两个函数,5个方法。增删改查查

    
    class test(APIView):
    	# 获取所有资源
    	def get()
    	
    	# 添加一个资源
    	def post()
    	
    class testDetail(APIView):
    	# 获取单个资源
    	def get()
    	
    	# 修改单个资源
    	def put()
    	
    	#删除单个资源
    	def delete()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如下图:

    用户
    列表页路由 test.as_view
    详情页路由 testDetail.as_view
    test视图类
    testDetail视图类
    def get:查询所有资源
    def post:添加资源
    def delete:删除单个资源
    def put:修改单个资源
    def get:获取单个资源

    五、通用视图类

    1.视图类的缺点

    使用了APIview确实有些了增强的部分,但是如果有多个应用,或者一个应用里多个业务类,那就会有很多的重复代码。
    重复代码指的是:
    1.每一个业务类当中都要重复写get、post方法
    2.每一个方法当中都要重复写 查询集合,序列化器、数据校验、数据保存等
    于是:DRF又提供了genericAPIview和mixins

    2.GenericAPIView

    GenericAPIView主要是提供了两个变量,这两个变量的名称是固定写法。因为源码里是这么写的。这两个变量主要是提供给Mixins类使用的。

    from rest_framework.generics import GenericAPIView
    
    1.queryset: 得出查询数据库数据的集合
    2.serializer_class:  指定序列化器
    
    • 1
    • 2
    • 3
    • 4

    3.mixins类

    在View和APIView中,我们都需要手写了get、put、post等方法,来处理增删改查查的数据。
    DRF中提供了这5个类,和get、put、delete、get、post的功能一样,而且比自己写的处理数据逻辑更严谨

    ListModelMixin: 查所有
    RetrieveModelMixin:查单条
    
    CreateModelMixin:新增
    UpdateModelMixin:更新
    DestroyModelMixin:删除
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.组合使用

    4.1 总路由

    urlpatterns = [
        path('app1/',include('app1.urls')),
    ]
    
    • 1
    • 2
    • 3

    4.2 子路由

    from django.urls import  path
    from .views import *
    
    urlpatterns = [
        path('',test.as_view()),
        
        # 这里的变量名必须叫做pk. 前边的视图函数可以自定义,参数也可以自定义,所以这里的路由变量也可以自定义。
    	# 但是现在用的是GenericAPIView类,源码当中定义的叫做pk,所以这里必须叫做pk
        path('',testDetail.as_view())
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.3 model类和序列化器

    model类和序列化器没有变化,依然使用上边"四"中的配置即可

    4.4 视图函数

    编辑views.py文件

    from rest_framework.generics import GenericAPIView
    from rest_framework.mixins import (
        ListModelMixin,RetrieveModelMixin,
        CreateModelMixin,UpdateModelMixin,DestroyModelMixin
    )
    from .models import *
    from .serializers import *
    
    
    class test(GenericAPIView,ListModelMixin,CreateModelMixin):
        queryset = student.objects.all()
        serializer_class = student_serializers
    
        # 这里之所以要这样赋值,根本原因是APIView和View都是通过dispatch方法,根据方法的名称进行的请求分发。
        # 所以DRF提供的5个处理数据的方法,最终名称还是要和put、post等5个方法的名称对应上。否则dispatch方法无法找到视图类
        get = ListModelMixin.list
        post = CreateModelMixin.create
    
    class testDetail(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
        queryset = student.objects.all()
        serializer_class = student_serializers
    
        get = RetrieveModelMixin.retrieve
        put = UpdateModelMixin.update
        delete = DestroyModelMixin.destroy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4.5 测试

    DRF默认不支持批量新增和批量修改

    1.get所有资源   http://127.0.0.1:8000/app1/
    2.get单个资源   http://127.0.0.1:8000/app1/2
    3.delete单个数据  http://127.0.0.1:8000/app1/9
    
    4.post单个数据   http://127.0.0.1:8000/app1/    
     数据如下:
    {
        "name": "zhangsanfeng",
        "age": 20
    }
    
    5.put 单个数据   http://127.0.0.1:8000/app1/2
    数据如下:
    {
        "name": "xiaoli",
        "age": 25
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    六、concreteAPIView(混凝土)

    在 五 中我们需要手动继承和组合genenicAPIView和mixins类。genenicAPIView和minxinx提供的功能类进行自由组合。这样继承的类相对来说较多。
    因此DRF又提供了写好的genicAPIview和mixIn的组合类。如果不需要自定义的化,可以使用提前定义好的view类

    1.组合方式

    concreteAPIview组合方式方法功能
    CreateAPIViewGenericAPIView, CreateModelMixinpost列表页新增对象功能
    ListAPIViewGenericAPIView, ListModelMixinget获得列表页内容
    ListCreateAPIViewGenericAPIView, ListModelMixin, CreateModelMixinpost,get完整的列表页功能
    RetrieveAPIViewGenericAPIView, RetrieveModelMixinget获取单个对象
    UpdateAPIViewGenericAPIView, UpdateModelMixinput、 patch修改单个对象
    DestroyAPIViewGenericAPIView, DestroyModelMixindelete删除单个对象
    RetrieveUpdateDestroyAPIViewGenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixinget、 put、 patch、 delete完整的详情页查、 改、删 功能

    2.路由

    总路由和子路由配置不变。和五中的配置保持一致

    3.视图使用

    视图函数如下:这里需要指定queryset和序列化器类就可以了。
    优点:
    1.继承的类更加简洁明了
    2.不用再写get、post、put、delete等所有方法了。

    from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView
    from .models import *
    from .serializers import *
    
    class test(ListCreateAPIView):
        queryset = student.objects.all()
        serializer_class = student_serializers
    
    
    class testDetail(RetrieveUpdateDestroyAPIView):
        queryset = student.objects.all()
        serializer_class = student_serializers
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    七、路由Router与视图集ViewSets

    concreteAPIView视图类已经非常简练了,已经不用再写操作数据库的逻辑代码。只需要指定数据集合和序列化器类就可以了。
    但是目前的状况还是有一点问题:
    1.路由要写两条。一条是列表页路由,一条是详情页路由
    2.视图函数也是两个,一个是列表页处理函数,一个是详情页处理函数。并且要重复指定queryset和serializer_class

    DRF中的router解决了问题1
    DRF中的ViewSets解决了问题2

    1.配置总路由

    编辑urls.py

    from django.urls import path,include
    
    urlpatterns = [
        path('app1/',include('app1.urls')),
    
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.配置子路由

    编辑app1下的urls.py文件

    from django.urls import  path
    from .views import *
    from rest_framework.routers import SimpleRouter
    
    
    urlpatterns = [
    
    ]
    
    
    router = SimpleRouter()
    router.register('db',test)
    
    # 查看生成了哪些路由条目
    for url in router.urls:
        print(f'url = {url}')
    
    urlpatterns += router.urls
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在启动项目的时候在日志中可以看到生成了两条路由

    # 这个对应列表页路由
    url = <URLPattern '^db/$' [name='student-list']> 
        
    # 这个对应详情页路由
    url = <URLPattern '^db/(?P[^/.]+)/$' [name='student-detail']> 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.配置视图集

    这里的视图类也不在用写两个了,写一个就可以了

    from rest_framework.viewsets import ModelViewSet
    from .models import *
    from .serializers import *
    
    class test(ModelViewSet):
        queryset = student.objects.all()
        serializer_class = student_serializers
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试增删改查查

  • 相关阅读:
    关于windows上使用qemu分别仿真stm32和a9以及串口输出
    展商企业【广东伟创科技开发有限公司】| 2024水科技大会暨技术装备成果展
    深度学习——(2)几种常见的损失函数
    后端总说他啥也没动,我从线上调了一下测试接口,你再说一句动没动
    操作系统权限提升(二十五)之数据库提权-Centos7 安装MySQL
    【web-攻击用户】(9.5)同源策略:与浏览器扩展、HTML5、通过代理服务应用程序跨域
    (附源码)spring boot校园拼车微信小程序 毕业设计 091617
    从android源码获取版本信息的方法
    java反射机制
    在 Kubernetes 集群中部署现代应用的通用模式
  • 原文地址:https://blog.csdn.net/smile_pbb/article/details/136197094