openfire实现消息回执
消息处理流程参考: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;@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实现消息回执相关推荐
- openfire smack消息回执设置,处理掉包问题
在网络不稳定时,openfire容易出现掉包情况,原因是在客户端掉线时,openfire并不能马上知道客户端已经断线,至于要多久才能发现客户端断线,跟服务器端设置的Idle Connections 时 ...
- openfire消息回执插件开发业务处理流程
消息回执插件 参考: OpenFire源码学习之二十四:消息回执与离线消息(上) - Spark_莫然 - 博客园 OpenFire源码学习之二十五:消息回执与离线消息(下) - Spark_莫然 - ...
- OpenFire源码学习之二十五:消息回执与离线消息(下)
这一篇紧接着上面继续了. 方案二 基于redis的消息回执.主要流程分为下面几个步骤: 1)将消息暂存储与redis中,设置好消息的过期时间 2)客户端回执消息id来消灭暂存的消息 3)开通单独线程论 ...
- JAVA中调用阿里云语音通知Api并接收消息回执
JAVA中调用阿里云语音通知Api并接收消息回执 配置文件 需要的包 <dependency><groupId>com.aliyun</groupId><ar ...
- 即时通信中消息回执及阅后即焚相关专利研究
即时通信中消息回执及阅后即焚相关专利研究 在即时通讯中,消息发送后发送方收到回执的形式可以作为重要消息的方式来进行,还有就是阅后即焚也是即时消息里相关的专利,这些专利在专利系统里相对来说不是很多,以下 ...
- openfire 接受消息流程
博客分类: openfire & xmpp openfire底层采用了MINA框架,它是采用事件监听的方式,其中IoHandler接口定义了不同的事件类型,因此根据不同的事件类型做相关的处理 ...
- java xmpp消息推送_基于XMPP协议(openfire服务器)的消息推送实现
最近好像有不少朋友关注Android客户端消息推送的实现,我在之前的项目中用到过Java PC客户端消息推送,从原理讲上应该是一致的,在这里分享一下个人的心得. 消息推送实现原理 这里的消息推送,通常 ...
- im即时通讯开发:IM群聊消息的已读回执功能
我们平时在使用即时通讯应用时候,每当发出一条聊天消息,都希望对方尽快看到,并尽快回复,但对方到底有没有真的看到?我却并不知道. 一个残酷的现实是,很多时候对方其实是早就已经看到了这条消息,但出出种种原 ...
- IM消息重试机制Java实现_IM群聊消息的已读回执功能该怎么实现?
本文引用了架构师之路公众号作者沈剑的文章,内容有改动,感谢原作者. 1.前言我们平时在使用即时通讯应用时候,每当发出一条聊天消息,都希望对方尽快看到,并尽快回复,但对方到底有没有真的看到?我却并不知道 ...
最新文章
- 向流程组的所有成员发送信号的最佳方法是什么?
- 逆误差函数:torch.erfinv
- FZU 2214 Knapsack problem(背包问题)
- VTK:IO之GLTFExporter
- 京东php asp,jd jd demo 关于php的代码里面是 一些京东的接口 包含了 重要 WEB(ASP,PHP,...) 238万源代码下载- www.pudn.com...
- c++ qt qlistwidget清空_Qt编写控件属性设计器12-用户属性
- apache虚拟主机名不区分大小写的解决办法
- 我的第一个C++程序,还像个C++c程序的样子吧
- 计算机课的实训小结,课程实训心得体会与小结三篇
- fopen()函数的文件模式
- 输入设备名输入输出设备的重定向
- 苹果计算机键盘usb,没有USB3.0驱动的苹果电脑与键盘鼠标失灵的关系
- 思科CDP/LLDP协议
- roseha 11 用VM虚拟机创建集群测试
- 红包裂变被动吸粉引流方法,如何通过红包裂变的方式吸粉
- 用VBA破解Excel密码
- 【C系列】How to printf “unsigned long” in C?
- flink的基础介绍
- pagehelper circular references
- 图像特征提取算法:加速鲁棒特征SURF
热门文章
- Java数据结构——二叉树的遍历
- STM32HAL库使用详解
- 匿名类(Anonymous Classes)
- 亚马逊云成为瑞士电信首选公有云提供商;思科完成收购网络漏洞管理公司Kenna Security | 全球TMT...
- SOLIDWORKS Manage多种自定义类型的BOM管理
- python写公式函数_python的数学算法函数及公式用法
- Java从键盘输入数据
- navicat 怎么调试存储过程_mysql如何调试存储过程
- 安装Qt4.5.3平民攻略
- 有线网已连接,但不能上网,qq等软件不能打开,提示cannot initialize等的解决办法