我们在实际项目中,最重要的沟通就是所谓的“群聊”的方式,即一个项目组在物理位置上并不能坐在一起,因此需要经常一起通过“聊天”的方式进行沟通。YAHOO在这方面提供了很好的群聊功能,并且支持语音群聊。但是它有一个不太方便的地方,就是接收者必须同意后才可以开始接收消息,否则是无法接收到群消息的,这样造成群组工作中消息传达容易出现丢失,影响工作。
2.MSN
4.Skype
这是一款以语音效果见长的软件,我在进行语音聊天的时候,这是首选的工具,它的语音无论是从清晰度或延时方面,都比其它工具有很大的优势。Skype新版的群组功能也相当强健,但是Skype有一个最大的特点就是不支持离线发送消息,只有发送端和接收端同时在线的时候,消息才会被发出,这就很容易造成消息传达的失误,比如我们在和海外分公司沟通的时候,时差非常大,我发出消息后,对方不在线,而对方在线的时候,我又不在线,只有两端同时在线的时候,才会接收到消息,所以经常会收到很久之前的离线消息,对工作的负面影响较大。
5.OfficeIM
我在尝试了上述几个主要的工具后,才试图去寻找更合适的工具。
这个工具属于收费软件,暂且不说费用如何,我发现了它的一个问题,就是一个员工只能隶属于组织结构的一个节点,这对于我们项目管理的方式来说,基本已经被否定,因为我们一个人需要属于多个项目组。
6.飞鸽传书UM
第二部分为WebService服务器,这个服务器是客户端与数据库之间交互的通道,在客户端与数据库服务器之间传递信息。在这种架构模式下,客户端之间没有互相的通信,全部需要通过服务器来中转。
本系统在使用过程中,已经由项目组多个成员不断的完善,现在已经相当好用,并且新的功能也在不断的添加进来。下面分成三部分依次对数据库、WebService及客户端程序做一个介绍。
数据库主要存储以下内容:
用户信息
组织结构信息
消息历史记录
其它辅助信息
下面对每个表进行一个详细的说明:
1.用户表:ZR_USER
字段名
字段类型
说明
USER_ID
INTEGER
用户的内部ID,在和其它表关联的时候使用此字段
USER_ALIAS
VARCHAR2(255)
NAME
VARCHAR2(100)
用户的真实姓名,在聊天工具中显示真实姓名
PASSWORD
STATUS
状态
CREATION_USER
此记录的创建用户
CREATION_TIME
DATE
LAST_MOD_TIME
DELETED
删除标志
0:正常
1:已经删除
LAST_LOGIN_TIME
2.组织表:ZR_ORGANIZATION
ORG_ID
NUMBER
组织的内部ID,供关联使用
PARENT_ID
上级组织的ID
ORG_TYPE
组织类型
ORG_CODE
组织代码
ORG_NAME
VARCHAR2(200)
组织名称
0表示临时群组
1表示永久生效的群组
创建者
ORDERING
排序值
3.用户-组织对应表:ZR_USER_ORGANIZATION
用户ID,在这个表中,可以描述出用户与组织的多对多的关系
4.消息表:ZR_MESSAGE,目前考虑所有的消息均在服务器上保存,直到需要删除为止。
OID
本表的主键
TO_ID
接收人的ID,可以是个人,也可是一个群组
TO_TYPE
接收者的类型
0表示个人
1表示群组
FROM_ID
发送者的ID
CONTENT
VARCHAR2(2000)
发送的内容,根据字段宽度,不能超过1000个汉字
5.消息查阅历史表:ZR_MESSAGE_HIS,在系统中需要记录哪些消息被哪些人看过,以免下次重复发送,因为发给某个群组的消息,需要群组中所有成员都有权阅读,所以不能简单地在消息中加一个“是否已经阅”的字段。
HIS_ID
接收人的ID
MESSAGE_ID
消息的ID
BATCH_ID
批ID,某个会员可能同时接收一批的消息,而不是一个个的发送,在此记录批的信息,以减少与服务器的交互量
利用Oracle作为存储数据库,除了它的大容量支持是一个很好的特性之外,能编写非常优秀的存储过程,也是它的一个非常大的优势。所谓存储过程,就是在数据库中写一些程序,以保证可以快速方便的操作数据库,而不用编写非常复杂的C#代码。
本系统中有一个命名为ZR_TIM_PKG的包,里面包括以下几个主要的存储过程,在下面给出一个详细的说明:
2.发送消息,此函数用于接收客户端发来的消息,并记录在数据库中,函数体如下:
functionSendMessage(toIDnumber,toTypenumber,fromIDnumber,contentvarchar2)returnnumberispragmaautonomous_transaction;valuenumber:=0;beginselectseq_message.nextvalintovaluefromdual;insertintozr_message(oid,to_id,to_type,creation_time,from_id,content)values(value,toID,toType,sysdate,fromID,content);commit;returnvalue;end;
3.取得消息,本函数主要用于判断哪些消息需要发送给指定的用户,即不能发送重复,也不能丢下任何有用的信息,如果信息是发送给某个群组,则群组里的所有成员都需要得到这条消息,程序如下:
functionGetMessage(userIDinnumber)returnnumberispragmaautonomous_transaction;valuenumber;mBatchIDnumber;beginselectseq_message_batch.nextvalintomBatchIDfromdual;insertintozr_message_his(his_id,user_id,message_id,creation_time,batch_id)select1,userID,oid,sysdate,mBatchIDfromzr_messagemwhere(to_idin(selectorg_idfromzr_organizationstartwithorg_idin(selectorg_idfromzr_user_organizationwhereuser_id=userID)connectbypriorparent_id=org_id)orto_id=userID)andfrom_id!=userIDandnotexists(select'x'fromzr_message_hishwhereuser_id=pUserIDandh.message_id=m.oid);if(sql%rowcount=0)thenvalue:=0;elsevalue:=mBatchID;endif;updatezr_usersetlast_login_time=sysdatewhereuser_id=userID;commit;returnvalue;end;
段代码是整个存储过程中最长的一段,在判断是否已经读过的地方,有一个小技巧,代码片断如下:
notexists(select'x'fromzr_message_hishwhereuser_id=pUserIDandh.message_id=m.oid)
在写程序的时候,一般都喜欢用notin的语法,这种语法写法简单,易懂,但是有一个很大的问题,就是效率不好保证,在要求表设计必须满足一系列的条件之后,有时还不能达到效果。而notexists则对效率的满足会更容易一些。
上面还用到了一个“批”的概念,即一次可以向用户发送多条消息,依靠batch_id这个字段来保证批的完整性。
4.创建临时的群聊
functionInviteUser(pUserIDinnumber,pGuestIDsinvarchar2)
returnvarchar2is
pragmaautonomous_transaction;
valuevarchar2(1000);
mGroupIDnumber;
mGroupNamevarchar2(1000):='';
mNamevarchar2(100);--每个人的名字,临时变量
cursorcursor_isselectnamefromzr_userwhereuser_id
in(selectuser_idfromzr_user_organizationwhereorg_id=mGroupID);
begin
selectseq_group_temp.nextvalintomGroupIDfromdual;
insertintozr_organization(org_id,parent_id,status,deleted,
creation_time,creation_user,ordering)
values(mGroupID,0,0,0,sysdate,pUserID,0);
insertintozr_user_organization(user_id,org_id)
selectto_number(a.column_value),mGroupID
fromtable(split(pGuestIDs,','))a
unionall
selectpUserID,mGroupIDfromdual;
opencursor_;
fetchcursor_intomName;
while(notcursor_%notfound)loop
mGroupName:=mGroupName||mName||',';
endloop;
closecursor_;
mGroupName:=rtrim(mGroupName,',');
updatezr_organizationsetorg_name=mGroupNamewhereorg_id=mGroupID;
commit;
value:=mGroupID||'~'||mGroupName;
returnvalue;
end;
到此为止,数据库部分已经大致介绍完成,虽然代码比较冗长,但是为了不让读者产生任何误解,所以还是贴了大部分代码出来。下面的一个环节就是WebService的编写,敬请待等下期。