前一段flask框架的一个小项目虽然写完了,但是里面有些知识,或遗忘或用的稀里糊涂.对于其中涉及到的一些知识点掌握的并不是很透彻,因此在写笔记的时候表述的也不是清晰,今天就来一次大盘点,让我们彻底弄懂这些问题.
我们主要按照下面的思路进行一个分析阐述,大家对于以后的学习也可以借鉴这个思路:
先提一个小点---->你项目里面哪些地方用到了这个知识点----->介绍一下它以及它的优缺点----->你是如何使用的---->用到了其中的哪些知识.
MySQL
项目中凡是涉及到要永久保存的内容我们都用到了MySQL数据库,比如用户的信息(客户的资料可是公司最重要的资料,如果不永久保存,丢了财源,那么就等着倒闭吧,哈哈哈),还有新闻、用户的评论、用户的粉丝(就是关注功能实现的信息)、用户收藏的新闻、用户对评论的点赞、还有新闻的分类(用户界面的首页header部分,还有用户发表新闻的时候所选的分类,管理员在后台审核信息和编辑新闻板式的时候都用到了)等等这些都是用到了MySQL数据库。下面我将后台的数据展示给大家,大家对照着就明白有哪些东西了:
既然这么多地方用到了MySQL这个数据库,那么我们就不能不知道它的相关信息了。MySQL是一款关系型数据库(RDBMS),它使用的是SQL(结构化查询语言)语句进行查询。数据库中的数据都是以表格的形式进行存储的,行(元组、记录),列(字段、属性)和域(指的是我们某一字段设置一个取值范围)前面这三个构成了关系表。
它的特点就是跨平台(管你使用的是Mac、Linux还是Windows,它都支持,你只要想使用就能用,这也就使得它的用户群体很广,因为没有限制)、开源(它的源码是公布的,大家都是可以查看的)、免费(这才是最重要的一点,免费才利于推广,普通用户是富豪的毕竟是少数,免费才是我们选择的第一要素,哈哈哈),然后就是应用范围广。我们在操作数据库的时候一般都是远程,或者在机房里面,哪里有图形界面啊?对着终端,难道安装一个office三件套?不现实吧,正是因为它方便,利于我们远程操作,成为了所有人的最爱,哪哪用的都是它。
项目中我们就用到了扩展Flask-SQLAlchemy,它提供了高层的 ORM(对象关系映射) 和底层的原生数据库的操作。它是一个关系型数据库框架,使用的时候,舍弃了一些性能开销的同时,换来的是开发效率的大大提升。我们直接使用对象来操作数据库就好了,它会帮我们翻译成SQL语句去和MySQL交互,我们就不用记忆SQL语句了,想想就开心。具体的操作可以点击链接查看:flask框架(三)
那么我们就看一下在项目中怎么利用flask-SQLAlchemy使用数据库吧:
1class User(BaseModel, db.Model): 2 """用户""" 3 __tablename__ = "info_user"# 指定表的名称 4 5 #参数1:表示整数类型, 参数2:表示主键 6 id = db.Column(db.Integer, primary_key=True) # 用户编号,设置了主键 7 nick_name = db.Column(db.String(32), unique=True, nullable=False) # 用户昵称 8 password_hash = db.Column(db.String(128), nullable=False) # 加密的密码 9 mobile = db.Column(db.String(11), unique=True, nullable=False) # 手机号 10 avatar_url = db.Column(db.String(256)) # 用户头像路径 11 last_login = db.Column(db.DateTime, default=datetime.now) # 最后一次登录时间 12 is_admin = db.Column(db.Boolean, default=False) 13 signature = db.Column(db.String(512)) # 用户签名 14 gender = db.Column( # 订单的状态 15 db.Enum( 16 "MAN", # 男 17 "WOMAN" # 女 18 ), 19 default="MAN") 20 21# 初始化 user 模型,并设置数据 22user = User() 23user.nick_name = mobile 24user.mobile = mobile 25 26# 将上面的模型添加到数据库,并进行提交 27db.session.add(user) 28db.session.commit()
上面是网站,用户进行注册的时候,我们需要走的一个大体流程,就是:我们初始化一个用户的模型,也就是创建一个对象user,而SQLAlchemy也就让我们可以像给对象设置属性一样给字段里面添加数据,每个字段的内容都进行了填充,最后将这个对象添加到数据库,然后提交即可。比直接使用数据库用SQL语句要方便的多。
Redis
我们在图片验证码、短信验证码、session的存储这几个地方使用到了redis数据库。
redis数据库是NOSQL,即非关系型数据库,它是没有外键的,从nosql也可以看出来,它是不支持SQL语句的,刚开始学的时候你也许会觉的头疼,毕竟数据库类型不同,就意味着要记忆更多的语法了。但是当你学习后,你会发现它虽然有很多类型,但是我们常用的语法是没有几句的,而且常用类型的也就一种字符串。具体的操作语句可以点击链接:redis操作命令总结
redis是缓存数据库,即将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限。但是这不正与我们用到的session非常的契合吗?session与cookie是一对共存的概念,我们的HTTP默认是无响应的,因为使用了socket套接字,每一次请求完毕之后都会关闭,这样就有一个问题,那就是每次都是全新的访问,大大降低了体验。用session和cookie就可以解决这个问题,浏览器在访问的时候服务器会设置一个cookie发给浏览器,这里面存的是用户的一些信息(比如你浏览过我们网站的哪些东西,你喜欢看哪些文章,方便我给你下次推送,省去你查找的麻烦),然后给服务器存一个session,记录的是一些比较敏感的信息,也是用户的信息(比如用户的用户名、余额、等级、验证码这些东西)。有了这些,我们就可以进行状态保持了,你登陆一个账号,下次访问的时候,只要没有过了session的存储时间,你都不用再次输入账号,这样就很方便了。
redis适用于存储使用频繁的数据,这样减少访问数据库的次数,提高运行效率。说到使用频繁,你想到了什么,那肯定是验证的时候啊,比如我们注册的时候,我们将用户名,密码,验证码等信息一提交,立马就会收到是否注册成功的反馈,这样的效果正是redis存储的一个好处体现。在我们处理验证模块的时候,回想一下过程:
用户一打开注册的弹窗,那么就会有一张图片验证码(这个过程虽然快,但是逻辑操作并不简单。前端会计算一个随机的编码UUID,然后将这个编码发送给后端,后端生成一个验证码图片,生成的时候,有三个值,一个图片的编号,一个是这张图片,一个是图片上的验证码。但是图片的编号我们不使用,我们将前端传过来的UUID作为key,将验证码作为value保存在redis中,然后将图片发送给前端,用户就看到了,因为这个过程很快,所以看不出延迟的,但并不代表编写过程简单)
然后用户填写手机号,图片验证码,点击获取手机验证码,后台收到三个参数(手机号,图片验证码,图片验证码的编号UUID),后台校验参数完整后,看手机号格式是否正确,然后利用UUID从redis中取出验证码和用户输入的验证码进行比对,正确的话就将redis中的图片验证码删掉,然后生成一个随机的短信验证码利用第三方SDK向用户发送短信验证码。后台将用户的手机号作为key,短信验证码作为value保存在redis中。
用户收到之后填写短信验证码,然后点击注册,后台收到三个参数(手机号,短信验证码和用户密码)之后校验短信验证码是否过期,因为redis中保存设置了有效期300s,没有过期的话,验证码校验是否输入正确,正确的话就可以将用户名和密码存储到数据库中了,此处就是MySQL了,因为用户的信息我们要永久保存啊,这就是MySQL的特点了。然后我们给session中保存用户的登录状态,并且返回给前端注册结果。
下面我将项目中上面几种情况用到redis的代码挑拣了出来,大家可以回忆一下:
1import redis 2 3# 配置redis 4redis_store = redis.StrictRedis(host=Config.REDIS_HOST, port=Config.REDIS_PORT) 5 6# 保存当前生成的图片验证码内容 7redis_store.setex('ImageCode_' + code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text) 8 9# redis中保存短信验证码内容 10redis_store.set("SMS_" + mobile, sms_code, constants.SMS_CODE_REDIS_EXPIRES) 11 12# 保存用户登录状态 13session["user_id"] = user.id 14session["nick_name"] = user.nick_name 15session["mobile"] = user.mobile
请求钩子
我们的项目中,在完善CSRFToken逻辑和拦截普通用户进入管理员页面的时候,用到了请求钩子。
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理的时候,为了让每个视图函数避免编写重复的代码,Flask提过了通用设施的功能,这就是请求钩子。
请求钩子是通过装饰器的形式实现的,有4种:
- before_first_request:在处理第一个请求前执行
- before_request:在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值,在返回之前做最后一步处理,再返回
- after_request:如果没有抛出错误,在每次请求后执行
- teardown_request:在每次请求后执行
- 接受一个参数:用来接收错误信息
但是我们常用的只有2和3两种,在项目中具体的代码展示一下,方便大家进行回忆:
1 #使用请求钩子拦截所有的请求,通过的在cookie中设置csrf_token 2 @app.after_request 3 def after_request(resp): 4 #调用系统方法,获取csrf_token 5 csrf_token = generate_csrf() 6 7 #将csrf_token设置到cookie中 8 resp.set_cookie("csrf_token",csrf_token) 9 10 #返回响应 11 return resp 12
1# 使用请求钩子,拦截用户的请求,只有访问了admin_blue,所装饰的视图函数需要拦截 2# 1.拦截的是访问了非登录页面 3# 2.拦截的是普通的用户 4@admin_blue.before_request 5def before_request(): 6 if not request.url.endswith("/admin/login"): 7 if not session.get("is_admin"): 8 return redirect("/")
CSRF攻击
什么是csrf攻击?
简单来说就是: 你访问了信任网站A,然后A会用保存你的个人信息并返回给你的浏览器一个cookie,然后呢,在cookie的过期时间之内,你去访问了恶意网站B,它给你返回一些恶意请求代码,要求你去访问网站A,而你的浏览器在收到这个恶意请求之后,在你不知情的情况下,会带上保存在本地浏览器的cookie信息去访问网站A,然后网站A误以为是用户本身的操作,导致来自恶意网站C的攻击代码会被执:发邮件,发消息,修改你的密码,购物,转账,偷窥你的个人信息,导致私人信息泄漏和账户财产安全收到威胁
如何解决?
在psot请求时,form表单或ajax里添加csrf_token(实际项目代码里就是如此简单)
解决原理:
添加csrf_token值后,web框架会在响应中自动帮我们生成cookie信息,返回给浏览器,同时在前端代码会生成一个csrf_token值,然后当你post提交信息时,web框架会自动比对cookie里和前端form表单或ajax提交上来的csrf_token值,两者一致,说明是当前浏览器发起的正常请求并处理业务逻辑返回响应,那么第三方网站拿到你的cookie值为什么不能验证通过呢?因为他没你前端的那个随机生成的token值啊,他总不能跑到你电脑面前查看你的浏览器前端页面自动随机生成的token值吧
注意:你打开浏览器访问某个url(页面),默认是get请求,也就是说,你只要访问了url,对应的视图函数里只要不是if xx == post的逻辑就会执行,所以你打开页面,他会先生成cookie(token)值,返回给浏览器, 然后你提交表单,或者发ajax请求时,会将浏览器的cookie信息(token值)发送给服务器进行token比对,这个过程相对于你发起了两次请求,第一次是get,第二次才是post,搞清楚这个,你才能明白csrf怎么比对的