一、思路
1、新建立永久房间,同时保证房间中的成员存储到“ofmucmember”表当中。
2、新建一个用户登陆监听的插件,这个插件监听用户的登陆行为,同时在用户登陆时查询“ofmucmember”表,查询用户所在的房间。
3、将房间信息封装到自定义的IQ包中,发送到客户端。
4、不管使用Android客户端,还是对Spark进行二次开发,对Openfire发送过来的自定义IQ包进行解析。
5、显示群信息。
二、难点
关于“将Openfire中的MUC改造成类似QQ群一样的永久群”类似的文章不多,其中可见的就是“
改造MUC实现Openfire群”和“
spark openfire conference修改使群组编程持久的。 类似qq群(1)”这两篇文章,但是如果你按照这两篇文章实现来改造Openfire的话,基本上是实现不了QQ群的功能的,但是还是感谢两位作者提供的思路。两位作者的思路都是可以的,但是都用一个共同的问题,就是没有提到如何将房间成员的关系信息存储到“ofmucmember”中。
Persistent Room
A room that is not destroyed if the last occupant exits; antonym: Temporary Room.
也就是说,可以建立一个持久的room,但是这个room不同于其他room的特点是,当我们都离开这个Persistent room之后,其不会消失,其会存储在“ofmucroom”表中,而其他room不会存储在这个表中。同时这个Persistent room和其他的room一样都不会存储
room中用户的信息到“ofmucmember”表中。具体在Openfire的论坛中,
有很多人都在问为什么ofmucmember这个表是空的,但是,没有人回答,为什么是空的,什么时候想数据库里面插入数据。包括博主自己在读完org.jivesoftware.openfire.muc、org.jivesoftware.openfire.muc.cluster、org.jivesoftware.openfire.muc.spi这三个与MUC相关的三个包中的类之后,虽然在org.jivesoftware.openfire.muc.spi.MUCPersistenceManager类中有
- private static final String ADD_MEMBER =
- "INSERT INTO ofMucMember (roomID,jid,nickname) VALUES (?,?,?)";
三、Openfire端修改方案
3.1 建立永久房间
打开Spark新建一个房间,同时将房间设置为固定的如下图:
验证是否创建成功SQL语句如下:
- SELECT * FROM `openfire`.`ofmucroom`
如图,我们已经创建了一个名为csdn的房间。接下来就是修改Openfire的部分代码,实现对对持久房间的用户插入ofmucmember功能。
3.2 实现插入ofmucmember功能
在org.jivesoftware.openfire.muc.spi.MUCPersistenceManager类中增加方法public static void saveMember(LocalMUCRoom localMUCRoom, JID bareJID, String resource) 如下:
- public static void saveMember(LocalMUCRoom localMUCRoom, JID bareJID,
- String resource) {
- Connection con = null;
- PreparedStatement pstmt = null;
- try {
- con = DbConnectionManager.getConnection();
- pstmt = con.prepareStatement(ADD_MEMBER);
- pstmt.setLong(1, localMUCRoom.getID());
- pstmt.setString(2, bareJID.toBareJID());
- pstmt.setString(3, resource);
- pstmt.executeUpdate();
- }
- catch (SQLException sqle) {
- Log.error(sqle.getMessage(), sqle);
- }
- finally {
- DbConnectionManager.closeConnection(pstmt, con);
- }
- }
在
- }
- inally {
- lock.writeLock().unlock();
- try {
- if (isPersistent()) {
- MUCPersistenceManager.saveMember(this, bareJID,
- bareJID.getNode());
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- CREATE TABLE `ofmucmember` (
- `roomID` BIGINT(20) NOT NULL,
- `jid` TEXT NOT NULL,
- `nickname` VARCHAR(255) NULL DEFAULT NULL,
- `firstName` VARCHAR(100) NULL DEFAULT NULL,
- `lastName` VARCHAR(100) NULL DEFAULT NULL,
- `url` VARCHAR(100) NULL DEFAULT NULL,
- `email` VARCHAR(100) NULL DEFAULT NULL,
- `faqentry` VARCHAR(100) NULL DEFAULT NULL,
- PRIMARY KEY (`roomID`, `jid`(70))
- )
好了,以上就是实现插入ofmucmember功能的主要代码。以上这个方法只是一个临时方法,如果,网友有更好的方案,欢迎留言探讨。
3.3 新建一个用户登陆监听的插件
关于如何创建Openfire插件的工作,可以在网上搜索一下,有很多博主都有对其详细的描述,在这里就不再一一描述,在这里只是将主要代码贴下来:
3.3.1 创建plugin.xml文件
- <?xml version="1.0" encoding="UTF-8"?>
- <plugin>
- <!-- Main plugin class -->
- <class>com.yang.plugin.MUCPersistencePlugin</class>
- <!-- Plugin meta-data -->
- <name>SimplePlugin</name>
- <description>This is the Yang sample plugin.</description>
- <author>YangZhilong</author>
- <version>1.0</version>
- <date>15/11/2014</date>
- <url>http://localhost:9090/openfire/plugins.jsp</url>
- <minServerVersion>3.4.1</minServerVersion>
- <licenseType>gpl</licenseType>
- <adminconsole>
- </adminconsole>
- </plugin>
3.3.2 创建MUCDao.java文件
- package com.yang.plugin.dao;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import org.jivesoftware.database.DbConnectionManager;
- /**
- * @Title: MUCDao.java
- * @Package com.yang.plugin.dao
- * @Description: 根据用户名查询出用户所在群组的信息
- * @author Yang
- * @blog http://blog.csdn.net/yangzl2008
- * @date 2013年11月27日 下午7:16:22
- * @version V1.0
- */
- public class MUCDao {
- public static List<Map<String, String>> getMUCInfo(String jid) {
- List<Map<String, String>> list = new ArrayList<Map<String, String>>();
- String sql = "select ofmucroom.serviceID, ofmucroom.name, ofmucroom.roomid ,ofmucmember.nickname from "
- + "ofmucroom join ofmucmember on ofmucroom.roomID = ofmucmember.roomID and ofmucmember.jid = ?";
- Connection connection = null;
- PreparedStatement statement = null;
- ResultSet resultSet = null;
- Map<String, String> map = null;
- try {
- connection = DbConnectionManager.getConnection();
- statement = connection.prepareStatement(sql);
- statement.setString(1, jid);
- resultSet = statement.executeQuery();
- while (resultSet.next()) {
- map = new HashMap<String, String>();
- map.put("serviceID", resultSet.getString(1));
- map.put("name", resultSet.getString(2));
- map.put("roomid", resultSet.getString(3));
- map.put("nickname", resultSet.getString(4));
- list.add(map);
- }
- } catch (Exception e1) {
- e1.printStackTrace();
- } finally {
- DbConnectionManager.closeConnection(resultSet, statement,
- connection);
- }
- return list;
- }
- }
3.3.3 创建MUCPersistencePlugin
- package com.yang.plugin;
- import java.io.File;
- import java.util.List;
- import java.util.Map;
- import org.dom4j.Document;
- import org.dom4j.DocumentHelper;
- import org.dom4j.Element;
- import org.dom4j.Namespace;
- import org.jivesoftware.openfire.IQRouter;
- import org.jivesoftware.openfire.XMPPServer;
- import org.jivesoftware.openfire.container.Plugin;
- import org.jivesoftware.openfire.container.PluginManager;
- import org.jivesoftware.openfire.event.SessionEventDispatcher;
- import org.jivesoftware.openfire.event.SessionEventListener;
- import org.jivesoftware.openfire.handler.IQHandler;
- import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
- import org.jivesoftware.openfire.muc.spi.MultiUserChatServiceImpl;
- import org.jivesoftware.openfire.session.Session;
- import org.xmpp.packet.IQ;
- import org.xmpp.packet.JID;
- import com.yang.plugin.dao.MUCDao;
- /**
- *
- * @Title: MUCPersistencePlugin.java
- * @Package com.yang.plugin
- * @Description: 监听用户登录信息,用户登录之后,查询用户所在room的信息,将room信息发送到客户端。
- * @author Yang
- * @blog http://blog.csdn.net/yangzl2008
- * @date 2013年11月27日 上午9:32:21
- * @version V1.0
- */
- public class MUCPersistencePlugin implements Plugin, SessionEventListener {
- private XMPPServer server;
- private MultiUserChatServiceImpl mucService;
- private IQRouter router;
- @Override
- public void sessionCreated(Session session) {
- JID userJid = session.getAddress();
- joinRooms(userJid);
- }
- @Override
- public void sessionDestroyed(Session session) {
- }
- @Override
- public void resourceBound(Session session) {
- }
- @Override
- public void anonymousSessionCreated(Session session) {
- }
- @Override
- public void anonymousSessionDestroyed(Session session) {
- }
- @Override
- public void initializePlugin(PluginManager manager, File pluginDirectory) {
- server = XMPPServer.getInstance();
- SessionEventDispatcher.addListener(this);
- System.out.println("Join room plugin is running!");
- IQHandler myHandler = new MyIQHander();
- IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
- iqRouter.addHandler(myHandler);
- router = XMPPServer.getInstance().getIQRouter();
- }
- public void joinRooms(JID userJid) {
- List<Map<String, String>> data = MUCDao.getMUCInfo(userJid.toBareJID());
- if (data == null || data.isEmpty()) {
- return;
- }
- Map<String, String> map = null;
- /**
- * 构建iq的扩展包,用于发送用户所在房间的名称。
- */
- Document document = DocumentHelper.createDocument();
- Element iqe = document.addElement("iq");
- iqe.addAttribute("type", "result");
- iqe.addAttribute("to", userJid.toFullJID());
- iqe.addAttribute("id", "YANG");
- Namespace namespace = new Namespace("""YANG");
- Element muc = iqe.addElement("muc");
- muc.add(namespace);
- for (int i = 0, len = data.size(); i < len; i++) {
- map = data.get(i);
- String serviceID = map.get("serviceID");
- mucService = (MultiUserChatServiceImpl) server
- .getMultiUserChatManager().getMultiUserChatService(
- Long.parseLong(serviceID));
- String roomName = map.get("name");
- LocalMUCRoom room = (LocalMUCRoom) mucService.getChatRoom(roomName);
- //增加room和account信息
- Element roome = muc.addElement("room");
- roome.setText(room.getJID().toBareJID());
- roome.addAttribute("account", userJid.toFullJID());
- }
- //最后发送出去!
- IQ iq = new IQ(iqe);
- System.out.println("iq " + iq.toXML());
- router.route(iq);
- }
- @Override
- public void destroyPlugin() {
- SessionEventDispatcher.removeListener(this);
- server = null;
- mucService = null;
- }
- }
- package com.yang.plugin;
- import org.jivesoftware.openfire.IQHandlerInfo;
- import org.jivesoftware.openfire.auth.UnauthorizedException;
- import org.jivesoftware.openfire.handler.IQHandler;
- import org.xmpp.packet.IQ;
- /**
- * @Title: MyIQHander.java
- * @Package com.yang.plugin
- * @Description: TODO
- * @author Yang
- * @blog http://blog.csdn.net/yangzl2008
- * @date 2013年11月27日 下午9:56:27
- * @version V1.0
- */
- public class MyIQHander extends IQHandler {
- private static final String MODULE_NAME = "group tree handler";
- private static final String NAME_SPACE = "com:im:group";
- private IQHandlerInfo info;
- public MyIQHander() {
- super(MODULE_NAME);
- info = new IQHandlerInfo("gruops", NAME_SPACE);
- }
- @Override
- public IQHandlerInfo getInfo() {
- return info;
- }
- @Override
- public IQ handleIQ(IQ packet) throws UnauthorizedException {
- IQ reply = IQ.createResultIQ(packet);
- System.out.println("XML " + reply.toXML());
- return reply;
- }
- }
四、修改基于smack客户端
所有基于smack的代码都可以使用以下方案实现接收Openfire发送过来的自定义IQ包。其中主要是MUCPacketExtensionProvider类和对这个Provoider的注册操作最为重要。MUCPacketExtensionProvider代码如下:
- import org.jivesoftware.smack.packet.IQ;
- import org.jivesoftware.smack.provider.IQProvider;
- import org.xmlpull.v1.XmlPullParser;
- import ouc.sei.suxin.android.data.Application;
- import ouc.sei.suxin.android.data.entity.MUCInfo;
- import ouc.sei.suxin.android.utils.Logger;
- public class MUCPacketExtensionProvider implements IQProvider {
- @Override
- public IQ parseIQ(XmlPullParser parser) throws Exception {
- int eventType = parser.getEventType();
- MUCInfo info = null;
- while (true) {
- if (eventType == XmlPullParser.START_TAG) {
- if ("room".equals(parser.getName())) {
- String account = parser.getAttributeValue("", "account");
- String room = parser.nextText();
- info = new MUCInfo();
- info.setAccount(account);
- info.setRoom(room);
- info.setNickname(account);
- Logger.d("account is " + account + " and room is " + room);
- Application.getInstance().addMUCInfo(info);
- }
- } else if (eventType == XmlPullParser.END_TAG) {
- if ("muc".equals(parser.getName())) {
- break;
- }
- }
- eventType = parser.next();
- }
- return null;
- }
- }
对这个Provoider的注册操作代码如下:
- ProviderManager.getInstance().addIQProvider("muc", "YANG", new MUCPacketExtensionProvider());
辅助存储信息的MUCInfo如下:
- /**
- * @Title: MUCInfo.java
- * @Package ouc.sei.suxin.android.data.entity
- * @Description: 用于传输从Server端传输过来的MUC的信息
- * @author Yang Zhilong
- * @blog http://blog.csdn.net/yangzl2008
- * @date 2013年11月27日 上午9:27:25
- * @version V1.0
- */
- public class MUCInfo {
- private String account;
- private String room;
- private String nickname;
- public String getAccount() {
- return account;
- }
- public void setAccount(String account) {
- this.account = account;
- }
- public String getRoom() {
- return room;
- }
- public void setRoom(String room) {
- this.room = room;
- }
- public String getNickname() {
- return nickname;
- }
- public void setNickname(String nickname) {
- if (nickname.contains("@")) {
- this.nickname = nickname.substring(0, account.indexOf("@"));
- return;
- }
- this.nickname = nickname;
- }
- @Override
- public String toString() {
- return "MUCInfo [account=" + account + ", room=" + room + ", nickname="
- + nickname + "]";
- }
- }
通过以上的代码我们就可以接受到服务器端发送而来的登陆用户的房间信息,关于这个信息的处理,方式就很灵活了。如果是Android可以里ListView的形式显示给用户,如果是Spark,可以对Spark进行二次开发,显示给用户群信息。