接上文
WebService伺服器
上一部分我們已經對資料庫部分的結構做了完整的說明,其實有了資料庫的結構之後,這個WebService層次的功能,也僅是對資料庫的調用了,也就是相當於封裝了一些外層的介面,實現用戶端與資料庫的串連,下面對主要介面做一下詳細的說明,並且在程式中,會用到我以前發表的ZRCMS架構的內容,如果遇到這方面的程式,請參考我以前的文章:
1. 登入
登入是指使用者根據登入名稱及密碼登入到伺服器,主要調用代碼如下:
[WebMethod]
public string Login(string userAlias, string password)
{
if (Authentication != null && Authentication.DevToken == DEV_TOKEN)
{
UserManager manager = new UserManager();
string user = manager.IsPasswordValid(userAlias.ToUpper(), password, true);
return user;
}
else
{
return null;
}
}
這個函數主要調用的就是manager.IsPasswordValid這個方法,這個方法返回的是使用者的名字,供前台用戶端使用,如果調用失敗,或驗證失敗,則返回為空白。
2. 取得組織資訊
在使用者登入成功後,首先要在程式中產生組織圖的樹,並在樹形結構中填充該結構下的成員名單,以實現和他們的聊天,這個函數的功能就是取得某個會員所能看到的組織圖及該結構下的所有的成員,代碼如下:
[WebMethod]
public DataSet GetOrgsPriv(long userID)
{
if (Authentication != null && Authentication.DevToken == DEV_TOKEN)
{
string sql = "select 0 type,org_id,parent_id,org_name,"
+ " level l, 1 status from zr_organization t "
+ " where deleted=0 and status = 1 and org_id in(select org_id "
+ " from zr_organization start with org_id in (select org_id"
+ " from zr_user_organization where user_id = " + userID.ToString()
+ " ) connect by prior parent_id = org_id) start with parent_id=0 "
+ " connect by prior org_id=parent_id"
+ " union all select 1,u.user_id,org_id,name,999999,"
+ " zr_tim_pkg.GetUserStatus(last_login_time) from zr_user u,"
+ " zr_user_organization uo where u.deleted=0 and u.user_id=uo.user_id "
+ " and org_id in (select org_id from zr_organization t where deleted = 0"
+ " and org_id in (select org_id from zr_organization start with org_id "
+ " in (select org_id from zr_user_organization where user_id = "
+ userID.ToString() + ") connect by prior parent_id = org_id)"
+ " start with parent_id = 0 connect by prior org_id = parent_id) "
+ " order by l";
DataAccessor data = new DataAccessor();
DataSet ds = data.ExecuteReader(sql);
return ds;
}
else
{
return null;
}
}
這個函數中有80%的篇幅就是一個SQL語句,這個SQL語句會檢查登入使用者的許可權,並返回他所擁有的許可權的資訊,並且為了簡化操作,把組織樹及使用者在一個DataTable中返回,靠一個標誌位來區分是哪種類型,在用戶端程式中會檢查這個狀態位並做相應的處理。
3. 發送訊息
看到這裡,您也許更確認,此WebService就是對資料庫函數的一個封裝,形成一個可以被外界調用的介面!沒錯,就是這樣,這個發送訊息也不例會,從程式中就可以看到它是如何調用資料庫的預存程序的,代碼如下:
[WebMethod]
public string SendMessage(long toID, int toType, long fromID, string content)
{
if (Authentication != null && Authentication.DevToken == DEV_TOKEN)
{
long mType;
if (toID < 9000000000) { mType = 0;}
else{ mType = 1;}
string sql = "select zr_tim_pkg.sendmessage(" + toID + "," + mType + ","
+ fromID + ",'" + content.Replace("'", "''") + "') from dual";
DataAccessor data = new DataAccessor();
string result = data.ExecuteScalar(sql);
return result;
} else {return null; }
}
其中zr_tim_pkg.sendmessage就是資料庫中的預存程序。
在本系統中,有一個簡單的約定,使用者的ID號小於9000000000,因此發現大於9000000000的ID,則表明這個實體是一個組織,而非一個單一使用者。
4. 取得訊息
這個函數與資料庫的取得訊息的函數相對應,但是略有不同,即調用預存程序之後,沒有直接返回訊息,而是再次使用一個select語句從資料庫中取得,也就是說這個函數本身的事務完整性是沒有保障的,在某些特別的情況下,可能對訊息標誌為已讀,但是並沒有真正傳送到用戶端,而且在下次也無法再次接收。這是本系統的一個隱患,但是因為處理的比較簡單,這一點也就直接放過沒有處理。可以參考以下代碼:
public DataSet GetMessages(long userID)
{
if (Authentication != null && Authentication.DevToken == DEV_TOKEN)
{
string sql = "select zr_tim_pkg.GetMessage(" + userID + ") from dual";
DataAccessor data = new DataAccessor();
string result = data.ExecuteScalar(sql);
if (result == "0"){ return null; }else {
sql = "select * from zr_message_vw where oid in(select message_id from"
+ " zr_message_his where batch_id=" + result + ") order by from_id,oid";
System.Diagnostics.Debug.WriteLine(sql);
DataSet ds = data.ExecuteReader(sql);
return ds; } } else{return null; }
}
5. 建立臨時組
這個函數沒有特別的地方,也是調用資料庫伺服器的相關函數進行處理,操作方式與上述函數類似,在此不再貼原始碼。
看了上述幾個函數,您也許會發現幾個相同的地方,就是在函數中都會檢查Authentication這個變數的相關屬性,這是一個安全性考慮,因為WebService的安全性較低,所以我在網上查了一下,找了一個最簡單的方式處理了一下,做法大致如下:
在此類中聲明一個變數:public ValidationSoapHeader Authentication;
同樣,在用戶端調用時候,需要對這個屬性進行賦值操作,以實現兩端的一致,就可以實現安全性的控制,這當然不是最好的方式,但是我想它是性價比最高的一個方式吧。
WebService伺服器端的程式就是這麼的簡單,到目前已經講完,在下一講裡,我們將重點講一下用戶端的程式的開發,包括如何調用伺服器端的程式。
用戶端程式
上一講我們談了WebService端的程式,這一講我們來討論一下用戶端的程式。用戶端程式是直接展示在普通使用者面前的程式,因此強健性及穩定性也是主要考慮的一個因素。
我們先來簡單的看一下系統登入後的效果吧,到目前才展示最有吸引力的部分,真是讓大家久等了J
大家請看,這是一個多麼“簡潔”的工作視窗!哈,其實並不是我有意做成這麼簡單,因為複雜的效果對我來說,在短時間內實現有些困難,就沒有在介面上面花太多的功夫,不過好在實用就行了。
如果登入使用者同時隸屬於多重專案組,會同時顯示多重專案組的資訊,在此可以實現群聊或單獨私聊,當然請記住,伺服器上同時儲存了聊天記錄!
因為用戶端的代碼比較多,我將不再粘貼大段的代碼,只是找一些有代表性的代碼做一下說明。先看一下產生組織圖樹的代碼:
gate = new TIMClient.TIMServer.TIMGate();
header = new TIMClient.TIMServer.ValidationSoapHeader();
header.DevToken = "xxxxxxx";
gate.ValidationSoapHeaderValue = header;
frmAbout about = new frmAbout();
hashNode = new Hashtable();
GetOrgs();
GetMessage();
這段代碼很有代表性,首先是控制了WebService的安全性,然後調用幾個私人函數取得資訊。
從WebService 取得資料的幾個函數類似,我只說一下GetOrgs的片斷:
DataSet ds = gate.GetOrgsPriv(userID);
if (ds == null){
MessageBox.Show(" 組織圖為空白", "GetOrgs");
return;}
foreach (DataRow row in ds.Tables[0].Rows){
string orgID = row["ORG_ID"].ToString();
string orgName = row["ORG_NAME"].ToString();
string parentID = row["PARENT_ID"].ToString();
string l = row["L"].ToString();
string type = row["TYPE"].ToString();
string status = row["STATUS"].ToString();
TreeNode node = new TreeNode();
node.Tag = orgID;
node.Text = orgName;
hashNode[orgID] = node;
if (hashNode[parentID] != null) {
if (type == "0"){
node.ImageIndex = 1;
node.SelectedImageIndex = 1;}
else{
if (status == "1"){
node.ImageIndex = 2;
node.SelectedImageIndex = 2;
}else if (status == "0"){
node.ImageIndex = 3;
node.SelectedImageIndex = 3;}}
TreeNode nodeParent = (TreeNode)hashNode[parentID];
nodeParent.Nodes.Add(node); }
else{
if (type == "0"){
tvList.SelectedNode = node;
tvList.Nodes.Add(node);}}}
tvList.SelectedNode.ExpandAll();
在上述片斷中,第一句:DataSet ds = gate.GetOrgsPriv(userID);即實現了從伺服器取資料,後面的代碼全部是在用戶端的處理;
在填充TreeView的時候,我喜歡用一個Hashtable,這樣可以簡化TreeNode的尋找,提高開發的效率;
在本段程式中,根據檢查所接收的資料是組織節點或者是使用者,根據不同的類型,設定不同的表徵圖,並同時根據線上或離線的狀態,設計表徵圖的顏色。
在雙擊一個使用者或一個組織圖後,即可以彈出對話方塊,進行聊天,如:
這同樣是一個簡單的不能再簡單的訊息發送視窗,在下面的部分輸入內容,在上面的視窗即可以顯示自己的及對方回複的內容。
為了達到更好的顯示效果,兩個文字框均為RichTextBox,RichTextBox可以顯示帶顏色的字型,可以直接開啟URL串連,但是我還沒能實現發送笑臉等小表徵圖的功能,有興趣的朋友可以繼續處理。
當在發送視窗打斷行符號或按“發送”按鈕後,將調用發送的函數,如下:
private void Send(){
if (txtSrc.Text.Trim() != "") {
string text = txtSrc.Text.Trim();
gate.SendMessage(long.Parse(this.nodeID), 0, long.Parse(userID), text);
AppendContent(this.name, System.DateTime.Now.ToString(), text);
}}
這個函數的動作分成兩個部分,首先是調用伺服器端組件,向資料庫中寫入發送的內容,同時也沒有忘給自己的訊息視窗填充一下,在填充的時候,會考慮顏色的問題,即寄件者的名字與發送的訊息按不同的顏色來顯示,以提高可讀性,部分代碼如下:
public void AppendContent(string name, string time, string content){
Color colorTitle = Color.FromArgb(0, 128, 64);
Color colorContent = Color.FromArgb(0, 0, 200);
string title = name + "(" + time + ")\r\n";
txtShow.SelectionColor = colorTitle;
txtShow.AppendText(title);
string _content = content + "\r\n";
txtShow.SelectionColor = colorContent;
txtShow.AppendText(_content);
ContentProcess(content);
try{
string fileName = Application.StartupPath + "\\messages\\" + this.Text + ".txt";
StreamWriter writer = new StreamWriter(fileName, true);
writer.Write(title + _content);
writer.Close();
} catch (Exception) { }
}
對於顏色的切換,用法非常簡單,按上述代碼處理即可。
在這個函數中還有一個重要的功能就是向本地的檔案寫入聊天記錄,在聊天結束後,可以在原生目錄中找到聊天的所有的內容,方便尋找工作中需要的資訊。
在用戶端程式中,還有一個重要的部分就是不斷的輪詢伺服器,檢查是否有新的訊息,如果有的話, 自動啟用相應的視窗,並填充內容,主要代碼如下:
int count = ds.Tables[0].Rows.Count;
int i = 0;
foreach (DataRow row in ds.Tables[0].Rows){
string fromID = row["FROM_ID"].ToString();
string content = row["CONTENT"].ToString();
string creationTime = row["CREATION_TIME"].ToString();
string toID = row["TO_ID"].ToString();
string toType = row["TO_TYPE"].ToString();
string fromName = row["from_name"].ToString();
string toName = row["to_name"].ToString();
string sendName = fromName;
if (toType == "1"){
fromName = toName;
fromID = toID; }
frmMessage frm = null;
if (hashForm[fromID] != null) {
frm = (frmMessage)hashForm[fromID]; } else {
frm = new frmMessage(fromID, fromName, userID.ToString(), name);
hashForm[fromID] = frm; }
frm.Show();
frm.AppendContent(sendName, creationTime, content);
i++;
if (i == count) {
if (Form.ActiveForm != frm){
frm.FlashIt(); } } }
在本函數中,會靈活的檢查所需要的視窗是否已經開啟,如果沒有開啟,則建立一個,如果已經開啟了,則直接啟用它。
經過以上幾個基本的函數,已經可以實現訊息的發送與接收功能,當然本系統還支援更進階的功能,如臨時建立群組,這個在資料庫及伺服器端已經做了介紹,但是在用戶端的代碼較多,就不在說明。還有一些其它的功能,如每天第一次登入可以看到一段笑話,保證每天都有好心情等,有興趣的朋友自己繼續完善吧!