會議系統的用戶支持遊客帳號參加會議,同時也提供跟其它用戶系統的接口,比如EasyJF官網中的開源論壇系統。
會議系統暫時使用文字聊天的方式,並提供語音及視頻的接口。
客戶端使用AJAX技術與服務器端交互數據;
會議歷史信息儲存格式使用文本格式,方便系統安裝運行,也便於管理。
首先,根據會議室要實現的功能進行抽象分析。一個會議室對象,應該包括會議主題、會議簡介、參會人數限制、公告、會議室類型、訪問權限設定、房間密碼、當前參會的人員、當前發言的人員、排隊等待發言的人員等參數信息。我們把他封裝一個Java對象當中。如下面的ChatRoom代碼所示:
public class ChatRoom{
private String cid;//主鍵
private String title;//會議室主題
private String intro;//會議室簡介
private String announce;//會議室公告
private String owner;//會議室創建人
private Integer maxUser;//最大在線人數
private Integer intervals;//最大刷新時間間隔
private String vrtype;//訪問權限
private String vrvalue;//訪問值
private Integer status;//會議室狀態
private Date inputTime;
}
public class ChatService implements Runnable {
private static final Map service=new HashMap();//會議室服務,系統中的當前會議室存放到該表集合中
private static final int maxServices=10;//可以同時開的最大會議室數
private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
private final List msgs;//會議發言信息Chat
private final List users;//在線用戶,ChatUser
private final List talkers;//排隊發言人數Talker
private final List manager;//會議室管理員
private Talker currentTalker;//當前發言人
public ChatService()
{
this.msgs=new ArrayList();
this.users=new ArrayList();
this.talkers=new ArrayList();
this.manager=new ArrayList();
this.maxUser=1000;//最大1000人同時
this.interval=1000*60*5;//5分鐘以前的信息
}
}
public class Chat {
private String cid;
private String sender;
private String reciver;
private String content;
private Date vdate;
private Integer types;
private Integer status;
}
public class ChatUser {
private String ip;
private String port;
private String userName;
private Date lastAccessTime;
private Integer status;
}
客戶端的第二個部分也即會議系統的主要部分,該部分主要有兩個界面,第一個頁面是會議室進入的選擇頁面。也即把已經啓動的會議室列出來,用戶選擇一個會議室進入,這個頁面也是使用傳統的Java Web技術。第二個頁面是進入會議室後的主界面,這個界面是整個會議系統的主要界面,所有參與會議的操作都在這裏運行的。這個界面需要不斷的與服務器端交互傳輸數據,傳輸的內容包括用戶的發言、其它人給用戶的發言、會議室的狀態等。有的傳輸信息需要即時響應(如用戶發言),有的信息可以設置成定時響應(如會議室狀態)。
Java Web程序中與服務器端交互數據主要有兩種方式,一種是直接刷新頁面,另外一種是使用Socket直接跟Web服務器端口通訊。由於Socket編程相對複雜,我們選擇第一種直接刷新頁面的方式,這種方式又可以分爲幾種,包括傳統的Form提交,傳統的自動刷新網頁取得數據以及使用ActiveXObject對象(如xmlhttp)直接與服務器交互數據,也即AJAX方式。由於使用AJAX方式用戶感覺不到頁面在刷新,表現起來好於手動或自動刷新頁面的方式,因此我們決定選擇AJAX方式實現客戶端與服務器端進行數據交互。
另外還有一些操作,鎖定會議室、踢人、指定發言人的發言時間、給會議室加密碼等功能,也通過xmlhttp的方式與服務器傳輸命令實現。
1、服務器端核心代碼
在EasyJF開源團隊的會議系統中,由於是以EasyJF官網的論壇系統、後臺管理等是集成一起的。服務器ChatService與ChatRoom共同合併到了一個ChatService.java類中,實現會議室管理及會議服務功能。ChatService類的部分主要代碼如下:
package com.easyjf.chat.business;
private static final Map service=new HashMap();//會議室服務,系統中的當前會議室存放到該表集合中
private static final int maxServices=10;//可以同時開的最大會議室數
private static final SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
private final List msgs;//會議發言信息Chat
private final List users;//在線用戶,ChatUser
private final List talkers;//排隊發言人數Talker
private final List manager;//會議室管理員
private Talker currentTalker;//當前發言人
private String cid;//會議室id
private String title;//會議室主題
private String intro;//會議室簡介
private String owner;//會議室創建人
private int maxUser;//最大在線人數
private int interval;//最大刷新時間間隔
private String vrtype;//訪問權限
private String vrvalue;//訪問值
private String announce;
private String password;//房間進入密碼
private int status;//會議室狀態
private String filePath;
//private Thread thread;
private boolean isStop=false;
public ChatService()
{
this.msgs=new ArrayList();
this.users=new ArrayList();
this.talkers=new ArrayList();
this.manager=new ArrayList();
this.maxUser=1000;//最大1000人同時
this.interval=1000*60*5;//5分鐘以前的信息
}
/**
* 停止所有會議室
*
*/
public static void clear()
{
if(!service.isEmpty())
{
Iterator it=service.values().iterator();
while(it.hasNext())
{
ChatService chat=(ChatService)it.next();
chat.stop();
}
}
service.clear();
}
/**
* 創建一個會議室
* @param name 會議室ID
* @return
*/
public static ChatService create(String name)
{
ChatService ret=null;
if(service.containsKey(name))
{
ChatService s=(ChatService)service.get(name);
s.stop();
service.remove(name);
}
if(service.size()<maxServices)
{
ret=new ChatService();
service.put(name,ret);
}
return ret;
}
/**
* 停止某個會議室
* @param name 會議室ID
* @return
*/
public static boolean close(String name)
{
ChatService chatRoom=ChatService.get(name);
if(chatRoom!=null)
{
chatRoom.stop();
service.remove(name);
}
return true;
}
/**
* 獲得一個會議室信息
* @param name 會議室ID
* @return
*/
public static ChatService get(String name)
{
if(service.containsKey(name))return (ChatService)service.get(name);
else return null;
}
// TODO Auto-generated method stub
//this.thread=Thread.currentThread();
while(!isStop)
{
//System.out.println("開始監控一個會議室!"+this.title);
this.flash();
try{
Thread.sleep(5000);
}
catch(Exception e)
{
e.printStackTrace();
}
}
//System.out.println("結束!");
}
public void stop()
{
this.flashAll();
isStop=true;
}
//會議室中有人發言
public boolean talk(Chat chat)
{
boolean ret=false;
if(canTalk(chat.getSender()))
{
this.msgs.add(chat);
ret=true;
}
return ret;
}
{
talk(geneSystemMsg(user.getUserName()+"退出了會議室!"));
return this.users.remove(user);
}
}
//刷新信息,保存會議信息
public void flash()
{
flashChatMsg();
flashChatUser();
}
}
在EasyJF的會議系統中,由於使用EasyJWeb作爲MVC框架,因此處理Ajax比較簡單,下面是會議室系統的核心Action主要代碼。
package com.easyjf.chat.action;
public class ChatAction extends AbstractCmdAction {
private ChatService chatRoom;
public Object doBefore(WebForm form, Module module) {
// TODO Auto-generated method stub
if(chatRoom==null)chatRoom=ChatService.get((String)form.get("cid"));
return super.doBefore(form, module);
}
public Page doInit(WebForm form, Module module) {
// TODO Auto-generated method stub
return doMain(form,module);
}
//用戶登錄進入會議室
public Page doMain(WebForm form, Module module) {
if(chatRoom!=null){
ChatUser user=getChatUser();
if(!chatRoom.join(user))form.addResult("msg","不能加入房間,可能是權限不夠!");
form.addResult("chatRoom",chatRoom);
form.addResult("user",user);
}
else
{
form.addResult("msg","會議未啓動或者會議室不存在!");
}
return module.findPage("main");
}
//處理用戶發言信息
public Page doSend(WebForm form, Module module) {
if(chatRoom==null)return new Page("err","/err.html","thml");//返回會議室不存在的錯誤
Chat chat=(Chat)form.toPo(Chat.class);
chat.setCid(chatRoom.geneId());
chatRoom.talk(chat);
return doRecive(form,module);
}
//用戶接收發言信息
public Page doRecive(WebForm form, Module module) {
if(chatRoom==null)return new Page("err","/err.html","thml");//返回會議室不存在的錯誤
String lastReadId=CommUtil.null2String(form.get("lastReadId"));
//System.out.println(lastReadId);
form.addResult("list", chatRoom.getNewestMsg(getChatUser(),lastReadId));
return module.findPage("msgList");
}
//用戶刷新會議狀態信息
public Page doLoadConfig(WebForm form, Module module) {
if(chatRoom==null)return new Page("err","/err.html","thml");//返回會議室不存在的錯誤
form.addResult("userList", chatRoom.getUsers());
form.addResult("talkerList", chatRoom.getTalkers());
return module.findPage("config");
}
//用戶退出
public Page doExit(WebForm form, Module module) {
if(chatRoom==null)return new Page("err","/err.html","thml");//返回會議室不存在的錯誤
chatRoom.exit(getChatUser());
form.addResult("msg","退出成功");
ActionContext.getContext().getSession().removeAttribute("chatUser");
return new Page("msg","/chat/xmlMsg.xml",Globals.PAGE_TEMPLATE_TYPE);
}
EasyJF會議系統中,服務器發送給客戶端的都是格式化的xml文檔數據。下面是核心的AJAX函數及發送接收會議信息的客戶端代碼。
function newXMLHttpRequest() {
var xmlreq = false;
if (window.XMLHttpRequest) {
xmlreq = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
xmlreq = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e1) {
try {
xmlreq = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
}
}
}
return xmlreq;
}
//處理返回信息
//xmlHttp返回值,
//method:方法名 方法必須帶一個參數如doRecive(xNode);
function handleAjaxResult(req,method) {
return function () {
if (req.readyState == 4) {
if (req.status == 200) {
// 將載有響應信息的XML傳遞到處理函數
var objXMLDoc=new ActiveXObject("Microsoft.XMLDOM");
objXMLDoc.loadXML(req.responseText);
eval("if(objXMLDoc.firstChild)"+method+"(objXMLDoc.firstChild.nextSibling);");
} else {
//alert("HTTP error: "+req.status);
}
}
}
}
//執行客戶端Ajax命令
//url 數據post地址
//postData 發送的數據包
//handleMethod 處理返回的方法
function executeAjaxCommand(url,postData,handleMethod)
{
var req = newXMLHttpRequest();
req.onreadystatechange =handleAjaxResult(req,handleMethod);
req.open("POST", url, true);
req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
req.setRequestHeader("charset","utf-8");
req.send(postData);
}
//用戶發言
unction doSend()
{
if(!check())return false;
var msg=EditForm.content.value;
var reciver=EditForm.reciver.value;
var url="/chat.ejf?easyJWebCommand=send&cid="+roomId+"&lastReadId="+lastReadId;
var postData="sender="+myName+"&reciver="+reciver+"&content="+msg;
clearTimeout(reciveTime);
executeAjaxCommand(url,postData,"recive");
EditForm.content.value="";
}
//接收發言信息
function doRecive()
{
var reciver=EditForm.reciver.value;
var url="/chat.ejf?easyJWebCommand=recive&cid="+roomId+"&lastReadId="+lastReadId;
executeAjaxCommand(url,"","recive");
}
//處理接收到的發言信息
function recive(list)
{
var id="";
for(var oNode=list.firstChild;oNode;oNode=oNode.nextSibling) // 依次分析每個節點
{
chatContent.innerHTML+=showMsg(oNode);
id=oNode.getAttribute("cid");
}
if(id!="") lastReadId=id;
chatContent.scrollTop=chatContent.scrollHeight;
reciveTime=setTimeout("doRecive();",5000);
}
六、系統演示
http://www.easyjf.com/chatRoom.ejf?easyJWebCommand=show&ejid=2538093638804337
結束語
Ajax從技術上講主要就是javascript、dhtml、css、xmldom、xmlhttp等一些我們很早就接觸了的技術。而xmldom及xmlhttp也沒有什麼東西,寫程序的時候把參考文檔打開Copy就OK,dhtml及javascript涉及的東西就多了,不能只是看參考文檔,需要把他真正消化,並能靈活動用,這就需要大家都練習了。筆者建議大家不要濫用Ajax。對於高手建議多研究一些業務及系統級算法設計等,對於新手嘛,把基本的技術(客戶端的包括dhtml、css、javascript、xml等,J2EE服務器端的設計模式、UML建模、Servlet、JDBC或ORM系統、XML、EJB及一些框架、工具等)學好纔是硬道理。