首页

11.3 开发过程

关灯 护眼    字体:

上一章 目录 下一章




11.3.1  会话管理的基本原理


1.什么是Session

由于HTTP是没有状态的,所以在默认情况下,如果浏览器访问了同一个网站两次,网站是不知道这两次请求来自同一个浏览器的。为了让网站知道这两次请求来自同一个网站,需要在浏览器的请求中带上一段信息。浏览器中带上的这段信息就是Cookie。

但是由于Cookie是明文存放的,任何人都能看到也能修改,所以显然不能把用户名和密码放在Cookie中,于是就需要在服务器中放一段信息,这个信息就是Session。

当用户第一次登录成功以后,网站服务器会生成一段Session。这段Session是存放在网站自己这边的,但是网站会在浏览器的  Cookie  中添加一段字符串,叫作  SessionID。由于每次浏览器请求网站时都会带上  Cookie,那么网站就可以从每一次请求中获得  SessionID。有了这个SessionID  以后,网站就可以在自己这边查询到这个请求实际对应的  Session  是什么。在解析Session里面的内容后,可以知道这个用户的用户名、什么时候登录的、账号状态等信息。

Session本质上就是网站可以理解的信息。

Session  存在哪里实际上并没有严格的要求,无论是内存中,还是文件中,或是数据库中。只要网站在需要时能够查询和理解就可以。

2.负载均衡与共享Session

由于技术的限制,一旦网站规模变大,一台服务器无论配置多么好,都无法承受同时产生的越来越多的请求,所以就有了负载均衡技术。

负载均衡技术可以在同一个域名后面接入非常多的服务器,并且自动为每一个请求选择最优的服务器。

例如,“双11”购物节,淘宝使用了数十万台服务器,但是使用淘宝购物的消费者用到的域名始终是taobao,消费者不需要关心具体自己访问的这个页面是运行在哪一台服务器上面的。

如果使用了负载均衡技术,那Session储存在什么地方就显得尤为重要了。因为,即使是不同的进程要共享同一台服务器的内存都非常困难,更不要说不同的服务器读取其中某一台服务器上某个进程的内存了。

如果使用Redis来存储Session,那么只要每一台服务器都能访问Redis,那共享Session的问题就不再是问题了。

3.本实例的Session储存机制

本实例使用Redis的哈希表来存储Session。当用户注册账号,或者登录成功以后,网站会使用下面一个函数生成Session信息:

代码11-1  生成Session与SessionID

其中,主要代码说明如下。

●  第2、3行:设置Session的过期时间为“距离现在30天”。

●  第4~6行:在Session中储存的信息包括用户的ID、用户名和过期时间对应的时间戳。

●  第7行:生成SessionID,这里使用的是UUID。

其中,Session  里面具体保存什么信息,可以根据项目的要求自己确定。SessionID  只要保证不重复即可,使用什么方式生成都没有问题。

网站使用这个函数生成  Session  和  SessionID  以后,会把它们保存到  Redis  的哈希表中,  SessionID作为字段名,Session转换为JSON字符串以后作为值。同时,SessionID还会被添加到用户浏览器的Cookie中。

4.本实例的Session查询原理

当用户访问了一个需要登录的页面以后,网站会首先从请求的  Cookie  中获得  SessionID,然后使用这个SessionID去Redis中查询Session。查询会有4种情况。

●  Cookie中没有SessionID,说明用户没有登录,则转到登录页面。

●  Cookie中有SessionID,但是Redis的哈希表中找不到这个SessionID,也认为用户没有登录,则转到登录页面。

●  在Redis中根据SessionID成功找到Session,但是比对发现这个Session的expire_time小于现在的时间,说明这个Session已经过期了,则需要让用户重新登录。

●  找到Session并且它没有过期,则可以正常使用。

当找到正常可用的Session后,网站就会从Session中读取出用户名,将其显示在网页的右上角。由于用户名是不重复的,那么如果一个问题的提问者的名字和Session中的用户名是一样的,则说明这个问题就是当前这个用户提的。于是就给他打开修改问题的功能。回答也是一样的原理。



11.3.2  保存与读取用户信息


在用户注册时,需要保存用户信息;在用户登录时,需要读取用户信息。操作  MongoDB保存和读取用户信息的方法如下:

代码11-2  保存用户信息与读取用户信息

其中,主要代码说明如下。

●  第2行代码:记录当前时间,并转化为“yyyy-mm-dd  HH:MM:SS”格式。

●  第4行代码:向MongoDB中插入数据,同时获取被插入数据的ObjectId。

这是常规的MongoDB读写操作。

提示:

save_user_info方法传入的password_hash参数是经过不可逆加密的密码。因为直接把用户的密码保存在网站数据库是非常没有职业道德也没有安全意识的行为,一旦发生数据泄露将会导致非常严重的灾难。



11.3.3  更新问题和回答


更新问题和回答,涉及的是常规的MongoDB更新操作,代码如下:

代码11-3  更新问题和更新答案



11.3.4  检查用户名是否已经注册


在用户注册时,需要检查用户名是否已经被注册过。对于数据量不大的情况,可以使用Redis的集合:

代码11-4  判断用户名是否已经注册

其中,第2行代码使用的Redis  Key为qa_system:user:duplicate。注意,这里的冒号仅仅是普通的分隔符,和下画线字母之类的字符没有区别。

提示:

使用冒号分割是一种约定俗成的习惯。如果读者更习惯下划线,那么用下画线分割也没有问题。

如果用户名已经存在,则执行sadd操作以后返回的是0,而“0  ==  0”是True,所以这个方法就会返回True。

如果sadd操作以后返回的数据是1,由于“1  ==  0”是False,所以这个方法会返回False。

修改完成这个函数以后,就不会所有的用户名都提示已经注册了。



11.3.5  在Redis中储存与删除Session


使用Redis的哈希表来储存Session,哈希表的字段是SessionID,字段值是Session对应的JSON字符串。

代码11-5  在Redis中储存或者删除Session

其中,主要代码说明如下。

●  第2行代码:把Session字典转化为JSON字符串,以便存在Redis中。

●  第3行代码:使用hset把Session存入哈希表中。

●  第6行代码:使用hdel从哈希表中删除SessionID对应的字段。



11.3.6  从Redis中获取Session


根据  SessionID  从哈希表中读取  Session,并将其转化为字典。在这个过程中,需要注意Session过期的情况。

代码11-6  从Redis中读取Session

其中,主要代码说明如下。

●  第2、3行代码:如果SessionID为空,则直接返回空字典。

●  第4行代码:根据SessionID从哈希表中读取Session。

●  第5、6行代码:如果SessionID找不到Session,则返回空字典。

●  第7行代码:将Session对应的JSON字符串转化为字典。

●  第8、9行代码:调用login_expire函数检查Session是否过期,如果过期,仍然返回空字典。

修改完成这个方法以后,网站可以实现正常注册和登录了。



11.3.7  记录和检查“用户回答是否回答了某个问题”


仍然使用哈希表来记录用户是否回答了某个问题。字段名为用户名和问题ID拼接的长字符串。

●  如果哈希表中存在这个字符串,则说明用户已经回答了这个问题。

●  如果哈希表中不存在这个字符串,则说明用户还没有回答过这个问题。

代码11-7  检查用户是否回答了问题,设置用户已回答标记

其中,主要代码说明如下。

●  第2行:判断用户名与问题id拼接成的字符串是否为哈希表中的一个字段。

●  第5行:把用户名和问题id拼接成的字符串作为哈希表的一个字段存入哈希表中,它的值可以随意设置。

提示:

请读者思考,这个地方能使用Redis集合的sadd来判断吗?

修改完成这个方法以后,用户将不能回答同一个问题超过一次。



本章小结


本章实现了问答网站的账户机制,能够注册账号和登录。由于能够确认用户身份了,所以可以限制用户回答同一个问题的个数,用户也能修改自己提的问题和自己的回答。

本项目使用的网络框架为Falsk,这个框架其实有第三方的Session管理插件和登录插件。但是为了介绍如何使用Redis来管理Session,因此本实例采用了自己写的逻辑。

如果读者阅读本实例的后台代码,会发现本实例把代码直接写在了网站的路由函数中,这样做是为了更加直观地表示出Session的储存和查询的位置,但实际上这种写法是不够规范的。在第14章,问答网站的后台代码将会重构,从而实现更加规范的代码。




上一章 目录 下一章