今年大家都在炒作web2.0,其中的一门技术ajax也是跟着火了起来,因此前面我写了一篇名为《忽悠一下ajax》的文章,简单地分析了一下ajax的技术的实质。虽然笔者不太喜欢跟风,但ajax有一些地方还是比较有用的。前段时间做了easyjf开源团队的网上会议系统,就用到了ajax技术,下面把设计思路发出来跟大家分享一下。
一、系统实现的功能
本会议室系统主要用于easyjf开源团队的成员网上会议使用,会议系统模拟传统的会议形式,可以同时开设多个不同主题的会议室,每个会议室需要提供访问权限控制功能,会议中能够指定会议发言模式(分为排队发言、自由发言两种),系统能自动记录每个会议室的发言信息,可以供参会人员长期查阅。
会议系统的用户支持游客帐号参加会议,同时也提供跟其它用户系统的接口,比如easyjf官网中的开源论坛系统。
会议系统暂时使用文字聊天的方式,并提供语音及视频的接口。
会议系统的用户支持游客帐号参加会议,同时也提供跟其它用户系统的接口,比如easyjf官网中的开源论坛系统。
会议系统暂时使用文字聊天的方式,并提供语音及视频的接口。
二、技术体系
服务器端使用java语言,mvc使用easyjweb框架;
客户端使用ajax技术与服务器端交互数据;
会议历史信息储存格式使用文本格式,方便系统安装运行,也便于管理。
客户端使用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;
}
首先,根据会议室要实现的功能进行抽象分析。一个会议室对象,应该包括会议主题、会议简介、参会人数限制、公告、会议室类型、访问权限设定、房间密码、当前参会的人员、当前发言的人员、排队等待发言的人员等参数信息。我们把他封装一个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;
}
需要一个管理会议室的类,与会议有关的操作(如启动会议、关闭会议)等都直接找他。该类还应该即有自动定时检测用户在线情况(防止用户意外退出)、把内存中的会议历史发言信息保存到文本文件中等功能。这里可以考虑使用一个chatservice类提供这些功能:
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 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分钟以前的信息
}
}
会议发言信息也需要封装成一个类,表示发言人、接收人、内容、发言时间、类型等,大致如下面的chat类:
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 chat {
private string cid;
private string sender;
private string reciver;
private string content;
private date vdate;
private integer types;
private integer status;
}
还有表示参加会议的人的信息,包括参会人名称、ip地址、状态等,如下面的chatuser类所示:
public class chatuser {
private string ip;
private string port;
private string username;
private date lastaccesstime;
private integer status;
}
public class chatuser {
private string ip;
private string port;
private string username;
private date lastaccesstime;
private integer status;
}
另外还需要一个表示当前发言人的talker类,表示当前的发言人,发言开始时间,发言预计结束时间等。
在服务器端的设计中,会议室信息服务器应该能以多线程的方式运行,即启动一个会议就新开一个线程,每个会议线程维护自己的会议状态,如参会人、发言人,保存会议历史发言信息以及清空内存中的数据等操作。
四、客户端设计
会议室客户端包括两个部分,一个部分是会议室的管理界面,主要包会议室的“添删改查”及“启动”或“关闭”会议服务的操作。这部分我们直接使用easyjweb tools中的添删改查业务引擎abstractcrudaction可以快速实现。界面也比较简单,直接使用easyjweb tools代码生成工具引擎生成即可。会议室管理的客户端是传统的java web技术,因此没有什么要考虑的。
客户端的第二个部分也即会议系统的主要部分,该部分主要有两个界面,第一个页面是会议室进入的选择页面。也即把已经启动的会议室列出来,用户选择一个会议室进入,这个页面也是使用传统的java web技术。第二个页面是进入会议室后的主界面,这个界面是整个会议系统的主要界面,所有参与会议的操作都在这里运行的。这个界面需要不断的与服务器端交互传输数据,传输的内容包括用户的发言、其它人给用户的发言、会议室的状态等。有的传输信息需要即时响应(如用户发言),有的信息可以设置成定时响应(如会议室状态)。
java web程序中与服务器端交互数据主要有两种方式,一种是直接刷新页面,另外一种是使用socket直接跟web服务器端口通讯。由于socket编程相对复杂,我们选择第一种直接刷新页面的方式,这种方式又可以分为几种,包括传统的form提交,传统的自动刷新网页取得数据以及使用activexobject对象(如xmlhttp)直接与服务器交互数据,也即ajax方式。由于使用ajax方式用户感觉不到页面在刷新,表现起来好于手动或自动刷新页面的方式,因此我们决定选择ajax方式实现客户端与服务器端进行数据交互。
客户端的第二个部分也即会议系统的主要部分,该部分主要有两个界面,第一个页面是会议室进入的选择页面。也即把已经启动的会议室列出来,用户选择一个会议室进入,这个页面也是使用传统的java web技术。第二个页面是进入会议室后的主界面,这个界面是整个会议系统的主要界面,所有参与会议的操作都在这里运行的。这个界面需要不断的与服务器端交互传输数据,传输的内容包括用户的发言、其它人给用户的发言、会议室的状态等。有的传输信息需要即时响应(如用户发言),有的信息可以设置成定时响应(如会议室状态)。
java web程序中与服务器端交互数据主要有两种方式,一种是直接刷新页面,另外一种是使用socket直接跟web服务器端口通讯。由于socket编程相对复杂,我们选择第一种直接刷新页面的方式,这种方式又可以分为几种,包括传统的form提交,传统的自动刷新网页取得数据以及使用activexobject对象(如xmlhttp)直接与服务器交互数据,也即ajax方式。由于使用ajax方式用户感觉不到页面在刷新,表现起来好于手动或自动刷新页面的方式,因此我们决定选择ajax方式实现客户端与服务器端进行数据交互。
用户发言的时候,直接使用xmlhttp对象post数据到服务器。为了能不断接收到别人的发言信息,需要定时不断的从服务器端读取数据,因此,需要在客户端启动一个定时器,每隔一定的时候自动使用xmlhttp对象到服务器端下载别人的发言信息,并显示到会议室信息主界面中。另外还要定时刷新参会的人数、会议室当前发言人、会议室的公告等会议状态信息,这也可以通过从客户端启动一个定时器,通过xmlhttp对象与服务器交互得到。
另外还有一些操作,锁定会议室、踢人、指定发言人的发言时间、给会议室加密码等功能,也通过xmlhttp的方式与服务器传输命令实现。
另外还有一些操作,锁定会议室、踢人、指定发言人的发言时间、给会议室加密码等功能,也通过xmlhttp的方式与服务器传输命令实现。
五、核心代码说明
1、服务器端核心代码
在easyjf开源团队的会议系统中,由于是以easyjf官网的论坛系统、后台管理等是集成一起的。服务器chatservice与chatroom共同合并到了一个chatservice.java类中,实现会议室管理及会议服务功能。chatservice类的部分主要代码如下:
package com.easyjf.chat.business;
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;//当前发言人
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;
}
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;
}
public void run() {
// 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;
}
// 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;
}
public boolean exit(chatuser user)
{
talk(genesystemmsg(user.getusername()+"退出了会议室!"));
return this.users.remove(user);
}
}
//刷新信息,保存会议信息
public void flash()
{
flashchatmsg();
flashchatuser();
}
}
{
talk(genesystemmsg(user.getusername()+"退出了会议室!"));
return this.users.remove(user);
}
}
//刷新信息,保存会议信息
public void flash()
{
flashchatmsg();
flashchatuser();
}
}
2、mvc处理部分的action代码
在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);
}
3、客户端ajax部分核心代码
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);
}
六、系统演示
大家可以到easyjf开源团队的官方网站看程序演示效果,地址是:
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及一些框架、工具等)学好才是硬道理。
闽公网安备 35060202000074号