• DRF


    1.简介

    drf 是django rest framewok的简称,主要用于前后端分离项目,用于前端调取接口,后端返回json

    drf是一个基于django开发的组件,本质是一个django的app。
    drf可以办我们快速开发出一个遵循restful规范的程序。

    简单说 只是一个项目而已,然后调取

    涉及restful规范 是约束接口的规则

    1.给别人提供一个URL,根据URL请求方式的不同,做不同操作。
    	get,获取
    	post,增加
    	put,全部更新
    	patch,局部更新
    	delete,删除
    2.数据传输基于json格式。
    
    3. 建议用https代替http
    4. 在URL中体现api,添加api标识
    	https://www.cnblogs.com/xwgblog/p/11812244.html   # 错误
    	https://www.cnblogs.com/api/xwgblog/p/11812244.html  # 正确
    	https://api.cnblogs.com/xwgblog/p/11812244.html # 正确
    	
    	建议:https://www.cnblogs.com/api/...
    5. 在URL中要体现版本
    	https://www.cnblogs.com/api/v1/userinfo/
    	https://www.cnblogs.com/api/v2/userinfo/
    6. 一般情况下对于api接口,用名词不用动词。
    	https://www.cnblogs.com/api/v1/userinfo/
    7. 如果有条件的话,在URL后面进行传递。
    	https://www.cnblogs.com/api/v1/userinfo/?page=1&category=2
    8. 返回给用户状态码(code)
    9. 对下一个请求返回一些其他接口
    

    当然根据Django也可以实现遵循restful规范的接口开发:

    其中两种视图模式

    • FBV,可以实现比较麻烦。
    • CBV,相比较简答根据method做的了不同的区分。

    drf组件的功能

    • 解析器,解析请求体中的数据,将其变成我们想要的格式。request.data request.query_params.get('article')

      在进行解析时候,drf会读取http请求头 content-type.
      如果content-type:x-www-urlencoded,那么drf会根据 & 符号分割的形式去处理请
      求体。
      user=wang&age=19
      如果content-type:application/json,那么drf会根据 json 形式去处理请求体。
      {"user":"wang","age":19}

    • 序列化,可以对QuerySet进行序列化,也可以对用户提交的数据进行校验。

    • 视图,继承APIView(在内部apiview继承了django的View)

    • 渲染器,可以帮我们把json数据渲染到页面上进行友好的展示。(内部会根据请求设备不同做不同的
      展示)

    • 分页,筛选

    2.安装

    pip3 install djangorestframework
    

    3.使用

    3.1注册

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework'  #添加项目
    ]
    

    3.2路由

    from django.conf.urls import url
    from django.contrib import admin
    from api import views
    
    urlpatterns = [
        url(r'^drf/info/', views.DrfInfoView.as_view()),
    ]
    

    3.3视图

    注意 APIView中没有JsonResponse

    使用要使用Response

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class DrfInfoView(APIView):
    
        def get(self,request,*args,**kwargs):
            data = [
                {'id': 1, 'title': '震惊了...你居然吃...', 'content': '...'},
                {'id': 2, 'title': '震惊了...他居然吃...', 'content': '...'},
    
            ]
            return Response(data)    
    

    4.基于接口的增删改查low版本

    from django.urls import path,re_path
    from api import  views
    urlpatterns = [
        re_path('drf/category$', views.DrfCategoryView.as_view()),
        re_path('drf/category/(?P<pk>d+)/$', views.DrfCategoryView.as_view()),
    ]
    
    from api import models
    from django.forms.models import model_to_dict
    class DrfCategoryView(APIView):
        def get(self,request,*args,**kwargs):
            """获取所有文章分类/单个文章分类"""
            pk = kwargs.get('pk')
            if not pk:
                queryset = models.Category.objects.all().values('id','name')
                data_list = list(queryset)
                return Response(data_list)
            else:
                category_object = models.Category.objects.filter(id=pk).first()
                data = model_to_dict(category_object)
                return Response(data)
    
        def post(self,request,*args,**kwargs):
            """增加一条分类信息"""
            models.Category.objects.create(**request.data)
            return Response('成功')
    
        def delete(self,request,*args,**kwargs):
            """删除"""
            pk = kwargs.get('pk')
            models.Category.objects.filter(id=pk).delete()
            return Response('删除成功')
    
        def put(self,request,*args,**kwargs):
            """更新"""
            pk = kwargs.get('pk')
            models.Category.objects.filter(id=pk).update(**request.data)
            return Response('更新成功')
    
    from django.db import models
    
    class Category(models.Model):
        """
        文章分类
        """
        name = models.CharField(verbose_name='分类',max_length=32)
    
    
    class Article(models.Model):
        """
        文章表
        """
        title = models.CharField(verbose_name='标题',max_length=32)
        summary = models.CharField(verbose_name='简介',max_length=255)
        content = models.TextField(verbose_name='文章内容')
        cates = models.ForeignKey(to=Category,blank=True,null=True,on_delete=models.CASCADE)
    

    5.序列化

    (可以直接看6)

    drf的 serializers帮助我们提供了

    • 数据校验
    • 序列化
        url(r'^new/category/$', views.NewCategoryView.as_view()),
        url(r'^new/category/(?P<pk>d+)/$', views.NewCategoryView.as_view()),
    
    
    from rest_framework import serializers
    from rest_framework.views import APIView
    class NewCategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Category
            # fields = "__all__"
            fields = ['id','name']
    
    class NewCategoryView(APIView):
        def get(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            if not pk:
                queryset = models.Category.objects.all()
                ser = NewCategorySerializer(instance=queryset,many=True)
                return Response(ser.data)
            else:
                model_object = models.Category.objects.filter(id=pk).first()
                ser = NewCategorySerializer(instance=model_object, many=False)
                return Response(ser.data)
    
        def post(self,request,*args,**kwargs):
            ser = NewCategorySerializer(data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def put(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            category_object = models.Category.objects.filter(id=pk).first()
            ser = NewCategorySerializer(instance=category_object,data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def delete(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            models.Category.objects.filter(id=pk).delete()
            return Response('删除成功')
    
    

    6.跨表展示,跨字典展示,一对多

    from django.db import models
    
    class Category(models.Model):
        """
        文章分类
        """
        name = models.CharField(verbose_name='分类',max_length=32)
    
    
    class Article(models.Model):
        """
        文章表
        """
        status_choices = (
            (1,'发布'),
            (2,'删除'),
        )
        status = models.IntegerField('状态',choices=status_choices,default=1) #字典
        title = models.CharField('标题',max_length=32)
        summary = models.CharField('简介',max_length=255)
        content = models.TextField('文章内容')
        cates = models.ForeignKey(to=Category,on_delete=models.CASCADE)  #跨表
    
    
    urlpatterns = [
        re_path('drf/text/$', views.DrfNewText.as_view()),
        re_path('drf/text/(?P<pk>d+)/$', views.DrfNewText.as_view()),
    ]
    
    
    from rest_framework.views import APIView
    from  rest_framework.response import Response
    from   api import  models
    from  api.serializer import NewText
    
    
    
    class DrfNewText(APIView):
        def get(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            if not pk:
                obj = models.Article.objects.all()
                ser = NewText(instance=obj,many=True)
                return Response(ser.data)
            else:
                obj = models.Article.objects.filter(id=pk).first()
                ser = NewText(instance=obj,many=False)
                return Response(ser.data)
    
    
        def post(self,request,*args,**kwargs):
            ser = NewText(data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def put(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            obj = models.Article.objects.filter(id=pk).first()
            ser = NewText(instance=obj, data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def delete(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            models.Article.objects.filter(id=pk).delete()
            return Response('删除成功')
    
    
    from   rest_framework import serializers
    from api import models
    
    class NewText(serializers.ModelSerializer):
        #跨表查询出name,名字与数据库字段名相同时,会覆盖,需要加read_only=True
        cates_l = serializers.CharField(source='cates.name', required=False)
        status_l = serializers.CharField(source='get_status_display',required=False)
        # x1 = serializers.SerializerMethodField()
        class Meta:
            model = models.Article
            # fields = '__all__'
            fields = ['id','title','summary','content','cates','cates_l','status','status_l']
    ##方法定义x1  设置get_x1方法
        # def get_x1(self,obj):
        #     return obj.cates.name
    
    

    postman请求

    只需要提交数据库有的字段

        {
            "title": "今天1w23",
            "summary": "明天",
            "content": "后天",
            "cates": 1,
            "status": 2
        }
    
    

    7.多对多

    可写两个serializers ,显示和提交分开

    from django.db import models
    
    class Category(models.Model):
        """
        文章分类
        """
        name = models.CharField(verbose_name='分类',max_length=32)
    
    
    class Article(models.Model):
        """
        文章表
        """
        status_choices = (
            (1,'发布'),
            (2,'删除'),
        )
        status = models.IntegerField('状态',choices=status_choices,default=1)
        title = models.CharField('标题',max_length=32)
        summary = models.CharField('简介',max_length=255)
        content = models.TextField('文章内容')
        cates = models.ForeignKey(to=Category,on_delete=models.CASCADE)
        tag = models.ManyToManyField(verbose_name='标签', to='Tag', blank=True)
    
    class Tag(models.Model):
        """标签"""
        title = models.CharField(verbose_name='标签',max_length=32)
    
    
    from   rest_framework import serializers
    from api import models
    
    class NewText(serializers.ModelSerializer):
        #跨表查询出name,并替换了fields的cates
        cates = serializers.CharField(source='cates.name', required=False)
        status = serializers.CharField(source='get_status_display',required=False)
        tag = serializers.SerializerMethodField()
    
        class Meta:
            model = models.Article
            fields = ['id','title','summary','content','cates','status','tag']
        def get_tag(self,obj):
    
                query_set = obj.tag.all()
                return [({'id':obj.id,'title':obj.title} )for obj in query_set]
    
    class Newpost(serializers.ModelSerializer):
        class Meta:
            model =models.Article
            fields = '__all__'
    
    
    from rest_framework.views import APIView
    from  rest_framework.response import Response
    from   api import  models
    from  api.serializer import NewText,Newpost
    
    
    
    class DrfNewText(APIView):
        def get(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            if not pk:
                obj = models.Article.objects.all()
                ser = NewText(instance=obj,many=True)
                return Response(ser.data)
            else:
                obj = models.Article.objects.filter(id=pk).first()
                ser = NewText(instance=obj,many=False)
                return Response(ser.data)
    
    
        def post(self,request,*args,**kwargs):
            ser = Newpost(data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def put(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            obj = models.Article.objects.filter(id=pk).first()
            ser = Newpost(instance=obj, data=request.data)
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)
    
        def delete(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            models.Article.objects.filter(id=pk).delete()
            return Response('删除成功')
    
    

    8.PageNumberPagination 分页

    REST_FRAMEWORK = {
        'PAGE_SIZE':3,
        # 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
    }   #指定分页方式
    
    
    from rest_framework.pagination import PageNumberPagination
    from rest_framework import serializers
    from   api import  models
    class PageArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            fields = "__all__"
    class  Page(APIView):
        def get(self,request,*args,**kwargs):
            queryset  = models.Article.objects.all()
            #方法一,仅数据
            #分页对象
            page_object = PageNumberPagination()
            # 调用 分页对象.paginate_queryset方法进行分页,得到的结果是分页之后的数据
            # result就是分完页的一部分数据
            result = page_object.paginate_queryset(queryset, request, self)
            # 序列化分页之后的数据
            ser = PageArticleSerializer(instance=result, many=True)
            return Response(ser.data)
    
    
            # 方式二:数据 + 分页信息
            return page_object.get_paginated_response(ser.data) #只有这行不一样
        	#方式三: 自定义返回数据
            return Response({'count': page_object.page.paginator.count, 'result': ser.data})
    
    
    

    9.LimitOffsetPagination 分页

    from rest_framework.pagination import LimitOffsetPagination
    from rest_framework import serializers
    
    
    class PageArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            fields = "__all__"
    
    class HulaLimitOffsetPagination(LimitOffsetPagination):
        max_limit = 2  #限定最大条数
    
    class Page(APIView):
        def get(self, request, *args, **kwargs):
            queryset = models.Article.objects.all()
    
            page_object = HulaLimitOffsetPagination() #调用
            result = page_object.paginate_queryset(queryset, request, self)
            ser = PageArticleSerializer(instance=result, many=True)
            return Response(ser.data)
    
    ###  http://127.0.0.1:8000/page/article/?offset=0&limit=7
    
    

    10.扩展用法

    调取内部方法设置分页

    REST_FRAMEWORK = {
        'PAGE_SIZE':3,
        'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
    }
    
    
    from rest_framework.generics import ListAPIView
    from rest_framework import serializers
    from   api import  models
    
    
    class PageViewArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            fields = "__all__"
    
    
    class PageViewArticleView(ListAPIView):
    
    
        queryset = models.Article.objects.all()
        serializer_class = PageViewArticleSerializer
    #http://127.0.0.1:8000/page/article/?page=1
    
    

    11.呼拉圈接口设置

    1.设置表结构

    不会经常变化的值放在内存:choices形式,避免跨表性能低。 如标题

    分表:如果表中列太多/大量内容可以选择水平分表 如内容

    from django.db import models
    
    class UserInfo(models.Model):
        """ 用户表 """
        username = models.CharField(verbose_name='用户名',max_length=32)
        password = models.CharField(verbose_name='密码',max_length=64)
    
    
    class Article(models.Model):
        """ 文章表 """
        category_choices = (
            (1,'咨询'),
            (2,'公司动态'),
            (3,'分享'),
            (4,'答疑'),
            (5,'其他'),
        )
        category = models.IntegerField(verbose_name='分类',choices=category_choices)
        title = models.CharField(verbose_name='标题',max_length=32)
        image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/....
        summary = models.CharField(verbose_name='简介',max_length=255)
    
        comment_count = models.IntegerField(verbose_name='评论数',default=0)
        read_count = models.IntegerField(verbose_name='浏览数',default=0)
    
        author = models.ForeignKey(verbose_name='作者',to='UserInfo')
        date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    
    class ArticleDetail(models.Model):
        article = models.OneToOneField(verbose_name='文章表',to='Article')
        content = models.TextField(verbose_name='内容')
    
    
    class Comment(models.Model):
        """ 评论表 """
        article = models.ForeignKey(verbose_name='文章',to='Article')
        content = models.TextField(verbose_name='评论')
        user = models.ForeignKey(verbose_name='评论者',to='UserInfo')
        # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)
    
    
    

    2.url

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^article/', views.ArticleView.as_view()),
    ]
    
    

    3.功能实现

    1.增加文章(可以不写)一次增加两个表中的数据:

    from . import models
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import serializers
    class ArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            # fields = '__all__'
            exclude = ['author',]
    
    class ArticleDetaSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.ArticleDetail
            # fields = '__all__'
            exclude = ['article',]
    
    
    class ArticleView(APIView):
        """文章的视图类"""
    
        def get(self,request,*args,**kwargs):
            """获取文章"""
            pass
    
        def post(self,request,*args,**kwargs):
            """新增文章"""
            ser = ArticleSerializer(data=request.data)
            ser_detail = ArticleDetaSerializer(data=request.data)
            if ser.is_valid() and ser_detail.is_valid():
                #增加文章
                article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个
                ser_detail.save(article=article_obj)
                return Response(ser.data)
    
            return  Response("错误")
    
    
    
    {"category":"2","title":"梦啊","image":"www.zhuimengnan.com","summary":"梦在哪啊你走的那么决然","content":"人生路远,不是不爱了,是想给自己一条退路"}
    
    

    2.get获取文章列表

    class ArticlelistSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            fields = '__all__'
    
    class ArticleView(APIView):
        """文章的视图类"""
        def post(self,request,*args,**kwargs):
    		pass #看上边 这里不写了
    
        def get(self,request,*args,**kwargs):
            """获取文章列表"""
            #分页
            queryset = models.Article.objects.all().order_by('-date')
            page = PageNumberPagination()
            result = page.paginate_queryset(queryset,request,self)
    
            #序列化
            ser = ArticlelistSerializer(instance=result,many=True)
            return Response(ser.data)
    
    

    3.文章详细

    #文章详细钩子
    class PageArticleSerializer(serializers.ModelSerializer):
        content = serializers.CharField(source='articledetail.content')
        author = serializers.CharField(source='author.username')
        category =serializers.CharField(source='get_category_display')
        date =serializers.SerializerMethodField()
        class Meta:
            model = models.Article
            fields = '__all__'
    
            def get_date(self,obj):
                return obj.date.strftime("%Y-%m-%d %H:%M:%S")
    
    class ArticleView(APIView):
        """文章的视图类"""
    
        def get(self,request,*args,**kwargs):
            """获取文章列表"""
            pk = kwargs.get('pk')
            if not pk:
                #分页
                queryset = models.Article.objects.all().order_by('-date')
                page = PageNumberPagination()
                result = page.paginate_queryset(queryset,request,self)
    
                #序列化
                ser = ArticlelistSerializer(instance=result,many=True)
                return Response(ser.data)
            #文章详细
            article_obj = models.Article.objects.filter(id=pk).first()
            ser =PageArticleSerializer(instance=article_obj,many=False)
            return Response(ser.data)
    
    
    

    4.评论列表

    • 查看评论列表
      访问时:http://127.0.0.1:8000/comment/?article=2

      http://127.0.0.1:8000/comment/
      
      {
      	article:1,
      	content:'xxx'
      }
      
      
    url(r'^comment/$', views.CommentView.as_view()),
    
    
    class CommentView(APIView):
        """评论接口"""
    
        def get(self,request):
            #取得的是url传过来的参数
            article_id = request.query_params.get('article') #return self._request.GET
    
            queryset = models.Comment.objects.filter(article_id=article_id)
    
            ser = CommentSerializer(instance=queryset,many=True)
            return  Response(ser.data)
        def post(self,request,*args,**kwargs):
            print(request.data)
            ser = PostCommentSerializer(data=request.data)
            if ser.is_valid():
                ser.save(user_id=2)
                return Response('sucess')
            return Response(ser.errors)
    
    

    5.总结

    from django.db import models
    
    class UserInfo(models.Model):
        """ 用户表 """
        username = models.CharField(verbose_name='用户名',max_length=32)
        password = models.CharField(verbose_name='密码',max_length=64)
    
    
    class Article(models.Model):
        """ 文章表 """
        category_choices = (
            (1,'咨询'),
            (2,'公司动态'),
            (3,'分享'),
            (4,'答疑'),
            (5,'其他'),
        )
        category = models.IntegerField(verbose_name='分类',choices=category_choices)
        title = models.CharField(verbose_name='标题',max_length=32)
        image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/....
        summary = models.CharField(verbose_name='简介',max_length=255)
    
        comment_count = models.IntegerField(verbose_name='评论数',default=0)
        read_count = models.IntegerField(verbose_name='浏览数',default=0)
    
        author = models.ForeignKey(verbose_name='作者',to='UserInfo')
        date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    
    class ArticleDetail(models.Model):
        article = models.OneToOneField(verbose_name='文章表',to='Article')
        content = models.TextField(verbose_name='内容')
    
    
    class Comment(models.Model):
        """ 评论表 """
        article = models.ForeignKey(verbose_name='文章',to='Article')
        content = models.TextField(verbose_name='评论')
        user = models.ForeignKey(verbose_name='评论者',to='UserInfo')
        # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)
    
    
    
    
    from django.conf.urls import url
    from django.contrib import admin
    from api import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^article/$', views.ArticleView.as_view()),
        url(r'^article/(?P<pk>d+)/$', views.ArticleView.as_view()),
        url(r'^comment/$', views.CommentView.as_view()),
    ]
    
    
    
    from . import models
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import serializers
    from rest_framework.pagination import PageNumberPagination
    class ArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            # fields = '__all__'
            exclude = ['author',]
    
    class ArticleDetaSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.ArticleDetail
            # fields = '__all__'
            exclude = ['article',]
    
    class ArticlelistSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article
            fields = '__all__'
    
    class PageArticleSerializer(serializers.ModelSerializer):
        content = serializers.CharField(source='articledetail.content')
        author = serializers.CharField(source='author.username')
        category =serializers.CharField(source='get_category_display')
        date =serializers.SerializerMethodField()
        class Meta:
            model = models.Article
            fields = '__all__'
    
        def get_date(self,obj):
            print(obj)
            return obj.date.strftime("%Y-%m-%d %H:%M:%S")
    
    class ArticleView(APIView):
        """文章的视图类"""
        def post(self,request,*args,**kwargs):
            """新增文章"""
            ser = ArticleSerializer(data=request.data)
            ser_detail = ArticleDetaSerializer(data=request.data)
            if ser.is_valid() and ser_detail.is_valid():
                #增加文章
                article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个
                ser_detail.save(article=article_obj)
                return Response(ser.data)
    
            return  Response("错误")
    
        def get(self,request,*args,**kwargs):
            """获取文章列表"""
            pk = kwargs.get('pk')
            if not pk:
                #分页
                condition = {}
                category = request.query_params.get('category')
                if category:
                    condition['category'] = category
                queryset = models.Article.objects.filter(**condition).order_by('-date')
                # queryset = models.Article.objects.all().order_by('-date')
                page = PageNumberPagination()
                result = page.paginate_queryset(queryset,request,self)
    
                #序列化
                ser = ArticlelistSerializer(instance=result,many=True)
                return Response(ser.data)
            article_obj = models.Article.objects.filter(id=pk).first()
            ser =PageArticleSerializer(instance=article_obj,many=False)
            return Response(ser.data)
    
    class CommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Comment
            fields = '__all__'
    
    class PostCommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Comment
            exclude =['user',]
    
    class CommentView(APIView):
        """评论接口"""
    
        def get(self,request):
            #取得的是url传过来的参数
            article_id = request.query_params.get('article') #return self._request.GET
    
            queryset = models.Comment.objects.filter(article_id=article_id)
    
            ser = CommentSerializer(instance=queryset,many=True)
            return  Response(ser.data)
        def post(self,request,*args,**kwargs):
            print(request.data)
            ser = PostCommentSerializer(data=request.data)
            if ser.is_valid():
                ser.save(user_id=2)
                return Response('sucess')
            return Response(ser.errors)
    
    
    
    REST_FRAMEWORK = {
        'PAGE_SIZE':2,
        'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
    }
    
    

    12. 筛选

    案例:在文章列表时候,添加筛选功能。

    全部:http://127.0.0.1:8000/article/
    筛选:http://127.0.0.1:8000/article/?category=2
    
    
    class ArticleView(APIView):
        """ 文章视图类 """
    
        def get(self,request,*args,**kwargs):
            """ 获取文章列表 """
            pk = kwargs.get('pk')
            if not pk:
                condition = {}
                category = request.query_params.get('category')
                if category:
                    condition['category'] = category
                queryset = models.Article.objects.filter(**condition).order_by('-date')
                
                pager = PageNumberPagination()
                result = pager.paginate_queryset(queryset,request,self)
                ser = ArticleListSerializer(instance=result,many=True)
                return Response(ser.data)
            article_object = models.Article.objects.filter(id=pk).first()
            ser = PageArticleSerializer(instance=article_object,many=False)
            return Response(ser.data)  
    
    

    13.drf的组件:内置筛选

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from . import models
    from rest_framework.filters import BaseFilterBackend
    from rest_framework.pagination import PageNumberPagination
    
    
    class NewFilterBackend(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):    
            val = request.query_params.get('cagetory')
            return queryset.filter(category_id=val)
        
    class NewSerializers(serializers.ModelSerializer):
        class Meta:
            model = models.Tag
            fields = "__all__"
    
    class NewView(ListAPIView):
        queryset = models.news.objects.all()
        filter_backends = [NewFilterBackend,]
        serializer_class = NewSerializers
        pagination_class = PageNumberPagination
    
    

    14 .视图举例:

    实现 表Tag的增删改查

    from django.db import models
    
    class Tag(models.Model):
        title =models.CharField(max_length=32)
    
    
    from django.conf.urls import url,include
    from django.contrib import admin
    from shanxuan import views
    
    urlpatterns = [
        url(r'^tag/$', views.Tagviews.as_view()),
        url(r'^tag/(?P<pk>d+)/$', views.TagDetailviews.as_view()),
    ]
    
    
    

    查询单条数据和全部的 不能写在一起,会被覆盖掉,所以写两个views

    from  rest_framework import serializers
    from  rest_framework.generics import  ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView
    
    
    from  . import models
    
    class TagSer(serializers.ModelSerializer):
        class Meta:
            model = models.Tag
            fields = "__all__"
    
    class Tagviews(ListAPIView,CreateAPIView):
        queryset = models.Tag.objects.all()
        serializer_class = TagSer
    
    class TagDetailviews(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
        queryset = models.Tag.objects.all()
        serializer_class = TagSer
    
    

    分页写在配置文件中

    REST_FRAMEWORK = {
        'PAGE_SIZE':2,
        'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
    }
    
    

    15.自定制方法

    class TagSer(serializers.ModelSerializer):
        class Meta:
            model = models.Tag
            fields = "__all__"
    
    class TagView(ListAPIView,CreateAPIView):
        queryset = models.Tag.objects.all()
        #serializer_class = TagSer
    
        def get_serializer_class(self):
            # self.request
            # self.args
            # self.kwargs
            #分两个序列化的类
            if self.request.method == 'GET':
                return TagSer
            elif self.request.method == 'POST':
                return OtherTagSer
            #提交自定制数据
        def perform_create(self,serializer):
            serializer.save(author=1)
    
    class TagDetailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
        queryset = models.Tag.objects.all()
        serializer_class = TagSer
    
    

    16.类继承关系

    class View(object):
    	def dipatch(self):
    		print(123)
    	
    class APIView(View):
    	version_class = settings.xxx 
    	parser_class = settings.sxx
    	permision_classes = []
    	
    	def dipatch(self):
    	
    		self.initial()
    	
    		method = getattr(self,"get")
    		return method()
    		
    	def initial(self):
    		self.version_class()
    		self.parser_class()
    		for item in self.permision_classes:
    		 	item()
    	
    class GenericAPIView(APIView):
    	queryset = None
    	serilizer_class = None 
    	def get_queryset(self):
    		return self.queryset
    		
    	def get_serilizer(self,*arg,**kwargs):
    		cls = self.get_serilizer_class()
    		return cls(*arg,**kwargs)
    	
    	def get_serilizer_class(self):
    		return self.serilizer_class
    class ListModelMixin(object):
    	def list(self):
    		queryset = self.get_queryset()
    		ser = self.get_serilizer(queryset,many=True)
    		return Reponse(ser.data)
    		
    class ListAPIView(ListModelMixin,GenericAPIView):
    	def get(self):
    		return self.list(...)
    	
    	
    class TagView(ListAPIView):
    	queryset = models.User.object.all()
    	serilizer_class = TagSerilizer
    	version_class = URLPathClass
    	parser_class = JSONParser
    	permission_classes = [Foo,Bar ]
    
    obj = TagView()
    x = obj.dispatch()
    给用户返回x
    
    

    17.版本

    在url中显示版本信息,

    http://127.0.0.1:8000/version/v1/index  
    
    
    urlpatterns +=[
        url(r'^version/',include('version.urls'))
    ]
    
    
    urlpatterns = [
        url(r'^(?P<version>w+)/index/$',views.IndexViews.as_view()),
    ]
    
    
    class IndexViews(APIView):
        #versioning_class = URLPathVersioning  #这是局部
        def get(self,request,*args,**kwargs):
            print(request.version)
            print(request.versioning_scheme)
            return Response("123")
    
    
    
    REST_FRAMEWORK = {
        'PAGE_SIZE':2,
        'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination",
        'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
        'ALLOWED_VERSIONS':['v1','v2'],  #这是全局 #这是全局
        'VERSION_PARAM':'version',
    }
    
    

    18.认证

    流程

    1.新建项目,创建数据库

    class UserInfo(models.Model):
    
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=32)
        token = models.CharField(max_length=64,null=True,blank=True)
    
    

    2.url

    from django.conf.urls import url,include
    from django.contrib import admin
    from . import views
    urlpatterns = [
        url(r'^login/$', views.LoginView.as_view()),
        url(r'^order/$', views.OrderView.as_view()),
    ]
    
    

    3.views

    import uuid
    from django.shortcuts import render
    from django.views import View
    from django.views.decorators.csrf import csrf_exempt
    from django.utils.decorators import method_decorator
    from rest_framework.versioning import URLPathVersioning
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    from . import models
    
    
    #创建token随机字符创,添加到数据库
    class LoginView(APIView):
        def post(self,request,*args,**kwargs):
            user_obj = models.UserInfo.objects.filter(**request.data).first()
            if not user_obj:
                return Response('登录失败')
            strs =  str(uuid.uuid4())
            user_obj.token = strs
            user_obj.save()
            return  Response(strs)
    
    #判断条件,用户是否存在
    from rest_framework.authentication import BaseAuthentication
    class TokenAuthentication(BaseAuthentication):
        def authenticate(self, request):
            token = request.query_params.get('token')
            user_obj = models.UserInfo.objects.filter(token=token).first()
            if user_obj:
                return (user_obj,token)
            return (None,None)
    
    #登录验证后缀有token返回正常,无返回gun
    class OrderView(APIView):
        authentication_classes = [TokenAuthentication, ]
        def get(self,request,*args,**kwargs):
            #print(request.user)
            #print(request.auth)
            if request.user:
                return Response("123")
            return Response('gun')
    
    
    

    4. 简化代码

    全局配置

    需要把TokenAuthentication 单独提出放到auth.py中

    setting中配置

        'DEFAULT_AUTHENTICATION_CLASSES':['renzheng.auth.TokenAuthentication',]
    
    

    5.postman测试

    http://127.0.0.1:8000/renzheng/order/?token=df2d6aba-187b-4e9c-af1b-fe025fdffde3
    http://127.0.0.1:8000/renzheng/order/
    
    

    总结

    当用户发来请求时,  dispash 找到认证的所有类并实例化成为对象列表,然后将对象列表封装到新的request对象中。
    
    以后在视同中调用request.user
    
    在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给
    request.user/request.auth 
    
    

    19.权限

    其他同上

    from  rest_framework.views import APIView
    from  rest_framework.response import Response
    from rest_framework.permissions import BasePermission
    
    #判断是否有权限
    class MyPermission(BasePermission):
        message ={'error':"无权限"}  #方式一
        #多条数据
        def has_permission(self, request, view):
            if request.user:
                return True
    
            return False
        # from  rest_framework import exceptions
        # return exceptions.PermissionDenied({'error':"无权限"})
        #单条数据
        def has_object_permission(self, request, view, obj):
    
            return False
    
    
    
    class OrderView(APIView):
        permission_classes = [MyPermission,]
        def get(self,request,*args,**kwargs):
            return Response('123')
    
    
    

    全局的话

    REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES":["",]
    }
    
    
    

    源码分析

    class APIView(View):
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        
        def dispatch(self, request, *args, **kwargs):
            封装request对象
            self.initial(request, *args, **kwargs)
            通过反射执行视图中的方法
    
    	def initial(self, request, *args, **kwargs):
            版本的处理
            # 认证
            self.perform_authentication(request)
    		
            # 权限判断
            self.check_permissions(request)
            
            
            self.check_throttles(request)
            
        def perform_authentication(self, request):
            request.user
    	
        def check_permissions(self, request):
            # [对象,对象,]
            for permission in self.get_permissions():
                if not permission.has_permission(request, self):
                    self.permission_denied(request, message=getattr(permission, 'message', None))
        def permission_denied(self, request, message=None):
            if request.authenticators and not request.successful_authenticator:
                raise exceptions.NotAuthenticated()
            raise exceptions.PermissionDenied(detail=message)
            
        def get_permissions(self):
            return [permission() for permission in self.permission_classes]
        
    class UserView(APIView):
        permission_classes = [MyPermission, ]
        
        def get(self,request,*args,**kwargs):
            return Response('user')
    
    
    

    20.跨域

    由于浏览器具有“同源策略”的限制。
    如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止。
    如果在不同域下发送ajax,浏览器的同源策略会阻止。
    
    
    • 域相同,永远不会存在跨域。
      • 非前后端分离,没有跨域。
      • 前后端分离,nginx分流不存在跨域。
    • 域不同时,才会存在跨域。
      • l拉勾网,前后端分离,存在跨域(设置响应头解决跨域)

    1.解决跨域:CORS

    本质在数据返回值设置响应头   #在服务端
    
    from django.shortcuts import render,HttpResponse
    
    def json(request):
        response = HttpResponse("JSONasdfasdf")
        response['Access-Control-Allow-Origin'] = "*"
        return response    
    
    

    2.跨域时,发送了2次请求

    在跨域时,发送的请求会分为两种:

    条件:
        1、请求方式:HEAD、GET、POST
        2、请求头信息:
            Accept
            Accept-Language
            Content-Language
            Last-Event-ID
            Content-Type 对应的值是以下三个中的任意一个
                                    application/x-www-form-urlencoded
                                    multipart/form-data
                                    text/plain
     
    注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
    
    
    • 简单请求,发一次请求。

      设置响应头就可以解决
      from django.shortcuts import render,HttpResponse
      
      def json(request):
          response = HttpResponse("JSONasdfasdf")
          response['Access-Control-Allow-Origin'] = "*"
          return response
      
      
      
    • 复杂请求,发两次请求。

      • 预检
      • 请求
      from django.views.decorators.csrf import csrf_exempt
      
      @csrf_exempt
      def put_json(request):
          response = HttpResponse("JSON复杂请求")
          if request.method == 'OPTIONS':
              # 处理预检
              response['Access-Control-Allow-Origin'] = "*"
              response['Access-Control-Allow-Methods'] = "PUT"
              return response
          elif request.method == "PUT":
              return response
      
      

    3.总结

    1. 由于浏览器具有“同源策略”的限制,所以在浏览器上跨域发送Ajax请求时,会被浏览器阻止。
    2. 解决跨域
      • 不跨域
      • CORS(跨站资源共享,本质是设置响应头来解决)。
        • 简单请求:发送一次请求
        • 复杂请求:发送两次请求

    21.访问频率限制

    用法

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    from rest_framework.throttling import AnonRateThrottle,BaseThrottle####
    
    class ArticleView(APIView):
        throttle_classes = [AnonRateThrottle,] #####
        def get(self,request,*args,**kwargs):
            return Response('文章列表')
    
    class ArticleDetailView(APIView):
        def get(self,request,*args,**kwargs):
            return Response('文章详细')
    
    
    REST_FRAMEWORK = {
        'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
        "ALLOWED_VERSIONS":['v1',],
        "DEFAULT_THROTTLE_RATES":{  ##############
            "anon":'3/m'
        }
    }
    #全局DEFAULT_AUTHENTICATION_CLASSES=[rest_framework.throttling.AnonRateThrottle]
    
    

    源码

    class BaseThrottle:
        """
        Rate throttling of requests.
        """
    
        def allow_request(self, request, view):
            """
            Return `True` if the request should be allowed, `False` otherwise.
            """
            raise NotImplementedError('.allow_request() must be overridden')
    
        def get_ident(self, request):
            """
            Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
            if present and number of proxies is > 0. If not use all of
            HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
            """
            xff = request.META.get('HTTP_X_FORWARDED_FOR')
            remote_addr = request.META.get('REMOTE_ADDR')
            num_proxies = api_settings.NUM_PROXIES
    
            if num_proxies is not None:
                if num_proxies == 0 or xff is None:
                    return remote_addr
                addrs = xff.split(',')
                client_addr = addrs[-min(num_proxies, len(addrs))]
                return client_addr.strip()
    
            return ''.join(xff.split()) if xff else remote_addr
    
        def wait(self):
            """
            Optionally, return a recommended number of seconds to wait before
            the next request.
            """
            return None
    
    
    class SimpleRateThrottle(BaseThrottle):
        """
        A simple cache implementation, that only requires `.get_cache_key()`
        to be overridden.
    
        The rate (requests / seconds) is set by a `rate` attribute on the View
        class.  The attribute is a string of the form 'number_of_requests/period'.
    
        Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
    
        Previous request information used for throttling is stored in the cache.
        """
        cache = default_cache
        timer = time.time
        cache_format = 'throttle_%(scope)s_%(ident)s'
        scope = None
        THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
    
        def __init__(self):
            if not getattr(self, 'rate', None):
                self.rate = self.get_rate()
            self.num_requests, self.duration = self.parse_rate(self.rate)
    
        def get_cache_key(self, request, view):
            """
            Should return a unique cache-key which can be used for throttling.
            Must be overridden.
    
            May return `None` if the request should not be throttled.
            """
            raise NotImplementedError('.get_cache_key() must be overridden')
    
        def get_rate(self):
            """
            Determine the string representation of the allowed request rate.
            """
            if not getattr(self, 'scope', None):
                msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                       self.__class__.__name__)
                raise ImproperlyConfigured(msg)
    
            try:
                return self.THROTTLE_RATES[self.scope]
            except KeyError:
                msg = "No default throttle rate set for '%s' scope" % self.scope
                raise ImproperlyConfigured(msg)
    
        def parse_rate(self, rate):
            """
            Given the request rate string, return a two tuple of:
            <allowed number of requests>, <period of time in seconds>
            """
            if rate is None:
                return (None, None)
            num, period = rate.split('/')
            num_requests = int(num)
            duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
            return (num_requests, duration)
    
        def allow_request(self, request, view):
            """
            Implement the check to see if the request should be throttled.
    
            On success calls `throttle_success`.
            On failure calls `throttle_failure`.
            """
            if self.rate is None:
                return True
    
            # 获取请求用户的IP
            self.key = self.get_cache_key(request, view)
            if self.key is None:
                return True
    
            # 根据IP获取他的所有访问记录,[]
            self.history = self.cache.get(self.key, [])
    
            self.now = self.timer()
    
            # Drop any requests from the history which have now passed the
            # throttle duration
            while self.history and self.history[-1] <= self.now - self.duration:
                self.history.pop()
            if len(self.history) >= self.num_requests:
                return self.throttle_failure()
            return self.throttle_success()
    
        def throttle_success(self):
            """
            Inserts the current request's timestamp along with the key
            into the cache.
            """
            self.history.insert(0, self.now)
            self.cache.set(self.key, self.history, self.duration)
            return True
    
        def throttle_failure(self):
            """
            Called when a request to the API has failed due to throttling.
            """
            return False
    
        def wait(self):
            """
            Returns the recommended next request time in seconds.
            """
            if self.history:
                remaining_duration = self.duration - (self.now - self.history[-1])
            else:
                remaining_duration = self.duration
    
            available_requests = self.num_requests - len(self.history) + 1
            if available_requests <= 0:
                return None
    
            return remaining_duration / float(available_requests)
    
    
    class AnonRateThrottle(SimpleRateThrottle):
        """
        Limits the rate of API calls that may be made by a anonymous users.
    
        The IP address of the request will be used as the unique cache key.
        """
        scope = 'anon'
    
        def get_cache_key(self, request, view):
            if request.user.is_authenticated:
                return None  # Only throttle unauthenticated requests.
    
            return self.cache_format % {
                'scope': self.scope,
                'ident': self.get_ident(request)
            }
    
    
    

    总结

    1. 如何实现的评率限制

      - 匿名用户,用IP作为用户唯一标记,但如果用户换代理IP,无法做到真正的限制。
      - 登录用户,用用户名或用户ID做标识。
      具体实现:
      	在django的缓存中 = {
              throttle_anon_1.1.1.1:[100121340,],
              1.1.1.2:[100121251,100120450,]
          }
      
      
          限制:60s能访问3次
          来访问时:
              1.获取当前时间 100121280
              2.100121280-60 = 100121220,小于100121220所有记录删除
              3.判断1分钟以内已经访问多少次了? 4 
              4.无法访问
          停一会
          来访问时:
              1.获取当前时间 100121340
              2.100121340-60 = 100121280,小于100121280所有记录删除
              3.判断1分钟以内已经访问多少次了? 0
              4.可以访问
      
      

    22..jwt

    用于在前后端分离时,实现用户登录相关。
    
    

    一般用户认证有2中方式:

    • token

      用户登录成功之后,生成一个随机字符串,自己保留一分+给前端返回一份。
      
      以后前端再来发请求时,需要携带字符串。
      后端对字符串进行校验。
      
      

      优势:

      • token只在前端保存,后端只负责校验。
      • 内部集成了超时时间,后端可以根据时间进行校验是否超时。
      • 由于内部存在hash256加密,所以用户不可以修改token,只要一修改就认证失败。
    • jwt

      用户登录成功之后,生成一个随机字符串,给前端。
      	- 生成随机字符串
      		{typ:"jwt","alg":'HS256'}     {id:1,username:'alx','exp':10}
      		98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928374
      		- 类型信息通过base64加密
      		- 数据通过base64加密
      		- 两个密文拼接在h256加密+加盐 base64url
      	- 给前端返回
      		98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375
      
      前端获取随机字符串之后,保留起来。
      以后再来发送请求时,携带98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375。
      
      
      后端接受到之后,
      	1.先做时间判断
          2.字符串合法性校验。
      
      

    安装

    pip3 install djangorestframework-jwt
    
    

    案例

    • app中注册

      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'api.apps.ApiConfig',
          'rest_framework',
          'rest_framework_jwt'
      ]
      
      
    • 用户登录

      import uuid
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import URLPathVersioning
      from rest_framework import status
      
      from api import models
      
      class LoginView(APIView):
          """
          登录接口
          """
          def post(self,request,*args,**kwargs):
              #1.根据用户名和密码验证用户登录
              user = models.UserInfo.objects.filter(username=request.data.get('username'),password=request.data.get('password')).first()
              if not user:
                  return  Response({'code':10001,'error':"用户名密码错误"})
              #2.根据user对象生成payload(中间值的数据)
              jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
              payload = jwt_payload_handler(user)
      
              #3.构造前面数据,base64加密,中间数据base64加密,前两段拼接然后做hs256加密(加盐),在做base64加密.生成token
      
              jwt_encode_hander = api_settings.JWT_ENCODE_HANDLER
              token = jwt_encode_hander(payload)
      
              return Response({'code':10000,'data':token})
      
      
      
    • 用户认证

      from rest_framework.views import APIView
      from rest_framework.response import Response
      
      # from rest_framework.throttling import AnonRateThrottle,BaseThrottle
      
      
      class ArticleView(APIView):
          # throttle_classes = [AnonRateThrottle,]
      
          def get(self,request,*args,**kwargs):
              # 获取用户提交的token,进行一步一步校验
              import jwt
              from rest_framework import exceptions
              from rest_framework_jwt.settings import api_settings
              jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
      
              jwt_value = request.query_params.get('token')
              try:
                  payload = jwt_decode_handler(jwt_value)
              except jwt.ExpiredSignature:
                  msg = '签名已过期'
                  raise exceptions.AuthenticationFailed(msg)
              except jwt.DecodeError:
                  msg = '认证失败'
                  raise exceptions.AuthenticationFailed(msg)
              except jwt.InvalidTokenError:
                  raise exceptions.AuthenticationFailed()
              print(payload)
      
              return Response('文章列表')
      
      
  • 相关阅读:
    C# WCF的POST请求包含Stream及多个参数
    C# Fakes
    C# 计算文件的MD5
    C# 获取计算机的硬件、操作系统信息
    整数拆分
    整数拆分问题的四种解法【转载】
    python爬虫
    【转载】Scrapy安装及demo测试笔记
    Python野生库
    【转载】python3安装scrapy之windows32位爬坑
  • 原文地址:https://www.cnblogs.com/zdqc/p/11845706.html
Copyright © 2020-2023  润新知