“拉”的方法充分地体现 “富客户端”的思想,让客户端每个隔一段时间就向服务器发送一个请求,服务器就向客户端返回相应的查询结果。由于我们对新消息的实时不是很严格,只要满足在3分钟之内能够收到。所以我们可以要求客户端每个3分钟提交一次查询申请就可以轻松地解决这一问题。
于是在我们的撮合系统中选择用“客户端拉”的方式实现新消息提示,实现也是比较简单的。伪代码如下:
function getNew(){ 向服务器发送查询请求 setTimeout(“getNew()”,30000); }
3.2.2 数据库存储类型的选择
系统数据库要求存储最基本的点和线等一些拼车时出现的地理信息。这些信息会在地图显示的时候从数据库中查询得到,然后在根据查询得到的信息在地图上面显示出来,因为根据要求我们的查询支持框选、点选等一系列操作,这样就要求我们能够在用户在地图上点一个点的时候查询出通过这个点方圆一定范围(比如说是500米)以内的所有线路的信息,框选也是一样,要求我们能够通过框的位置、大小查询出通过该区域的所有有效线路的信息。
根据需求,数据库方面要求线路存储一些经过的点,还要存储一些点的信息。小组在解决存储时可以简单的用一些常用类型来实现:存储点时,用两个Double类型的字段来记录经纬度(其它字段这里不做讨论);存储线路时,要包括需要记录的所有点信息,这样势必要形成一个新的表来记录线与点之间的关系。
如果通过上面的方式来设计地理信息的存储,系统将面临这一下几个问题:记录线与点时间关系的表将相当冗余,在查询时增加了表之间的连接操作,在记录线路的时候在必须记录里面相邻点之间的方程(因为系统在查询是要计算该路线是不是通过以查询点为中心的区域),同时系统在查询时不得不遍历所有的线路,计算存储着的所有的方程,从数学的角度分析它们之间有没有相交。这样大大地增加了系统的开销和算法的设计难度,使得搜索部分难以按要求完成。
既然这样的普通类型在设计和实现上难度很大,而且即使是实现出来效率也值得怀疑的情况下。怎么办呢?
感谢OpenGIS标准,它给我们设计出了针对地理信息系统的标准。同时感谢MySQL的Spetial Extention。它为我们提供了很多OpenGIS所规定的函数,同时把OpenGIS中规定的类
28
具体化。让系统在空间信息的存储和检索方面得到很大的优化。
于是系统存储点的时候就可以用一个Point类型的字段在记录其经纬度,存储线路时用一个LineString类型的字段来记录线路上经过的点。当系统查询符合要求的线路的时候时,可以先根据要求生成一个区域类型(Polygon)的数据,再通过MySQL中提供的函数查询获得所有和这个区域相交的线路。这样就通过简单的设计,和简单的实现,和比较优化的性能实现了需求。
3.2.3 消息表的设计
经过分析发现,用户在消息方面需要接受3中类型的消息:发给车主的消息、发给乘客的消息和撮合成功时发给所有成员的评价消息。
经过这样的分析,发现可以用3个表分别记录这些消息,因为它们需要显示的消息的内容上相差很大。但是这样就多了很多的表间关系,我们查看新消息的时候就需要同时查询这些表,并且在返回信息时,也要用不同的格式来存储。这样在后台数据生成的时候复杂度也相对增加。
既然有这么多的缺点是不是能找到一种更加简单的方法来实现?这使我们想到了就用一张表,在表中设计了一个type字段,来记录消息的类型。用了一个content字段来记录消息的内容,值得指出的是这个content字段不是随便存储的而是按照一定的格式存储进去的,因为我们在前台要根据type来确定解析content的方法,将解析后的结果正确的显示出来。当然这样做有优点也有缺点,但权衡利弊觉得用这样的设计方法还是比较不错。
一张表可以很容易的对它进行扩展,也许随着需求的变更,那天需要增加一种消息,可以把消息的type设置的不同,再把content按照该新消息的要求填入即可,没有必要想早期的想法那样去增加一张表。这样设计使得本系统课扩展性更好,使用更加灵活,后台的压力也会小一些。同时也增加了前台解析的难度,一个比较大的缺点是数据的冗余相对较大。
3.2.4 前后台新消息的交互数据
在撮合系统中,前后台的交互相当活跃。但是大量的数据交互都只涉及到极个别参数传递,同时也都是在js中通过AJAX技术实现的。所以我们不用花过多的时间去提取Form中的内容,也不用花大把的时间去解决后台的XML解析。唯一需要通过生成XML文件传输的也只有服务器向客户端传递的新消息。我们只用根据用户的编号获取用户的未读消息,将这样的一条条消息写入到XML中,向客户端发过去就完成了生成和发送部分。解析部分在客户端完成。
29
3.2.5 撮合时信息的处理
在客户端处理新消息的XML:通过DOM解析技术解析该XML,获取所有消息,将他们记录如一个个的Element,遍历这些Element,获取每个Element的中type元素的值,根据这些值的不同,通过不同的方法来解析content,从而生成记录不同消息记录的HTML DOM的Element,用这些Element重构HTML的DOM树。这样就大体的实现了客户端对XML的解析和HTML DOM树的重构。具体实现的时候需要解决浏览器兼容问题。
用户回馈评价结果:获取被评对象在该线路上的评分,重计算该评分,获取用户的总评分,加上上面的重计算的评分差,更新数据库。
用户给服务器发送撮合消息:这里系统使用到了单例模式和观察者模式来设计,根据提交的数据,如果状态是1或是2的话,表示车主和乘客的撮合记录还不存在,这样系统应该生成一条撮合记录,同时想对方按前面所说的格式添加一条新消息,在设置对方的消息数。如果是其它数字的话应该读取数据库中的该条撮合记录更改其状态,同时向上面一样对对方进行设置。
3.2.6 线路撮合成功后的评价生成
车主如果觉得线路的撮合完成了,就可以点击线路撮合成功。然后后台就根据传递过来的线路编号查询这条线路上所有撮合成功的成员。遍历这些成员,向每个人发送一跳待评价消息,消息的内容生成过程如下:获取路线信息写入相应的字段,遍历撮合成功成员,除掉自己以外的所有成员编号记录入content字段,插入数据库。
3.3 数据库的设计与封装
本节主要介绍数据库表的设计,我们可以将数据库中的表大致的为用户相关、线路相关、消息相关和撮合相关四类。以下的内容将围绕着这四类表的设计展开,用一小节的来就数据库的整体设计做一个总结。
3.3.1 用户相关表的设计
1. 用户信息表(user_information):
? ui_id,varchar类型,长度50,用户本站ID(主键),本站注册用户的本站ID与
其登陆ID一致;
? user_name,varchar类型,长度20,姓名;
30
? ui_sex,varchar类型,长度6,性别; ? ui_birthday,date类型,出生年月; ? ui_city,varchar类型,所在城市; ? ui_phone,varchar类型,电话; ? ui_email,varchar类型,邮箱; ? ui_remark,varchar类型,个人说明;
? ui_score,integer类型,总积分(等级可按积分计算出来)。积分与用户登陆次数、成功撮合次数、用户评价有关;
? ui_sharing_num,integer类型,新消息的数量。
2. 用户权限表(user_authentication):
? ua_id,varchar类型,长度50,用户登陆id(主键)
? ua_is_openid,Boolean类型,长度1,是否是openid用户,1表示是、0表示不是; ? ua_password,varchar类型,长度20,登陆密码,当ua_is_openid为1时为null、为0时此项是not null的;
? ua_native_ui_id,varchar类型,长度50,用户本站ID(主键)。 3.3.2 线路相关表的设计 1. 地标表(point):
? p_id,integer类型,长度10,地点id(主键); ? p_name,varchar类型,长度50,地点名称;
? p_point,POINT类型,长度10,点(空间数据类型,包括坐标信息)
2. 线路信息表(router_information):
? ri_id,integer类型,长度10,线路信息id(主键); ? ri_start_p_id,integer类型,长度10,起点id(外键); ? ri_end_p_id,integer类型,长度10,终点id(外键);
? ri_route,LineString类型,xianlu(空间数据类型,包括线路上所有的点信息); ? ri_publish_ui_id,varchar类型,长度50,发布者用户id(外键);
? ri_publish_is_owner,Boolean类型,长度1,发布者是不是车主,是则为1、不是则为0;
? ri_publish_time,DATETIME类型,发布线路的时间; ? ri_leave_time,DATETIME类型,出发时间;
31
? ri_arrival_time,DATETIME类型,到达时间; ? ri_type_st_id,拼车类型id(外键);
? ri_status,varchar类型,长度10,线路状态:发布中,已结束(乘客撮合后即为已结束,车主停止加人后为已结束);
? ri_request_rr_id,integer类型,长度10,拼车要求id(外键);
3. 线路要求表(route_request):
? rr_id,integer类型,长度10,拼车要求id(主键); ? rr_passager_num,integer类型,长度2,乘客数量; ? rr_sex,varchar,长度6,性别限制;
? rr_people_only,Boolean类型,长度1,是否只载人,1为是,0为不是;? rr_driver_age,integer类型,长度3,司机驾龄要求; ? rr_other,varchar类型,长度200,其他要求。
4. 拼车类型(sharing_type):
? st_id,integer类型,长度10,拼车类型id(主键);
? st_type,varchar类型,长度20,拼车类型:上下班、旅游、临时等。 3.3.3 消息相关表的设计 1. 消息表(message):
? m_id,integer类型,长度10,站内短消息id(主键); ? m_form_ui_id,varchar类型,长度50,来自用户id(外键); ? m_to_ui_id,varchar类型,长度50,目标用户id(外键); ? m_type,integer类型,长度4,消息的类型; ? m_content,varchar类型,长度200,消息的内容; ? m_send_time,DATATIME类型,发送时间;
? m_has_read,Boolean类型,消息是否已阅读,1为已阅读、0为未读; 3.3.4 撮合相关表的设计 1. 撮合记录表(match_record):
? mr_id,integer类型,长度10,撮合记录id(主键); ? mr_ri_id,integer类型,长度10,撮合线路id(外键)。 2. 撮合乘客记录表(match_passenger):
32