openfire的MUC改造类似QQ永久群
在此非常感谢CSDN的博文:http://blog.csdn.net/yangzl2008/article/details/16991175,因为是这篇文章能够更快的解决问题。
我的解决方案类似上面博文的方案,但是没有修改openfire的源码,并且解决了上面博文中出现的两个问题:
1.数据是插入到数据库ofmucmember中了,可是openfire管理后台查看群成员还是没有,必须重启openfire
2.解决掉插入ofmucmember可能会发生的主键冲突和异常
下面进入正题: 先说明下openfire的muc房间,房间里面的member是分几个角色的,分别为拥有着、管理员、成员、被排除者。如果你用spark 创建的群,默认自己的角色是拥有着。您会看见在ofmucaffiliation表中有一条对应记录。如下图:
图片的详情页面
要实现muc成员持久有两个方案:
第一,可以在openfire管理后台直接把用户添加到成员角色里面;(实现方式很简单,但是不便于实际使用)
第二,在客户端用户进入房间后把用户 存入ofmucmember表中。(下面讲解的就是这种实现)
如果想在客户端join的同时把这个用户持久化,让下次登录后可以自动返回成员加入的群列表。我们想象一下:如果这个地方有一个监听器是否很好,监听到有成员join的时候就进行数据库操作。答案是肯定的,openfire提供了这个类,类名是:org.jivesoftware.openfire.muc.MUCEventListener,类源码如下:
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.muc;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
/**
* Interface to listen for MUC events. Use the {@link MUCEventDispatcher#addListener(MUCEventListener)}
* method to register for events.
*
* @author Gaston Dombiak
*/
public interface MUCEventListener {
/**
* Event triggered when a new room was created.
*
* @param roomJID JID of the room that was created.
*/
void roomCreated(JID roomJID);
/**
* Event triggered when a room was destroyed.
*
* @param roomJID JID of the room that was destroyed.
*/
void roomDestroyed(JID roomJID);
/**
* Event triggered when a new occupant joins a room.
*
* @param roomJID the JID of the room where the occupant has joined.
* @param user the JID of the user joining the room.
* @param nickname nickname of the user in the room.
*/
void occupantJoined(JID roomJID, JID user, String nickname);
/**
* Event triggered when an occupant left a room.
*
* @param roomJID the JID of the room where the occupant has left.
* @param user the JID of the user leaving the room.
*/
void occupantLeft(JID roomJID, JID user);
/**
* Event triggered when an occupant changed his nickname in a room.
*
* @param roomJID the JID of the room where the user changed his nickname.
* @param user the JID of the user that changed his nickname.
* @param oldNickname old nickname of the user in the room.
* @param newNickname new nickname of the user in the room.
*/
void nicknameChanged(JID roomJID, JID user, String oldNickname, String newNickname);
/**
* Event triggered when a room occupant sent a message to a room.
*
* @param roomJID the JID of the room that received the message.
* @param user the JID of the user that sent the message.
* @param nickname nickname used by the user when sending the message.
* @param message the message sent by the room occupant.
*/
void messageReceived(JID roomJID, JID user, String nickname, Message message);
/**
* Event triggered when a room occupant sent a private message to another room user
*
* @param toJID the JID of who the message is to.
* @param fromJID the JID of who the message came from.
* @param message the message sent to user.
*/
void privateMessageRecieved(JID toJID, JID fromJID, Message message);
/**
* Event triggered when the subject of a room is changed.
*
* @param roomJID the JID of the room that had its subject changed.
* @param user the JID of the user that changed the subject.
* @param newSubject new room subject.
*/
void roomSubjectChanged(JID roomJID, JID user, String newSubject);
}
OK,我们已经知道利用这个类的 occupantJoined
方法来持久化这个加入成员的用户数据,这个时候有个问题来了,我们知道openfire的群可以设置仅成员可以加入,或者是任何人可以加入,如图:
现在把你创建的群设置为只有群成员可以加入,然后我们用spark客户端测试是否可以加入,结果提示如下:
怎么办呢,有的业务就是只能管理员把成员批量拉入然后持久化数据库,不能随便加入;其中原因是由于openfire的MUC群成员默认都是放缓存里面的,一旦离线就从缓存把数据清理掉。细心的会发现,我们在openfire管理后台加入的用户在客户端就能够使用,那么我们看下openfire管理后台的那个界面是如何实现的哈:muc-room-affiliations.jsp 里面有一段代码如下:
IQ iq = new IQ(IQ.Type.set);
if ("owner".equals(affiliation) || "admin".equals(affiliation)) {
Element frag = iq.setChildElement("query", "http://jabber.org/protocol/muc#owner");
Element item = frag.addElement("item");
item.addAttribute("affiliation", affiliation);
item.addAttribute("jid", userJID);
// Send the IQ packet that will modify the room's configuration
room.getIQOwnerHandler().handleIQ(iq, room.getRole());
}
else if ("member".equals(affiliation) || "outcast".equals(affiliation)) {
Element frag = iq.setChildElement("query", "http://jabber.org/protocol/muc#admin");
Element item = frag.addElement("item");
item.addAttribute("affiliation", affiliation);
item.addAttribute("jid", userJID);
// Send the IQ packet that will modify the room's configuration
room.getIQAdminHandler().handleIQ(iq, room.getRole());
}
看了上面代码后,你是否已经知道如何去实现了呢;其实我们可以在成员join的时候模拟后台操作加入成员还是管理者等,也就是 occupantJoined
方法实现如下:
@Override
public void occupantJoined(JID roomJID, JID user, String nickname) {
String mjid = user.toBareJID();
if(MUCDao.exists(mjid, roomJID.getNode())) //如果成员存在
return ;
else if( !MUCDao.existsMember(roomJID.getNode()) ){ //如果muc房间里面一个用户都还不存在
return ;
}
log.warn("occupantJoined:"+roomJID+">"+user+":"+nickname);
MUCRoom mucroom =XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(roomJID).getChatRoom(roomJID.getNode());
IQ iq = new IQ(IQ.Type.set);
Element frag = iq.setChildElement("query", "http://jabber.org/protocol/muc#member");
Element item = frag.addElement("item");
item.addAttribute("affiliation", "member");
item.addAttribute("jid", mjid);
item.addAttribute("nick",nickname);
// Send the IQ packet that will modify the room's configuration
try {
mucroom.getIQAdminHandler().handleIQ(iq, mucroom.getRole());
if(nickname != null && !"".equals(nickname))
MUCDao.updateNick(mucroom.getID(), mjid, nickname);
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
其中
MUCDao
类是自己定义的一个DAO类