网上部分关于Openfire的代码,只实现了,从单一应用与Openfire已经封装好的Spark客户端交互的功能,并没有实现一个应用内,多个用户交互的功能。
下面编写一个Openfire网页版单对单聊天的实例,用Smack的api与Struts2实现。仅包含两在线用户聊天部分。
如下图,开两个浏览器,模拟两个用户,实现网页版的Openfire聊天。
具体文件结构如下:
除了补充了一个fastjson-1.1.24.jar实现Java的容器类到Json字符串的转换,详见《【Java】读取网页中的内容》(点击打开链接),其余关于Openfire的用户处理部分在《【Openfire】网页版的用户注册、登录、修改密码》(点击打开链接)一文中已经详细介绍,这次工程是在其上面开发的。在struts.xml新增了部分关于聊天的Ajax Action,更新之后如下:
-
"1.0" encoding="UTF-8" xml version=
-
-
-
-
<struts>
-
<package name="test" extends="struts-default">
-
<action name="register" class="test.user" method="register">
-
<result name="success" type="redirect">/index.jsp </result>
-
</action>
-
<action name="login" class="test.user" method="login">
-
<result name="success" type="redirect">/chat.jsp </result>
-
<result name="input" type="redirect">/index.jsp </result>
-
</action>
-
<action name="logout" class="test.user" method="logout">
-
<result name="success" type="redirect">/index.jsp </result>
-
</action>
-
<action name="modify_password" class="test.user" method="modify_password">
-
<result name="success" type="redirect">/index.jsp </result>
-
</action>
-
-
<action name="init" class="test.chat" method="init" />
-
<action name="load_msg" class="test.chat" method="load_msg" />
-
<action name="submit_msg" class="test.chat" method="submit_msg" />
-
</package>
-
</struts>
具体改动如下:
基本上已经可以通过Struts2的Ajax Action略窥一二,网页聊天主要实现三个事情,一个是初始化init,一个是让前台javascript的定时器setInterval定时执行的,载入信息的Action,还有一个是用户点击按钮的发送信息的Action。
这些Action全写在chat.java这个文件里面,具体代码如下,详情请留意注释:
-
package test;
-
-
import java.io.IOException;
-
import java.io.PrintWriter;
-
import java.text.SimpleDateFormat;
-
import java.util.ArrayList;
-
import java.util.Date;
-
import java.util.HashMap;
-
import java.util.List;
-
import java.util.Map;
-
-
import org.apache.struts2.ServletActionContext;
-
import org.jivesoftware.smack.Chat;
-
import org.jivesoftware.smack.ChatManager;
-
import org.jivesoftware.smack.ChatManagerListener;
-
import org.jivesoftware.smack.Connection;
-
import org.jivesoftware.smack.MessageListener;
-
import org.jivesoftware.smack.XMPPException;
-
import org.jivesoftware.smack.packet.Message;
-
-
import tool.DB;
-
-
import com.alibaba.fastjson.JSON;
-
import com.opensymphony.xwork2.ActionContext;
-
import com.opensymphony.xwork2.ActionSupport;
-
-
"unchecked", "rawtypes", "serial" })({
-
public class chat extends ActionSupport {
-
-
private String msg; //用户要发送的信息
-
private String to_user; //用户选择的收信人
-
-
private Map session = ActionContext.getContext().getSession(); //Session
-
-
//将1970年到现在的long时间记法,转换成真实时间,人看的时间。
-
private String get_realTime(long systime) {
-
return new SimpleDateFormat( "hh:mm:ss").format( new Date(systime))
-
.toString();
-
}
-
-
//聊天系统的初始化
-
public void init() throws IOException {
-
PrintWriter printWriter = ServletActionContext.getResponse()
-
.getWriter(); //用于打印Ajax
-
-
//新建一个名为message_list存信息的ArrayList放入session
-
ArrayList<Message> message_list = new ArrayList<Message>();
-
session.put( "message_list", message_list);
-
-
//Openfire的关键步骤,为当前的连接,新建聊天信息接受的接听器。
-
Connection connection = (Connection) session.get( "connection");
-
ChatManager chatmanager = connection.getChatManager();
-
chatmanager.addChatListener( new ChatManagerListener() {
-
-
public void chatCreated(Chat chat, boolean arg1) {
-
//对Openfire的消息处理进行重写
-
chat.addMessageListener( new MessageListener() {
-
//要求这个监听器,收到信息,通通放入session中的message_list
-
public void processMessage(Chat arg0,
-
Message message_receive) {
-
if (message_receive.getBody() != null) {
-
ArrayList<Message> message_list = (ArrayList<Message>) session
-
.get( "message_list");
-
//由于Smack的api是没有对Openfire中Time属性的处理
-
//因此必须自己再处理一下
-
message_receive.setProperty( "time",
-
System.currentTimeMillis());
-
message_list.add(message_receive);
-
session.put( "message_list", message_list);
-
}
-
}
-
});
-
}
-
}); //这样重写了监听器之后,就可以避免原来Openfire必须指定接听哪个用户发来的信息的情况
-
//任意用户给当前登录用户发来信息,都会放入session中,一会儿载入信息的load_msg()仅仅需要对session进行处理
-
-
//载入Openfire中所有用户列表,剔除当前用户,和在初始化Openfire服务器时设定系统总管理用户admin
-
DB db = DB.getInstance();
-
List<Object[]> userlist = db
-
.getBySql( "select username from ofuser where username !='"
-
+ session.get( "username") + "' and username!='admin'");
-
String json = JSON.toJSONString(userlist);
-
-
//将用户列表打印给前台
-
printWriter.print(json);
-
printWriter.flush();
-
printWriter.close();
-
}
-
-
//载入用户信息
-
public void load_msg() throws IOException, XMPPException {
-
ServletActionContext.getResponse().setContentType(
-
"text/html;charset=UTF-8"); //由于用户发送的信息可能有中文,所以必须要先设置编码
-
PrintWriter printWriter = ServletActionContext.getResponse()
-
.getWriter();
-
-
//对session中的message_list进行处理构造JSON
-
ArrayList<Message> message_list = (ArrayList<Message>) session
-
.get( "message_list");
-
-
ArrayList<HashMap<String, String>> message_json = new ArrayList<HashMap<String, String>>(); //这个message_json会被fastjson-1.1.24.jar转化成json字符串
-
for ( int i = 0; i < message_list.size(); i++) {
-
Message message = message_list.get(i);
-
-
HashMap<String, String> one_message = new HashMap<String, String>();
-
-
String from = message.getFrom().split( "@")[ 0]; //由于Openfire会自动在发信人后面带上一堆服务器、设备参数,因此,做此划分
-
String time = get_realTime(( long) message.getProperty( "time")); //将long时间转现实时间输出到前台
-
one_message.put( "from", from);
-
one_message.put( "time", time);
-
one_message.put( "body", message.getBody());
-
-
message_json.add(one_message); //每一条消息都这样处理
-
}
-
-
//打印到前台
-
String json = JSON.toJSONString(message_json);
-
printWriter.print(json);
-
printWriter.flush();
-
printWriter.close();
-
}
-
-
// 信息发送
-
public void submit_msg() throws IOException, XMPPException {
-
PrintWriter printWriter = ServletActionContext.getResponse()
-
.getWriter();
-
-
Connection connection = (Connection) session.get( "connection"); //取连接
-
//按Openfire要求的格式构造发信、收信人
-
String from = (String) session.get( "username") + "@"
-
+ connection.getServiceName();
-
String to = to_user + "@" + connection.getServiceName();
-
-
//发信息核心代码
-
ChatManager chatmanager = connection.getChatManager();
-
Chat chat = chatmanager.createChat(to, null); //新建一个会话,指定收信人,不对收信人所回复的信息进行监听
-
//因为在初始化的时候,已经对此连接收到信息都进行了监听,无须重复监听
-
chat.sendMessage(msg); //对此会话进行发送
-
//但同样要将发送的信息记录在session中的message_list里面,用户打印,这才符合现时主流的聊天工具一问一答的方式
-
ArrayList<Message> message_list = (ArrayList<Message>) session
-
.get( "message_list");
-
Message message_send = new Message();
-
message_send.setFrom(from);
-
message_send.setTo(to);
-
message_send.setBody(msg);
-
message_send.setProperty( "time", System.currentTimeMillis());
-
message_list.add(message_send);
-
session.put( "message_list", message_list);
-
-
//其实,消息发送,是没有任何东西交互给前台的,但必须打印一个空信息,才能保证ajax过程不出错。
-
printWriter.print( "");
-
printWriter.flush();
-
printWriter.close();
-
}
-
-
public String getMsg() {
-
return msg;
-
}
-
-
public void setMsg(String msg) {
-
this.msg = msg;
-
}
-
-
public String getTo_user() {
-
return to_user;
-
}
-
-
public void setTo_user(String to_user) {
-
this.to_user = to_user;
-
}
-
-
}
最后,配合前台的chat.jsp一切就大功告成,HTML布局部分不重要就表格里面的几行和一个按钮。关键是javascript部分:
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
-
pageEncoding= "UTF-8"%>
-
<%@ page isELIgnored="false"%>
-
-
<html>
-
<head>
-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-
<script src="jquery-1.8.3.js" type="text/javascript"> </script>
-
<title>聊天 </title>
-
</head>
-
<body>
-
<table border="1" width="100%">
-
<tr>
-
<td colspan="2" width="100%">
-
欢迎,${sessionScope.username}, <a href="logout">登出 </a>
-
</td>
-
</tr>
-
<tr height="150px" valign="top">
-
<td id="msg_list"> </td>
-
</tr>
-
<tr>
-
<td>
-
<textarea id="msg" style="width: 100%; height: 50px"> </textarea>
-
<select id="user_list"> </select>
-
<button onclick="submit_msg()">发送! </button>
-
</td>
-
</tr>
-
</table>
-
-
</body>
-
<script type="text/javascript">
-
-
function init(){
-
$.ajax({
-
type : "post",
-
url : "init",
-
dataType : "json",
-
success : function(data) {
-
for( var i= 0;i<data.length;i++){
-
$( "#user_list").append( "<option value='"+data[i]+ "'>"+data[i]+ "</option>");
-
}
-
},
-
error : function() {
-
alert( "出错了");
-
}
-
});
-
setInterval( "load_msg()", 2000);
-
}
-
-
function load_msg(){
-
var msg= "";
-
$.ajax({
-
type : "post",
-
url : "load_msg",
-
dataType : "json",
-
success : function(data) {
-
for( var i= 0;i<data.length;i++){
-
msg=msg+
-
"<p sytle='text-align:center'>"+data[i].time+ "</p>"+
-
"<p>"+data[i].from+ ":"+data[i].body+ "</p>";
-
$( "#msg_list").html(msg);
-
}
-
},
-
error : function() {
-
alert( "出错了");
-
}
-
});
-
}
-
-
function submit_msg() {
-
var msg = $( "#msg").val();
-
var to_user = $( "#user_list").val();
-
$( "#msg").val( "");
-
$.ajax({
-
type : "post",
-
url : "submit_msg",
-
dataType : "text",
-
data : {
-
msg : msg,
-
to_user : to_user
-
},
-
success : function(data) {
-
},
-
error : function() {
-
alert( "出错了");
-
}
-
});
-
}
-
-
init();
-
-
</script>
-
</html>
要求进入这个页面就马上执行init()的代码,载入用户列表的同时,同时触发读取信息的定时器,每2秒一次执行load_msg()里面的所有内容。
将发送信息的按钮的点击事件,绑定于submit_msg()事件与后台进行交互。