zoukankan      html  css  js  c++  java
  • Django基础篇

    1.1 Web框架本质----socket

      1、web框架本质

          1. 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。

          2. 真实web框架一般会分为两部分:服务器程序和应用程序。  

              1)服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理

              2)应用程序则负责具体的逻辑处理

      2、WSGI(Web Server Gateway Interface)

          1. 不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

          2. 这样,服务器程序就需要为不同的框架提供不同的支持,只有支持它的服务器才能被开发出的应用使用,这时就需要有一个标准

          3. WSGI是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。

      3、使用socket创建一个简单web服务

    import socket
    
    def handle_request(client):
        buf = client.recv(1024)
        client.send("HTTP/1.1 200 OK
    
    ".encode('utf-8'))      # 伪造浏览器请求头
        client.send("Hello, Seven".encode('utf-8'))                  # 返回数据到客户端浏览器
    
    def main():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('localhost', 8000))
        sock.listen(5)
        while True:
            connection, address = sock.accept()
            handle_request(connection)
            connection.close()
    
    if __name__ == '__main__':
        main()
    使用socket创建一个简单web服务

      4、自定义Web框架

          说明: 通过python标准库提供的wsgiref模块开发一个自己的Web框架

    from wsgiref.simple_server import make_server
    
    def handle_index():
        return ['<h1>Hello, Index!</h1>'.encode('utf-8'),]
    def handle_date():
        return ['<h1>Hello, date!</h1>'.encode('utf-8'),]
    
    URl_DICT = {
        "/index":handle_index,
        "/date":handle_date,
    }
    
    #1 environ客户端发来的所有数据
    #2 start_response封装要返回给用户的数据,响应头状态
    #3 return返回的是正真用户在浏览器中看到的内容(Hello, web!)
    def RunServer(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html')])
        current_url = environ['PATH_INFO']                           #用户访问的url目录:/index 或 /date
        func = None
        if current_url in URl_DICT:
            func = URl_DICT[current_url]
            return func()
        else:
            return ['<h1>404</h1>'.encode('utf-8'),]
    
    if __name__ == '__main__':
        httpd = make_server('', 8001, RunServer)
        print("Serving HTTP on port 8000...")
        httpd.serve_forever()
    自定义web框架

    1.2 MVC和MTV架构

      1、MVC架构

          1. MVC架构把一个完整的程序或者网站项目分成三个主要的组成部分,分别是Model模型,View视图,Controller控制器

          2. 希望一个项目可以让内部数据的储存方式,外部的可见部分以及过程控制逻辑相互配合运行

          3. 进一步简化项目复杂度,提高可扩充性,维护性,有助于不同成员之间的分工

      2、MTV框架(Django)

          1. 对于网站而言,网页服务器在接收到远程浏览器的请求的时候,不同的网址做出不同的响应

          2. 有不同的链接方式其实就隐含了逻辑控制,因此很难严谨的将其定义为上述三个部分

          3. 因此Django另外设计了MTV结构(Model,Template,View)。

      3、MVC vs MTV

        MVC

            Model       View          Controller

            数据库      模板文件      业务处理

        MTV

            Model      Template       View

            数据库      模板文件      业务处理

    1.3 Django常用命令

      1、安装Django

          pip3 install django

      2、创建Django工程

          D:>  django-admin startproject laoniu                                               //创建项目名称laoniu

          D:>  cd laoniu

          D:laoniu>  python manage.py runserver 127.0.0.1:8002                 //运行laoniu即可用浏览器访问了

          D:laoniu>  python manage.py createsuperuser                               //创建Django admin用户

      3、创建app

          python manage.py startapp app01

      4、创建表  

          python manage.py makemigrations
          python manage.py migrate

      5、执行python manage.py migrate无法创建表

          执行时报:No migrations to apply.

          1. 进入数据库,找到django_migrations的表,删除该app名字对应的所有记录

              python manage.py dbshell
              delete from django_migrations where app='app01';

          2. 删除该app名字下的migrations下的除了__init__.py之外的文件。

          3. 执行下面这两条命令:(在项目目录下)

              python manage.py makemigrations
              python manage.py migrate

                    注:如果个别app表依然无法建立可以在后面添加app名称:python manage.py makemigrations/migrate app01

      6、“Column 'last_login' cannot be null”报错

          mysql> SELECT * FROM django_migrations;

          mysql> TRUNCATE TABLE django_migrations;

          $ python manage.py migrate --fake-initial
          Make sure this message appears: 0005_alter_user_last_login_null - [OK]

      7、windows中杀死指定端口号进程

          D:>netstat -aon|findstr "8000"
          D:>taskkill /PID 12516 -t -f

    1.4 配置settings.py文件

      1、配置模板的路径

    TEMPLATES = [
        {
           'DIRS': [os.path.join(BASE_DIR,'templates')],
        },
    ]

      2、 配置静态目录

    #像ccs和js这些静态文件如果想要使用必须在这里配置路径
    STATICFILES_DIRS = (
        os.path.join(BASE_DIR,'static'),
    )

      3、注释CSRF

    MIDDLEWARE = [
        # 'django.middleware.csrf.CsrfViewMiddleware',
    ]

      4、修改settings.py中时区

    # LANGUAGE_CODE = 'en-us'
    LANGUAGE_CODE = 'zh-hans'
    
    # TIME_ZONE = 'UTC'
    TIME_ZONE = 'Asia/Shanghai'
    修改settings.py中时区

    1.5 Django各种url写法

      1、无正则匹配url  (http://127.0.0.1:8000/index/?nid=1&pid=2)

    urlpatterns = [
        url(r'^index/', views.index),
    ]
    urls.py
    from django.shortcuts import render
    
    def index(request):
        print(request.GET.get('nid'))             # 1
        print(request.GET.get('pid'))             # 2
        return render(request,'index.html')
    views.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <a href="/index/?nid=1&pid=2"> url.py中不必用正则匹配 </a>
    </body>
    </html>
    index.html

      2、基于(d+)正则的url

    urlpatterns = [
        url(r'^index/(d+)/(d+)/', views.index),
    ]
    urls.py
    def index(request,nid,pid):
        return render(request,'index.html')
    views.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <a href="/index/1/2/"> url.py中必须使用正则匹配传入的参数 </a>
    </body>
    </html>
    index.html

      3、基于正则分组(?P<nid>d+),可以不考虑接收参数顺序 (推荐)

    urlpatterns = [
        url(r'^index/(?P<nid>d+)/(?P<pid>d+)/', views.index),
    ]
    urls.py
    def index(request,nid,pid):
        return render(request,'index.html')
    views.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <a href="/index/1/2/"> 使用分组匹配,不必关心传入参数顺序 </a>
    </body>
    </html>
    index.html

      4、使用name构建自己想要的url

         1. 使用name在前端和后端分别构建自己想要的url

    urlpatterns = [
        url(r'^index1/', views.index,name='indexname1'),
        url(r'^index2/(d+)/(d+)/', views.index,name='indexname2'),
        url(r'^index2/(?P<pid>d+)/(?P<nid>d+)/', views.index,name='indexname3'),
    ]
    urls.py中定义路由系统
    from django.shortcuts import render
    from django.core.urlresolvers import reverse
    
    def index(request,*args,**kwargs):
        url1 = reverse('indexname1')                                      # /index1/
        url2 = reverse('indexname2', args=(1,2,))                         # /index2/1/2/
        url3 = reverse('indexname3', kwargs={'pid': 11, "nid":22})        # /index2/11/22/
        return render(request,'index.html')
    views.py中根据reverse模块构建url路径
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <p><a href="{% url 'indexname1' %}">  http://127.0.0.1:8000/index/  </a></p>
        <p><a href="{% url 'indexname2' 1 2 %}">  http://127.0.0.1:8000/index2/1/2/  </a></p>
        <p><a href="{% url 'indexname3' 11 22 %}">  http://127.0.0.1:8000/index2/11/22/  </a></p>
    </body>
    </html>
    index.html中构建url路径:前端

        2. 根据request.path中的绝对路径反解出url中的name名字

    resolve_url_obj = resolve(request.path)    #request.path路径: /student/homework_detail/52
    resolve_url_obj.url_name                   #从path中解析出url名字 url_name = homework_detail

      5、Django路由分发    并使用name构建url路径

          作用:对URL路由关系进行命名, ***** 以后可以根据此名称生成自己想要的URL *****

    from django.conf.urls import url,include
    from app01 import urls
    
    urlpatterns = [
        url(r'^app01/', include("app01.urls", app_name='app01', namespace='app01')),
    ]
    /project/urls.py
    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        url(r'^index/$', views.index, name='index'),
    ]
    /app01/urls.py
    from django.shortcuts import render
    
    def index(request):
        return render(request,'index.html')
    views.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <p> <a href="{% url "app01:index" %}">  http://127.0.0.1:8000/app01/index/</a></p>
    </body>
    </html>
    index.html

    1.6 Django的CBV和FBV

      1、FBVfunction  base  view:views.py文件中使用函数

    def index(request):
        return render(request, 'index.html')
    FBV写法

      2、CBV(class  base  view):在views.py文件中使用类

          1、 dispatch是父类中用来反射的函数,找对应的函数(比对应函数先执行)

          2、 比如你发送post请求就可以通过dispatch找到对应的post函数进行处理,get就会找到get函数处理

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        url(r'^home/',views.Home.as_view()), #as_view是指定调用view的as_view这个方法固定写法
    ]
    CBV:urls.py
    from django.shortcuts import HttpResponse
    
    from django.views import View
    class Home(View):
        '''使用CBV时必须要继承view父类'''
        def dispatch(self, request, *args, **kwargs):
            # 调用父类中的dispatch
            result = super(Home,self).dispatch(request, *args, **kwargs)
            # 使用result主动继承view父类,然后return就可以重写父类的dispath方法
            return result
        # 在这里使用get发来请求就会调用get方法,使用post发来请求就会调用post方法
        def get(self,request):
            print(request.method)
            return HttpResponse('get')
    
        def post(self,request):
            print(request.method,'POST')
            return HttpResponse('post')
    CBV:views.py
    from django.shortcuts import HttpResponse
    from django.views import View
    from django.utils.decorators import method_decorator
    from django.views.decorators.csrf import csrf_exempt
    
    class Home(View):
        '''使用CBV时必须要继承view父类'''
        @method_decorator(csrf_exempt)        # 指定Home这个视图函数不使用csrf
        def dispatch(self, request, *args, **kwargs):
            # 调用父类中的dispatch
            result = super(Home,self).dispatch(request, *args, **kwargs)
            # 使用result主动继承view父类,然后return就可以重写父类的dispath方法
            return result
        # 在这里使用get发来请求就会调用get方法,使用post发来请求就会调用post方法
        def get(self,request):
            print(request.method)
            return HttpResponse('get')
    
        def post(self,request):
            print(request.method,'POST')
            return HttpResponse('post')
    CBC:指定某个视图函数不使用csrf验证

    1.7 模板渲染

      1、for循环

    <body>
        {% for k in user_dic.keys %}
            <p>{{ k }}</p>
        {% endfor %}
    
        {% for v in user_dic.values %}
            <p>{{ v }}</p>
        {% endfor %}
    
        {% for k,v in user_dic.items %}
            <p>{{ k }}--{{ v }}</p>
        {% endfor %}
    </body>
    for循环字典
    <body>
        {% for k in l %}
            <p>{{ k }}</p>
        {% endfor %}
    </body>
    for循环列表

      2if语句

    def index(request):
        return render(request,'index.html',{
            'current_user':"alex",
            'user_list':['alex','eric'],
            'user_dict':{'k1':'v1', 'k2':'v2'}
        })
    views.py视图函数
    <div>{{current_user}}</div>
    
    <a> {{ user_list.1 }} </a>          #在模板语言中获取列表中的元素
    <a> {{ user_dict.k1 }} </a>         #在模板语言中获取字典中元素
    
    {% if age %}
       <a>有年龄</a>
       {% if age > 16 %}
          <a>老男人</a>
       {% else %}
          <a>小鲜肉</a>
       {% endif %}
    {% else %}
       <a>无年龄</a>
    {% endif %}
    index.html模板渲染

       3、ifequal判断值相等

            {% ifequal a1 a2 %}
                <h1>equal!</h1>
            {% else %}
                <h1>not equal!</h1>
            {% endifequal %}
    判断两个值是否相等

      4、in判断在列表中

    {% if '讲师' in roles %}
    <ul class="nav nav-sidebar">
      <li><a data-url="/webapp/staff/attendance/">发起签到</a></li>
      <li><a data-url="/webapp/staff/score/">学员成绩</a></li>
    </ul>
    {% endif %}
    in判断是否在列表中

      5、HTML中转换时间格式

    # 1、使用下面方法转换
        <td>{{ c.start_time|date:'Y-m-d' }}</td>
    
    # 2、转换前显示的格式
        July 2, 2017, 10:27 a.m.
    
    # 3、转换后的格式
        2017-07-02
    时间格式化

       6、forloop:显示数据序号

    # 1、<td>{{ forloop.counter }}</td>
    # 2、<td>{{ forloop.counter0 }}</td>
    # 3、<td>{{ forloop.revcounter }}</td>
    # 4、<td>{{ forloop.revcounter0 }}</td>
    # 5、<td>{{ forloop.last }}</td>
    # 6、<td>{{ forloop.first }}</td>
    forloop模板语言中显示数据序号

      7、{% for detail in parameter|safe|json_to_obj %}

    @register.filter(name='json_to_obj')
    def json_to_obj(obj):
        return json.loads(obj)
        
    {% for detail in parameter|safe|json_to_obj %}    
    
    
    {% endfor %}
    for循环时 json化

    1.8 自定义simple_tag和自定义filter

      作用: 在模板语言中对数据加工后返回

      1、自定义simple_tag步骤

          1、app下创建templatetags目录,并在templatetags目录下新建任意python文件(比如:xxoo.py)

          2、settings中注册APP

          3、在templates/下的html文件中引用

      2、在templatetags/xxoo.py文件中写处理函数

    from django import template
    from django.utils.safestring import mark_safe
    
    register = template.Library()      # 对象名register不可变
    
    @register.simple_tag               #1 simple_tag用法
    def houyafan(a1,a2):
        return a1 + a2
    
    @register.filter                  #2 filter用法
    def jiajingze(a1,a2):
        return a1 + a2
    在templatetags/xxoo.py文件中写处理函数
    <!-- 1、 在html文件最顶部引用xxoo.py文件  -->
    {% load xxoo %}
    <html>
    <head lang="en">
        <meta charset="UTF-8">
    </head>
    <body>
    <!-- 2、在html文件最顶部引用xxoo.py文件  -->
    <!-- houyafan是xxoo.py文件中函数名,2 4是传入的参数 显示到页面的结果是2+4是6 -->
    
        {% houyafan 2 4 %}
    
    
    <!-- 3、filter参数格式  参数1|函数名:参数2  -->
    
        {{ "maliya"|jiajingze:"LS" }}
    
        {% if "maliya"|jiajingze:"LS" %}
    
        {% endif %}
    </body>
    </html>
    在templates/index.html文件中使用

      3、自定义simple_tag和自定义filter优缺点比较

        1、自定simple_tag

            缺点:   不能作为if条件

            优点:  参数任意个

        2、自定义filter

            缺点:  最多两个参数,不能加空格

            优点:  能作为if条件

    1.9 模板继承

       1、模板继承使用

           1. 在master.html中定义模板:  {% block css %} {% endblock %}

           2. 在子类中引入要继承的模板:  {% extends 'master.html' %}

       2、模板导入

           1. 使用时直接导入即可:  {% include "tag.html" %}

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
    
    {#1、子类标题#}
        <title>{% block title %}{% endblock %}</title>
    
        <link rel="stylesheet" href="/static/commons.css">
    {#2、子类css样式#}
        {% block css %} {% endblock %}
    
    </head>
    <body>
        <div class="pg-header">小男孩管理</div>
    
    {#3、子类页面主要内容#}
        {% block content %}{% endblock %}
    
        <script src="/static/jquery.js"></script>
    {#4、子类js样式#}
        {% block js %} {% endblock %}
    
    </body>
    </html>
    模板(templates/master.html)
    {#1 extends关键字引入要继承的模板#}
    {% extends 'master.html' %}
    
    {#2 有些时候我们继承的时候想要有一些自己的东西,比如:标题#}
    {% block title %} 用户管理 {% endblock %}
    
    {#3 我们可以将每个页面自己的内容放到这里#}
    {% block content %}
        <h1>用户管理</h1>
        <ul>
            {% for i in u %}
                <li>{{ i }}</li>
            {% endfor %}
        </ul>
    
        
    {#模板导入 这里的”tag.html”在右边#}
    {% include "tag.html" %}
    
    {% endblock %}
    
    
    {#4 本网页自己的css样式#}
    {% block css %}
        <style>
            body{
                background-color: red;
            }
        </style>
    {% endblock %}
    
    {#5 本网页自己的js样式#}
    {% block js %}
        <script>
            alert(1234)
        </script>
    {% endblock %}
    在子类中继承模板中内容

    1.10 前后端交互:提交数据、提交文件

    <form action="/login/" method="post" enctype="multipart/form-data">
        <p>
            <input type="text" name="user" placeholder="用户名">
        </p>
        
        {# 1、单选框,返回单条数据的列表 #}
        <p>     
            男:<input type="radio" name="gender" value="1">
            女:<input type="radio" name="gender" value="2">
            张扬:<input type="radio" name="gender" value="3">
        </p>
    
        {# 2、多选框、返回多条数据列表 #}
        <p>     
            男:<input type="checkbox" name="favor" value="11">
            女:<input type="checkbox" name="favor" value="22">
            张扬:<input type="checkbox" name="favor" value="33">
        </p>
    
        {# 3、多选,返回多条数据的列表 #}
        <p>     
            <select name="city" multiple>
                <option value="bj">北京</option>
                <option value="sh">上海</option>
                <option value="gz">广州</option>
            </select>
        </p>
    
        {# 4、提交文件 #}
        <p><input type="file" name="fff"></p>
        <input type="submit" value="提交">
    </form>
    index.html提交数据
    def login(request):
        if request.method == 'GET':
            return render(request,'login.html')
        elif request.method == 'POST':
            v = request.POST.get('gender')      #1 获取单选框的value值
            v = request.POST.getlist('favor')   #2 获取多选框value:['11', '22', '33']
            v = request.POST.getlist('city')    #3 获取多选下拉菜单:['bj', 'sh', 'gz']
            print(v.name,type(v))
    
    
            #当服务器端取客户端发送来的数据,不会将数据一下拿过来而是一点点取(chunks就是文件分成的块)
            
            obj = request.FILES.get('fff')      #4 下面是获取客户端上传的文件(如:图片)
            import os
            file_path = os.path.join('upload',obj.name)
            f = open(file_path,mode='wb')
            for i in obj.chunks():
                f.write(i)
            f.close()
    
            return render(request,'login.html')
    views.py获取数据
    # 1、request.POST
    # 2、request.GET
    # 3、request.FILES
    # 4、request.getlist
    # 5、request.method
    # 6、request.path_info                   #获取当前url
    # 7、request.body                        #自己获取数据
    # 8、a = ‘中国’
        response = HttpResponse(a)
        response[“name”] = ‘alex’        #HttpResponse不仅可以在响应头中传字符串,也可以传键值对
        response.set_cookie()                #设置cookie实质也是向请求头中传入键值对
    request获取数据方式

    1.11 Django生命周期与中间件

      1、中间件处理过程

          1、首先客户端发起请求,会将请求交给settings.py中排在最前面的中间件

          2、前面中间件收到请求会调用类中的process_request方法处理,然后交给下一个中间件的process_request函数

          3、到达最后一个中间件的process_request函数处理后会到达url路由系统

          4、然后从路由系统直接跳转到第一个中间件的process_view函数,依次向后面中间的process_view传递,最后
              到达views.py处理函数,获取网页中的数据

          5、获取的数据会交给最后一个中间件的process_response方法处理,然后依次向前面的中间件process_response
              方法提交请求的内容,最后由最前面的中间件将请求数据返回到客户端

          6、在任一中间件的process_request和process_view方法中有返回值就会直接返回给process_response

      2、生命周期图解

                     

      3、Django生命周期请求过程

        1客户端访问

          1、客户端在浏览器中输入url路径访问指定网页

        2.  请求发送给Django程序

          1、首先会交给中间件,中间件处理后交给路由系统

          2、路由系统

                1:Django程序会到urls.py文件中找到对应请求的处理函数(视图函数)

          3、视图函数

                1:视图函数会找到对应的html模板文件

                2:然后到数据库中取得数据替换html模板中的内容

                3:使用static中的js和css文件结合对html渲染

                4:最后Django将最终渲染后的html文件返回给中间件

          4、中间件再调用process_response方法处理,最后交给用户

    1.12 中间件使用举例

      1、创建存放中间件的文件夹

          1) 在工程目录下创建任意目录,这里创建路径为: /project/middle/m1.py

      2、在project/settings.py文件中注册我们自己的中间件

    MIDDLEWARE = [
        'middle.m1.Row1',
        'middle.m1.Row2',
        'middle.m1.Row3',
    ]

      3、 在views.py文件中写处理函数test

    def test(request):
        # int('fds')    #当views函数出现异常,中间件中的process_exception执行
        print('没带钱|')
        return HttpResponse('ok')
    views.py处理函数

      4、在/project/middle/m1.py文件中定义中间件

    from django.utils.deprecation import MiddlewareMixin
    class Row1(MiddlewareMixin):
        def process_request(self,request):
            print('process_request_1')
    
        def process_view(self,request, view_func, view_func_args, view_func_kwargs):
            #view_func_args:   url中传递的非字典的值会用这个变量接收
            #view_func_kwargs: url中传递的字典会传递到这个变量接收(如:nid=1)
            print('process_view_1')
    
        def process_response(self,request, response):    #response就是拿到的返回信息
            print('response_1')
            return response
    
        def process_exception(self, request, exception):
            '''只有当views函数中异常这个函数才会执行'''
            if isinstance(exception, ValueError):
                return HttpResponse('>>出现异常了')
    
    class Row2(MiddlewareMixin):
        def process_request(self,request):
            print('process_request_2')
            #1 如果在Row2中的process_request中有返回值,那么就不会到达Row3
            #2 Row2直接将返回值交给自己的process_response再交给Row1的process_response
            #3 最后客户端页面显示的就是‘走’请求没机会到达views函数,不会打印‘没带钱’
            # return HttpResponse('走')
    
        def process_view(self,request, view_func, view_func_args, view_func_kwargs):
            print('process_view_2')
    
        def process_response(self,request, response):
            print('response_2')
            return response
    
    class Row3(MiddlewareMixin):
        def process_request(self,request):
            print('process_request_3')
    
        def process_view(self,request, view_func, view_func_args, view_func_kwargs):
            print('process_view_3')
    
        def process_response(self,request, response):
            print('response_3')
            return response
    在/project/middle/m1.py文件中定义中间件
  • 相关阅读:
    JDK API文档下载
    idea技巧:查看一个类的所有子类以及子类的子类并以层级关系显示
    Vue项目优化
    deepin 安装netcore 记录
    UOS 下的VScode 使用经验
    UOS操作系统磁盘空间不够一例
    SRPBatcher优化的原理
    我的UOS生活
    假如美国禁用了Unity3D和Unreal怎么办
    BGFX学习笔记01
  • 原文地址:https://www.cnblogs.com/jiaxinzhu/p/12571708.html
Copyright © 2011-2022 走看看