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(); } } }
在本函数中,会灵活的检查所需要的窗口是否已经打开,如果没有打开,则创建一个,如果已经打开了,则直接激活它。
经过以上几个基本的函数,已经可以实现消息的发送与接收功能,当然本系统还支持更高级的功能,如临时创建群组,这个在数据库及服务器端已经做了介绍,但是在客户端的代码较多,就不在说明。还有一些其它的功能,如每天第一次登录可以看到一段笑话,保证每天都有好心情等,有兴趣的朋友自己继续完善吧!
本文转自Aicken(李鸣)博客园博客,原文链接:http://www.cnblogs.com/isline/archive/2010/04/12/1710344.html,如需转载请自行联系原作者