用AJAX+J2EE实现网上会议室系统

80酷酷网    80kuku.com

  ajax|j2ee|网上会议<br /> <DIV twffan="done">  今年大家都在炒作Web2.0,其中的一门技术Ajax也是跟着火了起来,因此前面我写了一篇名为《忽悠一下AJAX》的文章,简单地分析了一下Ajax的技术的实质。虽然笔者不太喜欢跟风,但Ajax有一些地方还是比较有用的。<table border="0" cellspacing="0" cellpadding="0" align="left" id="ad_pcdog_big"></span></td></tr></table>前段时间做了EasyJF开源团队的网上会议系统,就用到了Ajax技术,下面把设计思路发出来跟大家分享一下。</DIV><DIV twffan="done"> </DIV><DIV twffan="done">一、系统实现的功能 </DIV><DIV twffan="done"> </DIV><DIV twffan="done">  本会议室系统主要用于EasyJF开源团队的成员网上会议使用,会议系统模拟传统的会议形式,可以同时开设多个不同主题的会议室,每个会议室需要提供访问权限控制功能,会议中能够指定会议发言模式(分为排队发言、自由发言两种),系统能自动记录每个会议室的发言信息,可以供参会人员长期查阅。<br />会议系统的用户支持游客帐号参加会议,同时也提供跟其它用户系统的接口,比如EasyJF官网中的开源论坛系统。<br />会议系统暂时使用文字聊天的方式,并提供语音及视频的接口。</DIV><DIV twffan="done"> </DIV><DIV twffan="done">二、技术体系 <br /></DIV><DIV twffan="done"> </DIV><DIV twffan="done">  服务器端使用Java语言,MVC使用EasyJWeb框架;<br />  客户端使用AJAX技术与服务器端交互数据;<br />  会议历史信息储存格式使用文本格式,方便系统安装运行,也便于管理。</DIV><DIV twffan="done"> </DIV><DIV twffan="done">三、会议室服务器端设计 <br /></DIV><DIV twffan="done"> </DIV><DIV twffan="done">  会议室服务器端是整个会议系统的核心部分,服务器端程序设计的好坏影响到整个系统的质量。<br />  首先,根据会议室要实现的功能进行抽象分析。一个会议室对象,应该包括会议主题、会议简介、参会人数限制、公告、会议室类型、访问权限设定、房间密码、当前参会的人员、当前发言的人员、排队等待发言的人员等参数信息。我们把他封装一个Java对象当中。如下面的ChatRoom代码所示:<br />public class ChatRoom{<br /> private String cid;//主键<br /> private String title;//会议室主题<br /> private String intro;//会议室简介<br /> private String announce;//会议室公告<br /> private String owner;//会议室创建人<br /> private Integer maxUser;//最大在线人数<br /> private Integer intervals;//最大刷新时间间隔<br /> private String vrtype;//访问权限<br /> private String vrvalue;//访问值<br /> private Integer status;//会议室状态<br /> private Date inputTime;<br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done">  需要一个管理会议室的类,与会议有关的操作(如启动会议、关闭会议)等都直接找他。该类还应该即有自动定时检测用户在线情况(防止用户意外退出)、把内存中的会议历史发言信息保存到文本文件中等功能。这里可以考虑使用一个ChatService类提供这些功能:<br />public class ChatService implements Runnable {<br />private static final Map service=new HashMap();//会议室服务,系统中的当前会议室存放到该表集合中<br />private static final int maxServices=10;//可以同时开的最大会议室数<br />private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");<br />private final List msgs;//会议发言信息Chat<br />private final List users;//在线用户,ChatUser<br />private final List talkers;//排队发言人数Talker<br />private final List manager;//会议室管理员<br />private Talker currentTalker;//当前发言人<br />public ChatService()<br />{<br /> this.msgs=new ArrayList();<br /> this.users=new ArrayList();<br /> this.talkers=new ArrayList();<br /> this.manager=new ArrayList();<br /> this.maxUser=1000;//最大1000人同时<br /> this.interval=1000*60*5;//5分钟以前的信息<br />}<br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done">  会议发言信息也需要封装成一个类,表示发言人、接收人、内容、发言时间、类型等,大致如下面的Chat类:<br />public class Chat {<br />private String cid;<br />private String sender;<br />private String reciver;<br />private String content;<br />private Date vdate;<br />private Integer types;<br />private Integer status;<br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done">  还有表示参加会议的人的信息,包括参会人名称、IP地址、状态等,如下面的ChatUser类所示:<br />public class ChatUser {<br />private String ip;<br />private String port;<br />private String userName;<br />private Date lastAccessTime;<br />private Integer status;<br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done">  另外还需要一个表示当前发言人的Talker类,表示当前的发言人,发言开始时间,发言预计结束时间等。</DIV><DIV twffan="done">  在服务器端的设计中,会议室信息服务器应该能以多线程的方式运行,即启动一个会议就新开一个线程,每个会议线程维护自己的会议状态,如参会人、发言人,保存会议历史发言信息以及清空内存中的数据等操作。</DIV><DIV twffan="done"> </DIV><DIV twffan="done"> </DIV><DIV twffan="done">四、客户端设计 </DIV><DIV twffan="done"> </DIV><DIV twffan="done">  会议室客户端包括两个部分,一个部分是会议室的管理界面,主要包会议室的“添删改查”及“启动”或“关闭”会议服务的操作。这部分我们直接使用EasyJWeb Tools中的添删改查业务引擎AbstractCrudAction可以快速实现。界面也比较简单,直接使用EasyJWeb Tools代码生成工具引擎生成即可。会议室管理的客户端是传统的Java Web技术,因此没有什么要考虑的。<br />  客户端的第二个部分也即会议系统的主要部分,该部分主要有两个界面,第一个页面是会议室进入的选择页面。也即把已经启动的会议室列出来,用户选择一个会议室进入,这个页面也是使用传统的Java Web技术。第二个页面是进入会议室后的主界面,这个界面是整个会议系统的主要界面,所有参与会议的操作都在这里运行的。这个界面需要不断的与服务器端交互传输数据,传输的内容包括用户的发言、其它人给用户的发言、会议室的状态等。有的传输信息需要即时响应(如用户发言),有的信息可以设置成定时响应(如会议室状态)。<br />  Java Web程序中与服务器端交互数据主要有两种方式,一种是直接刷新页面,另外一种是使用Socket直接跟Web服务器端口通讯。由于Socket编程相对复杂,我们选择第一种直接刷新页面的方式,这种方式又可以分为几种,包括传统的Form提交,传统的自动刷新网页取得数据以及使用ActiveXObject对象(如xmlhttp)直接与服务器交互数据,也即AJAX方式。由于使用AJAX方式用户感觉不到页面在刷新,表现起来好于手动或自动刷新页面的方式,因此我们决定选择AJAX方式实现客户端与服务器端进行数据交互。</DIV><DIV twffan="done">  用户发言的时候,直接使用xmlhttp对象Post数据到服务器。为了能不断接收到别人的发言信息,需要定时不断的从服务器端读取数据,因此,需要在客户端启动一个定时器,每隔一定的时候自动使用xmlhttp对象到服务器端下载别人的发言信息,并显示到会议室信息主界面中。另外还要定时刷新参会的人数、会议室当前发言人、会议室的公告等会议状态信息,这也可以通过从客户端启动一个定时器,通过xmlhttp对象与服务器交互得到。<br />  另外还有一些操作,锁定会议室、踢人、指定发言人的发言时间、给会议室加密码等功能,也通过xmlhttp的方式与服务器传输命令实现。</DIV><DIV twffan="done"> </DIV><DIV twffan="done">五、核心代码说明 </DIV><DIV twffan="done"><br />1、服务器端核心代码 <br />  在EasyJF开源团队的会议系统中,由于是以EasyJF官网的论坛系统、后台管理等是集成一起的。服务器ChatService与ChatRoom共同合并到了一个ChatService.java类中,实现会议室管理及会议服务功能。ChatService类的部分主要代码如下:<br />package com.easyjf.chat.business;</DIV><DIV twffan="done">public class ChatService implements Runnable {<br />private static final Map service=new HashMap();//会议室服务,系统中的当前会议室存放到该表集合中<br />private static final int maxServices=10;//可以同时开的最大会议室数<br />private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");<br />private final List msgs;//会议发言信息Chat<br />private final List users;//在线用户,ChatUser<br />private final List talkers;//排队发言人数Talker<br />private final List manager;//会议室管理员<br />private Talker currentTalker;//当前发言人<br />private String cid;//会议室id<br />private String title;//会议室主题<br />private String intro;//会议室简介<br />private String owner;//会议室创建人<br />private int maxUser;//最大在线人数<br />private int interval;//最大刷新时间间隔<br />private String vrtype;//访问权限<br />private String vrvalue;//访问值<br />private String announce;<br />private String password;//房间进入密码<br />private int status;//会议室状态<br />private String filePath;<br />//private Thread thread;<br />private boolean isStop=false;<br />public ChatService()<br />{<br /> this.msgs=new ArrayList();<br /> this.users=new ArrayList();<br /> this.talkers=new ArrayList();<br /> this.manager=new ArrayList();<br /> this.maxUser=1000;//最大1000人同时<br /> this.interval=1000*60*5;//5分钟以前的信息<br />}<br />/**<br /> * 停止所有会议室<br /> *<br /> */<br />public static void clear()<br />{<br /> if(!service.isEmpty())<br /> {<br /> Iterator it=service.values().iterator();<br /> while(it.hasNext())<br /> {<br /> ChatService chat=(ChatService)it.next();<br /> chat.stop();<br /> }<br /> }<br /> service.clear();<br />}<br />/**<br /> * 创建一个会议室<br /> * param name 会议室ID<br /> * return<br /> */<br />public static ChatService create(String name)<br />{<br />ChatService ret=null;<br />if(service.containsKey(name))<br />{<br /> ChatService s=(ChatService)service.get(name);<br /> s.stop();<br /> service.remove(name);<br />}<br />if(service.size()<maxServices)<br />{<br /> ret=new ChatService(); <br /> service.put(name,ret);<br />}<br />return ret;<br />}<br />/**<br /> * 停止某个会议室<br /> * param name 会议室ID<br /> * return<br /> */<br />public static boolean close(String name)<br />{<br /> ChatService chatRoom=ChatService.get(name);<br /> if(chatRoom!=null)<br /> {<br /> chatRoom.stop();<br /> service.remove(name);<br /> }<br /> return true;<br />}<br />/**<br /> * 获得一个会议室信息<br /> * param name 会议室ID<br /> * return<br /> */<br />public static ChatService get(String name)<br />{<br /> if(service.containsKey(name))return (ChatService)service.get(name);<br /> else return null;<br />}</DIV><DIV twffan="done">public void run() {<br /> // TODO Auto-generated method stub<br /> //this.thread=Thread.currentThread();<br /> while(!isStop)<br /> {<br /> //System.out.println("开始监控一个会议室!"+this.title);<br /> this.flash();<br /> try{<br /> Thread.sleep(5000);<br /> }<br /> catch(Exception e)<br /> {<br /> e.printStackTrace(); <br /> } <br /> }<br /> //System.out.println("结束!");<br />}<br />public void stop()<br />{<br /> this.flashAll();<br /> isStop=true;<br />}<br />//会议室中有人发言<br />public boolean talk(Chat chat)<br />{<br /> boolean ret=false;<br /> if(canTalk(chat.getSender()))<br /> { <br /> this.msgs.add(chat);<br /> ret=true;<br /> }<br /> return ret;<br />}</DIV><DIV twffan="done">public boolean exit(ChatUser user)<br />{ <br /> talk(geneSystemMsg(user.getUserName()+"退出了会议室!"));<br /> return this.users.remove(user);<br />}<br />}<br />//刷新信息,保存会议信息<br />public void flash()<br />{<br /> flashChatMsg();<br /> flashChatUser();<br />}<br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done"> </DIV><DIV twffan="done">2、MVC处理部分的Action代码 </DIV><DIV twffan="done"> </DIV><DIV twffan="done"><br />  在EasyJF的会议系统中,由于使用EasyJWeb作为MVC框架,因此处理Ajax比较简单,下面是会议室系统的核心Action主要代码。<br />package com.easyjf.chat.action;<br />public class ChatAction extends AbstractCmdAction {<br /> private ChatService chatRoom;<br /> public Object doBefore(WebForm form, Module module) {<br /> // TODO Auto-generated method stub<br /> if(chatRoom==null)chatRoom=ChatService.get((String)form.get("cid"));<br /> return super.doBefore(form, module);<br /> }<br /> public Page doInit(WebForm form, Module module) {<br /> // TODO Auto-generated method stub <br /> return doMain(form,module);<br /> } <br /> //用户登录进入会议室<br /> public Page doMain(WebForm form, Module module) { <br /> if(chatRoom!=null){<br /> ChatUser user=getChatUser(); <br /> if(!chatRoom.join(user))form.addResult("msg","不能加入房间,可能是权限不够!");<br /> form.addResult("chatRoom",chatRoom);<br /> form.addResult("user",user);<br /> }<br /> else<br /> {<br /> form.addResult("msg","会议未启动或者会议室不存在!");<br /> } <br /> return module.findPage("main");<br /> } <br /> //处理用户发言信息<br /> public Page doSend(WebForm form, Module module) { <br /> if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误<br /> Chat chat=(Chat)form.toPo(Chat.class);<br /> chat.setCid(chatRoom.geneId());<br /> chatRoom.talk(chat);<br /> return doRecive(form,module);<br /> } <br /> //用户接收发言信息<br /> public Page doRecive(WebForm form, Module module) { <br /> if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误<br /> String lastReadId=CommUtil.null2String(form.get("lastReadId"));<br /> //System.out.println(lastReadId);<br /> form.addResult("list", chatRoom.getNewestMsg(getChatUser(),lastReadId)); <br /> return module.findPage("msgList");<br /> }<br /> //用户刷新会议状态信息<br /> public Page doLoadConfig(WebForm form, Module module) { <br /> if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误 <br /> form.addResult("userList", chatRoom.getUsers());<br /> form.addResult("talkerList", chatRoom.getTalkers());<br /> return module.findPage("config");<br /> }<br /> //用户退出<br /> public Page doExit(WebForm form, Module module) { <br /> if(chatRoom==null)return new Page("err","/err.html","thml");//返回会议室不存在的错误<br /> chatRoom.exit(getChatUser());<br /> form.addResult("msg","退出成功");<br /> ActionContext.getContext().getSession().removeAttribute("chatUser");<br /> return new Page("msg","/chat/xmlMsg.xml",Globals.PAGE_TEMPLATE_TYPE);<br /> }</DIV><DIV twffan="done"> </DIV><DIV twffan="done"> </DIV><DIV twffan="done">3、客户端AJAX部分核心代码 </DIV><DIV twffan="done"> </DIV><DIV twffan="done"><br />  EasyJF会议系统中,服务器发送给客户端的都是格式化的xml文档数据。下面是核心的AJAX函数及发送接收会议信息的客户端代码。<br />function newXMLHttpRequest() {<br /> var xmlreq = false;<br /> if (window.XMLHttpRequest) { <br /> xmlreq = new XMLHttpRequest();<br /> } else if (window.ActiveXObject) { <br /> try { <br /> xmlreq = new ActiveXObject("Msxml2.XMLHTTP");<br /> } catch (e1) { <br /> try { <br /> xmlreq = new ActiveXObject("Microsoft.XMLHTTP");<br /> } catch (e2) { <br /> }<br /> }<br /> }<br /> return xmlreq;<br />}<br />//处理返回信息<br />//xmlHttp返回值,<br />//method:方法名 方法必须带一个参数如doRecive(xNode);<br />function handleAjaxResult(req,method) { <br /> return function () { <br /> if (req.readyState == 4) { <br /> if (req.status == 200) {<br /> // 将载有响应信息的XML传递到处理函数<br /> var objXMLDoc=new ActiveXObject("Microsoft.XMLDOM");<br /> objXMLDoc.loadXML(req.responseText); <br /> eval("if(objXMLDoc.firstChild)"+method+"(objXMLDoc.firstChild.nextSibling);"); <br /> } else { <br /> //alert("HTTP error: "+req.status);<br /> }<br /> }<br /> }<br />}<br />//执行客户端Ajax命令<br />//url 数据post地址<br />//postData 发送的数据包<br />//handleMethod 处理返回的方法<br />function executeAjaxCommand(url,postData,handleMethod)<br />{<br /> var req = newXMLHttpRequest(); <br /> req.onreadystatechange =handleAjaxResult(req,handleMethod); <br /> req.open("POST", url, true); <br /> req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");<br /> req.setRequestHeader("charset","utf-8"); <br /> req.send(postData);<br />}<br />//用户发言<br />unction doSend()<br />{<br /> <br /> if(!check())return false;<br /> var msg=EditForm.content.value;<br /> var reciver=EditForm.reciver.value; <br /> var url="/chat.ejf?easyJWebCommand=send&cid="+roomId+"&lastReadId="+lastReadId;<br /> var postData="sender="+myName+"&reciver="+reciver+"&content="+msg;<br /> clearTimeout(reciveTime);<br /> executeAjaxCommand(url,postData,"recive");<br /> EditForm.content.value="";<br />}<br />//接收发言信息<br />function doRecive()<br />{ <br /> var reciver=EditForm.reciver.value; <br /> var url="/chat.ejf?easyJWebCommand=recive&cid="+roomId+"&lastReadId="+lastReadId;<br /> executeAjaxCommand(url,"","recive"); <br />}<br />//处理接收到的发言信息<br />function recive(list)<br />{<br /> var id=""; <br /> for(var oNode=list.firstChild;oNode;oNode=oNode.nextSibling) // 依次分析每个节点<br /> {<br /> chatContent.innerHTML+=showMsg(oNode);<br /> id=oNode.getAttribute("cid");<br /> }<br /> if(id!="") lastReadId=id; <br /> chatContent.scrollTop=chatContent.scrollHeight;<br /> reciveTime=setTimeout("doRecive();",5000); <br />}</DIV><DIV twffan="done"> </DIV><DIV twffan="done"><br />六、系统演示 <br /></DIV><DIV twffan="done"> </DIV><DIV twffan="done">  大家可以到EasyJF开源团队的官方网站看程序演示效果,地址是:</DIV><DIV twffan="done"><br />   http://www.easyjf.com/chatRoom.ejf?easyJWebCommand=show&ejid=2538093638804337</DIV><DIV twffan="done"><br />结束语 </DIV><DIV twffan="done"><br />  Ajax从技术上讲主要就是javascript、dhtml、css、xmldom、xmlhttp等一些我们很早就接触了的技术。而xmldom及xmlhttp也没有什么东西,写程序的时候把参考文档打开Copy就OK,dhtml及javascript涉及的东西就多了,不能只是看参考文档,需要把他真正消化,并能灵活动用,这就需要大家都练习了。笔者建议大家不要滥用Ajax。对于高手建议多研究一些业务及系统级算法设计等,对于新手嘛,把基本的技术(客户端的包括dhtml、css、javascript、xml等,J2EE服务器端的设计模式、UML建模、Servlet、JDBC或ORM系统、XML、EJB及一些框架、工具等)学好才是硬道理。</DIV>

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: