会话技术
HTTP在web开发中基本都是短连接[一个请求的生命周期都是从request开始到response结束]。
下次再来请求就是一个新的连接了。为了让服务器端记住用户端是否登陆过就出现了会话技术
种类:
- Cookie: 客户端[浏览器端]会话技术。
- Session:服务端会话技术
- Token: 服务端会话技术
总结:
-
-
Session服务器要维护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原本默认存储在内存[RAM]中,
-
Django中会默认会把Session持久化[存]到数据库session表中,默认过期时间是14天
-
主键是字符串。数据是使用了数据安全[使用的base64,在前部添加了一个混淆串]
-
- 下次请求的时候,服务器端通过Cookie中的session_key去数据库中查询,有则认证通过
-
简单总结
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。一对钥匙,相互制约,公钥加密的数据只有私钥可解开,私钥加密的数据只有公钥才可解开。安全性高,算法复杂需要时间长