同学们好!在上一节课,我们为大家介绍了《我家租房网》应用的用户注册流程。当注册页面打开时,浏览器向服务器发送图片ID,服务器向浏览器发送验证码图片,同时将图片ID和验证码文本,以键值对的形式保存起来。当用户获取短信验证码时,浏览器向服务器发送图片ID、用户输入的图片验证码文本和手机号。服务器根据图片ID,从存储中获得正确的图片验证码文本,验证用户的输入是否正确。如果正确,则随机生成一个短信验证码,发到用户的手机上,同时将手机号和短信验证码,以键值对的形式保存起来。当用户提交注册表单时,浏览器向服务器发送手机号、用户输入的短信验证码和密码。服务器根据手机号,从存储中获得正确的短信验证码,验证用户的输入是否正确。如果正确,则将用户的手机号和密码哈希值保存到数据库中,完成注册。从这段叙述中,我们不难发现,整个用户注册流程,共涉及到五次数据存取。第一次是在打开注册页面时,服务器要将图片ID和图片验证码文本,以键值对的形式保存起来。第二次和第三次是在获取短信验证码时,服务器一方面要根据图片ID,从存储中获得正确的图片验证码文本,另一方面还要将手机号和短信验证码,以键值对的形式保存起来。第四次和第五次是在提交注册表单时,服务器一方面要根据手机号,从存储中获得正确的短信验证码,另一方面还要将用户的手机号和密码哈希值保存到数据库中。在这五次数据存取中,只有最后一次是针对关系型数据的持久化数据存取,存储介质可以选择类似MySQL这样的关系型数据库。而前四次,其实都是针对键值对形式数据的临时性存取。象图片ID——图片验证码文本键值对,或者手机号——短信验证码键值对,这些数据仅在注册过程中有效,并不需要永久保存,选择类似Redis这样的,非关系型内存数据库作为存储介质更为合理。这节课我们要关注的重点,就是基于Redis的内存数据库操作,及其在微服务中的集成。


我们首先对Redis做一个快速入门,然后了解一下基于Go语言的Redis数据库访问,并实际编写一个简单的测试程序,最后将保存图片ID——图片验证码文本键值对的操作,添加到GetImageCode微服务中。


我们首先对Redis做一个快速入门。


Redis的安装包可以从其官网或GitHub上直接下载,其64位Windows版本是一个类似Redis-x64-3.2.100.msi的安装文件。象安装其它软件一样,双击该文件,遵循安装向导的提示,一步步完成安装即可。有关Redis的配置信息都集中在它的配置文件中。Windows版Redis的配置文件位于其安装目录下,名为redis.windows.conf。打开该文件可以看到这样一些配置项。bind和port表示Redis服务器绑定的地址和端口,默认为127.0.0.1和6379。


dbfilename表示持久化存储的文件名。默认为dump.rdb,保存数据快照,体积小,数据恢复快,性能高,但丢失率高,耐久性差。也可以人为指定扩展名为“.aof”的文件,保存操作日志,丢失率低,耐久性高,但体积大,数据恢复慢,性能差。


Redis的底层存储引擎采用红黑树结构,其Windows版,服务器程序名为redis-server.exe,客户机程序名为redis-cli.exe。我们可以直接在控制台启动Redis客户机,通过交互命令访问Redis数据库。比如通过set命令添加键值对或修改键的值,通过get命令获取键的值,通过keys命令获取键列表,通过flushall命令清空数据库,通过exit命令退出客户机程序。


作为应用程序的开发者,我们更关心的,当然是在Go语言程序中,如何访问Redis数据库。


在Redis官方提供的客户端资源网站上,我们可以看到面向不同编程语言的客户端软件包。其中的Redigo,就是针对Go语言的Redis客户端库。


我们可以从GitHub上下载Redigo,并解压到正确的路径下。


Redigo提供了一套非常简单易用的API,用于在Go语言程序中访问Redis数据库。连接数据库可以调用Dial函数,其参数中包括协议、地址和端口。操作数据库可以调用Do函数,其参数中包括具体的操作指令和数据。该函数返回操作结果。如果想从操作结果中,获得特定类型的数据,还需要调用一系列与类型具体化有关的函数。


下面我们将编写一个基于Go语言的测试程序,体验一下通过Redigo访问Redis数据库的基本方法。


我们在front-end工程目录下的test子目录中创建一个名为redis.go的文件:

在这段代码中,我们首先通过Redigo提供的Dial函数,建立与Redis数据库的连接。传给该函数的参数中包括所使用的通信协议,及Redis服务器的IP地址和端口号。Dial函数会返回一个连接对象,后续对数据库的所有操作,包括关闭数据库连接,都会用到这个连接对象。接着,我们通过连接对象的Do方法,在数据库中添加了一个键值对,键为“name”值为“zhangfei”。该函数返回两个值,一个应答对象和一个错误对象。应答对象需要通过Redigo的String函数具体化为字符串类型后,才能被当作字符串处理。我们也可以将这两步合二为一,将Do方法的返回值直接传给String函数,一次性得到具体类型的处理结果。比如后面获取“name”键的值,我们就是这样做的。对于某些操作,比如添加键值对或修改键的值,我们其实并不关心应答对象的具体内容,只需根据错误对象是否为空判断出错与否即可。这时我们可以采用简化书写的形式,代码看上去会更简洁。如果应答对象中包含的是一个整型数据,类型具体化应使用Redigo的Int函数,而如果是字符串切片,则使用Strings函数。数据库一旦被清空,所有的键值对都会立刻消弭于无形。


最后,我们将把保存图片ID——图片验证码文本键值对的操作,添加到GetImageCode微服务中。


回想一下上节课,我们为GetImageCode微服务所定义的接口描述。其中表示前端提供给后端的请求数据的CallRequest,只是一对空的花括号。那是因为那时我们的前端确实不需要为后端提供什么。但现在的情况不同了。我们既然要在后端,将图片ID——图片验证码文本键值对,保存到Redis数据库中,我们就必须先拿到图片ID,该信息只能由前端提供。因此,我们需要修改GetImageCode微服务的接口描述。


编辑back-end工程目录下,GetImageCode微服务目录中,proto子目录里的GetImageCode.proto脚本文件:

CallRequest表示前端提供给后端的请求数据,其中包含一个字符串型的uuid,即图片ID。


因为我们修改了接口描述文件,所以还要重新做一次编译,以使与之对应的Go语言代码文件,能够反映出我们所做的修改。


为此,我们在GetImageCode微服务目录中执行make命令,只携带一个目标参数,proto。


我们在前序课程中,对MVC架构曾做过详细的讲解。其中的M,即Model,代表模型层,通常指与数据库操作有关的代码。保存图片ID——图片验证码文本键值对的操作,显然属于模型层。


我们在GetImageCode微服务目录下,创建一个名为model的子目录,表示该微服务的模型层。在该子目录中创建redis.go文件,封装所有与操作Redis数据库有关的代码:

这里我们定义了一个名为SaveImageCode的函数,用于将图片验证码的UUID和验证码字符串保存到Redis数据库中。该函数带有两个输入参数,uuid为图片ID,str为图片验证码文本。首先调用Redigo的Dial函数,连接数据库,同时获得用于后续数据库操作的连接对象。然后通过连接对象的Do方法,将图片ID——图片验证码文本键值对添加到数据库中。注意这里使用的操作命令是“setex”,该命令可以指定超时,比如五分钟,超过五分钟数据库不响应即宣告失败。该函数返回Do方法返回的错误对象。


有了SaveImageCode函数,我们还需要在GetImageCode微服务的Call方法中调用该函数。


编辑back-end工程目录下,GetImageCode微服务目录中,handler子目录里的GetImageCode.go文件:

在上节课编写的Call方法的最后,添加对模型层SaveImageCode函数的调用,将从请求中获得的图片ID,和之前生成的图片验证码文本,作为参数传递给该函数,以键值对的形式保存到Redis数据库中。


请求中的图片ID,显然来自前端。因此我们还需要修改,前端服务器中调用后端远程方法的代码,在请求参数中增加图片ID。


因为我们已经修改了GetImageCode微服务的接口描述,这里需要将其同步到前端工程中。为此,我们将GetImageCode微服务目录下proto子目录中的所有文件,原封不动地复制到front-end工程目录proto子目录下的GetImageCode目录中。


编辑front-end工程目录下,controller子目录里的user.go文件:

很明显,在调用Call方法所提供的CallRequest参数中,增加了图片ID。


启动Consul服务器、GetImageCode微服务和前端服务器。从浏览器进入注册页面,在看到图片验证码的同时。


前端打印图片ID,后端打印图片验证码文本,Redis数据库中保存图片ID——图片验证码文本键值对。


谢谢大家,我们下节课再见!