用AJAX+J2EE實現網上會議室系統
來源:互聯網
上載者:User
ajax|j2ee|網上會議 <br /> <DIV twffan="done"> 今年大家都在炒作Web2.0,其中的一門技術Ajax也是跟著火了起來,因此前面我寫了一篇名為《忽悠一下AJAX》的文章,簡單地分析了一下Ajax的技術的實質。雖然筆者不太喜歡跟風,但Ajax有一些地方還是比較有用的。<table border="0" cellspacing="0" cellpadding="0" align="left" style="margin-top:10px;margin-right:7px;margin-bottom:3px;margin-left:0px"><tr><td><span 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>