• django 之(三) --- 会话|关系|静态*


    会话技术

      HTTP在web开发中基本都是短连接[一个请求的生命周期都是从request开始到response结束]。

      下次再来请求就是一个新的连接了。为了让服务器端记住用户端是否登陆过就出现了会话技术

     种类:

    • Cookie: 客户端[浏览器端]会话技术。
    • Session:服务端会话技术
    • Token:  服务端会话技术

     总结:

    • Cookie使用更简洁,服务器压力更小,数据不是很安全

    • Session服务器要维护Session,相对安全

    • Token拥有Session的所有优点,自己维护略微麻烦,支持更多的终端

    Coookie

     简单介绍

    • Cookie本身是由浏览器生成,客户端[浏览器端]会话技术。
    • Cookie中的数据是以“键值对”方式存储在客户端的。
    • Cookie是可以支持过期时间、默认不支持中文[加盐也不支持]
    • Cookie不可跨域名、不可跨网站、不可跨浏览器使用
    • 默认Cookie会携带本网站所有Cookie通过Response将cookie值写到浏览器端,下一次request访问时浏览器会携带cookie到服务器端验证身份。

     设置Cookie

    • response.set_cookie(key, value [,max_age=None,exprise=None]) 
      • response.set_cookie(key, value, max_age=None, exprise=None)
      • max_age:整数,指定cookie过期时间。[max_age和expries两个任选一个指定]  
        • max_age 设置为0浏览器关闭失效,设置为None永不过期
      • expries:整数,指定过期时间,还支持是datetime或timedelta,可以指定一个具体日期时间 
        • expires = timedelta(days=10) 10天后过期

     获取Cookie:

    • request.COOKIES.get('key')

     简单使用:

    • urls.py
    from django.conf.urls import url, include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'App/',include('App.urls',namespace='App')),
    ]
    • App/urls.py
    1 from django.conf.urls import url
    2 from App import views
    3 
    4 urlpatterns=[
    5     url(r'^setcookie/',views.set_cookie,name='set_cookie'),
    6     url(r'^getcookie/',views.get_cookie,name='get_cookie'),
    7 ]
    • App/views.py
     1 # cookile 设置
     2 def set_cookie(request):
     3     response = HttpResponse('设置cookie')
     4     response.set_cookie('username','gypls')
     5     return response
     6 
     7 
     8 # coockie 获取
     9 def get_cookie(request):
    10     username = request.COOKIES.get('username')
    11     return HttpResponse(username)

      加密 [加盐]

    • response.set_signed_cookie('key', value, '加盐密钥')
      • response.set_signed_cookie('content', uname, 'gypls')

     解密

    • request.get_signed_cookie('key', salt='加盐的密钥')
      • request.get_signed_cookie('content', salt='gypls')

     删除

    • response.delete_cookie('key')
      • response.delete_cookie('content')

     使用方法:

    • App/urls.py 
     1 from django.conf.urls import url
     2 from App import views
     3 
     4 urlpatterns=[
     5     #登陆
     6     url(r'^login/',views.login,name='login'),
     7     url(r'^mine/',views.mine,name='mine'),
     8     #退出
     9     url(r'^logout/',views.logout,name='logout'),
    10 ]
    • App/views.py
     1 # 登陆
     2 def login(request):
     3     if request.method == "POST":
     4         uname = request.POST.get('uname')
     5         response = redirect(reverse('app:mine'))
     6         response.set_signed_cookie('content', uname, 'gypls')      # 加盐[加密]
     7         return response
     8     return render(request, 'login.html')
     9 
    10 # 个人中心
    11 def mine(request):
    12     try:
    13         uname = request.get_signed_cookie('content', salt='gypls')  # 解密
    14         if uname:
    15             return render(request, 'mine.html', context={'uname': uname})
    16     except Exception as e:
    17         print('获取失败')
    18     return redirect(reverse('app:login'))
    1 def logout(request):
    2     response = redirect(reverse('app:login'))
    3     response.delete_cookie('content')
    4     return response

    Session

      简单介绍

    • Session服务端会话技术,数据存储在服务器中。Session原本默认存储在内存[RAM]中,

    • Django中会默认会把Session持久化[存]到数据库session表中,默认过期时间是14天

    • 主键是字符串。数据是使用了数据安全[使用的base64,在前部添加了一个混淆串]

    • Session依赖于Cookie,将Session的session_key赋值到Cookie中某个建传给服务器端。

    • 下次请求的时候,服务器端通过Cookie中的session_key去数据库中查询,有则认证通过
    • 经验之谈:数据中以"="结尾,一般使用的是base64编码

      简单总结

      Session是服务端会话技术,数据存储在服务端。当我们调用request.session['key']时,Django默认会帮我们将这个session键和值加密并持久化的

      存到名为Django_session的数据库表中,"键"存储到一个session_key字段,我们的session数据会存到session_data字段中,同时会有一个过期时

      间字段expire_data,默认设置过期时间为14天。同时通过Cookie将这条数据的唯一标识session_key传给客户端,客户端给这个标识起了一个新的

      名字叫session_id,就把session_key的值存到了cookie的session_id里面。以后再来请求服务器就会带着这个session_id,根据session_id中存放的

      session_key就可以在数据库中的Django_session表中找到我们的session值,从而实现会话技术

     设置Session

    • request.session['key'] = value  [存session是存在服务器端的,所以用request]
      • username = request.POST.get('username')

      • request.session['username'] = username 

     获取Session

    • request.session.get('key')
      • request.session.get('username')

     删除Session  

    •  删除cookie的sessionid键值对来实现session退出登陆。response.delete_cookie('sessionid')
      • 缺陷:django_session表中的数据不会被删除
    •  删除session直接来实现退出登陆。 del request.session['key']
      • 缺陷:django_session表中的数据不会被删除
      • del request.session['username']
    • 全部删除数据 session 和 cookie。    request.session.flush()
      • 最优选择

     简单使用:

    •  urls.py
    1 from django.conf.urls import url, include
    2 from django.contrib import admin
    3 
    4 urlpatterns = [
    5     url(r'^admin/', admin.site.urls),
    6     url(r'Two/',include('Two.urls',namespace='two')),
    7 ]
    • Two/urls.py
    1 from django.conf.urls import url
    2 from Two import views
    3 
    4 urlpatterns = [
    5     url(r'^login/',views.login,name='login'),
    6     url(r'^mine/',views.mine,name='mine'),
    7     url(r'^logout/',views.logout,name='logout'),
    8 ]
    • Two/views.py
     1 from django.shortcuts import render, redirect
     2 from django.urls import reverse
     3 
     4 # 登陆
     5 def login(request):
     6     if request.method == "GET":
     7         return render(request,'two_login.html')
     8     elif request.method == "POST":
     9         username = request.POST.get('username')
    10         request.session['username'] = username
    11         return redirect(reverse('two:mine'))
    12 
    13 def mine(request):
    14     username = request.session.get('username')
    15     # username = request.session['username']  此方法如果key不存在会抛异常,不建议使用
    16     return render(request,'two_mine.html',context=locals())
    17 
    18 # 退出登陆的三种方式
    19 def logout(request):
    20     response = redirect(reverse('two:login'))
    21     # 删除cookie 来实现退出登陆。缺陷,django_session表中还会有数据
    22     response.delete_cookie('sessionid')
    23     # 删除session 来实现退出登陆。缺陷,django_session表中还会有数据
    24     del request.session['username']
    25     # 全部删除数据 session 和 cookie。最优,django_session表中的数据也会被清除掉
    26     request.session.flush()
    27     return response 

    总结

     常用操作

    • get(key, default=None)根据键获取会话的值
    • clear()清楚所有会话
    • flush()删除当前的会话数据并删除会话的cookie
    • delete request['session_id'] 删除会话
    • session.session_key获取session的key

     设置数据

    • request.session[‘user’] = username
    • 数据存储到数据库中会进行编码使用的是Base64

    Token

     简单介绍: 

    • Token是服务器端会话技术,相当于自定义的session。
    • Token如果在web开发中,使用起来基本和session一致,也是依赖于cookie。
    • Token是在服务器端生成唯一标识"键",真实开发中会Token当作"键"与用户信息当作"值"一起存储到缓存cache中,并设置过期时间。
      • 如果是使用在移动端类型的客户端开发中,通常将Token以json格式传输给移动端,然后移动端需要自己存储Token。
      • 移动端获取需要Token认证的相关数据的时候[如登陆后的界面],主动将移动端存储的Token传递给服务器端进行验证。
      • 服务器端通过移动端传递过来的Token"键",去缓存cache中查找对应的值"用户信息",从而实现移动端和服务器端的会话技术。

     简单使用:

    • Two/urls.py
    1 from django.conf.urls import url
    2 from Two import views
    3 
    4 urlpatterns = [
    5     url(r'^register/',views.register,name='register'),
    6     url(r'^studentlogin/',views.student_login,name='student_login'),
    7     url(r'^studentmine/',views.student_mine,name='student_mine'),
    8 ]
    • Two/models.py
    1 from django.db import models
    2 
    3 class Student(models.Model):
    4     s_name = models.CharField(max_length=16,unique=True)
    5     s_password = models.CharField(max_length=128)
    • Two/views.py
    from django.core.cache import cache
    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    from django.urls import reverse
    from Two.models import Student
    import uuid
     1 # token 会话。用户注册
     2 def register(request):
     3     if request.method == 'GET':
     4         return render(request, 'student_register.html')
     5     elif request.method == 'POST':
     6         username = request.POST.get('username')
     7         password = request.POST.get('password')
     8         try:
     9             student = Student()
    10             student.s_name = username
    11             student.s_password = password
    12             student.save()
    13         except Exception as e:
    14             return redirect(reverse("two:register"))
    15         return redirect(reverse("two:student_login"))
     1 # token 会话。用户登陆
     2 def student_login(request):
     3     if request.method == 'GET':
     4         return render(request, 'student_login.html')
     5     elif request.method == 'POST':
     6         username = request.POST.get('username')
     7         password = request.POST.get('password')
     8         students = Student.objects.filter(s_name=username).filter(s_password=password)
     9         if students.exists():
    10             student = students.first()
    11             names = student.s_name
    12             # 生成唯一表示Token
    13             token = generate_token()
    14             # 将token存到缓存中。cache.set(键[Token],值,过期时间)
    15             cache.set(token, names, 60 * 60 * 24)
    16             # pc端的请求返回方式
    17             response = HttpResponse('用户登陆成功')
    18             # 依赖于cookie将token返回给pc浏览器端
    19             response.set_cookie('token', token)
    20             return response
    21         # pc端返回
    22         return redirect(reverse('two:student_login'))
    23         # 移动端登陆成功返回数据的方法,仅为样式不可执行
    24         #     data = {
    25         #         'status':200,
    26         #         'msg':'login success',
    27         #         'token':token
    28         #     }
    29         #     return JsonResponse(data=data)
    30 
    31         # 移动端登陆验证失败返回数据的方法,仅为样式不可以执行
    32         # data = {
    33         #     'status':800,
    34         #     'msg':'verify fail',
    35         # }
    36         # return JsonResponse(data=data)
    1 # 自定义tokon
    2 def generate_token():
    3     # 使用uuid设置token[键]的唯一值
    4     token = uuid.uuid4().hex
    5     return token
     1 def student_mine(request):
     2     token = request.COOKIES.get('token')
     3     try:
     4         # 验证是否有token值[是否是登陆状态]
     5         sname = cache.get(token)
     6         print(sname)
     7         print(type(sname))  # str
     8     except Exception as e:
     9         return redirect(reverse('two:student_login'))
    10     # pc浏览器端返回
    11     return HttpResponse("用户的姓名:", sname)
    12     # 移动端返回数据样式。此处代码仅为样式不可执行
    13     # data = {
    14     #     'msg':'ok',
    15     #     'status':200,
    16     #     'data':{
    17     #         'username':student.s_name,
    18     #     }
    19     # }
    20     # return JsonResponse(data=data)

    模型关系

     常见的几种数据关系,django都提供了很好的支持。主从表定义:当系统遭遇不可避免的毁灭时多张只能保留一张表,保留的就是主表。[如用户表]

     在企业开发中,一般主表数据不会被随意删除,因为其可能及联好多从表的数据,主表数据一旦删除从表数据也会被及联删除,可能导致系统崩溃

    一对一 [1:1]

     业务场景: 

    • 拆分:
      • 比如一张用户表,其功能过于强大,一张用户表中就可能会出现两百多个字段甚至更多,管理起来很麻烦,查询效率也会大大降低。
      • 这时就需要将其表中的数据拆分开,将一张用户表拆分成一张主表和若干张从表,这样每张表有十几个字段,每张表负责不同的功能。
    • 级联:
      • 比如在已经设计好的一张用户表中,开发过程中功能是增量式的操作,需要增加新的字段来实现增加新的功能,比如token认证。
      • 就可以新建一张表来设置这些字段,然后和原来的用户表进行一对一的绑定,就可以实现不改变原来表的基础上增加新字段。

     使用实现

    • 实现: 
      • 在Django模型中使用 models.OneToOneField(要关联表的模型名其他约束) 进行关联。谁声明关系谁是从表
      • 其实还是借助外键实现的。使用外键实现,对外键添加唯一约束。主表数据删除时,从表数据会被及联删除;从表数据删除时,主表数据不会被及联删除
      • 在DDL中 从表声明一个外键和主表进行绑定,开始其实是从表是多对主表是一的关系。之后在从表对多的上面添加了唯一约束,从而实现一对一的约束。
    • 约束:
      • p_id = models.OneToOneField(Person, null=True, blank=True, on_delete=models.PROTECT)

      • on_delete = 以下约束字段
      • models.CASCADE 默认模式。主表数据删除时,从表数据及联删除;从表数据删除,主表数据不受影响

      • models.PROTECT 保护模式。

        • 主表数据删除时,如果主表无从表,主表数据可以删除成功。如果主表有级联从表,从表数据受到保护删除请求会抛出保护异常

        • 开发中为了防止误操作,通常设置成此模式。若必须删除主表数据时,需要提前将主表对应的所有从表的对应数据删除。然后再操作删除主表相应数据
      • models.SET_NULL       置空模式。主表删除时将从表的外键关系设置为空值[前提是此字段允许为NULL]。在一对一关系中常用此设置
      • models.SET_DEFAULT 置默认值。主表删除时将从表的外键关系设置为一个默认值[前提是存在设置的默认值]
      • models.SET()              删除的时候重新动态指向一个实体
     1 from django.db import models
     2 
     3 # 1:1 人员表和身份证表一对一关联
     4 # 人员的表
     5 class Person(models.Model):
     6     p_name = models.CharField(max_length=16)
     7     p_sex = models.BooleanField(default=False)
     8 
     9 # 身份证表
    10 class IDCard(models.Model):
    11     id_num = models.CharField(max_length=18, unique=True)
    12     id_person = models.OneToOneField(Person, null=True, blank=True, on_delete=models.PROTECT)
    • 注意
      • 在迁移同步到mysql数据库中时从表身份证表中声明的外键属性id_person会自动被生成名为id_person_id字段。
      • 当 ‘从表对象.从表模型中定义的外键字段[属性]名’ 时,获取到的是主表的对象,拿着此对象去获取主表中的字段
    •  获取
      • 由主表对象获取从表对象的字段数据:主表对象 . 从表在数据库中的名[隐性属性] . 从表相应的字段名
      • 由从表对象获取主表对象的字段数据:从表对象 . 从表外键字段属性名[显性属性] . 主表相应的字段名
     1 def get_person(request):
     2     idcard = IDCard.objects.last()
     3     person = idcard.id_person
     4     return HttpResponse(person.p_name)
     5 
     6 
     7 def get_idcard(request):
     8     person = Person.objects.last()
     9     idcard = person.idcard
    10     return HttpResponse(idcard.id_num)

    一对多 [1:N]

     使用实现:

    • 实现:
      • 在django中使用 models.ForeignKey(要关联表的模型名其他约束字段) 进行关联,多是从表。
    • 获取

      • 主获取从: [隐性属性]级联模型对应数据库中的表名_set

        • 如 班级[主]和学生[从]关系模型:student_set

        • 也是Manager的子类,Manager上能使用的函数在这都能使用。如:all、filter、exclude、切片

      • 从获取主: [显性属性]

    • 删除
      • 同一对一操作 

    多对多 [N:M]

     使用实现:

    • 实现:
      • 在django中使用 models.ManyToManyField(要关联的表的模型名,其他约束字段)
      • 实际上关系最复杂。开发中很少直接使用多对多属性,而是自己维护多对多的关系[如自己创建一个购物车表存放客户和商品的主键]
      • 迁移同步时,数据库会自己新建一张额外的关系表,关系表中的多个外键字段是需要关联关系的各表的主键,以及和一些自己的字段。
    1 # N : M 一类商品可以被多个人购买;一个人可以购买多类商品。产生的关系表就是典型购物车模型
    2 class Customer(models.Model):
    3     c_name = models.CharField(max_length=16)
    4 
    5 
    6 class Goods(models.Model):
    7     g_name = models.CharField(max_length=16)
    8     g_customer = models.ManyToManyField(Customer)
    • 注意:
      • 迁移同步在数据库中生成表的时候会单独的生成一张关系表。关系表中的各外键字段不能同时相等

      • 关系表中存储有关联各表的主键,通过多个外键实现的。约束是同时加在各外键上的,多个外键值不能同时相等

      • 在ManyToManyField中,删除一个不存在的数据不会报错,添加一个已存在的数据,不会被添加成功也不报错
    • 获取

      • 主获取从:[隐性属性] 也是Manager子类,操作和从操作主完全一样

      • 从获取主:[显性属性] 也是Manager子类,支持all、filter、exclude、切片使用

      • 方法:级连数据操作的方法有:add、remove、clear、set
     1 def add_to_cart(request):
     2     customer = Customer.objects.last()
     3     goods = Goods.objects.last()
     4     goods.g_customer.add(customer)# 由商品添加顾客,从设置主
     5     customer.goods_set.add(goods) # 由顾客添加商品,主设置从
     6     return HttpResponse('添加成功')
     7 
     8 
     9 # 获取级连数据
    10 def get_goods_list(request, customerid):
    11     customer = Customer.objects.get(pk=customerid)
    12     goods_list = customer.goods_set.all() # 主获取从
    13     return render(request, 'goods_list.html', context=locals())
    1 # 注意
    2 def add_to_cart(request):
    3     customer = Customer.objects.last()
    4     goods = Goods.objects.last()
    5     print(goods.g_customer)       # App.Customer.None
    6     print(type(goods.g_customer)) # <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
    7     return HttpResponse('添加成功')

      注意:ManyRelatedManager 函数中定义的类,并且父类是一个函数的参数,此方法叫做动态创建。

      

    静态

    静态资源

    • 配置  

        在settings.py中的底部配置static的文件夹,用来加载些模板中用到的资源提供给全局使用,这个静态文件主要用来配置CSS、HTML、图片、字体文件等。

        和Template的区别在于,不需要MTV渲染就可以显示,只能支持原生的HTML,里面的东西是静态的。

      • STATIC_URL = '/static/'    
      • STATICFILES_DIRS = [os.path.join(BASE_DIR,'static')]   
    • 使用

        之后在模板中,首先加载静态文件,之后调用静态,就不用写绝对全路径了

      • 模板中的声明:{% load static%} 或 {% load staticfiles %}
      • 引用资源时使用:{% static 'xxx' %}    [xxx就是相对于staticfiles_dirs的一个位置]

     文件上传 [头像上传]

      注意 要使用POST方式上传

    • urls.py 
     1 # 主路由
     2 from django.conf.urls import url, include
     3 from django.contrib import admin
     4 
     5 urlpatterns = [
     6     url(r'^admin/', admin.site.urls),
     7     url(r'^App/',include('App.urls',namespace='app')),
     8 ]
     9 
    10 # 子路由
    11 from django.conf.urls import url
    12 from App import views
    13 
    14 urlpatterns = [
    15     url(r'^uploadfile/', views.upload_file, name='upload_file'),
    16     url(r'^imagefield/', views.image_field, name='image_filed'),
    17 ]
    •  原生写法
      •  views.py
     1 from django.http import HttpResponse
     2 from django.shortcuts import render
     3 
     4 def upload_file(request):
     5     if request.method == 'GET':
     6         return render(request,'upload.html')
     7     elif request.method == 'POST':
     8         icon = request.FILES.get('icon')
     9         # 存储
    10         with open('/Users/guoyapeng/pyword/1django1.11/django05/SqlToModel/static/icon.jpg','wb') as save_file:
    11             # chunks()函数 是将文件打成一块一块的
    12             for part in icon.chunks():
    13                 save_file.write(part)
    14                 save_file.flush()
    15         return HttpResponse('文件上传成功')
      • upload.html
     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>文件上传</title>
     6 </head>
     7 <body>
     8 {# enctype="multipart/form-data 意思是将图片包打碎加密传输 #}
     9 <form action="{% url 'app:upload_file' %}" method="post" enctype="multipart/form-data">
    10    {% csrf_token %}
    11     <span>文件:</span>
    12     <input type="file" name="icon">
    13     <br>
    14     <input type="submit" value="上传">
    15 </form>
    16 
    17 </body>
    18 </html>
    • 封装实现 
      • 在settings.py中配置指定相对路径位置:MEDIA_ROOT = os.path.join(BASE_DIR,'static/upload')
      • 安装pillow:pip install pillow -i https://pypi.douban.com
      • models.py
    1 from django.db import models
    2 
    3 class UserModel(models.Model):
    4     u_name = models.CharField(max_length=16)
    5     # upload_to是相对于settions.py配置的MEDIA_ROOT媒体路径的相对路径
    6     u_icon = models.ImageField(upload_to='%Y/%m/%d/icon')
      • views.py
     1 from django.http import HttpResponse
     2 from django.shortcuts import render
     3 from App.models import UserModel
     4 # 上传到数据库中
     5 def image_field(request):
     6     if request.method == 'GET':
     7         return render(request,'image_filed.html')
     8     elif request.method == 'POST':
     9         username = request.POST.get('usernmae')
    10         icon = request.FILES.get('icon')
    11 
    12         user = UserModel()
    13         user.u_name = username
    14         user.u_icon = icon
    15         user.save()
    16 
    17     return HttpResponse('上传成功')
      • image_filed.html
     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>Title</title>
     6 </head>
     7 <body>
     8 
     9 <form action="{% url 'app:image_filed' %}" method="post" enctype="multipart/form-data">
    10    {% csrf_token %}
    11     <span>用户名:</span><input type="text" name="username" placeholder="请输入用户名">
    12     <br>
    13     <span>头像:</span><input type="file" name="icon">
    14     <br>
    15     <input type="submit" value="上传">
    16 </form>
    17 
    18 </body>
    19 </html>
      •  views.py
     1 from django.http import HttpResponse
     2 from django.shortcuts import render
     3 from App.models import UserModel
     4 
     5 
     6 # 获取文件信息[比如头像]
     7 def mine(request):
     8     username = request.GET.get("username")
     9     user = UserModel.objects.get(u_name=username)
    10     print("/static/upload/" + user.u_icon.url)
    11     data = {
    12         "username": username,
    13         "icon_url": "/static/upload/" + user.u_icon.url,
    14     }
    15     return render(request, "mine.html", context=data)
      • mine.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Mine</title>
    </head>
    <body>
    <h3>{{ username }}</h3>
    <img src="{{ icon_url }}" alt="{{ username }}">
    </body>
    </html>

    补充

    常用算法

     常用算法:Base64、urlencode

     算法种类:

    • 摘要算法,指纹算法,杂凑算法:
      • MD5:默认是128位的,四位一组变32位
      • SHA:当向不可逆,不叫加密解密、不管输入多长,输出都是固定长度、只要输入有任意的变更,输出都会发生很大的变化
    • 加密算法
      • 对称加密:DES、AES。可以用同一把钥匙加密解密。加密效率高,钥匙一旦丢失数据就全部丢失。
      • 非对称加密:RSA、PGP。一对钥匙,相互制约,公钥加密的数据只有私钥可解开,私钥加密的数据只有公钥才可解开。安全性高,算法复杂需要时间长 

     

    生如逆旅 一苇以航
  • 相关阅读:
    深入理解六边形架构
    boost::lockfree使用介绍
    分布式监控系统zipkin介绍
    深入理解std::chrono的时钟Clock
    arcgis地图空白原因收集
    VM16
    ubuntu16.04 搭建简单http代理服务器 TinyProxy
    git 提交:gnutls_handshake() failed: Error in the pull function
    scrapy-redis redis 认证
    python把html网页转成pdf文件
  • 原文地址:https://www.cnblogs.com/TMMM/p/11740379.html
Copyright © 2020-2023  润新知