同学们好!成功登录《我家租房网》系统的用户,可以点击位于搜索页面右上角的用户名,进入用户页面。点击用户页面中的“我的房源”,会打开我的房源页面。在“发布新房源”的下面,会显示当前用户所发布的房源列表。点击房源列表中的房源条目,会显示房屋详情。这节课我们来实现获取房屋详情的功能。


我们将首先实现支持获取房屋详情的后端微服务,再在前端服务器中添加路由处理函数并远程调用后端微服务,最后再将前后端连在一起做完整的功能测试。


首先我们来实现后端服务器。


第一步是创建特定的微服务。


创建微服务的过程还是和先前一样,直接在back-end工程目录下执行go-micro命令,子命令为new,参数为service,微服务名为GetHouse。


接着,需要修改一下go-micro为我们自动生成的接口描述。在back-end工程目录下的GetHouse微服务目录中,有一个名为proto的子目录,这就是存放接口描述脚本及其Go代码文件的地方。现阶段里面只有一个名为GetHouse.proto的ProtoBuf脚本文件。我们需要对该文件做一些修改:

这段代码描述的是,前端服务器和后端微服务之间的数据交换格式。CallRequest表示前端提供给后端的请求数据,其中包含两个字符串,用户名和房屋ID。CallResponse表示后端返回给前端的响应数据,其中包含错误代码、错误描述和房屋及浏览用户信息。房屋及浏览用户信息中包含房屋信息和浏览用户ID。房屋信息中包含面积、地址、床铺配置、容纳人数、评论列表、押金、设施ID列表、房屋ID、房屋图片URL列表、最多入住天数、最少入住天数、价格、房间数、标题、房间配置、房主用户头像URL、房主用户ID、房主用户名等共计十八项内容。其中评论列表中的每条评论均包含评论内容、发布时间和下单用户名等三项内容。


有了用ProtoBuf语言描述的远程调用接口,我们还需要把它编译成基于Go语言的程序代码。在GetHouse微服务目录中执行make命令,并携带四个目标参数,它们是init、proto、update和tidy。


在GetHouse微服务目录下,创建一个名为model的子目录,表示该微服务的模型层。在该子目录中创建mysql.go文件,封装所有与操作MySQL数据库有关的代码。在实际编写数据库访问代码之前,我们可以先把之前为前端服务器编写的mysql.go文件中的内容原封不动地复制到这里:


修改GetHouse微服务目录中的main.go文件,添加对InitDB函数的调用,将服务发现改为Consul,绑定IP地址和端口:

程序启动伊始,即完成对数据库的初始化。其中包括两个动作,其一是连接ihomedb数据库并获得连接池对象,其二是在该数据库中创建表。当然,如果这些表已经存在,则不再创建。接着,在服务器对象的初始化部分,指示其注册到Consul服务器,同时绑定本机的9013端口。


接下来,我们为GetHouse微服务编写与数据持久化有关的代码。


打开GetHouse微服务目录下,model子目录中的mysql.go文件,在其中添加有关从MySQL数据库中读取房屋详情、房主用户、浏览用户、房屋订单、下单用户、房屋设施和房屋副图的代码:

定义名为ReadHouse的函数,用于从MySQL数据库中读取房屋详情。该函数以从参数传入的房屋ID为条件,查询房屋表中的特定记录,获得表示该记录的房屋结构体变量,连同错误对象,返回给函数的调用者。

定义名为ReadOwner的函数,用于从MySQL数据库中读取房主用户。该函数以从参数传入的房屋结构体变量为依据,基于房屋表的房主用户ID外键,和用户表的用户ID主键,在房屋表和用户表之间做一对一的关联查询,得到表示用户表中特定记录的用户结构体变量。最后将参数房屋的房主用户信息,连同错误对象,返回给函数的调用者。

定义名为ReadBrowser的函数,用于从MySQL数据库中读取浏览用户。该函数以从参数传入的浏览用户名为条件,查询用户表中的特定记录,获得表示该记录的用户结构体变量,连同错误对象,返回给函数的调用者。

定义名为ReadOrders的函数,用于从MySQL数据库中读取房屋订单。该函数以从参数传入的房屋结构体变量为依据,基于房屋表的房屋ID主键,和订单表的预定房屋ID外键,在房屋表和订单表之间做一对多的关联查询,得到表示订单表中多条记录的订单结构体切片。最后将参数房屋的所有订单信息,连同错误对象,返回给函数的调用者。

定义名为ReadOrderer的函数,用于从MySQL数据库中读取下单用户。该函数以从参数传入的订单结构体变量为依据,基于订单表的下单用户ID外键,和用户表的用户ID主键,在订单表和用户表之间做一对一的关联查询,得到表示用户表中特定记录的用户结构体变量。最后将参数订单的下单用户信息,连同错误对象,返回给函数的调用者。

定义名为ReadFacilities的函数,用于从MySQL数据库中读取房屋设施。该函数以从参数传入的房屋结构体变量为依据,基于房屋表的房屋ID主键,和设施表的设施ID主键,借助房屋设施中间表,在房屋表和设施表之间做一对多的关联查询,得到表示设施表中多条记录的设施结构体切片。最后将参数房屋的所有设施信息,连同错误对象,返回给函数的调用者。

定义名为ReadImages的函数,用于从MySQL数据库中读取房屋副图。该函数以从参数传入的房屋结构体变量为依据,基于房屋表的房屋ID主键,和图片表的所属房屋ID外键,在房屋表和图片表之间做一对多的关联查询,得到表示图片表中多条记录的图片结构体切片。最后将参数房屋的所有副图信息,连同错误对象,返回给函数的调用者。


在完成GetHouse微服务模型层的所有开发工作之后,我们将着手编写用于处理获取房屋详情业务的代码。


打开GetHouse微服务目录下,handler子目录中的GetHouse.go文件,在其中的Call方法里,添加与获取房屋详情有关的操作:

这里首先调用模型层的ReadHouse函数,根据请求对象中的房屋ID,获取该房屋的详情信息。调用模型层的ReadOwner函数,获取该房屋的房主用户信息。调用模型层的ReadBrowser函数,根据请求对象中的用户名,获取浏览用户信息。将前面得到的房屋及浏览用户信息填入响应对象,其中有些字符串信息,需要先转换为无符号整数,URL信息,需要在文件凭证前面拼接Nginx服务器的地址和端口。调用模型层的ReadOrders函数,获取该房屋的所有订单信息,遍历其中的每一张订单,调用模型层的ReadOrderer函数,获取该订单的下单用户信息,将该订单的评论内容、发布时间和下单用户名,添加到响应对象的评论列表中。调用模型层的ReadFacilities函数,获取该房屋的所有设施信息,遍历其中的每一件设施,将其ID添加到响应对象的设施ID列表中。若该房屋存在主图,则将其文件凭证拼接到Nginx服务器的地址和端口之后,添加到响应对象的房屋图片URL列表中。最后调用模型层的ReadImages函数,获取该房屋的所有副图信息,遍历其中的每一幅副图,将其文件凭证拼接到Nginx服务器的地址和端口之后,添加到响应对象的房屋图片URL列表中。


在完成GetHouse后端服务器的开发后,我们需要为前端服务器添加一条路由,并在路由处理函数中完成对后端微服务远程方法的调用。


前端和后端共用同一套接口描述。


因此,这里我们将GetHouse微服务目录下proto子目录中的所有文件,原封不动地复制到front-end工程目录proto子目录下的GetHouse目录中。


这里我们要为获取房屋详情添加一条路由,GET方法结合/api/v1.0/houses/:id路径,处理函数名为GetHouse。


在front-end工程目录下的main.go文件中添加一条路由:

这里我们调用了路由对象的GET方法,为获取房屋详情添加了一条路由,将GET方法结合/houses/:id路径,路由到controller包的GetHouse函数。将GetHouse函数定义在controller包里是因为该函数的主要任务是执行业务逻辑,属于MVC中的C,即控制器层的部分。


当然,在controller包里真的得有GetHouse函数。为此,我们打开front-end工程目录下controller子目录中的house.go文件。在该文件的import部分增加一行,同时定义GetHouse函数:

这段代码首先从Session中读取用户名,接着调用了Gin框架调用此函数时传入的上下文对象的Param方法,取添加路由时放在请求路径中的占位符“id”为参数,接收其返回的房屋ID。通过Consul服务发现,获取有关名为gethouse的微服务的信息,远程调用其中的GetHouse对象的Call方法。该方法以包含用户名和房屋ID的请求对象为参数,并返回包含错误代码、错误描述和房屋及浏览用户信息的响应对象。最后将该响应对象序列化为一个JSON字符串,编码到HTTP响应中,回传给浏览器。


至此,我们已经完成获取房屋详情的全部开发工作。下面我们将对这部分功能进行测试。


在虚拟机中启动FastDFS和Nginx。启动Consul服务器、UserLogin、GetUser、GetHouses、GetHouse后端微服务和前端服务器。通过登录页面登录系统,点击位于搜索页面右上角的用户名,进入用户页面,点击“我的房源”,进入我的房源页面,当前用户已发布的房源列表,显示在“发布新房源”下方,点击房源列表中的房源条目,显示房屋详情。


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