网上部分关于Openfire的代码,只实现了,从单一应用与Openfire已经封装好的Spark客户端交互的功能,并没有实现一个应用内,多个用户交互的功能。
下面编写一个Openfire网页版单对单聊天的实例,用Smack的api与Struts2实现。仅包含两在线用户聊天部分。
如下图,开两个浏览器,模拟两个用户,实现网页版的Openfire聊天。
具体文件结构如下:
除了补充了一个fastjson-1.1.24.jar实现Java的容器类到Json字符串的转换,详见《【Java】读取网页中的内容》(点击打开链接),其余关于Openfire的用户处理部分在《【Openfire】网页版的用户注册、登录、修改密码》(点击打开链接)一文中已经详细介绍,这次工程是在其上面开发的。在struts.xml新增了部分关于聊天的Ajax Action,更新之后如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<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;
@SuppressWarnings({ "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() {
@Override
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"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<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 οnclick="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()事件与后台进行交互。