XMPP即时通讯协议使用(六)——开发Openfire聊天记录插件
转载地址:http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html
开发环境:
System:Windows
WebBrowser:IE6+、Firefox3+
JavaEE Server:tomcat5.0.2.8、tomcat6
IDE:eclipse、MyEclipse 6.5
开发依赖库:
Jdk1.6、jasper-compiler.jar、jasper-runtime.jar、openfire.jar、servlet.jar
Email:hoojo_@126.com
如果你觉得这篇文章不错或对你有帮助的话,请你支持我。如果觉这里的文章不错的话,请你关注我的博客。
推荐文章:
【Openfire 的安装和配置】 手把手教你配置Openfire服务器
【跟我一步一步开发自己的Openfire插件】教你一步步开发自己的插件
【JavaScript/jQuery、HTML、CSS 构建 Web IM 远程及时聊天通信程序】 优美清新的界面,可以多窗口聊天
【Smack 结合 Openfire服务器,建立IM通信,发送聊天消息】 可以基于他开发Java的聊天应用
【Apache MiNa 实现多人聊天室】 多人聊天室,如果结合Smack和Openfire,就可以实现外网聊天应用
【JavaScript/jQuery WebIM 及时聊天通信工具 本地客户端】 本地应用,不需要Openfire服务器
【Openfire与XMPP协议】理论知识,便于连接Openfire
【Jwchat 的安装和配置、Service unavailable、Authorization failed问题汇总】 拓展你的应用,可以了解开源的jwchat,全JS的应用
【移动应用(手机应用)开发IM聊天程序解决方案】 移动手机和Openfire的整合方案
【Spring整合DWR comet 实现无刷新 多人聊天室】 DWR实现聊天应用,简单快速
我把自己写好的插件打包,下载后部署到openfire服务器,就可以用了。如果出现什么问题的话,你可以看看这篇文章,都有解决方法。
插件下载:http://files.cnblogs.com/hoojo/%E8%81%8A%E5%A4%A9%E6%8F%92%E4%BB%B6.rar
基本原理(流程)
一、准备工作
1、 这里的开发环境就是上一篇文章的开发环境,如果你还没有配置好环境或不知道怎么样配置。那么烦请你按照上一篇博文的讲述方法配置好开发环境,然后跟着我一步步开发聊天记录插件。
2、 基于之前讲的,现在在环境中创建好插件的目录结构。新建一个plugins/chatlogs目录,新插件的目录文件如下,里面的部分文件是通用的,只有在src/plugins/chatlogs目录中文件才是这次新增的文件。这次的插件目录结构会按照下面的结构来。
先熟悉下上面的目录,就简单大致介绍下上面的目录。在src/plugins/chatlogs目录中的包是主要开发的插件核心代码。
其中ChatLogsPlugin.java是聊天记录拦截聊天记录,并保存到数据库中的重要代码。
ChatLogsServlet.java是对外公开访问的Servlet,它会返回一些xml的内容,主要是聊天记录的内容和聊天用户的等XML格式的数据。
DbChatLogsManager.java这个也是很主要的,它主要完成对聊天记录数据库表的CRUD操作。
database目录中存放的是sql脚本,这里我提供的是oracle和hsql两个数据库的脚步。至于使用说明脚步要看你的openfire服务器使用的数据库才行。
web目录上次介绍到了,主要是jsp页面。
同时在src/plugins/chatlogs目录中还存在一些gif和html,这些都是插件的介绍和安装内容、图标等。
plugin.xml是插件核心代码的配置和jsp页面的配置文件。
其他内容之前介绍过了,这里就不再一一赘述。
3、 执行你的聊天记录数据库脚本,聊天记录表内容如下
hsql db
- -- Create table --openfire聊天记录
- createtable OFCHATLOGS
- (
- MESSAGEID int primary key, --消息id
- SESSIONJID VARCHAR(30), --用户session jid名称
- SENDER VARCHAR(30), --消息发送者
- RECEIVER VARCHAR(30), --接受者
- CREATEDATE VARCHAR(30), --消息发送、创建时间
- LENGTH int, --消息长度、大小
- CONTENT VARCHAR(2000), --消息内容
- DETAIL VARCHAR(4000), --消息源报文
- STATE int --删除状态,1表示删除
- );
Oracle db
- -- Create table
- createtable OFCHATLOGS
- (
- MESSAGEID int not null,
- SESSIONJID NVARCHAR2(30),
- SENDER NVARCHAR2(30),
- RECEIVER NVARCHAR2(30),
- CREATEDATE NVARCHAR2(30),--TIMESTAMP(12),
- LENGTH int,
- CONTENT NVARCHAR2(2000),
- DETAIL NVARCHAR2(4000),
- STATE int
- );
- -- Add comments to the table
- comment on table OFCHATLOGS
- is'openfire聊天记录';
- -- Add comments to the columns
- comment on column OFCHATLOGS.MESSAGEID
- is'消息id';
- comment on column OFCHATLOGS.SESSIONJID
- is'用户session jid名称';
- comment on column OFCHATLOGS.SENDER
- is'消息发送者';
- comment on column OFCHATLOGS.RECEIVER
- is'接受者';
- comment on column OFCHATLOGS.CREATEDATE
- is'消息发送、创建时间';
- comment on column OFCHATLOGS.LENGTH
- is'消息长度、大小';
- comment on column OFCHATLOGS.CONTENT
- is'消息内容';
- comment on column OFCHATLOGS.DETAIL
- is'消息源报文';
- comment on column OFCHATLOGS.STATE
- is'删除状态,1表示删除';
- -- Create/Recreate primary, unique and foreign key constraints
- Alter table OFCHATLOGS
- Add constraint PKMESSAGEID primary key (MESSAGEID);
注意:如果你是oracle数据库,执行上面的脚本可能没有什么问题。如果你是使用openfire默认的数据库hsql db。那么你可能不知道在哪里运行hql 脚本。不要急,跟着我做!这些都是小菜一碟的事情。
下面的内容是openfire默认数据库的脚本和数据库的使用方法,不是用openfire默认数据库的“攻城师”可以跳过。
3.1 进入到你的openfire安装目录C:\Program Files\openfire\bin\extra,在该目录下你可以看到
这个就是数据库启动的dos程序,如果你是Linux的系统。那当然是运行embedded-db-viewer.sh这个。如果你的windows的系统,就运行embedded-db-viewer.bat程序。
注意:在启动数据库前,请保证你的openfire服务器没有启动。要不然你是无法启动的。因为你启动了openfire服务器的话,数据库已经在使用了,Openfire会锁定数据库的。因为openfire不希望在读写数据的时候,有人在干预它,导致存在脏读,重写的情况。
3.2 启动后就可以看到如下界面
在空白区域你可以写你的SQL脚本,写完后点击Excute SQL 就可以运行。执行完成后,在右下方区域可以看到结果。
Ok,你现在就可以将hsql的聊天记录表的脚本放在这里执行。数据库表就可以创建成功了。创建成功后你可以看到下面输出update count 0。在左边可以看到OFCHATLOGS这个table。
创建好数据库表后,下面我们就开发自己的openfire聊天插件核心代码。
4、 在开始核心代码之前,我们需要建立一个简单的JavaEntity实体对象。在src/plugins/chatlogs目录中建立com.hoo.openfire.chat.logs.entity包,在包下建立文件
- package com.hoo.openfire.chat.logs.entity;
- import java.sql.Timestamp;
- import org.jivesoftware.util.JiveConstants;
- /**
- * <b>function:</b> 聊天记录对象实体
- * @author hoojo
- * @createDate 2012-9-19 下午08:28:03
- * @file ChatLogs.java
- * @package com.hoo.openfire.chat.logs.entity
- * @project OpenfirePlugin
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class ChatLogs {
- private long messageId;
- private String sessionJID;
- private String sender;
- private String receiver;
- private Timestamp createDate;
- private String content;
- private String detail;
- private int length;
- private int state; // 1 表示删除
- public interface LogState {
- int show = 0;
- int remove = 1;
- }
- /**
- * <b>function:</b> 自增id序列管理器,类型变量
- * @author hoojo
- * @createDate 2012-9-20 下午02:38:52
- * @file ChatLogs.java
- * @package com.hoo.openfire.chat.logs.entity
- * @project OpenfirePlugin
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class ChatLogsConstants extends JiveConstants {
- // 日志表id自增对应类型
- public static final int CHAT_LOGS = 52;
- // 用户在线统计id自增对应类型
- public static final int USER_ONLINE_STATE = 53;
- }
- public ChatLogs() {
- }
- public ChatLogs(String sessionJID, Timestamp createDate, String content, String detail, int length) {
- super();
- this.sessionJID = sessionJID;
- this.createDate = createDate;
- this.content = content;
- this.detail = detail;
- this.length = length;
- }
- public ChatLogs(long messageId, String sessionJID, Timestamp createDate, String content, String detail, int length, int state) {
- super();
- this.messageId = messageId;
- this.sessionJID = sessionJID;
- this.createDate = createDate;
- this.content = content;
- this.detail = detail;
- this.length = length;
- this.state = state;
- }
- // setter/getter
- }
二、开发聊天记录插件
按照上面给出的工程的目录结构,新建我们需要的文件。
1、 在src/plugins/chatlogs目录新建包com.hoo.openfire.chat.logs,在包中建立DbChatLogsManager 聊天记录CRUD数据库操作类
- package com.hoo.openfire.chat.logs;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.ResultSetMetaData;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.List;
- import org.apache.commons.lang.StringUtils;
- import org.jivesoftware.database.DbConnectionManager;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.hoo.openfire.chat.logs.entity.ChatLogs;
- /**
- * <b>function:</b> 聊天记录db操作类
- * @author hoojo
- * @createDate 2012-9-19 下午04:15:43
- * @file DbChatLogsManager.java
- * @package com.iflashbuy.openfire.chat.logs
- * @project OpenfirePlugin
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class DbChatLogsManager {
- private static final Logger Log = LoggerFactory.getLogger(DbChatLogsManager.class);
- private static final DbChatLogsManager CHAT_LOGS_MANAGER = new DbChatLogsManager();
- private DbChatLogsManager() {
- }
- public static DbChatLogsManager getInstance() {
- return CHAT_LOGS_MANAGER;
- }
- private static final String LOGS_COUNT = "SELECT count(1) FROM ofChatLogs";
- private static final String LOGS_LAST_MESSAGE_ID = "SELECT max(messageId) FROM ofChatLogs";
- private static final String LOGS_FIND_BY_ID = "SELECT messageId, sessionJID, sender, receiver, createDate, length, content FROM ofChatLogs where messageId = ?";
- private static final String LOGS_REMOVE = "UPDATE ofChatLogs set state = 1 where messageId = ?";//"DELETE FROM ofChatLogs WHERE messageId = ?";
- private static final String LOGS_INSERT = "INSERT INTO ofChatLogs(messageId, sessionJID, sender, receiver, createDate, length, content, detail, state) VALUES(?,?,?,?,?,?,?,?,?)";
- private static final String LOGS_QUERY = "SELECT messageId, sessionJID, sender, receiver, createDate, length, content FROM ofChatLogs where state = 0";
- private static final String LOGS_SEARCH = "SELECT * FROM ofChatLogs where state = 0";
- private static final String LOGS_LAST_CONTACT = "SELECT distinct receiver FROM ofChatLogs where state = 0 and sender = ?";
- private static final String LOGS_ALL_CONTACT = "SELECT distinct sessionJID FROM ofChatLogs where state = 0";
- /**
- * <b>function:</b> 获取最后一个id
- * @author hoojo
- * @createDate 2012-9-19 下午08:13:33
- * @return 最后一个记录id
- */
- public int getLastId() {
- Connection con = null;
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- int count = -1;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_LAST_MESSAGE_ID);
- rs = pstmt.executeQuery();
- if (rs.next()) {
- count = rs.getInt(1);
- } else {
- count = 0;
- }
- } catch (SQLException sqle) {
- Log.error(sqle.getMessage(), sqle);
- return 0;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- return count;
- }
- /**
- * <b>function:</b> 获取总数量
- * @author hoojo
- * @createDate 2012-9-19 下午08:14:59
- * @return 总数量
- */
- public int getCount() {
- Connection con = null;
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- int count = -1;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_COUNT);
- rs = pstmt.executeQuery();
- if (rs.next()) {
- count = rs.getInt(1);
- } else {
- count = 0;
- }
- } catch (SQLException sqle) {
- Log.error(sqle.getMessage(), sqle);
- return 0;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- return count;
- }
- /**
- * <b>function:</b> 删除聊天记录信息
- * @author hoojo
- * @createDate 2012-9-19 下午08:25:48
- * @param id 聊天信息id
- * @return
- */
- public boolean remove(Integer id) {
- Connection con = null;
- PreparedStatement pstmt = null;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_REMOVE);
- pstmt.setInt(1, id);
- return pstmt.execute();
- } catch (SQLException sqle) {
- Log.error("chatLogs remove exception: {}", sqle);
- return false;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 添加聊天记录信息
- * @author hoojo
- * @createDate 2012-9-19 下午08:37:52
- * @param logs ChatLogs 聊天记录对象
- * @return 是否添加成功
- */
- public boolean add(ChatLogs logs) {
- Connection con = null;
- PreparedStatement pstmt = null;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_INSERT);
- int i = 1;
- pstmt.setLong(i++, logs.getMessageId());
- pstmt.setString(i++, logs.getSessionJID());
- pstmt.setString(i++, logs.getSender());
- pstmt.setString(i++, logs.getReceiver());
- pstmt.setTimestamp(i++, logs.getCreateDate());
- pstmt.setInt(i++, logs.getLength());
- pstmt.setString(i++, logs.getContent());
- pstmt.setString(i++, logs.getDetail());
- pstmt.setInt(i++, logs.getState());
- return pstmt.execute();
- } catch (SQLException sqle) {
- Log.error("chatLogs add exception: {}", sqle);
- return false;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 通过id查询聊天记录信息
- * @author hoojo
- * @createDate 2012-9-19 下午09:32:19
- * @param id 消息id
- * @return ChatLogs
- */
- public ChatLogs find(int id) {
- Connection con = null;
- PreparedStatement pstmt = null;
- ChatLogs logs = null;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_FIND_BY_ID);
- pstmt.setInt(1, id);
- ResultSet rs = pstmt.executeQuery();
- while (rs.next()) {
- logs = new ChatLogs();
- logs.setMessageId(rs.getInt("messageId"));
- logs.setContent(rs.getString("content"));
- logs.setCreateDate(rs.getTimestamp("createDate"));
- logs.setLength(rs.getInt("length"));
- logs.setSessionJID(rs.getString("sessionJID"));
- logs.setSender(rs.getString("sender"));
- logs.setReceiver(rs.getString("receiver"));
- }
- return logs;
- } catch (SQLException sqle) {
- Log.error("chatLogs find exception: {}", sqle);
- return logs;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 多条件搜索查询,返回List<ChatLogs>集合
- * @author hoojo
- * @createDate 2012-9-19 下午09:34:45
- * @param entity ChatLogs
- * @return 返回List<ChatLogs>集合
- */
- public List<ChatLogs> query(ChatLogs entity) {
- Connection con = null;
- Statement pstmt = null;
- ChatLogs logs = null;
- List<ChatLogs> result = new ArrayList<ChatLogs>();
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.createStatement();
- String sql = LOGS_QUERY;
- if (entity != null) {
- if (!StringUtils.isEmpty(entity.getSender()) && !StringUtils.isEmpty(entity.getReceiver())) {
- sql += " and (sender = '" + entity.getSender() + "' and receiver = '" + entity.getReceiver() + "')";
- sql += " or (receiver = '" + entity.getSender() + "' and sender = '" + entity.getReceiver() + "')";
- } else {
- if (!StringUtils.isEmpty(entity.getSender())) {
- sql += " and sender = '" + entity.getSender() + "'";
- }
- if (!StringUtils.isEmpty(entity.getReceiver())) {
- sql += " and receiver = '" + entity.getReceiver() + "'";
- }
- }
- if (!StringUtils.isEmpty(entity.getContent())) {
- sql += " and content like '%" + entity.getContent() + "%'";
- }
- if (entity.getCreateDate() != null) {
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
- String crateatDate = df.format(new Date(entity.getCreateDate().getTime()));
- //sql += " and to_char(createDate, 'yyyy-mm-dd') = '" + crateatDate + "'";
- sql += " and createDate like '" + crateatDate + "%'";
- }
- }
- sql += " order by createDate asc";
- ResultSet rs = pstmt.executeQuery(sql);
- while (rs.next()) {
- logs = new ChatLogs();
- logs.setMessageId(rs.getInt("messageId"));
- logs.setContent(rs.getString("content"));
- logs.setCreateDate(rs.getTimestamp("createDate"));
- logs.setLength(rs.getInt("length"));
- logs.setSessionJID(rs.getString("sessionJID"));
- logs.setSender(rs.getString("sender"));
- logs.setReceiver(rs.getString("receiver"));
- result.add(logs);
- }
- return result;
- } catch (SQLException sqle) {
- Log.error("chatLogs search exception: {}", sqle);
- return result;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 多条件搜索查询,返回List<Map>集合
- * @author hoojo
- * @createDate 2012-9-19 下午09:33:28
- * @param entity ChatLogs
- * @return List<HashMap<String, Object>>
- */
- public List<HashMap<String, Object>> search(ChatLogs entity) {
- Connection con = null;
- Statement pstmt = null;
- List<HashMap<String, Object>> result = new ArrayList<HashMap<String, Object>>();
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.createStatement();
- String sql = LOGS_SEARCH;
- if (entity != null) {
- if (!StringUtils.isEmpty(entity.getSender()) && !StringUtils.isEmpty(entity.getReceiver())) {
- sql += " and (sender = '" + entity.getSender() + "' and receiver = '" + entity.getReceiver() + "')";
- sql += " or (receiver = '" + entity.getSender() + "' and sender = '" + entity.getReceiver() + "')";
- } else {
- if (!StringUtils.isEmpty(entity.getSender())) {
- sql += " and sender = '" + entity.getSender() + "'";
- }
- if (!StringUtils.isEmpty(entity.getReceiver())) {
- sql += " and receiver = '" + entity.getReceiver() + "'";
- }
- }
- if (!StringUtils.isEmpty(entity.getContent())) {
- sql += " and content like '%" + entity.getContent() + "%'";
- }
- if (entity.getCreateDate() != null) {
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
- String crateatDate = df.format(new Date(entity.getCreateDate().getTime()));
- sql += " and to_char(createDate, 'yyyy-mm-dd') = '" + crateatDate + "'";
- }
- }
- sql += " order by createDate asc";
- ResultSet rs = pstmt.executeQuery(sql);
- ResultSetMetaData rsmd = rs.getMetaData();
- /** 获取结果集的列数*/
- int columnCount = rsmd.getColumnCount();
- while (rs.next()) {
- HashMap<String, Object> map = new HashMap<String, Object>();
- /** 把每一行以(key, value)存入HashMap, 列名做为key,列值做为value */
- for (int i = 1; i <= columnCount; ++i) {
- String columnVal = rs.getString(i);
- if (columnVal == null) {
- columnVal = "";
- }
- map.put(rsmd.getColumnName(i), columnVal);
- }
- /** 把装有一行数据的HashMap存入list */
- result.add(map);
- }
- return result;
- } catch (SQLException sqle) {
- Log.error("chatLogs search exception: {}", sqle);
- return result;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 最近联系人
- * @author hoojo
- * @createDate 2013-3-24 下午4:38:51
- * @param entity 聊天记录实体
- * @return 最近联系人集合
- */
- public List<String> findLastContact(ChatLogs entity) {
- Connection con = null;
- PreparedStatement pstmt = null;
- List<String> result = new ArrayList<String>();
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_LAST_CONTACT);
- pstmt.setString(1, entity.getSender());
- ResultSet rs = pstmt.executeQuery();
- while (rs.next()) {
- result.add(rs.getString("receiver"));
- }
- return result;
- } catch (SQLException sqle) {
- Log.error("chatLogs find exception: {}", sqle);
- return result;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- /**
- * <b>function:</b> 查找所有聊天用户
- * @author hoojo
- * @createDate 2013-3-24 下午4:37:40
- * @return 所有聊天用户sessionJID集合
- */
- public List<String> findAllContact() {
- Connection con = null;
- PreparedStatement pstmt = null;
- List<String> result = new ArrayList<String>();
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(LOGS_ALL_CONTACT);
- ResultSet rs = pstmt.executeQuery();
- while (rs.next()) {
- result.add(rs.getString("sessionJID"));
- }
- return result;
- } catch (SQLException sqle) {
- Log.error("chatLogs find exception: {}", sqle);
- return result;
- } finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
- }
比较简单,都是数据库的增删改查的JDBC操作。就是打开数据库连接和关闭数据库连接是使用openfire提供的DbConnectionManager类完成的。
2、 插件核心类,也就是保存聊天记录的类。这里对PacketInterceptor、Plugin进行继承。如果开发插件就一定要继承Plugin,而继承PacketInterceptor是拦截用户发送的消息包。对消息包进行过滤、拦截,保存我们需要的数据。openfire 的插件可以访问所有openfire的API。这给我们的插件实现提供了巨大的灵活性。以下提供了四种比较常用的插件集成方式。
2.1、Component:可以接收一个特定子域(sub-domain)的所有包。比如test_componet.hoo.com。所以一个发送给jojo@test_componet.hoo.com的包将被转发给这个componet.
2.2、IQHandler:相应包中特定的元素名或命名空间。下面的代码展示了如何注册一个IQHandler.
IQHandler myHandler = new MyIQHander();
IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
iqRouter.addHandler(myHandler);
2.3、PacketInterceptor:这种方式可以接收系统传输的所有包,并可以随意的丢弃它们。例如,一个interceptor 可以拦截并丢弃所有含有不健康信息的消息,或者将它们报告给系统管理员。
2.4、使用JiveGlobals.getProperty(String) 和 JiveGlobals.setProperty(String, String) 方法将我们的插件设置为openfire的一个全局属性。通过实现org.jivesoftware.util.PropertyEventListener方法可以将我们的插件做成一个属性监听器监听任何属性的变化。通过 PropertyEventDispatcher.addListener(PropertyEventListener)方法可以注册监听。要注意的一点是,一定要在destroyPlugin()方法中将注册的监听注销。
在src/plugins/chatlogs目录下新建ChatLogsPlugin类,插件核心类代码如下
- package com.hoo.openfire.chat.logs;
- import java.io.File;
- import java.sql.Timestamp;
- import java.util.Date;
- import java.util.List;
- import org.jivesoftware.database.SequenceManager;
- import org.jivesoftware.openfire.XMPPServer;
- import org.jivesoftware.openfire.container.Plugin;
- import org.jivesoftware.openfire.container.PluginManager;
- import org.jivesoftware.openfire.interceptor.InterceptorManager;
- import org.jivesoftware.openfire.interceptor.PacketInterceptor;
- import org.jivesoftware.openfire.interceptor.PacketRejectedException;
- import org.jivesoftware.openfire.session.Session;
- import org.jivesoftware.openfire.user.UserManager;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.xmpp.packet.IQ;
- import org.xmpp.packet.JID;
- import org.xmpp.packet.Message;
- import org.xmpp.packet.Packet;
- import org.xmpp.packet.Presence;
- import com.hoo.openfire.chat.logs.entity.ChatLogs;
- import com.hoo.openfire.chat.logs.entity.ChatLogs.ChatLogsConstants;
- /**
- * <b>function:</b> 聊天记录插件
- * @author hoojo
- * @createDate 2012-9-19 下午01:47:20
- * @file ChatLogsPacketInterceptor.java
- * @package com.hoo.openfire.chat.logs
- * @project OpenfirePlugin
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class ChatLogsPlugin implements PacketInterceptor, Plugin {
- private static final Logger log = LoggerFactory.getLogger(ChatLogsPlugin.class);
- private static PluginManager pluginManager;
- private static DbChatLogsManager logsManager;
- public ChatLogsPlugin() {
- interceptorManager = InterceptorManager.getInstance();
- logsManager = DbChatLogsManager.getInstance();
- }
- //Hook for intercpetorn
- private InterceptorManager interceptorManager;
- /**
- * <b>function:</b> 拦截消息核心方法,Packet就是拦截消息对象
- * @author hoojo
- * @createDate 2013-3-27 下午04:49:11
- */
- @Override
- public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed) throws PacketRejectedException {
- if (session != null) {
- debug(packet, incoming, processed, session);
- }
- JID recipient = packet.getTo();
- if (recipient != null) {
- String username = recipient.getNode();
- // 广播消息或是不存在/没注册的用户.
- if (username == null || !UserManager.getInstance().isRegisteredUser(recipient)) {
- return;
- } else if (!XMPPServer.getInstance().getServerInfo().getXMPPDomain().equals(recipient.getDomain())) {
- // 非当前openfire服务器信息
- return;
- } else if ("".equals(recipient.getResource())) {
- }
- }
- this.doAction(packet, incoming, processed, session);
- }
- /**
- * <b>function:</b> 执行保存/分析聊天记录动作
- * @author hoojo
- * @createDate 2013-3-24 下午12:20:56
- * @param packet 数据包
- * @param incoming true表示发送方
- * @param session 当前用户session
- */
- private void doAction(Packet packet, boolean incoming, boolean processed, Session session) {
- Packet copyPacket = packet.createCopy();
- if (packet instanceof Message) {
- Message message = (Message) copyPacket;
- // 一对一聊天,单人模式
- if (message.getType() == Message.Type.chat) {
- log.info("单人聊天信息:{}", message.toXML());
- debug("单人聊天信息:" + message.toXML());
- // 程序执行中;是否为结束或返回状态(是否是当前session用户发送消息)
- if (processed || !incoming) {
- return;
- }
- logsManager.add(this.get(packet, incoming, session));
- // 群聊天,多人模式
- } else if (message.getType() == Message.Type.groupchat) {
- List<?> els = message.getElement().elements("x");
- if (els != null && !els.isEmpty()) {
- log.info("群聊天信息:{}", message.toXML());
- debug("群聊天信息:" + message.toXML());
- } else {
- log.info("群系统信息:{}", message.toXML());
- debug("群系统信息:" + message.toXML());
- }
- // 其他信息
- } else {
- log.info("其他信息:{}", message.toXML());
- debug("其他信息:" + message.toXML());
- }
- } else if (packet instanceof IQ) {
- IQ iq = (IQ) copyPacket;
- if (iq.getType() == IQ.Type.set && iq.getChildElement() != null && "session".equals(iq.getChildElement().getName())) {
- log.info("用户登录成功:{}", iq.toXML());
- debug("用户登录成功:" + iq.toXML());
- }
- } else if (packet instanceof Presence) {
- Presence presence = (Presence) copyPacket;
- if (presence.getType() == Presence.Type.unavailable) {
- log.info("用户退出服务器成功:{}", presence.toXML());
- debug("用户退出服务器成功:" + presence.toXML());
- }
- }
- }
- /**
- * <b>function:</b> 创建一个聊天记录实体对象,并设置相关数据
- * @author hoojo
- * @createDate 2013-3-27 下午04:44:54
- * @param packet 数据包
- * @param incoming 如果为ture就表明是发送者
- * @param session 当前用户session
- * @return 聊天实体
- */
- private ChatLogs get(Packet packet, boolean incoming, Session session) {
- Message message = (Message) packet;
- ChatLogs logs = new ChatLogs();
- JID jid = session.getAddress();
- if (incoming) { // 发送者
- logs.setSender(jid.getNode());
- JID recipient = message.getTo();
- logs.setReceiver(recipient.getNode());
- }
- logs.setContent(message.getBody());
- logs.setCreateDate(new Timestamp(new Date().getTime()));
- logs.setDetail(message.toXML());
- logs.setLength(logs.getContent().length());
- logs.setState(0);
- logs.setSessionJID(jid.toString());
- // 生成主键id,利用序列生成器
- long messageID = SequenceManager.nextID(ChatLogsConstants.CHAT_LOGS);
- logs.setMessageId(messageID);
- return logs;
- }
- /**
- * <b>function:</b> 调试信息
- * @author hoojo
- * @createDate 2013-3-27 下午04:44:31
- * @param packet 数据包
- * @param incoming 如果为ture就表明是发送者
- * @param processed 执行
- * @param session 当前用户session
- */
- private void debug(Packet packet, boolean incoming, boolean processed, Session session) {
- String info = "[ packetID: " + packet.getID() + ", to: " + packet.getTo() + ", from: " + packet.getFrom() + ", incoming: " + incoming + ", processed: " + processed + " ]";
- long timed = System.currentTimeMillis();
- debug("################### start ###################" + timed);
- debug("id:" + session.getStreamID() + ", address: " + session.getAddress());
- debug("info: " + info);
- debug("xml: " + packet.toXML());
- debug("################### end #####################" + timed);
- log.info("id:" + session.getStreamID() + ", address: " + session.getAddress());
- log.info("info: {}", info);
- log.info("plugin Name: " + pluginManager.getName(this) + ", xml: " + packet.toXML());
- }
- private void debug(Object message) {
- if (true) {
- System.out.println(message);
- }
- }
- @Override
- public void destroyPlugin() {
- interceptorManager.removeInterceptor(this);
- debug("销毁聊天记录插件成功!");
- }
- @Override
- public void initializePlugin(PluginManager manager, File pluginDirectory) {
- interceptorManager.addInterceptor(this);
- pluginManager = manager;
- debug("安装聊天记录插件成功!");
- }
- }
注意在初始化插件的时候,在系统的烂机器管理器中添加对当前插件对象的管理,即在interceptorManager中addInterceptor。而在销毁资源的时候则removeInterceptor当前对象。
上面已经在打印出用户聊天的Packet信息,当用户登陆、退出、发送消息等,都会被拦截到。我们只需要拦截我们要的消息数据,注意下面看这段代码
- // 程序执行中;是否为结束或返回状态(是否是当前session用户发送消息)
- if (processed || !incoming) {
- return;
- }
如果没有这段代码,那我们就可以保存很多重复或无用的信息。为什么这样写,看看在控制台或日子infohttp://127.0.0.1:9090/logviewer.jsp?log=info中的信息:
一个用户hoojo和离线用户boy聊天Packet内容
- ################### start ###################1364442703632
- id:172af964, address: hoojo@127.0.0.1/Spark 2.6.3
- info: [ packetID: 4O1WO-29, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: true, processed: false ]
- xml: <message id="4O1WO-29" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>哈哈,我上线了~~</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364442703632
- ################### start ###################1364442703659
- id:172af964, address: hoojo@127.0.0.1/Spark 2.6.3
- info: [ packetID: 4O1WO-29, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: true, processed: true ]
- xml: <message id="4O1WO-29" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>哈哈,我上线了~~</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364442703659
主要看incoming和processed的值:incoming都为ture,incoming为true就表示是自己发送的信息。而procesed为false,然后才是true,processed为true就表示发送结束。且session都是当前聊天用户。
hoojo@127.0.0.1/Spark 2.6.3 incoming:true,processed:false;
--> hoojo@127.0.0.1/Spark 2.6.3 incoming:true,processed:true;
通过这个状态,我们的判断代码应该可以拦截到的是 第一种状态。然后就可以将一种状态的Packet保存到聊天记录表中,其他的数据暂不处理!
用户hoojo和一个在线用户boy聊天Packet
- ################### start ###################1364443976157
- id:172af964, address: hoojo@127.0.0.1/Spark 2.6.3
- info: [ packetID: 4O1WO-30, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: true, processed: false ]
- xml: <message id="4O1WO-30" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>看状态……</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364443976157
- ################### start ###################1364443976223
- id:1f30f584, address: boy@127.0.0.1/WebIM
- info: [ packetID: 4O1WO-30, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: false, processed: false ]
- xml: <message id="4O1WO-30" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>看状态……</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364443976223
- ################### start ###################1364443976228
- id:1f30f584, address: boy@127.0.0.1/WebIM
- info: [ packetID: 4O1WO-30, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: false, processed: true ]
- xml: <message id="4O1WO-30" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>看状态……</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364443976228
- ################### start ###################1364443976232
- id:172af964, address: hoojo@127.0.0.1/Spark 2.6.3
- info: [ packetID: 4O1WO-30, to: boy@127.0.0.1, from: hoojo@127.0.0.1/Spark 2.6.3, incoming: true, processed: true ]
- xml: <message id="4O1WO-30" to="boy@127.0.0.1" from="hoojo@127.0.0.1/Spark 2.6.3" type="chat"><body>看状态……</body><thread>yOgoRq</thread><x xmlns="jabber:x:event"><offline/><composing/></x></message>
- ################### end #####################1364443976232
状态流程:
hoojo@127.0.0.1/Spark 2.6.3 incoming:true,processed:false;
--> boy@127.0.0.1/WebIM incoming:false,processed:false;
--> boy@127.0.0.1/WebIM incoming:false,processed:true;
-->hoojo@127.0.0.1/Spark 2.6.3 incoming:true,processed:true;
而我们保存消息的状态是在第一个状态,即incoming=true,processed=false的这个状态保存的。
看图,这样更利于理解
离线用户的流程就是没有红色部分的,其他用户就是整体的流程部分。
3、 为ChatLogsPlugin添加配置,在src/plugins/chatlogs目录下建立plugin.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <plugin>
- <class>com.hoo.openfire.chat.logs.ChatLogsPlugin</class>
- <!-- Plugin meta-data -->
- <name>Chat Logs Plugin</name>
- <description>User Chat Message Logs Plugins.</description>
- <author>hoojo [http://hoojo.cnblogs.com]</author>
- <version>1.0</version>
- <date>28/03/2013</date>
- <url>http://localhost:9090/openfire/plugins.jsp</url>
- <minServerVersion>3.7.1</minServerVersion>
- <licenseType>gpl</licenseType>
- <adminconsole>
- <tab id="tab-server">
- <sidebar id="sidebar-server-settings">
- <item id="chatLogs-service" name="ChatLogs Service" url="chatLogs-service.jsp"
- description="Click to manage the service that allows users chat logs." />
- </sidebar>
- </tab>
- </adminconsole>
- </plugin>
如果你看过上一篇文章,这个配置你就不陌生了。最主要的还是class这个配置。在上面的配置中有一个adminconsole配置是页面的配置,暂且忽略。稍后再提,ok!
至此插件的核心部分已经配置完成了,如果你现在打包部署的话,肯定是可以保存到数据库。如果你想试试的话,可以按照上一篇文章的步骤进行打包部署。打包不带jsp的ant命令-java-plug-jar即可。这里我就不运行这步,继续完成其他的操作演示。
4、 在src/plugins/chatlogs目录中的com.hoo.openfire.chat.logs包下新建ChatLogsServlet,提供对外调用的接口,一般用它查询聊天记录和联系人信息
- package com.hoo.openfire.chat.logs;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.sql.Timestamp;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.HashMap;
- import java.util.List;
- import javax.servlet.ServletConfig;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.codehaus.jackson.map.ObjectMapper;
- import org.jivesoftware.admin.AuthCheckFilter;
- import org.jivesoftware.util.ParamUtils;
- import com.hoo.openfire.chat.logs.entity.ChatLogs;
- /**
- * <b>function:</b> 聊天记录插件对外接口
- *
- * @author hoojo
- * @createDate 2012-9-18 下午09:32:21
- * @file ChatLogsServlet.java
- * @package com.hoo.openfire.chat.logs
- * @project OpenfirePlugin
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class ChatLogsServlet extends HttpServlet {
- private static final long serialVersionUID = 6981863134047161005L;
- private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
- private static final ObjectMapper mapper = new ObjectMapper();
- private static DbChatLogsManager logsManager;
- @Override
- public void init(ServletConfig config) throws ServletException {
- super.init(config);
- logsManager = DbChatLogsManager.getInstance();
- // 取消权限验证,不登陆即可访问
- AuthCheckFilter.addExclude("chatlogs");
- AuthCheckFilter.addExclude("chatlogs/ChatLogsServlet");
- }
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- doPost(request, response);
- }
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- response.setCharacterEncoding("utf-8");
- PrintWriter out = response.getWriter();
- ChatLogs entity = new ChatLogs();
- String action = ParamUtils.getParameter(request, "action");
- if ("last!contact".equals(action)) {
- String sender = ParamUtils.getParameter(request, "sender");
- entity.setSender(sender);
- List<String> result = logsManager.findLastContact(entity);
- request.setAttribute("lastContact", result);
- request.getRequestDispatcher("/plugins/chatlogs/chatLogs-last-contact-service.jsp").forward(request, response);
- } else if ("all!contact".equals(action)) {
- List<String> result = logsManager.findAllContact();
- request.setAttribute("allContact", result);
- request.getRequestDispatcher("/plugins/chatlogs/chatLogs-all-contact-service.jsp").forward(request, response);
- } else if ("remove!contact".equals(action)) {
- Integer messageId = ParamUtils.getIntParameter(request, "messageId", -1);
- logsManager.remove(messageId);
- request.getRequestDispatcher("/plugins/chatlogs/chatLogs-service.jsp").forward(request, response);
- } else if ("lately!contact".equals(action)) {
- String sender = ParamUtils.getParameter(request, "sender");
- entity.setSender(sender);
- List<String> result = logsManager.findLastContact(entity);
- StringWriter writer = new StringWriter();
- mapper.writeValue(writer, result);
- replyMessage(writer.toString(), response, out);
- } else if ("entire!contact".equals(action)) {
- List<String> result = logsManager.findAllContact();
- StringWriter writer = new StringWriter();
- mapper.writeValue(writer, result);
- replyMessage(writer.toString(), response, out);
- } else if ("delete!contact".equals(action)) {
- Integer messageId = ParamUtils.getIntParameter(request, "messageId", -1);
- StringWriter writer = new StringWriter();
- mapper.writeValue(writer, logsManager.remove(messageId));
- replyMessage(writer.toString(), response, out);
- } else if ("query".equals(action)) {
- String sender = ParamUtils.getParameter(request, "sender");
- String receiver = ParamUtils.getParameter(request, "receiver");
- String content = ParamUtils.getParameter(request, "content");
- String createDate = ParamUtils.getParameter(request, "createDate");
- try {
- if (createDate != null && !"".equals(createDate)) {
- entity.setCreateDate(new Timestamp(df.parse(createDate).getTime()));
- }
- } catch (Exception e) {
- }
- entity.setContent(content);
- entity.setReceiver(receiver);
- entity.setSender(sender);
- List<ChatLogs> logs = logsManager.query(entity);
- StringWriter writer = new StringWriter();
- mapper.writeValue(writer, logs);
- replyMessage(writer.toString(), response, out);
- } else {
- String sender = ParamUtils.getParameter(request, "sender");
- String receiver = ParamUtils.getParameter(request, "receiver");
- String content = ParamUtils.getParameter(request, "content");
- String createDate = ParamUtils.getParameter(request, "createDate");
- try {
- if (createDate != null && !"".equals(createDate)) {
- entity.setCreateDate(new Timestamp(df.parse(createDate).getTime()));
- }
- } catch (Exception e) {
- }
- entity.setContent(content);
- entity.setReceiver(receiver);
- entity.setSender(sender);
- List<HashMap<String, Object>> logs = logsManager.search(entity);
- StringWriter writer = new StringWriter();
- mapper.writeValue(writer, logs);
- replyMessage(writer.toString(), response, out);
- }
- }
- @Override
- public void destroy() {
- super.destroy();
- // Release the excluded URL
- AuthCheckFilter.removeExclude("chatlogs/ChatLogsServlet");
- AuthCheckFilter.removeExclude("chatlogs");
- }
- private void replyMessage(String message, HttpServletResponse response, PrintWriter out) {
- response.setContentType("text/json");
- out.println(message);
- out.flush();
- }
- }
这个类就是个普通的Servlet,做过JavaEE应该都比较了解了。更多关注的还是在servlet初始化的时候,将插件的配置目录忽略(即白名单)不通过URL资源权限验证,也就是用户不登陆也可以反问这个资源。
注意:这个类中使用到了jackson-all-1.6.2.jar这个包,你需要把这个jar包添加到当前项目的lib目录中,并且添加到构建路径中。添加到lib中后,在build.xml脚本运行的时候就不会编译错误了。
5、 写了Servlet当然少不了配置文件,在src/plugins/chatlogs根目录下新建一个web/WEB-INF目录,在web目录中新建一个web-custom.xml文件,内容如下
- <?xmlversion="1.0"encoding="ISO-8859-1"?>
- <!DOCTYPEweb-appPUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- <servlet>
- <servlet-name>ChatLogsServlet</servlet-name>
- <servlet-class>com.hoo.openfire.chat.logs.ChatLogsServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>ChatLogsServlet</servlet-name>
- <url-pattern>/ChatLogsServlet</url-pattern>
- </servlet-mapping>
- </web-app>
这里我配置的是/ChatLogsServlet,但是我在后面调用的时候是无法调用到的。http://127.0.0.1:9090/plugins/chatlogs/ChatLogsServlet?action=query;但如果我用http://127.0.0.1:9090/plugins/chatlogs?action=query就可以调用到ChatLogsServlet接口。之前在上一篇文章我也提到了这个问题。
6、 接下来就是完成页面的编写,刚才在上面的plugin.xml文件中有一个adminconsole管理员控制台的配置。其中<item id="chatLogs-service" name="ChatLogs Service" url="chatLogs-service.jsp"这个配置是最重要的,在上一篇文章中一提到,这里再说下。Item元素的id对象页面中的<meta name="pageID" content="chatLogs-service"/>这里的content的内容;item的url指向页面的路径名称。
chatLogs-service.jsp显示所有聊天记录、查询聊天记录、删除聊天记录
- <%@page import="com.hoo.openfire.chat.logs.entity.ChatLogs"%>
- <%@page import="java.text.SimpleDateFormat"%>
- <%@page import="java.text.DateFormat"%>
- <%@page import="java.sql.Timestamp"%>
- <%@page import="org.jivesoftware.util.ParamUtils"%>
- <%@page import="org.jivesoftware.openfire.XMPPServer"%>
- <%@page import="com.hoo.openfire.chat.logs.ChatLogsPlugin"%>
- <%@page import="com.hoo.openfire.chat.logs.DbChatLogsManager"%>
- <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>ChatLogs 聊天记录 openfire plugin</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta name="pageID" content="chatLogs-service"/>
- </head>
- <%
- //ChatLogsPlugin plugin = (ChatLogsPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("chatlogs");
- String sender = ParamUtils.getParameter(request, "sender");
- String receiver = ParamUtils.getParameter(request, "receiver");
- String content = ParamUtils.getParameter(request, "content");
- String createDate = ParamUtils.getParameter(request, "createDate");
- ChatLogs entity = new ChatLogs();
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
- try {
- entity.setCreateDate(new Timestamp(df.parse(createDate).getTime()));
- } catch (Exception e) {
- }
- entity.setContent(content);
- entity.setReceiver(receiver);
- entity.setSender(sender);
- DbChatLogsManager logsManager = DbChatLogsManager.getInstance();
- List<ChatLogs> logs = logsManager.query(entity);
- System.out.println(logsManager);
- System.out.println(logs);
- %>
- <body>
- <div class="jive-contentBoxHeader">所有聊天用户</div>
- <div class="jive-contentBox">
- <a href="${pageContext.request.contextPath }/plugins/chatlogs?action=all!contact">查看</a>
- </div>
- <div class="jive-contentBoxHeader">搜索</div>
- <div class="jive-contentBox">
- <form action="chatLogs-service.jsp">
- 发送人:<input type="text" name="sender" value="${param.sender }">
- 接收人:<input type="text" name="receiver" value="${param.receiver }">
- 内容:<input type="text" name="content" value="${param.content }">
- 发送时间:<input type="text" name="createDate" value="${param.createDate }">
- <input type="submit">
- <input type="reset">
- </form>
- </div>
- <div class="jive-table">
- <table cellpadding="0" cellspacing="0" border="0" width="100%">
- <thead>
- <tr>
- <th>发送人</th>
- <th>接收者</th>
- <th>内容</th>
- <th>发送时间</th>
- <th>删除</th>
- </tr>
- </thead>
- <tbody>
- <% for (int i = 0, len = logs.size(); i < len; i++) {
- ChatLogs log = logs.get(i);
- %>
- <tr class="jive-<%= i % 2 == 0 ? "even" : "odd" %>">
- <td><%=log.getSender() %></td>
- <td><%=log.getReceiver() %></td>
- <td><%=log.getContent() %></td>
- <td><%=log.getCreateDate() %></td>
- <td><a href="${pageContext.request.contextPath }/plugins/chatlogs?action=remove!contact&messageId=<%=log.getMessageId() %>">
- <img title="点击删除" src="data:images/delete-16x16.gif">
- </a></td>
- </tr>
- <% } %>
- </tbody>
- </table>
- </div>
- </body>
- </html>
chatLogs-all-contact-service.jsp显示查询所有聊天联系人
- <%@page import="org.xmpp.packet.JID"%>
- <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>ChatLogs 聊天记录 openfire plugin</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta name="pageID" content="chatLogs-all-contact-service"/>
- </head>
- <body>
- <div class="jive-contentBoxHeader">ChatLogs 所有聊天联系人</div>
- <div class="jive-table">
- <table cellpadding="0" cellspacing="0" border="0" width="100%">
- <thead>
- <tr>
- <th>联系人JID</th>
- <th>他/她的聊天记录</th>
- <th>【他/她】的联系人</th>
- </tr>
- </thead>
- <tbody>
- <%
- Object obj = request.getAttribute("allContact");
- if (obj != null) {
- List<String> allContact = (List<String>) obj;
- for (int i = 0, len = allContact.size(); i < len; i++) {
- String contact = allContact.get(i);
- JID jid = new JID(contact);
- %>
- <tr class="jive-<%= i % 2 == 0 ? "even" : "odd" %>">
- <td><%=contact %></td>
- <td>
- <a href="${pageContext.request.contextPath }/plugins/chatlogs/chatLogs-service.jsp?sender=<%=jid.getNode() %>">他/她的聊天记录</a>
- </td>
- <td>
- <a href="${pageContext.request.contextPath }/plugins/chatlogs?action=last!contact&sender=<%=jid.getNode() %>">他/她的联系人</a>
- </td>
- </tr>
- <% }
- }
- %>
- </tbody>
- </table>
- </div>
- </body>
- </html>
chatLogs-last-contact-service.jsp查询某个用户的最近联系人
- <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>ChatLogs 聊天记录 openfire plugin</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta name="pageID" content="chatLogs-all-contact-service"/>
- </head>
- <body>
- <div class="jive-contentBoxHeader">ChatLogs 所有聊天联系人</div>
- <div class="jive-table">
- <table cellpadding="0" cellspacing="0" border="0" width="100%">
- <thead>
- <tr>
- <th>联系人JID</th>
- <th>【他/她】的联系人</th>
- <th>他/她的聊天记录</th>
- </tr>
- </thead>
- <tbody>
- <%
- Object obj = request.getAttribute("lastContact");
- if (obj != null) {
- List<String> allContact = (List<String>) obj;
- for (int i = 0, len = allContact.size(); i < len; i++) {
- String contact = allContact.get(i);
- %>
- <tr class="jive-<%= i % 2 == 0 ? "even" : "odd" %>">
- <td><%=contact %></td>
- <td>
- <a href="${pageContext.request.contextPath }/plugins/chatlogs/chatLogs-service.jsp?sender=${param.sender}&receiver=<%=contact%>">和他/她的聊天记录信息</a>
- </td>
- <td>
- <a href="${pageContext.request.contextPath }/plugins/chatlogs/chatLogs-service.jsp?receiver=<%=contact%>">和他/她的聊天记录信息</a>
- </td>
- </tr>
- <% }
- }
- %>
- </tbody>
- </table>
- </div>
- </body>
- </html>
OK,至此整个插件基本编写完成,下面就开始打包部署,打包是很关键的。
三、打包发布插件
1、 上次编写ant命令不能编译JSP页面有引用类的页面,这次重新提供代码。打可部署jar包。在工程的根目录中新建一个build目录,新建build.xml
- <project name="Webapp Precompilation" default="openfire-plugins" basedir=".">
- <property file="build.properties" />
- <!-- java servlet相关文件编译jar存放位置 -->
- <property name="java.jar.dir" value="${webapp.path}/java-dist"/>
- <!-- jsp servlet编译后jar存放位置 -->
- <property name="jsp.jar.dir" value="${webapp.path}/jsp-dist/lib"/>
- <!-- 定义java servlet和jsp servlet的jar包名称 -->
- <property name="java.jar" value="${java.jar.dir}/plugin-${plugin.name}.jar"/>
- <property name="jsp.jar" value="${jsp.jar.dir}/plugin-${plugin.name}-jsp.jar"/>
- <!-- jsp servlet配置到web.xml中 -->
- <property name="plugin.web.xml" value="${webapp.path}/jsp-dist/web.xml"/>
- <!-- 编译jsp 并生成相关jar、xml文件 -->
- <target name="jspc">
- <taskdef classname="org.apache.jasper.JspC" name="jasper2">
- <classpath id="jspc.classpath">
- <pathelement location="${java.home}/../lib/tools.jar" />
- <fileset dir="${tomcat.home}/bin">
- <include name="*.jar" />
- </fileset>
- <fileset dir="${tomcat.home}/server/lib">
- <include name="*.jar" />
- </fileset>
- <fileset dir="${tomcat.home}/common/lib">
- <include name="*.jar" />
- </fileset>
- </classpath>
- </taskdef>
- <jasper2 javaEncoding="UTF-8" validateXml="false"
- uriroot="${plugin.path}/web"
- outputDir="${webapp.path}/jsp-dist/src"
- package="com.hoo.openfire.plugin.${plugin.name}" />
- <jasper2
- validateXml="false"
- uriroot="${plugin.path}/web"
- outputDir="${webapp.path}/jsp-dist/src"
- package="com.hoo.openfire.plugin.${plugin.name}"
- webXml="${plugin.web.xml}"/>
- </target>
- <!-- 编译jsp 并打jar包 -->
- <target name="compile">
- <mkdir dir="${webapp.path}/jsp-dist/classes" />
- <mkdir dir="${webapp.path}/jsp-dist/lib" />
- <mkdir dir="${webapp.path}/jsp-dist/src" />
- <javac destdir="${webapp.path}/jsp-dist/classes" optimize="off"
- encoding="UTF-8" debug="on" failonerror="false"
- srcdir="${webapp.path}/jsp-dist/src" excludes="**/*.smap">
- <!-- compilerarg value="-Xlint:unchecked"/
- <compilerarg value="-Xlint"/>-->
- <classpath>
- <pathelement location="${webapp.path}/jsp-dist/classes" />
- <fileset dir="${webapp.path}/jsp-dist/lib">
- <include name="*.jar" />
- </fileset>
- <pathelement location="${tomcat.home}/common/classes" />
- <fileset dir="${tomcat.home}/common/lib">
- <include name="*.jar" />
- </fileset>
- <pathelement location="${tomcat.home}/shared/classes" />
- <fileset dir="${tomcat.home}/shared/lib">
- <include name="*.jar" />
- </fileset>
- <fileset dir="${tomcat.home}/bin">
- <include name="*.jar" />
- </fileset>
- <pathelement location="${webapp.path}/bin" />
- <fileset dir="${webapp.path}/lib">
- <include name="**/*.jar" />
- </fileset>
- </classpath>
- <include name="**" />
- <exclude name="tags/**" />
- </javac>
- <jar jarfile="${jsp.jar}" basedir="${webapp.path}/jsp-dist/classes" />
- </target>
- <!-- 将java servlet打包成jar -->
- <target name="java-jar">
- <mkdir dir="${java.jar.dir}"/>
- <jar jarfile="${java.jar}">
- <fileset dir="${webapp.path}/bin" includes="**/*.class"/>
- </jar>
- </target>
- <!-- 生成可部署的插件包 -->
- <target name="plug-jar">
- <!-- 插件插件包相关lib、 web目录 -->
- <mkdir dir="${webapp.path}/${plugin.name}/lib"/>
- <mkdir dir="${webapp.path}/${plugin.name}/web/WEB-INF"/>
- <!-- 复制jsp servlet的jar和java servlet的相关jar包到插件包的lib目录下 -->
- <copy file="${java.jar}" todir="${webapp.path}/${plugin.name}/lib"/>
- <copy file="${jsp.jar}" todir="${webapp.path}/${plugin.name}/lib"/>
- <!-- 将相关的图片、帮助文档、修改日志等文件复制到插件目录下 -->
- <copy todir="${webapp.path}/${plugin.name}">
- <fileset dir="${plugin.path}" includes="*.*"/>
- </copy>
- <copy todir="${webapp.path}/${plugin.name}/web">
- <fileset dir="${plugin.path}/web">
- <include name="*"/>
- <include name="**/*.*"/>
- <exclude name="**/*.xml"/>
- <exclude name="**/*.jsp"/>
- </fileset>
- </copy>
- <!-- jsp servlet的web复制到插件目录下 -->
- <copy file="${plugin.web.xml}" todir="${webapp.path}/${plugin.name}/web/WEB-INF"/>
- <copy todir="${webapp.path}/${plugin.name}/web">
- <fileset dir="${plugin.path}/web" includes="**/*.xml"/>
- </copy>
- <!-- 将国际化相关资源文件复制到插件目录下
- <copy file="${webapp.path}/bin/i18n" todir="${webapp.path}/${plugin.name}"/>
- -->
- <!-- 产生可部署插件包 -->
- <jar jarfile="${webapp.path}/${plugin.name}.jar">
- <fileset dir="${webapp.path}/${plugin.name}" includes="**/**"/>
- </jar>
- </target>
- <!-- 生成没有Web资源的可部署插件包 -->
- <target name="java-plug-jar">
- <!-- 插件插件包相关lib、 web目录 -->
- <mkdir dir="${webapp.path}/${plugin.name}/lib"/>
- <!-- 复制java servlet的相关jar包到插件包的lib目录下 -->
- <copy file="${java.jar}" todir="${webapp.path}/${plugin.name}/lib"/>
- <!-- 将相关的图片、帮助文档、修改日志等文件复制到插件目录下 -->
- <copy todir="${webapp.path}/${plugin.name}">
- <fileset dir="${plugin.path}" includes="*.*"/>
- </copy>
- <!-- 产生可部署插件包 -->
- <jar jarfile="${webapp.path}/${plugin.name}.jar">
- <fileset dir="${webapp.path}/${plugin.name}" includes="**/**"/>
- </jar>
- </target>
- <!-- 清理生成的文件 -->
- <target name="clean">
- <delete file="${webapp.path}/${plugin.name}.jar"/>
- <delete dir="${webapp.path}/${plugin.name}"/>
- <delete dir="${webapp.path}/jsp-dist"/>
- <delete dir="${webapp.path}/java-dist"/>
- </target>
- <target name="all" depends="clean,jspc,compile"/>
- <target name="openfire-plugin" depends="jspc,java-jar"/>
- <target name="openfire-plugins" depends="all,java-jar,plug-jar"/>
- <target name="openfire-plugin-java" depends="clean,java-jar,java-plug-jar"/>
- </project>
build.properties文件内容
- tomcat.home=c:/SoftWare/tomcat-5.0.28/tomcat-5.0.28
- webapp.path=D:/eclipse_workspace/OpenfirePlugin
- plugin.name=chatLogs
- plugin.path=D\:/eclipse_workspace/OpenfirePlugin/src/plugins/chatlogs
至于这里为什么需要这个配置,上一篇文章已经有讲明。如果你需要了解的话,请看上一篇博文。运行默认的build.xml脚本中的默认ant命令,就可以看到当前项目的根目录中有一个chatLogs.jar 这个就是我们编写好打好的插件包了。
2、 发布插件
在发布之前,我们还需要做一件事情。在ChatLogsServlet中我们有使用ObjectMapper这个json的工具包。我们打包可以成功,但是在ChatLogsServlet这个接口在调用的时候会出现问题。你需要把jackson-all-1.6.2.jar这个jar包放在你的openfire服务的lib目录,也就是C:\Program Files\openfire\lib这个目录中,这样整个环境中就可以使用这个jar包了。
小提示:如果你的插件发布成功,但是没有达到预期的效果。那么很有可能插件中的代码出现了异常信息。你可以进入管理员控制台的日志http://127.0.0.1:9090/logviewer.jsp看到操作系统的日志信息。日志内容有常用的几个级别,经常看看日志对开发插件有帮助的。我拦截消息包的时候是分析消息包的xml内容作的判断,才完成这个插件的开发的。
发布插件:直接将插件放置在openfire服务器的plugins目录下。我的是在:C:\Program Files\openfire\plugins目录。重起openfire后你可以看到控制台输出我们插件中输出的内容,并且在C:\Program Files\openfire\plugins目录中可以看到该目录下多了一个chatlogs的目录(openfire可以自动解压jar包)。
当你在关闭服务器的瞬间,也会打印销毁插件的消息。
插件按照成功后,访问http://localhost:9090/plugin-admin.jsp页面你就可以看到安装好的插件了。
至此,整个插件部署成功了。
3、 测试插件
我们开一个spark和一个WebIM聊天程序进行互相聊天。内容大概如下:
发送聊天内容的时候你可以看到启动的openfire控制台有一些我们打印的报文。当聊天终止后,我们可以关闭openfire服务器。然后启动HSQL数据库查看下ofChatLogs这张表的数据,如果数据保存成功。那么效果达到了,如果没有消息内容,你得看看是哪里错误或遗漏了。建议你看看http://127.0.0.1:9090/logviewer.jsp的日志,可能会有提示信息。我这里查询后,大致内容如下:
OK,下面我们可以看看在 openfire管理员控制台服务器设置左侧菜单中的ChatLogs Servcie http://127.0.0.1:9090/profile-settings.jsp这个就是我们的插件。点击后就可以看到上面的测试的聊天内容了,你可以输入发送者、接受者等内容进行搜索,还可以点击“所有聊天用户”查看连接,查询所有聊天用户以及他们的聊天记录信息。
随后,我们应该测试下我们编写的ChatLogsServlet对外的查询接口。在浏览器中直接请求:http://127.0.0.1:9090/plugins/chatlogs?action=entire!contact会返回一段JSON数据,存放所有的聊天用户的sessionJID。
["boy@127.0.0.1/WebIM","girl@127.0.0.1/WebIM",hoojo@127.0.0.1/Spark 2.6.3]
请求:http://127.0.0.1:9090/plugins/chatlogs?action=last!contact&sender=boy
请求:http://127.0.0.1:9090/plugins/chatlogs?action=query
返回聊天记录所有结果,JSON格式的数组集合
请求:http://127.0.0.1:9090/plugins/chatlogs?action=query&sender=hoojo&receiver=boy
查询hoojo和boy的聊天内容,JSON格式的数组集合
其他的还可以通过内容,发送日期进行搜索等等,基本功能就这样的。如果你想要用到自己的系统中,如果涉及到跨域,你需要用到Proxy、URLConnection、HttpClient等网络编程的方法来解决跨域问题。
XMPP即时通讯协议使用(六)——开发Openfire聊天记录插件的更多相关文章
- openfire:基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件
基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件 上一篇文章介绍到怎么在自己的Java环境中搭建openfire插件开发的环境,同时介绍到怎样一步步简单的开发openfir ...
- 基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件
原文:http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html 随笔-150 评论- ...
- XMPP即时通讯协议使用(五)——搭建简单的Openfire插件
前言 在开发Openfire插件前需要构建完成服务器源码编辑环境,具体操作步骤请参照Openfire服务器源码编译的了解. 开发简单的Openfire插件 1.已构建完成的Openfire源码结构如下 ...
- XMPP即时通讯协议使用(前传)——协议详解
XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...
- 基于开源 Openfire 聊天服务器 - 开发Openfire聊天记录插件[转]
上一篇文章介绍到怎么在自己的Java环境中搭建openfire插件开发的环境,同时介绍到怎样一步步简单的开发openfire插件.一步步很详细的介绍到简单插件开发,带Servlet的插件的开发.带JS ...
- XMPP即时通讯协议使用(四)——Openfire服务器源码编译与添加消息记录保存
下载Openfire源码 下载地址:https://www.igniterealtime.org/downloads/index.jsp,当前最新版本为:4.2.3 Eclipse上部署Openfir ...
- XMPP即时通讯协议使用(一)——Openfire安装
Openfire服务器安装 下载地址:https://www.igniterealtime.org/downloads/index.jsp,根据你的操作系统,选择对应的下载版本.本文选择的是openf ...
- XMPP即时通讯协议使用(十一)——Openfire表结构汇总
行号 字段名称 字段描述 字段类型 长度 主键 说明 允许为空 用户组数据表(ofGroup) 1 groupName 组名 varchar2 50 ★ NOT NULL 2 descriptio ...
- XMPP即时通讯协议使用(七)——利用Strophe实现WebIM及strophe.plugins插件使用
Strophe简介与Openfire配置 Strophe.js是为XMPP写的一个js类库.因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接. 官方文档: http ...
随机推荐
- 二、MyBatis-HelloWorld
环境准备 1.创建数据库表 create table tbl_employee ( id ) primary key AUTO_INCREMENT comment "ID", la ...
- 关于pug的笔记
一.简介 Pug 是一款健壮.灵活.功能丰富的模板引擎,专门为 Node.js 平台开发.Pug 是由 Jade 改名而来,他可以帮助我们写html的时候更加的简单明了.安装.使用pug的过程打开cm ...
- 使用Fabric在tomcat中部署应用的问题总结
关闭tomcat时 A.为什么调用shutdown时,报错连接拒绝 结论——很可能是因为tomcat没启动或没完全启动:而这个时候调用shutdown就会出现此类报错 解决方法:time.sleep ...
- 3,LinkedList
一,LinkedList简介 1,LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. 2,LinkedList 实现 ...
- Linux Crontab命令定时任务基本语法
一.Crontab查看编辑重启 1.查看crontab定时执行任务列表 crontab -l 2.编辑crontab定时执行任务 crontab -e 3.删除crontab定时任务 crontab ...
- 元素隐藏visibility:hidden与元素消失display:none的区别
visibility属性用来确定元素是显示还是隐藏的,这用visibility="visible|hidden"来表示(visible表示显示,hidden表示隐藏). 当visi ...
- [CSP-S模拟测试]:甜圈(线段树)
题目描述 $D$先生,是一个了不起的甜甜圈制造商.今天,他的厨房准备在日出之前制作甜甜圈.$D$先生瞬间完成了$N$个油炸圈饼.但是,这些油炸圈饼得先经过各种装饰任务才可以成为甜甜圈销售:填充奶油,浸 ...
- 阿里云不支持stmp 的25端口,必须
第一种方法 到阿里云解封25端口 特别注意阿里云的<25端口使用服务协议>: 我/我公司承诺并保证TCP 25端口仅用来连接第三方的SMTP服务器,从第三方的SMTP服务器外发邮件. ...
- Python笔记(二十)_多态、组合
多态 对于函数中的变量,我们只需要知道它这个变量是什么类,无需确切地知道它的子类型,就可以放心地调用类的方法,而具体调用的这个方法是作用在父类对象还是子类对象上,由运行时该对象的确切类型决定,这就是多 ...
- [LeetCode] 421. Maximum XOR of Two Numbers in an Array(位操作)
传送门 Description Given a non-empty array of numbers, a0, a1, a2, … , an-1, where 0 ≤ ai < 231. Fin ...