==================== 废话 begin ============================
最近老大让我为研发平台增加即时通讯功能。告诉我用comet 在web端实现即时通讯。
最初狂搜集资料。不能让自己方向错了。这是很重要的。
不过还是难免的周折了一番。测试了一个comet4j的聊天小例子。用它前后端开发成本太大、对服务器也太大压力放弃了。
最终决定使用openfire +jsjac.js + JabberHTTPBind 然后实现老大要求的 web 及时通讯功能。
很庆幸找到了 hoojo大哥的demo 很不幸,他为了让大家复制代码,自己练习。不提供jar包js下载。(虽然好心但是足足浪费了我两天时间)一个jsjac.js库版本有问题。很费劲的看源码。哎。
然后、拿这个小demo 先交差。顺便展示了 spark 和 web聊天窗口,交互即时聊天。
顺便构想了一下,修改openfire用户表。让用户来自系统。组织则用自带的。(当然这是预想。其实openfire已经帮我们想过了。)
接着、全局搜索了openfire源码中包含ofuser表sql的类。只有两个类。很庆幸。改了之后,改造用户密码加密认证方式。
当然这样做是错误的。直到我发现类名字似乎有些不对的时候。DefaultUserProvider 哈哈、嘲讽啊。 不出所料有一个实现类JDBCUuserProvider 。直接配置就可以搞定、但是加密sha256加密过程和我平台不一样。改造后就顺利搞定。
接着、到了插件开发过程。这个过程很烦人。网上很多帖子很多人去讲这个开发过程。或许是两三年前的贴了。很多过时了。只能有一点帮助。更多的是走向了错误的道路。磕磕碰碰。最终还是只能从源码中寻求出路。
这个过程整整浪费了我两星期时间。很痛苦。所以。我会针对最新代码聊聊,spark 开发一个组织架构树插件。展示出用户。并可以与之聊天。打包openfire,spark插件过程。打包项目为exe文件。等
======================= 废话 end =========================
openfire10,出来啦。^_^ 支持自定义组,性能优化很多。
本文内容有:
1、openfire自定义用户表 需要注意的地方
2、openfire服务器插件开发 (开发环境搭建不说了。下载项目,目前3.10,3.93都没有任何错)
3、openfire插件打包。
3、spark插件开发
4、openfire/spark 打包exe
userManager 会在初始化的时候从数据库读取UserProvider/AuthProvider的实现类。默认是defaultUserProvider
这些实现类参数被保存在ofproperty 表中。我们直接通过更新表完成对自定义用户表的配置。
insert into ofProperty(name,propValue)values ('jdbcProvider.driver','com.mysql.jdbc.Driver'), ('jdbcProvider.connectionString','jdbc:mysql://主机地址/数据库?user=root&password=root'), --用户表数据库连接信息 ('admin.authorizedJIDs','admin@127.0.0.1'), --管理员账号信息,@amy-tang是安装openfire服务器时填写的IP,也可以写成服务器的域名。
('jdbcAuthProvider.passwordSQL','SELECT password FROM sys_user WHERE account=?'), -- 校验用户名密码sql语句 ('jdbcAuthProvider.passwordType','sha256'),-- 用户表加密方式 ('jdbcUserProvider.loadUserSQL','SELECT fullname, email FROM sys_user WHERE account=?'), -- 查询用户的sql语句 ('jdbcUserProvider.userCountSQL','SELECT COUNT(*) FROM sys_user'), ('jdbcUserProvider.allUsersSQL','SELECT account FROM sys_user'), ('jdbcUserProvider.usernameField','account'), ('jdbcUserProvider.nameField','fullname'), ('jdbcUserProvider.emailField','Email'), --用户表关键字段的字段名称。模糊查询用户拼装sql使用.account是账号唯一标示,fullname是用户名。sys_user是用户表 UPDATE ofProperty SET propValue='org.jivesoftware.openfire.user.JDBCUserProvider' WHERE name='provider.user.className' ; --设置用户数据库持久层实现类 UPDATE ofProperty SET propValue='org.jivesoftware.openfire.auth.JDBCAuthProvider' WHERE name='provider.auth.className' --设置密码校验持久层实现类
注意事项:1、加密key :plain/md5/sha1/sha256/sha512 加密过程详见org.jivesoftware.util.StringUtils.hash(password, "MD5");
2、admin.authorizedJIDs = admin@主机地址 当Openfire修改了主机地址/域名的配置后admin无法登陆服务器。需要修改数据库中配置的主机地址,
二、Openfire服务器插件开发
1、openfire中插件目录介绍,以及开发前准备。
Openfire源码环境搭建很简单。目前3.10版,3.9版都没有任何问题直接能用,不用多余下载任何jar。将源码解压至工作空间,新建相同项目名称即可。如图:
暂时没用的插件可以 build path remove from path先从工作空间移除。剩下需要的开发插件如上图右
10 的source output folder 有点问题,指向了一个插件里面。修改即可。
项目右键—》build path—》configure build path..—》 Source—》output folder: openfire_src/bin
9问题也不多,网上很多文章都很有用可以检索 openfire二次开发去查阅相关搭建开发环境的文章。
开发前,建议先吧xmldebugger 插件打开,或者留在src 中。会把所有交互xml打印输出非常有利于调试。
开发插件前,我们可以先看下,自带的那些插件的目录结构。
可以看出来开发插件都是依赖项目开发的,这样直接可以引用项目中的资源,bean。不建议单独起一个project去开发插件
2、插件开发介绍
例如组织架构插件,我是将它作为一个服务组件进行开发,对外提供组织树的服务。当客户端访问服务器 jabber:iq:loadOrg 的服务的时候,根据提供的参数返回组织架构,与用户信息、
该插件需要实现两个接口 Component, Plugin。 贴下代码
package com.hotent.openfire.plugin; import java.io.File; import java.util.List; import java.util.Map; import net.sf.json.JSONArray; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.auth.DefaultAuthProvider; import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.PropertyEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.component.Component; import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManagerFactory; import org.xmpp.packet.IQ; import org.xmpp.packet.JID; import org.xmpp.packet.Packet; public class OrgTreePlugin implements Component, Plugin { private static final Logger Log = LoggerFactory.getLogger(OrgTreePlugin.class); public static final String NAMESPACE_JABBER_IQ_SEARCH = "jabber:iq:loadOrg"; public static final String SERVICENAME = "plugin.loadOrg.serviceName"; private UserManager userManager; private PluginManager pluginManager; private ComponentManager componentManager; private String serviceName = "loadOrg"; private String serverName ; private OrgTreeProvider orgTreeProvider; // 插件初始化的时候,注入bean。 public OrgTreePlugin(){ userManager = UserManager.getInstance(); serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); orgTreeProvider = DefaultOrgTreeProvider.getInstance(); } public String getName() { return pluginManager.getName(this); } public String getDescription() { return pluginManager.getDescription(this); } // 初始化插件调用的方法。此时注册插件服务。 @Override public void initializePlugin(PluginManager manager, File pluginDirectory) { pluginManager = manager; componentManager = ComponentManagerFactory.getComponentManager(); try { //注册服务。 componentManager.addComponent(serviceName, this); } catch (ComponentException e) { Log.error("===================OrgTree 注册服务失败!!====================================="); throw new RuntimeException("OrgTree 注册服务失败!!",e); } Log.info("===================OrgTree 注册服务成功!!====================================="); } public void initialize(JID jid, ComponentManager componentManager) { } public void start() { } public void destroyPlugin() { pluginManager = null; try { componentManager.removeComponent(serviceName); componentManager = null; } catch (Exception e) { } serviceName = null; userManager = null; } @Override public void shutdown() { } // 当客户端请求该服务时,访问该方法,接下来处理消息包 //第一次访问注册服务时候, 也会测试性质的访问该服务。如果出现异常,将从服务列表移除 // @Override public void processPacket(Packet p) { if (!(p instanceof IQ)) { return; } final IQ packet = (IQ) p; if (packet.getType().equals(IQ.Type.error) || packet.getType().equals(IQ.Type.result)) { return; } //将消息包处理,并将结果数据,返回给请求者。 final IQ replyPacket = reply(packet); try { componentManager.sendPacket(this, replyPacket); } catch (ComponentException e) { } } /*** * 处理消息包,转成回执包 * @param packet * @return */ private IQ reply(IQ packet) { /*if (!packet.getType().equals(IQ.Type.get)) { throw new IllegalArgumentException("This method only accepts 'get' typed IQ stanzas as an argument."); }*/ String orgId =""; final Element element = packet.getChildElement(); if(element != null){ Element e = element.element("orgId"); if(e!= null)orgId = e.attribute("id").getText(); } String authId = packet.getFrom().toString(); if(authId .contains("@")) authId = authId.substring(0, authId.indexOf("@")); IQ replyPacket = IQ.createResultIQ(packet); Element queryResult = DocumentHelper.createElement(QName.get("query", NAMESPACE_JABBER_IQ_SEARCH)); //去数据库获取数据 List<OrgTreeNode> orgTreeList = orgTreeProvider.LoadOrgChildOrgUser(orgId, authId); //orgTreeList.remove(orgTreeList.size()-1); JSONArray json = JSONArray.fromObject(orgTreeList); queryResult.addElement("orgTreeList").addText(json.toString()); replyPacket.setChildElement(queryResult); return replyPacket; } }