消息处理流程参考: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>* &lt;?xml version="1.0" encoding="UTF-8"?&gt;* &lt;plugin&gt;*     &lt;class&gt;org.example.YourPlugin&lt;/class&gt;*     &lt;name&gt;Example Plugin&lt;/name&gt;*     &lt;description&gt;This is an example plugin.&lt;/description&gt;*     &lt;author&gt;Foo Inc.&lt;/author&gt;*     &lt;version&gt;1.0&lt;/version&gt;*     &lt;minServerVersion&gt;3.0.0&lt;/minServerVersion&gt;*     &lt;licenseType&gt;gpl&lt;/licenseType&gt;* &lt;/plugin&gt;</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;@Overridepublic void initializePlugin(PluginManager manager, File pluginDirectory) {chatMsgDeliveryReceiptsInterceptor = new ChatMsgDeliveryReceiptsInterceptor();interceptorManager.addInterceptor(chatMsgDeliveryReceiptsInterceptor);logger.info("消息回执插件加载成功!");}@Overridepublic 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<>();@Overridepublic 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前 直接将消息缓存 并直接回复回执消息给Aif (!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插件管理界面加载我们的插件即可。

openfire实现消息回执相关推荐

  1. openfire smack消息回执设置,处理掉包问题

    在网络不稳定时,openfire容易出现掉包情况,原因是在客户端掉线时,openfire并不能马上知道客户端已经断线,至于要多久才能发现客户端断线,跟服务器端设置的Idle Connections 时 ...

  2. openfire消息回执插件开发业务处理流程

    消息回执插件 参考: OpenFire源码学习之二十四:消息回执与离线消息(上) - Spark_莫然 - 博客园 OpenFire源码学习之二十五:消息回执与离线消息(下) - Spark_莫然 - ...

  3. OpenFire源码学习之二十五:消息回执与离线消息(下)

    这一篇紧接着上面继续了. 方案二 基于redis的消息回执.主要流程分为下面几个步骤: 1)将消息暂存储与redis中,设置好消息的过期时间 2)客户端回执消息id来消灭暂存的消息 3)开通单独线程论 ...

  4. JAVA中调用阿里云语音通知Api并接收消息回执

    JAVA中调用阿里云语音通知Api并接收消息回执 配置文件 需要的包 <dependency><groupId>com.aliyun</groupId><ar ...

  5. 即时通信中消息回执及阅后即焚相关专利研究

    即时通信中消息回执及阅后即焚相关专利研究 在即时通讯中,消息发送后发送方收到回执的形式可以作为重要消息的方式来进行,还有就是阅后即焚也是即时消息里相关的专利,这些专利在专利系统里相对来说不是很多,以下 ...

  6. openfire 接受消息流程

    博客分类: openfire & xmpp openfire底层采用了MINA框架,它是采用事件监听的方式,其中IoHandler接口定义了不同的事件类型,因此根据不同的事件类型做相关的处理 ...

  7. java xmpp消息推送_基于XMPP协议(openfire服务器)的消息推送实现

    最近好像有不少朋友关注Android客户端消息推送的实现,我在之前的项目中用到过Java PC客户端消息推送,从原理讲上应该是一致的,在这里分享一下个人的心得. 消息推送实现原理 这里的消息推送,通常 ...

  8. im即时通讯开发:IM群聊消息的已读回执功能

    我们平时在使用即时通讯应用时候,每当发出一条聊天消息,都希望对方尽快看到,并尽快回复,但对方到底有没有真的看到?我却并不知道. 一个残酷的现实是,很多时候对方其实是早就已经看到了这条消息,但出出种种原 ...

  9. IM消息重试机制Java实现_IM群聊消息的已读回执功能该怎么实现?

    本文引用了架构师之路公众号作者沈剑的文章,内容有改动,感谢原作者. 1.前言我们平时在使用即时通讯应用时候,每当发出一条聊天消息,都希望对方尽快看到,并尽快回复,但对方到底有没有真的看到?我却并不知道 ...

最新文章

  1. 向流程组的所有成员发送信号的最佳方法是什么?
  2. 逆误差函数:torch.erfinv
  3. FZU 2214 Knapsack problem(背包问题)
  4. VTK:IO之GLTFExporter
  5. 京东php asp,jd jd demo 关于php的代码里面是 一些京东的接口 包含了 重要 WEB(ASP,PHP,...) 238万源代码下载- www.pudn.com...
  6. c++ qt qlistwidget清空_Qt编写控件属性设计器12-用户属性
  7. apache虚拟主机名不区分大小写的解决办法
  8. 我的第一个C++程序,还像个C++c程序的样子吧
  9. 计算机课的实训小结,课程实训心得体会与小结三篇
  10. fopen()函数的文件模式
  11. 输入设备名输入输出设备的重定向
  12. 苹果计算机键盘usb,没有USB3.0驱动的苹果电脑与键盘鼠标失灵的关系
  13. 思科CDP/LLDP协议
  14. roseha 11 用VM虚拟机创建集群测试
  15. 红包裂变被动吸粉引流方法,如何通过红包裂变的方式吸粉
  16. 用VBA破解Excel密码
  17. 【C系列】How to printf “unsigned long” in C?
  18. flink的基础介绍
  19. pagehelper circular references
  20. 图像特征提取算法:加速鲁棒特征SURF

热门文章

  1. Java数据结构——二叉树的遍历
  2. STM32HAL库使用详解
  3. 匿名类(Anonymous Classes)
  4. 亚马逊云成为瑞士电信首选公有云提供商;思科完成收购网络漏洞管理公司Kenna Security | 全球TMT...
  5. SOLIDWORKS Manage多种自定义类型的BOM管理
  6. python写公式函数_python的数学算法函数及公式用法
  7. Java从键盘输入数据
  8. navicat 怎么调试存储过程_mysql如何调试存储过程
  9. 安装Qt4.5.3平民攻略
  10. 有线网已连接,但不能上网,qq等软件不能打开,提示cannot initialize等的解决办法