Day77--阶段复习02--类的魔法方法、cookie与session

今日内容

1. 后续课程安排

1 drf框架
2 git
3 redis使用
4 路飞项目(celery异步)
5 爬虫(mongodb)
6 linux
7 cmdb项目(资产收集)
8 代码发布系统
9 flask框架(给你一到两个项目)
10 数据结构和算法
11 docker,dockercompose(用docker部署项目,nginx负载均衡,横向扩展),k8s(看情况)
12 就业辅导(redis高级,Elasticsearch,如何提高项目并发量,分布式锁,分布式id,远程连接docker开发,git冲突如何解决)

2. 作业讲解

# 作业:用中间件或者装饰器前端不管传json还是其他格式,request对象中有个data属性
# 思路:用json.load()方法 直接将json格式转成Python类型;若成功,则说明请求数据是json格式;反之失败,则是普通post的请求数据

# 核心代码(中间件)
from django.utils.deprecation import MiddlewareMixin

import json
class JsonMiddel(MiddlewareMixin):
    def process_request(self, request):
        try:
            request.data=json.loads(request.body)
        except Exception as e:
            request.data=request.POST

            
# 关注的问题(注意):
1.form表单和ajax提交的重复:
	form表单中的提交使用 input的submit类型 或者 button id='submit',但却用了Ajax的button按钮来提交,此时会触发两次提交数据,从而出现重复或者错误。
    解决办法:将form表单的 input的submit类型,改成 input的button类型 (就是普通的按钮,不会触发form表单提交),只采用Ajax来提交数据
    
2.from django.http.request import QueryDict 读这个源码
	QueryDict对象 本质就是一个字典,比字典强大;但不能修改其中的值,一改就报错 (就是利用__setattr__() :在字典.属性=值时,自动触发该方法,进行拦截处理)
    
3.CommonMiddleware中间件 控制了url请求路径是否重定向到 '原路径+/' 的地址
    # 取消自动加斜杠 (setting.py)
	APPEND_SLASH = False/True	# 默认是自动加斜杠的 
	
	# django.middleware.common.CommonMiddleware  读这个中间件源码

3. python中的魔法方法

# __init__:类实例化会触发
# __str__:打印对象会触发
# __call__:对象()触发,类也是对象  类(),类的实例化过程调用元类的__call__
# __new__:在类实例化会触发,它比__init__早(造出裸体的人,__init__穿衣服)
# __del__:del 对象,对象回收的时候触发

# __setattr__,__getattr__: ( .拦截--点拦截方法 )
	当对象.属性是赋值会调用setattr,如果是取值 会调用getattr
    
# __getitem__,__setitem__: ( []拦截--中括号拦截方法)
	当对象[属性]是赋值会调用setitem,如果是取值 会调用getitem

# __enter__和__exit__ 上下文管理器

3.1 setattr,getattr,setitem,getitem演示

# 案例一:普通类(继承object) 允许'.'方法,但不支持'[]'
class Person:
    def __init__(self,name):
        self.name=name
        
    def __setitem__(self, key, value):
        setattr(self,key,value)  # 反射赋值:用Python内置方法 setattr() 
        
    def __getitem__(self, item):
        return getattr(self,item) # 反射取值: 用Python内置方法 getattr() 

p=Person('lqz')
p.name='ppp'  # 对象自带 __setattr__()方法 不报错
print(p.name)  # 'ppp'

p['name']=10  # 报错,如何可行:重写__setitem__()方法
print(p['name'])  # 10


# 案例二:字典类 允许'[]'方法,但不支持'.'

dic={'name':'lqz','age':19}
print(dic['name'])  # 'lqz'
print(dic.name)  # 报错

class Mydic(dict):
    def __setattr__(self, key, value):
        print("对象加点赋值,会触发我")
        self[key]=value
        
    def __getattr__(self, item):
        print("对象加点取值,会触发我")
        return self[item] # 不要加引号

    
mydic=Mydic(name='lqz',age=18)
print(mydic['name'])  # 'lqz'

print(mydic.name)  # 'lqz'
mydic.name=99
print(mydic.name)  # 99


# 自我总结规律:
	有些数据类型 要嘛能使用'.'方法赋值或取值,要嘛能使用'[]'方法赋值或取值
	要想实现另一种,则重写其类中的魔法方法,再通过内部的点或中括号来实现

3.2 with 上下文管理器

# __enter__() 与 __exit__()组合使用,可将 对象类似于文件 作为上下文管理器来打开使用
# 应用场景:常用于数据库链接操作 
	进入时:打开数据库链接,并返回链接对象conn给as; 
    退出时:关闭数据链接; 
    缩进内:执行正常的数据库操作

class Person:
    def __enter__(self):
        print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')
        return 'oo'

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('退出with代码块时执行此方法')
        print('1', exc_type)
        print('2', exc_val)
        print('3', exc_tb)

with Person() as p:   # 这句话执行,会触发类的__enter__
    print(p)

3.3 对象比较 '==' :eq

# __eq__(self,obj) :自定义比较判断两个对象

class A:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __eq__(self,obj):
        # 打印出比较的第二个对象的x值
        print(obj.x)
        if self.x +self.y == obj.x+obj.y:
            return True
        else:
            return False

a=A(1,2)
b=A(99,3)
print(a==b)   # 当执行'=='时,会触发__eq__的执行,并且把b传进去,就是obj
# 发现:'=='后只要是对象,就可以传进去,就是object

4. cookie,session,token

# HTTP协议:无状态,无连接,基于请求响应,基于tcp/ip的应用层协议
# 具体详细分:请求协议(请求头 请求体),响应协议(响应头 响应体)  # 复习 

# mysql:c/s架构:底层基于socket,自己封装的协议,mysql的客户端:navicate(c++图形化界面,实现了请求和响应协议),pymysql(用python语言实现了请求和响应协议)
# redis:c/s架构:底层基于socket,自己封装的协议

# docker:c/s架构,基于http协议,使用restfull规范
# elasticsearch:c/s架构,基于http协议,使用restfull规范 

# cookie:是存在于浏览器的键值对,向服务端发送请求,携带它过去(缺陷:不安全)
# session:存在于服务端的键值对(放在哪?内存中、文件、mysql、redis)
    # 缺陷:如果用户量很大,存储需要耗费服务器资源
    
# token:就是个字符串(通过加密算法和密钥加密的字符串 既安全,又存个人信息)
	现在应用非常广泛,契合了前后端分离(只存储在前端上,服务器只做校验),
	就不管前段是浏览器,是移动APP,还是微信小程序,只要请求给后端一个token字符串 就都可以访问 (token在浏览器上保存时就是cookie)

# JWT:json web token (json格式的token)  主流

5. django中的session底层原理

# 请求来的时候:
# 1.通过请求cookie  request中的sessionId,取出随机字符串
# 2.根据随机字符串,去数据库中session表 查出对应的值
# 3.将数据转成字典( {随机字符串:对应session值} ),赋值给request.session, 后面视图函数中就可以操作它了


# 响应走的时候
# 1.在中间件的process_response中,取出request.session的modify属性值
# 2.判断modify值是否是true
    # 2.1如果是true,表示在视图函数中改过session,数据库中session表同步修改
    # 2.2如果是false,就不修改,直接返回给前端(sessionId:随机字符串)   

6. 异常处理

# 最基本的异常
class  NotStrException(BaseException):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg


try:
except:
else:  # 什么时候执行
finally:


try:
    print("xxx")
    # print(1/0)
except Exception as e:
    print(e)
else:  # 基本上不会用到
    print("正常执行且没有出异常时,会走")
finally:
    print("我是finally")   # 永远会走,无论是否有异常

7. pymysql的使用

import pymysql

#连接数据库
conn=pymysql.connect(host='101.133.225.166', user='root', password="123456",database='test', port=3306) 

# 获取游标
cursor=conn.cursor() # 默认查出来数据是元祖格式,参数cursor=pymysql.cursors.DictCursor 数据是字典格式

# 操作 定义一个sql 查询
sql='select id,name from book'
cursor.execute(sql)
ret=cursor.fetchall()
print(ret)

# 插入   %s 是防止sql注入的问题
sql='insert into book(id,name) values (%s,%s)'
cursor.execute(sql,[3,'lqz'])
conn.commit()

# 删除
sql='delete from book where name=%s'
cursor.execute(sql,['lqz'])
conn.commit()

# 更新
sql='update book set name=%s where id=%s'
cursor.execute(sql,['xxx',1])
conn.commit()

作业

# 1 写一个类,有个name属性,如果name赋值为非字符串,就不让放
# isinstance(value, str)判断是否是实例对象   issubclass() 判断是否是子类

class Person:
    def __init__(self, name):
        self.name = name

    def __setattr__(self, key, value):
        if isinstance(value, str):
            # self.key = value  # 与 setattr(self, key, value) 类似,都会递归调用触发 self.__setattr__() 就无限递归下去了
            # 解决一: 用内部字典的形式修改值
            self.__dict__[key] = value
            # 解决二:重用父类的__setattr__()
            super().__setattr__(key, value)  # 或指名道姓:  object.__setattr__(self, key, value)
        else:
            print('name 值必须为字符串')

p = Person('jason')
print(p.name)  # 'jason'

p.name = 99
print(p.name)  # 'name 值必须为字符串'


# 2 通过上下文管理器写一个mysql的连接,通过with管理
import pymysql

class mysql:
    def __enter__(self):
        self.conn = pymysql.connect(
            host='127.0.0.1',
            port=3306,
            user='root',
            password='123456',
            database='bbs',
            charset='utf8',
        )
        self.cursor = self.conn.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()

with mysql() as cursor:
    sql = 'select title from app01_article'
    cursor.execute(sql)
    res = cursor.fetchall()
    print(res)


# 3 使用django实现token功能 详见--day77_homework -- tokenMiddleware.py
浏览器保存token
	{'token': 'user_id|加密后的签名'}  user_id=name+pwd

# 自我实现
# 问题:处理请求和处理响应没办法同时进行
    # 未生成token:执行process_response()  给浏览器生成一个本地用户token
    # 有token后:执行process_request()   验证浏览器上的token
    
# 解决:自己真是个大傻逼,写个判断不就行了 (并进行了优化,前端可传修改密文测试)
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
import hashlib

class TokenMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 只有点击登录 post请求时,才验证token; get请求 响应登录页面时,没有任何数据,不验证token
        if request.method == 'POST':
            # 1.从前端cookie取出token值
            token_value = request.COOKIES.get('token')  # 注意格式是字符串
            # 2. 判断前端是否有token
            # 如果有token,则进行token校验;如果没有,在post响应走的时候,设置token
            if token_value:
                # 3.以'|'分割token的 id和签名部分
                user_id, sign = token_value.split('|')
                # 4.将id加密
                encrypt = hashlib.md5(user_id.encode('utf-8')).hexdigest()
                # 5.判断是否与签名一致
                # 测试修改密文: te_sign = 前端传的sign 或 token后的密文
                te_sign = request.POST.get('sign') or sign
                if encrypt != te_sign:
                    return HttpResponse('登录失败,token已被篡改')

    def process_response(self, request, response):
        # 只有点击登录 post请求 并且后端验证成功时,才执行设置token; get请求 响应登录页面时,没有任何数据,不需要设置token
        if request.method == 'POST':
            # 如果没有token,设置token
            if not request.COOKIES.get('token'):
                name = request.POST.get('name')
                pwd = request.POST.get('pwd')
                user_id = name + pwd
                token_value = user_id + '|' + hashlib.md5(user_id.encode('utf-8')).hexdigest()
                response.set_cookie('token', token_value)
        return response
posted @ 2021-11-17 01:08  Edmond辉仔  阅读(14)  评论(0编辑  收藏  举报