转:http://blog.csdn.net/huwenfeng_2011/article/details/43458213

IOS离线推送

场景:

如果您有iOS端的APP,在会话聊天的时候,用户登陆了但可能会退出了界面。这时候其他终端给目标端发送消息时候,消息可以发送到ios的推送服务器。用过QQ的都知道,你会有哦一条消息在您的主屏上展示。这个就是利用了IOS的推送服务器呢。那么openfire只需要判断用户不在线的时候将消息推送给IOS端。

苹果服务器的消息推送都需要手机的唯一标志,也就是唯一的终端设备号。那么IOS端在登陆的时候需要将该手机的设备号传递给OF服务器。这个传递很简单,您可以自定义发送IQ消息。也可以在登陆后绑定资料的时候添加JID属性来绑定设备。(建议用绑定资源的形式,这样在服务端判断的时候可以很方便的根据JID的属性值来决定是都推送)

服务端实现ios消息推送所需2个证书(附件):测试推送证书.p12、正式推送正式.p12,密码都为123456.

2个证书的区别在于一个是用于开发测试的推送证书,一个是用于产品正式上线的推送证书。2个证书获取到的终端token是不一样的。这2个证书用于Java后台连接APNS的服务器地址也是不同的,测试推送证书对应服务器地址是:gateway.sandbox.push.apple.com , 正式推送证书对应的服务器地址是:gateway.push.apple.com .

具体怎么做呢:

1、安装IOS推送服务需要的证书到本地,这个在网上有很多中方法

2、IOS终端登陆发送设备消息给服务器,或者以绑定资源的形式。

3、OF服务端接收该设备ID,并保存起来。

4、当有消息推送时候,根据JID属性push消息。

接下来具体看看源码了。

源码

OfflinePushPlugin

  1. public class OfflinePushPlugin implements Component, Plugin, PropertyEventListener, PacketInterceptor{
  2. private static final Logger Log = LoggerFactory.getLogger(OfflinePushPlugin.class);
  3. public static final String NAMESPACE_JABBER_IQ_TOKEN_BIND= "jabber:iq:token:bind";
  4. public static final String NAMESPACE_JABBER_IQ_TOKEN_UNBUND= "jabber:iq:token:unbund";
  5. public static final String SERVICENAME = "plugin.offlinepush.serviceName";
  6. public static final String SERVICEENABLED = "plugin.offlinepush.serviceEnabled";
  7. private ComponentManager componentManager;
  8. private PluginManager pluginManager;
  9. private String serviceName;
  10. private boolean serviceEnabled;
  11. //证书安装的目录
  12. private static String dcpath = System.getProperty("openfireHome") + "\\conf\\";
  13. private String dcName;
  14. private String dcPassword;
  15. private boolean enabled;
  16. private static Map<String, String> map = new ConcurrentHashMap<String, String>(20);
  17. private static Map<String, Integer> count = new ConcurrentHashMap<String, Integer>(20);
  18. private static AppleNotificationServer appleServer = null;
  19. private static List<PayloadPerDevice> list ;
  20. public String getDcName() {
  21. return dcName;
  22. }
  23. public void setDcName(String dcName) {
  24. JiveGlobals.setProperty("plugin.offlinepush.dcName", dcName);
  25. this.dcName = dcName;
  26. }
  27. public String getDcPassword() {
  28. return dcPassword;
  29. }
  30. public void setDcPassword(String dcPassword) {
  31. JiveGlobals.setProperty("plugin.offlinepush.password", dcPassword);
  32. this.dcPassword = dcPassword;
  33. }
  34. public boolean getEnabled() {
  35. return enabled;
  36. }
  37. public void setEnabled(boolean enabled) {
  38. this.enabled = enabled;
  39. JiveGlobals.setProperty("plugin.offlinepush.enabled",  enabled ? "true" : "false");
  40. }
  41. public OfflinePushPlugin () {
  42. serviceName = JiveGlobals.getProperty(SERVICENAME, "offlinepush");
  43. serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true);
  44. }
  45. @Override
  46. public void xmlPropertySet(String property, Map<String, Object> params) {
  47. }
  48. @Override
  49. public void xmlPropertyDeleted(String property, Map<String, Object> params) {
  50. }
  51. @Override
  52. public void initializePlugin(PluginManager manager, File pluginDirectory) {
  53. dcName = JiveGlobals.getProperty("plugin.offlinepush.dcName", "");
  54. // If no secret key has been assigned to the user service yet, assign a random one.
  55. if (dcName.equals("")){
  56. dcName = "delementtest.p12";
  57. setDcName(dcName);
  58. }
  59. dcpath += dcName;
  60. dcPassword = JiveGlobals.getProperty("plugin.offlinepush.password", "");
  61. if (dcPassword.equals("")){
  62. dcPassword = "123456";
  63. setDcPassword(dcPassword);
  64. }
  65. enabled = JiveGlobals.getBooleanProperty("plugin.offlinepush.enabled");
  66. setEnabled(enabled);
  67. Log.info("dcpath: " + dcpath);
  68. Log.info("dcPassword: " + dcPassword);
  69. Log.info("enabled: " + enabled);
  70. try {
  71. appleServer = new AppleNotificationServerBasicImpl(dcpath, dcPassword, enabled );
  72. if (list == null ) {
  73. list = new ArrayList<PayloadPerDevice>();
  74. }
  75. } catch (KeystoreException e1) {
  76. Log.error("KeystoreException: " + e1.getMessage());
  77. }
  78. pluginManager = manager;
  79. componentManager = ComponentManagerFactory.getComponentManager();
  80. try {
  81. componentManager.addComponent(serviceName, this);
  82. }
  83. catch (ComponentException e) {
  84. Log.error(e.getMessage(), e);
  85. }
  86. InterceptorManager.getInstance().addInterceptor(this);
  87. PropertyEventDispatcher.addListener(this);
  88. }
  89. @Override
  90. public void destroyPlugin() {
  91. InterceptorManager.getInstance().removeInterceptor(this);
  92. PropertyEventDispatcher.removeListener(this);
  93. pluginManager = null;
  94. try {
  95. componentManager.removeComponent(serviceName);
  96. componentManager = null;
  97. }
  98. catch (Exception e) {
  99. if (componentManager != null) {
  100. Log.error(e.getMessage(), e);
  101. }
  102. }
  103. serviceName = null;
  104. }
  105. @Override
  106. public String getName() {
  107. return pluginManager.getName(this);
  108. }
  109. @Override
  110. public String getDescription() {
  111. return pluginManager.getDescription(this);
  112. }
  113. @Override
  114. public void processPacket(Packet p) {
  115. if (!(p instanceof IQ)) {
  116. return;
  117. }
  118. final IQ packet = (IQ) p;
  119. if (packet.getType().equals(IQ.Type.error)
  120. || packet.getType().equals(IQ.Type.result)) {
  121. return;
  122. }
  123. final IQ replyPacket = handleIQRequest(packet);
  124. try {
  125. componentManager.sendPacket(this, replyPacket);
  126. } catch (ComponentException e) {
  127. Log.error(e.getMessage(), e);
  128. }
  129. }
  130. private IQ handleIQRequest(IQ iq) {
  131. final IQ replyPacket; // 'final' to ensure that it is set.
  132. if (iq == null) {
  133. throw new IllegalArgumentException("Argument 'iq' cannot be null.");
  134. }
  135. final IQ.Type type = iq.getType();
  136. if (type != IQ.Type.get && type != IQ.Type.set) {
  137. throw new IllegalArgumentException(
  138. "Argument 'iq' must be of type 'get' or 'set'");
  139. }
  140. final Element childElement = iq.getChildElement();
  141. if (childElement == null) {
  142. replyPacket = IQ.createResultIQ(iq);
  143. replyPacket
  144. .setError(new PacketError(
  145. Condition.bad_request,
  146. org.xmpp.packet.PacketError.Type.modify,
  147. "IQ stanzas of type 'get' and 'set' MUST contain one and only one child element (RFC 3920 section 9.2.3)."));
  148. return replyPacket;
  149. }
  150. final String namespace = childElement.getNamespaceURI();
  151. if (namespace == null) {
  152. replyPacket = IQ.createResultIQ(iq);
  153. replyPacket.setError(Condition.feature_not_implemented);
  154. return replyPacket;
  155. }
  156. if (namespace.equals(NAMESPACE_JABBER_IQ_TOKEN_BIND)) {
  157. replyPacket = processSetUUID(iq, true);
  158. }
  159. else if (namespace.equals(NAMESPACE_JABBER_IQ_TOKEN_UNBUND)) {
  160. replyPacket = processSetUUID(iq, false);
  161. }
  162. else if (namespace.equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)) {
  163. replyPacket = handleDiscoInfo(iq);
  164. }
  165. else {
  166. // don't known what to do with this.
  167. replyPacket = IQ.createResultIQ(iq);
  168. replyPacket.setError(Condition.feature_not_implemented);
  169. }
  170. return replyPacket;
  171. }
  172. private static IQ handleDiscoInfo(IQ iq) {
  173. if (iq == null) {
  174. throw new IllegalArgumentException("Argument 'iq' cannot be null.");
  175. }
  176. if (!iq.getChildElement().getNamespaceURI().equals(
  177. IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)
  178. || iq.getType() != Type.get) {
  179. throw new IllegalArgumentException(
  180. "This is not a valid disco#info request.");
  181. }
  182. final IQ replyPacket = IQ.createResultIQ(iq);
  183. final Element responseElement = replyPacket.setChildElement("query",
  184. IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
  185. responseElement.addElement("identity").addAttribute("category",
  186. "directory").addAttribute("type", "user").addAttribute("name",
  187. "Offline Push");
  188. responseElement.addElement("feature").addAttribute("var",
  189. NAMESPACE_JABBER_IQ_TOKEN_BIND);
  190. responseElement.addElement("feature").addAttribute("var",
  191. IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
  192. responseElement.addElement("feature").addAttribute("var",
  193. ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
  194. return replyPacket;
  195. }
  196. private IQ processSetUUID(IQ packet, boolean isSet) {
  197. Element rsmElement = null;
  198. if (!packet.getType().equals(IQ.Type.set)) {
  199. throw new IllegalArgumentException(
  200. "This method only accepts 'set' typed IQ stanzas as an argument.");
  201. }
  202. final IQ resultIQ;
  203. final Element incomingForm = packet.getChildElement();
  204. rsmElement = incomingForm.element(QName.get("info",
  205. NAMESPACE_JABBER_IQ_TOKEN_UNBUND));
  206. if(rsmElement == null) {
  207. rsmElement = incomingForm.element(QName.get("info",
  208. NAMESPACE_JABBER_IQ_TOKEN_BIND));
  209. }
  210. resultIQ = IQ.createResultIQ(packet);
  211. if (rsmElement != null) {
  212. String osElement = rsmElement.attributeValue("os");
  213. String jidElement = rsmElement.attributeValue("jid");
  214. String username = new JID(jidElement).getNode();
  215. if (osElement == null || jidElement == null) {
  216. resultIQ.setError(Condition.bad_request);
  217. return resultIQ;
  218. }
  219. if (isSet) {
  220. String tokenElement = rsmElement.attributeValue("token");
  221. map.put(username, tokenElement);
  222. count.put(username, 0);
  223. Log.info("set token,username:" + username + " ,token:" + tokenElement);
  224. }
  225. else {
  226. map.remove(username);
  227. count.remove(username);
  228. Log.info("remove token,username:" + username );
  229. }
  230. }
  231. else{
  232. resultIQ.setError(Condition.bad_request);
  233. }
  234. return resultIQ;
  235. }
  236. public String getServiceName() {
  237. return serviceName;
  238. }
  239. public void setServiceName(String name) {
  240. JiveGlobals.setProperty(SERVICENAME, name);
  241. }
  242. public boolean getServiceEnabled() {
  243. return serviceEnabled;
  244. }
  245. public void setServiceEnabled(boolean enabled) {
  246. serviceEnabled = enabled;
  247. JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false");
  248. }
  249. public void propertySet(String property, Map<String, Object> params) {
  250. if (property.equals(SERVICEENABLED)) {
  251. this.serviceEnabled = Boolean.parseBoolean((String)params.get("value"));
  252. }
  253. if (property.equals("plugin.offlinepush.dcName")) {
  254. this.dcName = (String)params.get("value");
  255. }
  256. else if (property.equals("plugin.offlinepush.enabled")) {
  257. this.enabled = Boolean.parseBoolean((String)params.get("value"));
  258. }
  259. else if (property.equals("plugin.offlinepush.password")) {
  260. this.dcPassword = (String)params.get("value");
  261. }
  262. }
  263. /*
  264. * (non-Javadoc)
  265. *
  266. * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String,
  267. *      java.util.Map)
  268. */
  269. public void propertyDeleted(String property, Map<String, Object> params) {
  270. if (property.equals(SERVICEENABLED)) {
  271. this.serviceEnabled = true;
  272. }
  273. if (property.equals("plugin.offlinepush.dcName")) {
  274. this.dcName = "delementtest.p12";
  275. }
  276. else if (property.equals("plugin.offlinepush.enabled")) {
  277. this.enabled = false;
  278. }
  279. else if (property.equals("plugin.offlinepush.password")) {
  280. this.dcPassword = "123456";
  281. }
  282. }
  283. @Override
  284. public void initialize(JID jid, ComponentManager componentManager)
  285. throws ComponentException {
  286. // TODO Auto-generated method stub
  287. }
  288. @Override
  289. public void start() {
  290. // TODO Auto-generated method stub
  291. }
  292. @Override
  293. public void shutdown() {
  294. // TODO Auto-generated method stub
  295. }
  296. @Override
  297. public void interceptPacket(Packet packet, Session session,
  298. boolean incoming, boolean processed) throws PacketRejectedException {
  299. if (processed && incoming) {
  300. if (packet instanceof Message) {
  301. if (((Message) packet).getBody() == null) {
  302. return;
  303. }
  304. JID jid = packet.getTo();
  305. //获取用户的设备标志id
  306. String uuid = map.get(jid.getNode());
  307. if (uuid != null && !"".equals(uuid)) {
  308. User user = null;
  309. try {
  310. user = XMPPServer.getInstance().getUserManager().getUser(jid.getNode());
  311. } catch (UserNotFoundException e2) {
  312. e2.printStackTrace();
  313. }
  314. PresenceManager presenceManager = XMPPServer.getInstance().getPresenceManager();
  315. org.xmpp.packet.Presence presence = presenceManager.getPresence(user);
  316. if (presence == null) {
  317. String body = ((Message) packet).getBody();
  318. JSONObject jb = null;
  319. String msgType = "10015";
  320. try {
  321. jb = new JSONObject(body);
  322. msgType = jb.getString("msgType");
  323. if ("10012".equals(msgType) || "10001".equals(msgType) || "10002".equals(msgType)) {
  324. return;
  325. }
  326. } catch (JSONException e) {
  327. try {
  328. //根据不同的消息类型,发送不通的提示语
  329. msgType = jb.getInt("msgType")+"";
  330. if ("10012".equals(msgType) || "10001".equals(msgType) || "10002".equals(msgType)) {
  331. return;
  332. }
  333. } catch (JSONException e1) {
  334. msgType = "10015";
  335. }
  336. }
  337. if (msgType != null) {
  338. //msgType = "offlinepush." + msgType;
  339. String pushCont = LocaleUtils.getLocalizedString("offlinepush.10015", "offlinepush");
  340. if (!"10000".equals(msgType)) {
  341. msgType = "offlinepush." + msgType;
  342. pushCont = LocaleUtils.getLocalizedString(msgType, "offlinepush");
  343. }
  344. else {
  345. pushCont = LocaleUtils.getLocalizedString("offlinepush.10000", "offlinepush");
  346. String cont = LocaleUtils.getLocalizedString("offlinepush.other", "offlinepush");;
  347. String mtype  = "";
  348. try {
  349. mtype = jb.getString("mtype");
  350. } catch (JSONException e) {
  351. try {
  352. mtype = jb.getInt("mtype") + "";
  353. } catch (JSONException e1) {
  354. msgType = "10015";
  355. }
  356. }
  357. if ("0".equals(mtype)) {
  358. try {
  359. cont = jb.getString("Cnt");
  360. if (cont.length() > 20) {
  361. cont = cont.substring(0, 20);
  362. cont += "...";
  363. }
  364. } catch (JSONException e) {
  365. }
  366. }
  367. else if ("1".equals(mtype)) {
  368. cont = LocaleUtils.getLocalizedString("offlinepush.image", "offlinepush");
  369. }
  370. else if ("2".equals(mtype)) {
  371. cont = LocaleUtils.getLocalizedString("offlinepush.audio", "offlinepush");
  372. }
  373. else if ("4".equals(mtype)) {
  374. cont = LocaleUtils.getLocalizedString("offlinepush.file", "offlinepush");
  375. }
  376. else if ("3".equals(mtype)) {
  377. cont = LocaleUtils.getLocalizedString("offlinepush.location", "offlinepush");
  378. }
  379. else if ("6".equals(mtype)) {
  380. cont = LocaleUtils.getLocalizedString("offlinepush.video", "offlinepush");
  381. }
  382. pushCont += cont;
  383. }
  384. pushOfflineMsg(uuid, pushCont, jid);
  385. }
  386. }
  387. }
  388. }
  389. }
  390. }
  391. private void pushOfflineMsg(String token, String pushCont, JID jid) {
  392. NotificationThreads work = null;
  393. try {
  394. Integer size = count.get(jid.getNode()) + 1;
  395. if (size <= 1000)
  396. count.put(jid.getNode(), size);
  397. List<PayloadPerDevice> list = new ArrayList<PayloadPerDevice>();
  398. PushNotificationPayload payload = new PushNotificationPayload();
  399. payload.addAlert(pushCont);
  400. payload.addSound("default");
  401. payload.addBadge(size);
  402. payload.addCustomDictionary("jid", jid.toString());
  403. PayloadPerDevice pay = new PayloadPerDevice(payload, token);
  404. list.add(pay);
  405. work = new NotificationThreads(appleServer,list,1);
  406. work.setListener(DEBUGGING_PROGRESS_LISTENER);
  407. work.start();
  408. } catch (JSONException e) {
  409. Log.error("JSONException:" + e.getMessage());
  410. } catch (InvalidDeviceTokenFormatException e) {
  411. Log.error("InvalidDeviceTokenFormatException:" + e.getMessage());
  412. }finally{
  413. work.destroy();
  414. Log.info("push to apple: username: " + jid.getNode() + " ,context" + pushCont);
  415. }
  416. }
  417. public Runnable createTask(final String token, final String msgType, final JID jid) {
  418. return new Runnable() {
  419. @Override
  420. public void run() {
  421. PushNotificationPayload payload = new PushNotificationPayload();
  422. try {
  423. String pushCont = LocaleUtils.getLocalizedString(msgType, "offlinepush");
  424. List<PayloadPerDevice> list = new ArrayList<PayloadPerDevice>();
  425. payload.addAlert(pushCont);
  426. payload.addSound("default");
  427. payload.addBadge(1);
  428. payload.addCustomDictionary("jid", jid.toString());
  429. PayloadPerDevice pay = new PayloadPerDevice(payload, token);
  430. list.add(pay);
  431. NotificationThreads work = new NotificationThreads(appleServer,list,1);
  432. work.setListener(DEBUGGING_PROGRESS_LISTENER);
  433. work.start();
  434. } catch (JSONException e) {
  435. Log.error("JSONException:" + e.getMessage());
  436. } catch (InvalidDeviceTokenFormatException e) {
  437. Log.error("InvalidDeviceTokenFormatException:" + e.getMessage());
  438. }
  439. }
  440. };
  441. }
  442. public static final NotificationProgressListener DEBUGGING_PROGRESS_LISTENER = new NotificationProgressListener() {
  443. public void eventThreadStarted(NotificationThread notificationThread) {
  444. System.out.println("   [EVENT]: thread #" + notificationThread.getThreadNumber() + " started with " + " devices beginning at message id #" + notificationThread.getFirstMessageIdentifier());
  445. }
  446. public void eventThreadFinished(NotificationThread thread) {
  447. System.out.println("   [EVENT]: thread #" + thread.getThreadNumber() + " finished: pushed messages #" + thread.getFirstMessageIdentifier() + " to " + thread.getLastMessageIdentifier() + " toward "+ " devices");
  448. }
  449. public void eventConnectionRestarted(NotificationThread thread) {
  450. System.out.println("   [EVENT]: connection restarted in thread #" + thread.getThreadNumber() + " because it reached " + thread.getMaxNotificationsPerConnection() + " notifications per connection");
  451. }
  452. public void eventAllThreadsStarted(NotificationThreads notificationThreads) {
  453. System.out.println("   [EVENT]: all threads started: " + notificationThreads.getThreads().size());
  454. }
  455. public void eventAllThreadsFinished(NotificationThreads notificationThreads) {
  456. System.out.println("   [EVENT]: all threads finished: " + notificationThreads.getThreads().size());
  457. }
  458. public void eventCriticalException(NotificationThread notificationThread, Exception exception) {
  459. System.out.println("   [EVENT]: critical exception occurred: " + exception);
  460. }
  461. };
  462. }

Plugin.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <plugin>
  3. <class>com.....offlinepush.plugin.OfflinePushPlugin</class>
  4. <name>offlinepush</name>
  5. <description>.......</description>
  6. <author>huwenfeng</author>
  7. <version>1.5.1</version>
  8. <date>1/2/2014</date>
  9. <minServerVersion>3.7.0</minServerVersion>
  10. </plugin>

资源文件:offlinepush_i18n_zh_CN.properties

  1. offlinepush.10000=\u65B0\u6D88\u606F\uFF1A
  2. offlinepush.10001=\u7528\u6237\u64CD\u4F5C
  3. offlinepush.image=[\u56FE\u7247]
  4. offlinepush.audio=[\u8BED\u97F3]
  5. offlinepush.file=[\u6587\u4EF6]
  6. offlinepush.other=[\u5176\u4ED6]
  7. offlinepush.location=[\u4F4D\u7F6E]
  8. offlinepush.video=[\u89C6\u9891]
  9. ......

需要的jar包。

OK啦。

注意:IOS的推送服务器有两种模式都是免费,一种是测试的还一种是正式使用的。

所以这里最好将推送服务的使用模式在OF的管理台做配置。

本人在控制台配置了三个属性值:

最新文章

  1. C#类继承和接口继承时一些模棱两可的问题[转]
  2. EVE ToDo
  3. 如何将word中上下两行文字对齐?
  4. poj 2488 A Knight&#39;s Journey( dfs )
  5. std::numeric_limits&lt;int&gt;::max() error C2589: &#39;(&#39; : illegal token on right side of &#39;::&#39; 解决办法
  6. c#生成动态库并加载
  7. RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe)
  8. MySQL 主从复制那些事(一)
  9. Hadoop记录-queue mysql
  10. Cygwin,一个提供linux命令行体验的Windows命令行工具
  11. vim学习之改头换面(基础配置)
  12. Maven知识总结(转)
  13. [C++ Primer Plus] 第6章、分支语句和逻辑运算符(一)程序清单
  14. ajaxSubmit 提交form 表单
  15. [No0000161]IDEA初步接触
  16. 可执行 jar | 到底如何执行
  17. activity之间如何传递list
  18. @NotNull、@NotEmpty、@NotBlank的区别
  19. 伪随机数生成算法-梅森旋转(Mersenne Twister/MT)
  20. webView的使用以及总结

热门文章

  1. C#排列组合类,写彩票算法的朋友们可以来看一看
  2. Tomcat负载均衡、调优核心应用进阶学习笔记(三):LNMT nginx+tomcat、LAMT apache+tomcat、session会话保持、不错的站点
  3. promise基础用法
  4. 人工智能-动物识别专家系统算法Python + Pyqt 实现
  5. 2019浙江省赛 Strings in the Pocket【manacher】
  6. 爬虫(三)—— BeautifulSoup模块获取元素
  7. 常用numpy和pandas
  8. _cdecl 与 _stdcall 区别
  9. JavaScript中Function函数与Object对象的关系
  10. mysql 密码