天天看點

C#打造自己的企業内部溝通平台(上)

    在現在的工作中,最重要的一個環節就是溝通,所謂“溝通”,是指工作中大家的互相交流,交流的方式有多種多樣,可以通過電子郵件,電話或網上溝通工具,每種方式都有自己的特點,比如電子郵件具有歸檔功能,并可以友善的追查之前的交流記錄,但是電子郵件的溝通速度稍慢;電話的溝通最為快捷,可以用最快的方式表達自己的思想,使大家的思路達成共識,但是電話溝通的結果很難被跟蹤,如果沒有一個正式的會議紀要,則會在短時間内再次被人淡忘;第三種常用的方式就是種用網上的一些溝通工具,如MSN,QQ等,針對這些溝通工具,我也大緻做了個比較,僅代表我個人觀點,無意替IM工具做廣告,也無意诋毀任何廠商:

    1. YAHOO

    我們在實際項目中,最重要的溝通就是所謂的“群聊”的方式,即一個項目組在實體位置上并不能坐在一起,是以需要經常一起通過“聊天”的方式進行溝通。YAHOO在這方面提供了很好的群聊功能,并且支援語音群聊。但是它有一個不太友善的地方,就是接收者必須同意後才可以開始接收消息,否則是無法接收到群消息的,這樣造成群組工作中消息傳達容易出現丢失,影響工作。

    2. MSN

    MSN目前的市場占用量相當大,但是在中國來說,它的登入一直是一個讓人頭痛的問題,經常會出現無法登入的狀況,特别是在我們公司内部,70%的人無法正常使用MSN,除了登入問題外,還有一個問題,就是MSN會偶爾丢失資訊,在極端的時候,可以看到聊天的一方在拼命的回答,另一方隻是不停的問“你收到了嗎?”這個問題的不良後果非常可怕。

    3. QQ

    QQ是在國産IM工具中一個相當出色,相當具有代表性的老大哥,它的群聊功能相當不錯,但是建立群組有一個限制,就是必須具有一個“太陽”才能建一個群,或者申請QQ會員才可以建立更多的群,這是與其它工具最大的差別,可能是它商業化運作的比較成功的緣故吧。如果公司内部有多個項目組,就要建立多個群組,應該感覺起來不太理想。

    4. Skype

    這是一款以語音效果見長的軟體,我在進行語音聊天的時候,這是首選的工具,它的語音無論是從清晰度或延時方面,都比其它工具有很大的優勢。Skype新版的群組功能也相當強健,但是Skype有一個最大的特點就是不支援離線發送消息,隻有發送端和接收端同時線上的時候,消息才會被發出,這就很容易造成消息傳達的失誤,比如我們在和海外分公司溝通的時候,時差非常大,我發出消息後,對方不線上,而對方線上的時候,我又不線上,隻有兩端同時線上的時候,才會接收到消息,是以經常會收到很久之前的離線消息,對工作的負面影響較大。

    5. OfficeIM

    我在嘗試了上述幾個主要的工具後,才試圖去尋找更合适的工具。

    上述四個工具均屬于通用軟體,需要internet支援,僅在區域網路内,是無法使用的,對于有些公司來說,外網的通路權限需要開通,有些公司甚至不允許通路外網,而對于我們公司來說,通路外網需要經過嚴格的審察,而對QQ來說,應該是禁止使用的。于是我想找一個小型的溝通平台架設在自己的公司内部。

    這個工具屬于收費軟體,暫且不說費用如何,我發現了它的一個問題,就是一個員工隻能隸屬于組織結構的一個節點,這對于我們項目管理的方式來說,基本已經被否定,因為我們一個人需要屬于多個項目組。

    6. 飛鴿傳書UM

    這是一款源自于日本的軟體,用戶端免費使用,伺服器端會産生費用,這款軟體不但支援組織結構,而且一個人員可以同時隸屬于多個項目組,我進了試用,效果不錯,但是後來發現了一個比較嚴重的問題(說實話現在記不起具體的問題了),我直接電話與他們客服溝通,确認此問題無法解決。

    以上僅是我自己找的一些IM工具,我相信世界上有數不清的類似的工具,應該也有完全适合我的,隻是我沒有找到而已,不管這麼多了,既然找不到,何不嘗試自己寫一個呢,哈,說起來有點天方夜譚,但是我有自知之明,我知道我需要什麼樣的功能,我不會去做很多比較花哨漂亮的效果出來。說幹就開始準備吧,我首先要做的是找一個圈内的高人來指點一下IM的一般工作方式,得知最常用的方式是需要在聊天的用戶端之間建立TCP連接配接,實作用戶端之間的互相通信。天呢,我因為長時間隻是在寫C#程式,對這些底層通訊的方法基本沒太多的概念,這樣放棄?不行,在繼續求教後得知,也有人直接利用WebService,建立用戶端與伺服器的通訊,用戶端之間互相不通訊,全部通過伺服器來中轉,哈,其實這正是我想象的方式,沒想到真有人這麼做的。我也知道這種方式有很多的缺點,但是我想了一下我的實際場景,應該沒有太大的問題。說了這麼多,讓我們出發吧!

    在概述部分我們曾提起過一點關于架構的問題,為了簡化應用,我們必須在最短的時間内,用最快速的方式來實作一個最簡單的聊天的功能,因為我們做這個工具是為了項目本身服務,而它本身并不是作為一個專門的項目來存在的。

C#打造自己的企業内部溝通平台(上)

     其中資料庫采用Oracle10g,可以盡可能保證大的并發量及曆史資訊的存儲,因為此系統已經明确為企業内部使用,并且全部是與工作相關的,為了防止用戶端聊天記錄的丢失,本架構要求在伺服器上保留所有的聊天記錄,以待後期審計。

    第二部分為WebService伺服器,這個伺服器是用戶端與資料庫之間互動的通道,在用戶端與資料庫伺服器之間傳遞資訊。在這種架構模式下,用戶端之間沒有互相的通信,全部需要通過伺服器來中轉。

    第三部分就是用戶端,也就是企業内部使用者。在本系統中,不提供公開注冊的功能,所有的使用者必須由背景進行注冊,并配置設定一個初始密碼,使用者登入後可以自行修改自己的密碼,這種方式保證系統的安全性及私密性。

    本系統在使用過程中,已經由項目組多個成員不斷的完善,現在已經相當好用,并且新的功能也在不斷的添加進來。下面分成三部分依次對資料庫、WebService及用戶端程式做一個介紹。

    資料庫主要存儲以下内容:

Ø 使用者資訊

Ø 組織結構資訊

Ø 消息曆史記錄

Ø 其它輔助資訊

下面對每個表進行一個詳細的說明:

1. 使用者表:ZR_USER

字段名

字段類型

說明

USER_ID

INTEGER

使用者的内部ID,在和其它表關聯的時候使用此字段

USER_ALIAS

VARCHAR2(255)

使用者的登入名,在資料庫中按大寫字母來存儲

NAME

VARCHAR2(100)

使用者的真實姓名,在聊天工具中顯示真實姓名

PASSWORD

使用者登入的密碼,由系統進行初始化,使用者登入後可以自己修改

STATUS

狀态

0表示不可以登入

1表示可以登入

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的包,裡面包括以下幾個主要的存儲過程,在下面給出一個詳細的說明:

    1. 檢查使用者的登入狀态

function GetUserStatus(lastLoginTime in date) return number is

value number := 0;

begin

if (lastLoginTime is not null) then

if (sysdate - lastLoginTime < 2 / 24 / 60) then

value := 1;

end if;

return value;

end;

    本函數主要用于取出使用者的登入狀态,在本系統中,認為最後登入日期超過兩分鐘視為離線。

    2. 發送消息,此函數用于接收用戶端發來的消息,并記錄在資料庫中,函數體如下:

function SendMessage(toID number,

toType number,

fromID number,

content varchar2) return number is

pragma autonomous_transaction;

select seq_message.nextval into value from dual;

insert into zr_message

(oid, to_id, to_type, creation_time, from_id, content)

values

(value, toID, toType, sysdate, fromID, content);

commit;

    3. 取得消息,本函數主要用于判斷哪些消息需要發送給指定的使用者,即不能發送重複,也不能丢下任何有用的資訊,如果資訊是發送給某個群組,則群組裡的所有成員都需要得到這條消息,程式如下:

function GetMessage(userID in number) return number is

value number;

mBatchID number;

select seq_message_batch.nextval into mBatchID from dual;

insert into zr_message_his

(his_id, user_id, message_id, creation_time, batch_id)

select 1, userID, oid, sysdate, mBatchID

from zr_message m

where (to_id in

(select org_id

from zr_organization

start with org_id in (select org_id

from zr_user_organization

where user_id = userID)

connect by prior parent_id = org_id) or to_id = userID)

and from_id != userID

and not exists (select 'x'

from zr_message_his h

where user_id = pUserID

and h.message_id = m.oid);

if (sql%rowcount = 0) then

value := 0;

else

value := mBatchID;

update zr_user set last_login_time = sysdate where user_id = userID;

    段代碼是整個存儲過程中最長的一段,在判斷是否已經讀過的地方,有一個小技巧,代碼片斷如下:

not exists (select 'x' from zr_message_his h where user_id = pUserID and h.message_id = m.oid)

    在寫程式的時候,一般都喜歡用not in的文法,這種文法寫法簡單,易懂,但是有一個很大的問題,就是效率不好保證,在要求表設計必須滿足一系列的條件之後,有時還不能達到效果。而not exists則對效率的滿足會更容易一些。

    上面還用到了一個“批”的概念,即一次可以向使用者發送多條消息,依靠batch_id這個字段來保證批的完整性。

4. 建立臨時的群聊

    本系統的操作方式是在背景協助建立好了相關的項目組或組織架構,并建立相關的使用者,使用者自己無法注冊,也無法選擇自己所在的組,在聊天的時候,可以選擇一對一的聊天,也可以對項目組整個發起群聊。這兩種方式基本滿足了聊天的要求,但是對于一些特别的情況,比如一個項目組裡有十多個人,而我隻想和其中的兩個或三個群聊,這時如果以整個項目組為接收對象,則很多人會收到無用的資訊,甚至工作會受到打擾,為此,接受我們項目組團隊成員的建議,加入了“臨時群聊”的概念,就是可以臨時組建一個群組,在這個小的群組裡進行聊天,這個函數的作用就是建立這個群組,并标志這個群組為臨時性。好了, 現在看一下代碼部分:

function InviteUser(pUserID in number, pGuestIDs in varchar2)

return varchar2 is

value varchar2(1000);

mGroupID number;

mGroupName varchar2(1000) := ' ';

mName varchar2(100); -- 每個人的名字,臨時變量

cursor cursor_ is select name from zr_user where user_id

in (select user_id from zr_user_organization where org_id = mGroupID);

select seq_group_temp.nextval into mGroupID from dual;

insert into zr_organization (org_id, parent_id, status,deleted,

creation_time, creation_user, ordering)

values (mGroupID, 0, 0, 0, sysdate, pUserID, 0);

insert into zr_user_organization (user_id, org_id)

select to_number(a.column_value), mGroupID

from table(split(pGuestIDs, ',')) a

union all

select pUserID, mGroupID from dual;

open cursor_;

fetch cursor_ into mName;

while (not cursor_%notfound) loop

mGroupName := mGroupName || mName || ',';

end loop;

close cursor_;

mGroupName := rtrim(mGroupName, ',');

update zr_organization set org_name = mGroupName where org_id = mGroupID;

value := mGroupID || '~' || mGroupName;

    到此為止,資料庫部分已經大緻介紹完成,雖然代碼比較冗長,但是為了不讓讀者産生任何誤解,是以還是貼了大部分代碼出來。下面的一個環節就是WebService的編寫,敬請待等下期。

本文轉自Aicken(李鳴)部落格園部落格,原文連結:http://www.cnblogs.com/isline/archive/2010/04/12/1710343.html,如需轉載請自行聯系原作者