消息处理流程参考:openfire消息回执插件开发业务处理流程_ningkangming的博客-CSDN博客
在源码的plugin包下创建消息回执插件maven工程,如图
maven pom.xml参考如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>4.6.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openfire-plugin-message-receipts</artifactId>
<description>消息回执插件,实现单聊、群聊的消息回执</description>
<developers>
<developer>
<organization>广东新岸线科技有限公司</organization>
<email>kangming.ning@nufront.com</email>
<name>kangming.ning</name>
</developer>
</developers>
<build>
<!-- 插件打包后的jar包名称-->
<finalName>ofchat-message-receipts</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<includes>
<!-- 只在jar里面放class文件 -->
<include>**/com/nufront/openfire/plugin/**</include>
</includes>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>resources</goal>
</goals>
</execution>
</executions>
<configuration>
<resources>
<resource>
<directory>${project.basedir}</directory>
<filtering>true</filtering>
<includes>
<include>changelog.html</include>
<include>readme.html</include>
<include>plugin.xml</include>
<include>logo_large.gif</include>
<include>logo_small.gif</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/web</directory>
<targetPath>web</targetPath>
</resource>
</resources>
</configuration>
</plugin>
<plugin>
<!-- ant插件, 以上三个插件只是把零散的文件编译並放在target文件夾下,
ant插件 把文件整理起來, 并压缩到指定的jar里面 -->
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<!-- 调用阶段 -->
<phase>install</phase>
<goals>
<!-- 目标(命令) -->
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo message="开始构建插件包..."></echo>
<copy todir="${project.build.directory}/${project.build.finalName}-final/lib" overwrite="true" file="${project.build.directory}/${project.build.finalName}.jar"></copy>
<copydir dest="${project.build.directory}/${project.build.finalName}-final/" src="${project.build.directory}/classes/" excludes="org/**"></copydir>
<jar basedir="${project.build.directory}/${project.build.finalName}-final/" destfile="${project.build.directory}/${project.build.finalName}-final.jar"></jar>
<delete dir="${project.build.directory}/${project.build.finalName}-final/"></delete>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
实现openfire的Plugin接口,接口定义如下
/*
* Copyright (C) 2004-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.container;
import java.io.File;
/**
* Plugin interface. Plugins enhance the functionality of Openfire. They can:<ul>
*
* <li>Act as {@link org.xmpp.component.Component Components} to implement
* additional features in the XMPP protocol.
* <li>Dynamically modify the admin console.
* <li>Use the Openfire API to add new functionality to the server.
* </ul>
*
* Plugins live in the {@code plugins} directory of {@code home}. Plugins
* that are packaged as JAR files will be automatically expanded into directories. A
* plugin directory should have the following structure:
*
* <pre>[pluginDir]
* |-- plugin.xml
* |-- classes/
* |-- lib/</pre>
*
* The {@code classes} and {@code lib} directory are optional. Any files in the
* {@code classes} directory will be added to the classpath of the plugin, as well
* as any JAR files in the {@code lib} directory. The {@code plugin.xml} file is
* required, and specifies the className of the Plugin implementation. The XML file
* should resemble the following XML:
*
* <pre>
* <?xml version="1.0" encoding="UTF-8"?>
* <plugin>
* <class>org.example.YourPlugin</class>
* <name>Example Plugin</name>
* <description>This is an example plugin.</description>
* <author>Foo Inc.</author>
* <version>1.0</version>
* <minServerVersion>3.0.0</minServerVersion>
* <licenseType>gpl</licenseType>
* </plugin></pre>
* <p>
* Each plugin will be loaded in its own class loader, unless the plugin is configured
* with a parent plugin.</p>
*
* Please see the Plugin Developer Guide (available with the
* Openfire documentation) for additional details about plugin development.
*
* @author Matt Tucker
*/
public interface Plugin {
/**
* Initializes the plugin.
*
* @param manager the plugin manager.
* @param pluginDirectory the directory where the plugin is located.
*/
void initializePlugin( PluginManager manager, File pluginDirectory );
/**
* Destroys the plugin.<p>
*
* Implementations of this method must release all resources held
* by the plugin such as file handles, database or network connections,
* and references to core Openfire classes. In other words, a
* garbage collection executed after this method is called must be able
* to clean up all plugin classes.
*/
void destroyPlugin();
}
实现接口
package com.nufront.openfire.plugin.receipts;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* 消息回执插件
* @author kangming.ning
* @date 2020/11/25 16:04
*/
public class ChatMsgDeliveryReceiptsPlugin implements Plugin {
private static final Logger logger = LoggerFactory.getLogger(ChatMsgDeliveryReceiptsPlugin.class);
private InterceptorManager interceptorManager = InterceptorManager.getInstance();
private ChatMsgDeliveryReceiptsInterceptor chatMsgDeliveryReceiptsInterceptor;
@Override
public void initializePlugin(PluginManager manager, File pluginDirectory) {
chatMsgDeliveryReceiptsInterceptor = new ChatMsgDeliveryReceiptsInterceptor();
interceptorManager.addInterceptor(chatMsgDeliveryReceiptsInterceptor);
logger.info("消息回执插件加载成功!");
}
@Override
public void destroyPlugin() {
interceptorManager.removeInterceptor(chatMsgDeliveryReceiptsInterceptor);
logger.info("消息回执插件销毁成功!");
}
}
ChatMsgDeliveryReceiptsInterceptor为插件源码,实现了PacketInterceptor接口,可以对所有消息包进行拦截,然后可添加自己的逻辑。这样当收到我们需要做回执的消息类型时,我们就可以根据回执的处理流程来处理了。
package com.nufront.openfire.plugin.receipts;
import org.dom4j.Element;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.interceptor.PacketInterceptor;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 消息回执拦截器
* @author kangming.ning
* @date 2020/11/25 16:03
*/
public class ChatMsgDeliveryReceiptsInterceptor implements PacketInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ChatMsgDeliveryReceiptsInterceptor.class);
private static Map<String,Message> msgMap=new ConcurrentHashMap<>();
@Override
public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed) throws PacketRejectedException {
//processed 为true表示消息已发送(已处理) 此拦截方法会被执行两次 一次processed为false 一次为true 消息保存的话 只处理一次即可
//incoming true代表was read by the server,即服务器读取到的包 false为sent from the server,即服务器发出的包
//我们只需要处理 已处理过的(processed为true,代表服务器已经把消息发送给对方了或保存到ofoffline表里面),并且是服务器读到的包
this.doAction(packet,session,incoming,processed);
}
private void doAction(Packet packet, Session session,boolean incoming, boolean processed) {
//收到客户端的消息 则给from的用户直接回一条回执
Packet copyPacket = packet.createCopy();
if (packet instanceof Message) {
Message message = (Message) copyPacket;
//如果是一条delay消息 忽略不处理
Element delay = message.getChildElement("delay","urn:xmpp:delay");
if (delay!=null){
return;
}
JID jid = session.getAddress();
String sender = jid.getNode();
JID recipient = message.getTo();
String receiver = recipient.getNode();
if (sender != null && receiver != null
&& sender.equalsIgnoreCase(receiver)) {//sender和receiver是同一个 此消息不处理
return;
}
Element offline = message.getChildElement("offline", "urn:xmpp:offline");
if (offline!=null){
//接收方不在线的时候,需要接收到的消息被系统存储。当客户端登陆的时候,推送给接收者。该消息推送后,需要接收方回执
System.out.println("--offline--:"+message.toXML());
if (!incoming){//incoming为false,代表这消息是服务端发出去的
//再次放到消息队列 要求回执
}
}
//processed 为true的话 说明已经将消息发送到B了 所以回执的缓存和发送回执给A可以在processed为false处理 但确认B的回执要在processed为true时处理
//消息发送到B前 直接将消息缓存 并直接回复回执消息给A
if (!processed&&incoming){
// 一对一聊天,单人模式 不同的type可能有不同的处理
if (message.getType() == Message.Type.chat) {
sendReceiptsToFromUser(message,session);
msgMap.put(message.getID(),message);
}else if (message.getType() == Message.Type.groupchat) {
//发送回执给from
//sendReceiptsToFromUser(message,session);
}
}
//处理确认回执 processed为true,此时B已经收到消息并发来回执 当然 所有消息到这里都会调两次,注意逻辑处理即可
if (processed&&incoming){
if (message.getType() == Message.Type.normal){
//先看是不是回执消息
Element received = message.getChildElement("received", "urn:xmpp:receipts");
if (received!=null){
//收到的回执消息都是B发过来让服务端确认的(A发消息给B)
String namespaceURI = received.getNamespaceURI();
String id = received.attributeValue("id");
String namespace = received.attributeValue("xmlns");
Message cacheMessage = msgMap.get(id);
if (cacheMessage!=null){
System.out.println("确认回执:"+message.toXML());
msgMap.remove(id);
}
}else {
//一般消息发送回执
sendReceiptsToFromUser(message,session);
msgMap.put(message.getID(),message);
}
}
}
}
}
private void sendReceiptsToFromUser(Message message,Session session){
// 向客户端发回执
Message receiptMessage = new Message();
receiptMessage.setTo(message.getFrom());
receiptMessage.setFrom(session.getServerName());
receiptMessage.setType(Message.Type.normal);
Element received = receiptMessage.addChildElement("received", "urn:xmpp:receipts");
received.addAttribute("id", message.getID());
System.out.println("回执内容:" + receiptMessage.toXML());
try {
XMPPServer.getInstance().getPacketDeliverer().deliver(receiptMessage);
System.out.println("服务端回执成功!");
} catch (Exception e) {
logger.error("消息回执返回失败",e);
}
}
}
最后打包成jar包,通过openfire插件管理界面加载我们的插件即可。