插件的内部实现_Openfire插件化开发-指导手册

bbf1e59957d1729e22d85bca4deec3d6.png
所有的计算都表明它不工作,唯一的做法是:使其工作。
--Pierre-Georges Latécoère 早期法国航空企业家

1综述

1.1 前言

Openfire是免费的、开源的、基于可拓展通讯和表示协议(XMPP)、采用Java编程语言开发的IM服务器,Openfire的组件分为内部基础组件和外部扩展组件两种,我们在实际的业务应用时,为了满足不断变化的复杂业务,Openfire本身的基础功能已经很难满足我们实际的业务需求,这就需要我们使用其外部扩展功能(插件),来快速实现各种复杂业务,实现插件化方式功能扩展。

1.2 编写目的

本文档详细描述了Openfire插件化开发所涉及到的主要相关内容,能够很好的指导开发者对Openfire进行插件化的开发。

如下图为预期目标,将相互独立的业务模块作为一个单独的插件,采用Maven进行开发,可实现不同业务插件,进行独立的上线下线等操作,而不会造成相互影响。

92c86b20278928f69f3ec4937b81c765.png

1.3 参考资料

该文档的整理主要参考以下资料,大家在需要的时候可以进行翻阅,增加对相关内容和知识的理解。

Openfire官网(英文)https://www.igniterealtime.org/projects/openfire/index.jsp

Openfire文档(英文)http://download.igniterealtime.org/openfire/docs/latest/

Openfire源码 https://github.com/igniterealtime

以及一些其他的开源博客,实际工作中,可以看博客文章入门,但是需要阅读官方文档和源码进行深入的理解和应用。

2 插件化开发

2.1 Openfire插件分类

在我们实际应用中,openfire的插件化功能通常分为如下三大类:

1、消息等内部插件

这类插件主要用于对 openfire 内处理的XMPP消息(Iq、Prence、Message),进行一定功能的扩展。例如:客服消息、离线消息推送处理、多端同步等等。

2、WebUI 插件

我们知道Openfire提供了控制台(通过admin插件实现),方便我们通过控制台来进行一些功能的查看和操作,同时通过插件化也可以扩展自己所需要的web功能页面(需要通过Jsp进行实现)。

3、Http接口插件

这类插件主要用于对 openfire 后台接口扩展,例如我们对外暴露关于群的操作以及用户相关接口。

以上插件的是从功能上来划分的,在我们实际的开发中,在一个业务插件中可能同时涵盖这三种类型的插件,这个需要根据我们实际的业务进行选择实现,一个openfire插件的实现通常分为如下几步,1)、实现 Plugin 类。2)、添加 plugin.xml 配置插件启动类。3)、实现对应业务功能(以上三个类别)。4)、添加 changelog.html, logo_*.png/gif, readme.html 等说明文件及logo,然后发布上线。

2.2 Openfire插件工程结构

基本的目录结构如下图:

3e3809f45813cd8b890dbc830e89d8e2.png

1、其中 changelog.html, logo_*.png/gif, readme.html 说明文件,对应在插件安装后对应的信息会展示在插件列表中,对应如下:

da62ee0f52b7dbeba9cc6baa3339e57e.png

其中的logo大小中,small为16X16,large为32X32的大小

2、pom.xml maven进行包管理的文件。

3、lib 存放该插件开发时依赖的一些特殊外部jar包,也可以通过pom.xml配置管理。

4、src 为核心代码文件夹,其中 i18n 为国际化是需要配置的内容-其命名一定是规范化的[插件名称]_i18n[.国际化].properties,database 为该插件所需要的脚本,后面会专门描述。Java目录下为对应的源代码逻辑。

5、web 目录为对应的openfire管理后台添加的jsp页面。

2.3 核心配置文件说明

plugin.xml文件说明

通常 plugin.xml 是导入 PluginManager 中的默认配置文件,其核心配置如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <!-- 插件的入口HelloPlugin需要实现Plugin接口 -->
<class>com.demo.hello.HelloPlugin</class>
<!-- 插件的名称,在安装插件后会展示在插件列表中 -->
<name>hello</name>
	<!-- 插件的描述,展示在插件列表中 -->
    <description>First Openfire Custom Plugin.</description>
    <!-- 插件开发者 -->
	<author>ladd</author>
	<!-- 插件的版本 -->
<version>1.0.0</version>
    <!-- 插件发布时间The date must be in the form MM/dd/yyyy, such as 07/01/2006 -->
<date>11/07/2018</date>
    <!-- 最小支持的openfire 版本 -->
    <minServerVersion>4.2.0</minServerVersion>
    <!-- 控制台上追加界面 -->
    <adminconsole>
        <!-- tab位置 -->
        <tab id="tab-server">
            <!-- sidebar位置 -->
            <sidebar id="sidebar-server-manager">
                <!-- item位置,使用 name 进行 i18n 语言配置,url连接地址配置 -->
             	<item id="hello-setting" name="${hello.title}" url="hello.jsp"
                    description="Quickly change the HELLO configurations." />
            </sidebar>
         </tab>
    </adminconsole>
</plugin>

上面是一些常见的配置,下面介绍2个其他的关键配置项

  1. parentPlugin-父插件

我们在插件开发时,也会遇到一些插件存在一定的关联关系,例如我们开发的插件exampleplugin需要在search插件加载后,才进行加载,则需要在插件exampleplugin的plugin.xml中添加如下配置:

<parentPlugin>search</parentPlugin>

其中search为插件解压后的文件名称。

dde557a8ae49ffb55e82061115504560.png

66f6b2c20c69bcb1fd7db89ad217f3cb.png

2、databaseKey 和 databaseVersion 的配置

如果插件拥有它自己的数据库表,就需要设置databaseKey它的名称(格式:<databaseKey>pushnotification</databaseKey>)通常和插件的名称一致,对应脚本名称必须${ databaseKey }_mysql.sql 和 ${ databaseKey }_oracle.sql 且需要放到 database 目录结构下,不同的数据库配置不同的脚本。且在初始化时我们需要在库中插入一条数据

INSERT INTO ofVersion (name, version) VALUES ('foo', 0);

后续如果有更新的话通常我们需要设置databaseVersion(格式:<databaseVersion>1</databaseVersion>),且需要创建目录database/uprade/${ databaseVersion } 以及对应版本目录,将脚本${ databaseKey }_mysql.sql放置到该目录下,

例如:对于设置了在plugin.xml中设置了,如下参数的

644953df8f50df6684d72803e7485687.png

则对应的database下的目录应该如下:

f0bf16d10b9104786536d73fc0ac6475.png

且database下的sql 脚本应该是涵盖了所有版本的完整数据,upgrade下的是只含有特定版本的脚本。

web-custom.xml文件说明

[WEB-INF] 目录下 web-custom.xml 则是对 servlet 等动态注册的配置页面,类似 Tomcat 中的 web.xml 配置,详细参见 Web 接口开发,如下为配置的servlet项。

7568266cc9c27d0ff4a3dba4fe379e52.png

2.4 消息等内部插件

主要在插件主入口处拿到 XMPPServer 对象,并插入相关的回调函数,进行相应的处理即可。但要确保自己初始化的内容,能够在插件销毁时进行回收。常见格式如下:

public class HelloPlugin implements Plugin 
{
    private XMPPServer mXMPPServer;
public void initializePlugin(PluginManager manager, File pluginDirectory) 
{
        // 获取 XMPPServer 服务
        mXMPPServer = XMPPServer.getInstance();
        // 添加 IQHandler --> mHandler
        mXMPPServer.getIQRouter().addHandler(mHandler);
	// 添加拦截器
	InterceptorManager.getInstance().addInterceptor(interceptor);
        // 添加离线消息监听
        mXMPPServer.getOfflineMessageStrategy().addListener(this);
        // TODO 更多
    }
public void destroyPlugin() 
{
        // 删除 IQHandler --> mHandler
        mXMPPServer.getIQRouter().removeHandler(mHandler);
        // 删除离线消息监听
        mXMPPServer.getOfflineMessageStrategy().removeListener(this);
}
}

Openfire本身在系统内部提供了很多的回调函数,我们可以根据自己的需要来进行实现,如下为我列举的一些常见处理,

  1. 实现PropertyEventListener接口,可以实现监听动态参数的变更,通过 PropertyEventDispatcher.addListener(propertyEventListener)方法, 添加到openfire的回调函数。
  2. (最常用)实现PacketInterceptor接口,实现消息的拦截,通过InterceptorManager.getInstance().addInterceptor(interceptor),添加到Openfire的回调函数。
  3. 实现IQHandler接口,可针对实际需要定制IQ的消息。通过如下方法XMPPServer.getInstance().getIQRouter().addHandler( push0IQHandler ),添加到Openfire的回调钩子中。
  4. 实现UserEventListener接口,可以监听到用户状态的变更,进行特定的操作。通过UserEventDispatcher.addListener( this )添加到openfire内部。
  5. 实现OfflineMessageListener,可以监听消息离线的丢弃或则存储的逻辑,并根据需要实现自己的业务。通过如下方式注入到openfire内部OfflineMessageStrategy.addListener( listener)

以上列举的仅仅时很小的一部分,Openfire内部有很多的功能回调钩子子,我们根据自己的实际业务场景,多做分析,选择最合适的来进行实现。

2.5 WebUI 开发

如果所有UI都是界面,则可以通过 jsp 进行,具体进行如下几步即可:

  1. 配置入口

在 plugin.xml 中的 <adminconsole> 标签中添加相关的 item 即可:

    <!-- 控制台上追加界面 -->
    <adminconsole>
        <!-- tab位置 -->
        <tab id="tab-server">
            <!-- sidebar位置 -->
            <sidebar id="sidebar-server-manager">
                <!-- item位置,使用 name 进行 i18n 语言配置,url连接地址配置 -->
                <item id="hello-setting" name="${hello.title}"
                    url="hello.jsp"
                    description="Quickly change the HELLO configurations." />
            </sidebar>
        </tab>
    </adminconsole>

1c337d2ff5ea1472036a44bac28ed546.png

c7468d851d2bb37849111d32452c86dc.png

注意:这里 tab 和 sidebar 中设置的 id 代表 显示的位置, 具体可以查询 openfire_src/xmppserver/src/main/resources/admin-sidebar.xml 中的id,从而确认相关 tab 和 sidebar 的 id 名称。

  1. 编辑 jsp 文件

注意这里需要导入如下几个标签,不然 i18n 可能没法使用...

<%@ page import="org.jivesoftware.util.JiveProperties" errorPage="error.jsp"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

具体如下:

<%@ page import="org.jivesoftware.openfire.container.PluginManager, org.jivesoftware.util.JiveProperties"  errorPage="error.jsp"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.HashMap"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<%
    webManager.init(request, response, session, application, out);
%>
<%
    // Get parameters
    boolean update = request.getParameter("update") != null;
    Map<String, String> errors = new HashMap<String, String>();
    final JiveProperties mJiveProperties = JiveProperties.getInstance();
    if (update) {
             // TODO update params
    }
%>
<html>
<head>
<title><fmt:message key="hello.title" /></title>
<meta name="pageID" content="jpush-setting" />
</head>
<body>
    <p>
        <fmt:message key="hello.info" />
    </p>
</body>
</html>

2.6 Http接口插件开发

在 [WEB-INF] 目录下 web-custom.xml 则是对 servlet 等动态注册的配置页面,类似 Tomcat 中的 web.xml 配置, 如下两种方式进行配置:

1.Servlet 注册

比如 Fastpath 插件下的 servlet 配置如下:

<?xml version='1.0' encoding='ISO-8859-1'?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!-- Servlets -->
    <servlet>
        <servlet-name>ImageServlet</servlet-name>
        <servlet-class>org.jivesoftware.openfire.fastpath.ImageServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>SoundServlet</servlet-name>
        <servlet-class>org.jivesoftware.openfire.fastpath.SoundServlet</servlet-class>
    </servlet>
    <!-- Servlet mappings -->
    <servlet-mapping>
        <servlet-name>ImageServlet</servlet-name>
        <url-pattern>/getimage</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>SoundServlet</servlet-name>
        <url-pattern>/getsound</url-pattern>
    </servlet-mapping>
</web-app>

2.继承HttpServlet实现其方法

package org.jivesoftware.openfire.fastpath;

/**
 * A servlet that displays images.
 */
public class ImageServlet extends HttpServlet {
    private static final String CONTENT_TYPE = "image/jpeg";
    private ChatSettingsManager chatSettingsManager;
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        chatSettingsManager = ChatSettingsManager.getInstance();
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        String imageName = request.getParameter("imageName");
        String workgroupName = (String)request.getSession().getAttribute("workgroup");
        if (workgroupName == null) {
            workgroupName = request.getParameter("workgroup");
        }

        byte[] bytes = getImage(imageName, workgroupName);
        if (bytes != null) {
            writeBytesToStream(bytes, response);
        }
    }
}

另外可以采用框架jersey(Jersey是一个是 webservice框架,可以结合servelt实现类似SpringMvc的RestFul编程风格),这里不详细说明,可以自行百度或则参考openfire 开源插件 openfire-restAPI-plugin(https://github.com/igniterealtime/openfire-restAPI-plugin)。

3其他事项

3.1本地开发调试

我们目前是openfire的插件是独立开发的,如果需要在本地调试需要结合源码进行操作,这里以IDEA为例子进行说明:

  1. 本地启动openfire,下载github源码到本地,导入IDEA如下图。并执行mvn packege命令,如果出现如下build success 则表示成功。

8c2b9833d89e6ea8c10445084a1d266d.png
  1. 配置Openfire启动debug参数

0113b81709e79fc09d047db21dea982f.png

a).Main class: org.jivesoftware.openfire.starter.ServerStarter

b) VM options:在这里需要设置如下几个主要参数

-DopenfireHome: 指定编译后Openfire的目录(Openfire编译后在openfire_src/ distributiontargetdistribution-base).

-Dlog4j.configurationFile:主要指定配置的log日志。

-Dopenfire.lib.dir:依赖的所有Jar包

-server:是以服务方式启动

-DopenfireHome="D:projectsOpenfire-4.5.1distributiontargetdistribution-base"

-Xverify:none

-server

-Dlog4j.configurationFile="D:projectsOpenfire-4.5.1distributiontargetdistribution-baseliblog4j2.xml"

-Dopenfire.lib.dir="D:projectsOpenfire-4.5.1distributiontargetdistribution-baselib"

-Dfile.encoding=UTF-8

  1. 启动Openfire

02773901a98b2b0af7e7c5fd98642d89.png

启动Openfire后,根据提供的访问地址,可以访问Openfire的控制台进行配置,这里不在详述。

  1. 调试Openfire插件

将插件和Openfire的源码导入到同一个工作空间,

ef8cb613b23b550b504676a63ba9d94a.png

编译后将编译后的压缩包放入到:

D:projectsOpenfire-4.5.1distributiontargetdistribution-baseplugins目录下。重新已debug模式启动就可以了。


版权声明:本文为weixin_33355665原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。