• Django补充知识点——用户管理


    内容概要

    1、Form表单
    2、Ajax
    3、布局,Django母板
    4、序列化
    5、Ajax相关
    6、分页
    7、XSS攻击
    8、CSRF
    9、CBV、FBV

    10、类中用装饰器的两种方法

    11、上传文件

     12、数据库正向查询、反向查询、多对多查询

    13、jQuery对象和DOM对象可以互相转换

    14、cookie和session


    用户管理,功能:
    1、用户登录
    2、注册
    3、注销
    4、后台管理菜单
    5、班级操作
    6、老师、学生


    补充知识点:

    前端提交数据到后端的两种方法:

    ——form表单

    ——ajax

     

    1、Form表单                                                                                                

    用法:

    通过type=submit提交
    一般表单提交通过type=submit实现,input type="submit",浏览器显示为button按钮,通过点击这个按钮提交表单数据跳转到/url.do
    <form action="/url.do" method="post">
       <input type="text" name="name"/>
       <input type="submit" value="提交">
    </form>  

    学生管理的添加页面中,下拉框选班级用select   option标签

    add_student.html中
    <form action="/add_student.html" method="POST">
    
            <p>
                <input placeholder="学生姓名" type="text" name="name" />
            </p>
            <p>
                <input placeholder="学生邮箱" type="text" name="email" />
            </p>
            <p>
                <!-- <input placeholder="班级ID" type="text" name="cls_id" /> -->
    {#             form表单提交时会将select里面选中的结果cls_id=value的值一起提交过去 #}
                <select name="cls_id">
                    {% for op in cls_list %}
                        <option value="{{ op.id }}">{{ op.caption }}</option>
                    {% endfor %}
                </select>
            </p>
            <input type="submit" value="提交"/>
        </form>
    

     

    views中的函数
    
    def add_student(request):
        if request.method == "GET":
            cls_list = models.Classes.objects.all()[0: 20]
            return render(request, 'add_student.html', {'cls_list': cls_list})
        elif request.method == "POST":
            name = request.POST.get('name')
            email = request.POST.get('email')
            cls_id = request.POST.get('cls_id')
            models.Student.objects.create(name=name,email=email,cls_id=cls_id)
            return redirect('/student.html')
    

     

    详细内容:

    表单标签<form>

          表单用于向服务器传输数据。

    1.表单属性

      HTML 表单用于接收不同类型的用户输入,用户提交表单时向服务器传输数据,从而实现用户与Web服务器的交互。表单标签, 要提交的所有内容都应该在该标签中.

                action: 表单提交到哪. 一般指向服务器端一个程序,程序接收到表单提交过来的数据(即表单元素值)作相应处理,比如https://www.sogou.com/web

                method: 表单的提交方式 post/get 默认取值 就是 get(信封)

                              get: 1.提交的键值对.放在地址栏中url后面. 2.安全性相对较差. 3.对提交内容的长度有限制.

                              post:1.提交的键值对 不在地址栏. 2.安全性相对较高. 3.对提交内容的长度理论上无限制.

                              get/post是常见的两种请求方式.

    2.表单元素

               <input>  标签的属性和对应值              

    type:        text 文本输入框
    
                 password 密码输入框
    
                 radio 单选框
    
                 checkbox 多选框  
    
                 submit 提交按钮            
    
                 button 按钮(需要配合js使用.) button和submit的区别?
    
                 file 提交文件:form表单需要加上属性enctype="multipart/form-data"   
    
     name:    表单提交项的键.注意和id属性的区别:name属性是和服务器通信时使用的名称;而id属性是浏览器端使用的名称,该属性主要是为了方便客
              户端编程,而在css和javascript中使用的
     value:   表单提交项的值.对于不同的输入类型,value 属性的用法也不同:
    
    1.type="button", "reset", "submit" - 定义按钮上的显示的文本
     
    2.type="text", "password", "hidden" - 定义输入字段的初始值
     
    3.type="checkbox", "radio", "image" - 定义与输入相关联的值  
     checked:  radio 和 checkbox 默认被选中
    
    4.readonly: 只读. text 和 password
    
    5.disabled: 对所用input都好使.

    上传文件注意两点:

     1 请求方式必须是post

     2 enctype="multipart/form-data"

     

    实例:接收文件的py文件

    def index(request):
        print request.POST
        print request.GET
        print request.FILES
        for item in request.FILES:
            fileObj = request.FILES.get(item)
            f = open(fileObj.name, 'wb')
            iter_file = fileObj.chunks()
            for line in iter_file:
                f.write(line)
            f.close()
        return HttpResponse('ok')
    

      

    2、Ajax                                                                                                                       

    基于jQuery的ajax:

    $.ajax({
            url:"//",
            data:{a:1,b:2},   /*当前ajax请求要携带的数据,是一个json的object对象,ajax方法就会默认地把它编码成某种格式(urlencoded:?a=1&b=2)发送给服务端;*/
            dataType:   /*预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。*/
            type:"GET",
            success:function(){}
        })  
    

     

    基于JS的ajax:

      采用ajax异步方式,通过js获取form中所有input、select等组件的值,将这些值组成Json格式,通过异步的方式与服务器端进行交互,
    一般将表单数据传送给服务器端,服务器端处理数据并返回结果信息等

      用法:

    1. 处理浏览器兼容问题,来创建XMLHttpRequest对象:
    2. 创建XMLHttpRequest对象;
    3. 调用open()方法打开与服务器的连接;
    4. 调用send()方法发送请求;
    5. 为XMLHttpRequest对象指定onreadystatechange事件函数,这个函数会在 XMLHttpRequest的1、2、3、4,四种状态时被调用;
    <h1>AJAX</h1>
    <button onclick="send()">测试</button>
    <div id="div1"></div>
    
    
    <script>
           function createXMLHttpRequest() {
                try {
                    return new XMLHttpRequest();//大多数浏览器
                } catch (e) {
                    try {
                        return new ActiveXObject("Msxml2.XMLHTTP");
                    } catch (e) {
                        return new ActiveXObject("Microsoft.XMLHTTP");
                    }
                }
            }
    
            function send() {
                var xmlHttp = createXMLHttpRequest();
                xmlHttp.onreadystatechange = function() {
                    if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                        var div = document.getElementById("div1");
                        div.innerText = xmlHttp.responseText;
                        div.textContent = xmlHttp.responseText;
                    }
                };
    
                xmlHttp.open("POST", "/ajax_post/", true);
                //post: xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                xmlHttp.send(null);  //post: xmlHttp.send("b=B");
            }
    
    
    </script>
           
    #--------------------------------views.py 
    from django.views.decorators.csrf import csrf_exempt
    
    def login(request):
        print('hello ajax')
        return render(request,'index.html')
    
    @csrf_exempt   #csrf防御
    def ajax_post(request):
        print('ok')
        return HttpResponse('helloyuanhao')
    

     

      注:POST请求时,要在send之前,open之后加请求头

    3、布局,Django母板                                                                                                 

    母板中:

    {% block title(给这个block取一个名字) %}
    ………………
    {% endblock %}

      

     子模板中:

    {% extends "base.html" %}#继承自“base.html”
     
    {% block title %}Future time{% endblock %}
     
    {% block content %}
    <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
    {% endblock %}
    

      

     8.CSRF                                                                                                                         

    {%csrf_token%}:csrf_token标签

         用于生成csrf_token的标签,用于防治跨站攻击验证。注意如果你在view的index里用的是render_to_response方法,不会生效,而应该用render。

         其实,这里是会生成一个input标签,和其他表单标签一起提交给后台的。

    <form action="{% url "bieming"%}" >
              <input type="text">
              <input type="submit"value="提交">
              {%csrf_token%}
    </form>
    

      

     

    9.FBV 和 CBV                                                                                                                 

    views.py
    # 方法一:FBV
    def login(request):
        if request.method == "POST":
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            c = models.Administrator.objects.filter(username=user,password=pwd).count()
            print(c)
            if c:
                #设置session中存储的数据
                request.session["is_login"] = True
                request.session["username"] = user
                #尤其注意跳转到哪个页面!!!
                return render(request,"index.html",{"username":user})
            else:
                msg = "用户名或密码错误"
                return redirect("/login.html",{"msg":msg})
    
        return render(request,"login.html")
    -----------------------------------------------------------------
    urs.py
    # url(r'^login.html$', views.login),
    

      cbv:

    #CBV
    from django import views
    class Login(views.View):
        def get(self,request,*args,**kwargs):
            return render(request, "login.html")
    
        def post(self,request,*args,**kwargs):
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            print(c)
            if c:
                #设置session中存储的数据
                request.session["is_login"] = True
                request.session["username"] = user
                #尤其注意跳转到哪个页面!!!
                return render(request,"index.html",{"username":user})
            else:
                msg = "用户名或密码错误"
                return redirect("/login.html",{"msg":msg})
    -----------------------------------------------------
    urs.py
    
    url(r'^login.html$', views.Login.as_view()),#以类的方式进行创建
    

      

    10.类中用装饰器                                                                                                            

     方法一:自定义装饰器

    from django import views
    from django.utils.decorators import method_decorator#1.引入库
    #2.定义装饰器函数
    def outer(func):
        def inner(request,*args,**kwargs):
            print(request.method)
            return func(request,*args,**kwargs)
        return inner
    
    class Login(views.View):
        #3.使用装饰器
        @method_decorator(outer)
        def get(self,request,*args,**kwargs):
            return render(request, "login.html")
    

      

     方法二:自定义dispatch方法,同时继承父类该方法,等同于装饰器

    from django import views
    
    class Login(views.View):
        #1.先执行自定义的dispatch方法
        def dispatch(self, request, *args, **kwargs):
            print(11111)
            if request.method == "GET":
                return HttpResponse("ok")
            #2.再调用父类中的dispatch方法,dispatch方法类似路由分发
            ret = super(Login,self).dispatch(request, *args, **kwargs)
            print(22222)
            return ret
    
        def get(self,request,*args,**kwargs):
            return render(request, "login.html")
    
        def post(self,request,*args,**kwargs):
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            print(c)
            if c:
                #设置session中存储的数据
                request.session["is_login"] = True
                request.session["username"] = user
                #尤其注意跳转到哪个页面!!!
                return render(request,"index.html",{"username":user})
            else:
                msg = "用户名或密码错误"
                return redirect("/login.html",{"msg":msg})
    

       

    11、上传文件

    文件上传
    ----- Form表单上传文件 基于FormData() (上传后,页面需要刷新后才能显示)
    ----- Ajax上传文件 基于FormData() (无需刷新页面)
    ----- 基于form表单和iframe自己实现ajax请求 (因为有些浏览器可能不兼容FormData())


    a.form表单上传

    -------------------------views.py--------------------------------
    import os
    def upload(request):
        if request.method == 'GET':
            img_list = models.Img.objects.all()
            return render(request,'upload.html',{'img_list': img_list})
        elif request.method == "POST":
            user = request.POST.get('user')
            fafafa = request.POST.get('fafafa')
            obj = request.FILES.get('fafafa') # request.FILES.get来接收文件
    
            file_path = os.path.join('static','upload',obj.name) #地址拼接
            f = open(file_path, 'wb')
            for chunk in obj.chunks():  #obj.chunks很多块文件(文件是分块接收的)
                f.write(chunk)
            f.close()
    
            models.Img.objects.create(path=file_path)   #将图片保存到数据库
    
            return redirect('/upload.html')
    
    --------------------------------------upload.html------------------------
    <form method="POST" action="/upload.html" enctype="multipart/form-data">
            <input type="text" name="user" />
            <input type="file" name="fafafa" />
            <input type="submit" value="提交" />
        </form>
    
        <div>
            {% for item in img_list %}
                <img style="height: 200px; 200px;" src="/{{ item.path }}" />
            {% endfor %}
        </div>
    -----------------------------models.py------------------------------------
    class Img(models.Model):
        path = models.CharField(max_length=128)


    b. 悄悄的上传(ajax)

    xmlHttpRequest
                xml = new XMLHttpRequest();
                xml.open('post', '/upload.html', true) #以post方式发送到/upload.html,以异步方式发
                xml.send("k1=v1; k2=v2;")
    
     jQuery 
                $.ajax({
                    url:
                    data: {'k1': 'v1', 'k2': 'v2'}
                })
                
      FormData对象
                dict = new FormData()
                dict.append('k1','v1');
                dict.append('k2','v2');
                dict.append('fafafa', 文件对象);
                
                xml.send(dict)
                或
                $.ajax({
                    url:
                    data: dict,
                })
    

     实例:让用户看到当前上传的图片

    三种ajax方法的实例:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传文件</title>
        <style>
            .container img{
                 200px;
                height: 200px;
            }
        </style>
    
        <script>
            function li(arg) {
                console.log(arg);
            }
        </script>
    </head>
    <body>
        <h1>测试Iframe功能</h1>
        <input type="text" id="url" />
        <input type="button" value="点我" onclick="iframeChange();" />
        <iframe  id="ifr" src=""></iframe>
        <hr/>
        <h1>基于iframe实现form提交</h1>
        <form action="/upload.html" method="post" target="iframe_1" enctype="multipart/form-data">
            <iframe style="display: none"  id="iframe_1" name="iframe_1" src="" onload="loadIframe();"></iframe>
            <input type="text" name="user" />
            <input type="file" name="fafafa" />
            <input type="submit" />
        </form>
        <h1>图片列表</h1>
        <div class="container" id="imgs">
            {% for img in img_list %}
                <img src="/{{ img.path }}">
            {% endfor %}
        </div>    
    
        <input type="file" id="img" />
        <input type="button" value="提交XML" onclick="UploadXML()" />
        <input type="button" value="提交JQ" onclick="Uploadjq()" />
        <script src="/static/jquery-2.1.4.min.js"></script>
        <script>
            function UploadXML() {
                var dic = new FormData();
                dic.append('user', 'v1');
                dic.append('fafafa', document.getElementById('img').files[0]);
    
                var xml = new XMLHttpRequest();
                xml.open('post', '/upload.html', true);
                xml.onreadystatechange = function () {
                    if(xml.readyState == 4){
                        var obj = JSON.parse(xml.responseText);
                        if(obj.status){
                            var img = document.createElement('img');
                            img.src = "/" + obj.path;
                            document.getElementById("imgs").appendChild(img);
                        }
                    }
                };
                xml.send(dic);
            }
            function Uploadjq() {
                var dic = new FormData();
                dic.append('user', 'v1');
                dic.append('fafafa', document.getElementById('img').files[0]);
    
                $.ajax({
                    url: '/upload.html',
                    type: 'POST',
                    data: dic,
                    processData: false,  // tell jQuery not to process the data
                    contentType: false,  // tell jQuery not to set contentType
                    dataType: 'JSON',
                    success: function (arg) {
                        if (arg.status){
                            var img = document.createElement('img');
                            img.src = "/" + arg.path;
                            $('#imgs').append(img);
                        }
                    }
                })
    
            }
            
            function  iframeChange() {
                var url = $('#url').val();
                $('#ifr').attr('src', url);
            }
    
            function loadIframe() {
                console.log(1);
                // 获取iframe内部的内容
                var str_json = $('#iframe_1').contents().find('body').text();
                var obj = JSON.parse(str_json);
                if (obj.status){
                    var img = document.createElement('img');
                    img.src = "/" + obj.path;
                    $('#imgs').append(img);
                }
            }
        </script>
    
    </body>
    </html>
    

     views.py文件:

    import os
    def upload(request):
        if request.method == 'GET':
            img_list = models.Img.objects.all()
            return render(request,'upload.html',{'img_list': img_list})
        elif request.method == "POST":
            user = request.POST.get('user')
            fafafa = request.POST.get('fafafa')#只能获取到文件名
            obj = request.FILES.get('fafafa')#必须这样才能取到文件
    
            file_path = os.path.join('static','upload',obj.name)
            f = open(file_path, 'wb')
            for chunk in obj.chunks():
                f.write(chunk)
            f.close()
    
            models.Img.objects.create(path=file_path)
    
            ret = {'status': True, 'path': file_path}
    
            return HttpResponse(json.dumps(ret))
    

      

      

     12、数据库正向查询、反向查询、多对多查询

    一、一对多(ForeignKey)

    -------------------------------views.py----------------------------------------------
    
    def test(request):
    # 例一:
        # pro_list = models.Province.objects.all()
        # print(pro_list)
        # city_list = models.City.objects.all()
        # print(city_list)
    正向查找(通过具有外键字段的类,City)
        # filter(故意写错,可以知道可以添加哪些字段)
        # v = models.City.objects.all()
        # print(v)
    
    反向查找(如_ _name)
        # v = models.Province.objects.values('id','name','city__name')
        # print(v)
        # pro_list = models.Province.objects.all()
        # for item in pro_list:
        #     print(item.id,item.name,item.city_set.filter(id__lt=3))  #id<3
        return HttpResponse("OK")
    
    
    --------------------------------models.py----------------------------------
    class Province(models.Model):
        name = models.CharField(max_length=32)
    
    class City(models.Model):
        name = models.CharField(max_length=32)
        pro = models.ForeignKey("Province")
    

     

    二、多对多查询(ManyToMany)

    ----------------------------------------views.py-------------------------------------------------
    # 例二:
    def test(request):
    1、创建:多表操作时,可以忽略manytomany,不影响单表的字段创建
        # 如:
        # models.Book.objects.create(name='书名')
        # models.Author.objects.create(name='人名')
    
      正向查找 第三张表(因为m在author里面)
        # obj,人,金鑫
        # obj = models.Author.objects.get(id=1)
        # # 金鑫所有的著作全部获取到
        # obj.m.all()
    
      反向查找 第三张表(用...._set)
        # 金品买
        # obj = models.Book.objects.get(id=1)
        # # 金鑫,吴超
        # obj.author_set.all()
    
    2、性能方面:
        # 正向查找
    
        # 10+1   以下方法查询数据库次数多,性能不高
        # author_list = models.Author.objects.all()
        # for author in author_list:
        #     print(author.name,author.m.all())
    
        #推荐此方法,用values来传值
        # author_list = models.Author.objects.values('id','name','m', "m__name")
        # for item in author_list:
        #     print(item['id'],item['name'],'书籍ID:',item['m'],item['m__name'])
    
    3、添加、删除、清空、set
        #正向操作:
        # obj = models.Author.objects.get(id=1)
        # 第三张表中增加一个对应关系  1  5
        # 增加
        # obj.m.add(5)          第三张表中增加一个对应关系  1  5
        # obj.m.add(*[4,5])     第三张表中增加一个对应关系  1  4和1   5
    
        # obj.m.add(5,6)
    
        # 删除第三张表中.....的对应关系
        # obj.m.remove(5)
        # obj.m.remove(5,6)
        # obj.m.remove(*[5,6])
        # 清空
        # obj.m.clear()
    
        # 更新对应关系    id=1的只能对应1和对应2和对应3,id=1的其他对应关系会被自动删掉
        # obj.m.set([1,2,3])
    
    
        # 反向操作:
        # obj = models.Book.objects.get(id=1)
        # obj.author_set.add(1)
        # obj.author_set.add(1,2,3,4)
        # ...同上
    
        return HttpResponse("OK")
    -----------------------------models.py-----------------------------
    
    class Book(models.Model):
        name =models.CharField(max_length=32)
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
        m = models.ManyToManyField('Book')
    系统默认生成第三张表(关系表)
    

      

     三、等于和不等于(补充)

        models.tb.objects.filter(name='root')	name 等于
        models.tb.objects.exclude(name='root')	name 不等于
    
        
        models.tb.objects.filter(id__in=[11,2,3])    在
        models.tb.objects.exclude(id__in=[11,2,3])    不在
        
    

      

     13、jQuery对象和DOM对象可以互相转换

    obj = document.getElementById('sel')
    $(obj)	dom转为jQuery
    
    $('#sel')
    $('#sel')[0]	jQuery转成dom对象
    
    select标签的Dom对象中有 selectedOptions来获得选中的标签
    	op1 = $('#sel')[0].selectedOptions	dom标签
    	
    再用jQuery对象的appendTo()方法,就可将选中的标签加到右边的空标签里面
    	 $(op1).appendTo("#none")		jQuery标签
    

      

    14、JSONP原理(面试重点):跨域用的

    jsonp的本质就是动态的在head里面创建script标签,执行完后,又将其删除
    用jsonp原因:
      由于浏览器的同源策略,所以跨域请求不行,所以要想跨域请求,需要用jsonp,jsonp不是一个技术,而是一种策略,是小机智,由于script扩展src不受同源策略保护,所以可以动态的生成一个script标签,加上src属性,请求完成后就立即将此script标签删除。

     自己写一个jsonp:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <input type="button" onclick="jsonpRequest();" value="跨域请求" />
    
        <script>
            TAG = null;/* 定义一个全局变量tag*/
            function jsonpRequest() {
                TAG = document.createElement('script');
                TAG.src = "http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403"; // 返回值是list([11,22,33,44])
                document.head.appendChild(TAG);
            }
            function list(arg) {
                console.log(arg);
                document.head.removeChild(TAG);
            }
        </script>
    </body>
    </html>
    

    14、cookie和session

      Cookie:

    就是保存在浏览器端的键值对
    可以利用做登录

    1、保存在用户浏览器
    2、可以主动清楚
    3、也可以被“伪造”
    4、跨域名cookie不共享
    5、浏览器设置不接受cookie后,就登录不上了

    Cookie是什么?
    客户端浏览器上保存的键值对

    服务端操作的Cookie
    服务端操作的Cookie
                obj.set_cookie('k1','v1')
                obj.set_cookie('k1','v1',max_age=10)
                
                v = datetime.datetime.utcnow() + datetime.timedelta(seconds=10)
                obj.set_cookie('k1','v1',max_age=10,expires=v)
     ----------------------------------参数--------------------------------        
                1、可设置超时时间,超时后cookie就自动失效
                            max-age、expires都可用于设置超时时间
                2、path: 
                        /       表示,全局生效
                        /xxxx/  表示,只有当前url生效
                        
                3、domian:设置域名,可设置顶级域名,cookie可在顶级域名下生效。
                        obj.set_cookie('k4','v4',max_age=10,expires=v, domain='oldboy.com')
                        
                        obj.set_cookie('k1','v1')
                        
                4、httponly: 仅仅HTTP网络传输使用,不能通过js获取cookie。         
    

     

    客户端浏览器上操作cookie
    客户端浏览器上操作cookie
                dom          --> 麻烦
                jquery插件   --> 先准备:
                                    (1)jquery.cookie.js
                                    (2)jquery.cookie.js
                                用法:$.cookie()
    				一个参数:$.cookie(k)   获得值
    				两个参数:$.cookie(k,v) 设置值
    

    Session:
    session是服务器端的一个键值对
    session内部机制依赖于cookie

    request.session['k']		获取值
        request.session['k1'] = v	设置键值对
        request.session['k2'] = v
        
        del request.session['k1']	删除
        request.session.clear()		清空
        
        
        # 获取、设置、删除Session中数据
        request.session['k1']
        request.session.get('k1',None)
        
    	
        request.session['k1'] = 123
        request.session.setdefault('k1',123) # 存在则不设置
        del request.session['k1']
    
        # 所有 键、值、键值对
        request.session.keys()
        request.session.values()
        request.session.items()
        request.session.iterkeys()
        request.session.itervalues()
        request.session.iteritems()
    
    
        # 用户session的随机字符串
        request.session.session_key
    
        # 将所有Session失效日期小于当前日期的数据删除
        request.session.clear_expired()
    
        # 检查 用户session的随机字符串 在数据库中是否
        request.session.exists("session_key")
    
        # 删除当前用户的所有Session数据
        request.session.delete("session_key")
    

      

     

     

  • 相关阅读:
    153. Find Minimum in Rotated Sorted Array
    228. Summary Ranges
    665. Non-decreasing Array
    661. Image Smoother
    643. Maximum Average Subarray I
    4.7作业
    面向对象编程
    常用模块3
    3.31作业
    常用模块2
  • 原文地址:https://www.cnblogs.com/tangtingmary/p/7994148.html
Copyright © 2020-2023  润新知