• OpenFire源码学习之十八:IOS离线推送


    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

    public class OfflinePushPlugin implements Component, Plugin, PropertyEventListener, PacketInterceptor{
    
    	
    	private static final Logger Log = LoggerFactory.getLogger(OfflinePushPlugin.class);
    	
    	public static final String NAMESPACE_JABBER_IQ_TOKEN_BIND= "jabber:iq:token:bind";
    	public static final String NAMESPACE_JABBER_IQ_TOKEN_UNBUND= "jabber:iq:token:unbund";
    	
    	public static final String SERVICENAME = "plugin.offlinepush.serviceName";
        public static final String SERVICEENABLED = "plugin.offlinepush.serviceEnabled";
    	
        private ComponentManager componentManager;
        private PluginManager pluginManager;
    
        private String serviceName;
    private boolean serviceEnabled;
    //证书安装的目录
        private static String dcpath = System.getProperty("openfireHome") + "\conf\";
        private String dcName;
        private String dcPassword;
        private boolean enabled;
    	
        private static Map<String, String> map = new ConcurrentHashMap<String, String>(20);
        private static Map<String, Integer> count = new ConcurrentHashMap<String, Integer>(20);
        
        private static AppleNotificationServer appleServer = null;
        private static List<PayloadPerDevice> list ;
        
        public String getDcName() {
    		return dcName;
    	}
    
    	public void setDcName(String dcName) {
    		JiveGlobals.setProperty("plugin.offlinepush.dcName", dcName);
    		this.dcName = dcName;
    	}
    
    	public String getDcPassword() {
    		return dcPassword;
    	}
    
    	public void setDcPassword(String dcPassword) {
    		JiveGlobals.setProperty("plugin.offlinepush.password", dcPassword);
    		this.dcPassword = dcPassword;
    	}
    
    	public boolean getEnabled() {
    		return enabled;
    	}
    
    	public void setEnabled(boolean enabled) {
    		this.enabled = enabled;
    		JiveGlobals.setProperty("plugin.offlinepush.enabled",  enabled ? "true" : "false");
    	}
    
    	public OfflinePushPlugin () {
        	serviceName = JiveGlobals.getProperty(SERVICENAME, "offlinepush");
            serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true);
        }
    
    	@Override
    	public void xmlPropertySet(String property, Map<String, Object> params) {
    		
    	}
    
    	@Override
    	public void xmlPropertyDeleted(String property, Map<String, Object> params) {
    	}
    
    	@Override
    	public void initializePlugin(PluginManager manager, File pluginDirectory) {
    		
    		dcName = JiveGlobals.getProperty("plugin.offlinepush.dcName", "");
            // If no secret key has been assigned to the user service yet, assign a random one.
            if (dcName.equals("")){
            	dcName = "delementtest.p12";
                setDcName(dcName);
            }
            dcpath += dcName;
    		dcPassword = JiveGlobals.getProperty("plugin.offlinepush.password", "");
    		if (dcPassword.equals("")){
    			dcPassword = "123456";
                setDcPassword(dcPassword);
            }
    		
    		enabled = JiveGlobals.getBooleanProperty("plugin.offlinepush.enabled");
            setEnabled(enabled);
    		
            Log.info("dcpath: " + dcpath);
            Log.info("dcPassword: " + dcPassword);
            Log.info("enabled: " + enabled);
            
    		try {
    			appleServer = new AppleNotificationServerBasicImpl(dcpath, dcPassword, enabled );
    			if (list == null ) {
    				list = new ArrayList<PayloadPerDevice>();
    			}
    			
    		} catch (KeystoreException e1) {
    			Log.error("KeystoreException: " + e1.getMessage());
    		} 
    		
    		pluginManager = manager;
            
            componentManager = ComponentManagerFactory.getComponentManager();
            try {
                componentManager.addComponent(serviceName, this);
            }
            catch (ComponentException e) {
                Log.error(e.getMessage(), e);
            }
            
            InterceptorManager.getInstance().addInterceptor(this);
            PropertyEventDispatcher.addListener(this);
    		
    	}
    
    	@Override
    	public void destroyPlugin() {
    		InterceptorManager.getInstance().removeInterceptor(this);
    		PropertyEventDispatcher.removeListener(this);
            pluginManager = null;
            try {
                componentManager.removeComponent(serviceName);
                componentManager = null;
            }
            catch (Exception e) {
                if (componentManager != null) {
                    Log.error(e.getMessage(), e);
                }
            }
            serviceName = null;
    	}
    
    	@Override
    	public String getName() {
    		return pluginManager.getName(this);
    	}
    
    	@Override
    	public String getDescription() {
    		return pluginManager.getDescription(this);
    	}
    
    	@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 = handleIQRequest(packet);
    
            try {
                componentManager.sendPacket(this, replyPacket);
            } catch (ComponentException e) {
                Log.error(e.getMessage(), e);
            }
    	}
    
        private IQ handleIQRequest(IQ iq) {
            final IQ replyPacket; // 'final' to ensure that it is set.
    
            if (iq == null) {
                throw new IllegalArgumentException("Argument 'iq' cannot be null.");
            }
    
            final IQ.Type type = iq.getType();
            if (type != IQ.Type.get && type != IQ.Type.set) {
                throw new IllegalArgumentException(
                        "Argument 'iq' must be of type 'get' or 'set'");
            }
    
            final Element childElement = iq.getChildElement();
            if (childElement == null) {
                replyPacket = IQ.createResultIQ(iq);
                replyPacket
                        .setError(new PacketError(
                                Condition.bad_request,
                                org.xmpp.packet.PacketError.Type.modify,
                                "IQ stanzas of type 'get' and 'set' MUST contain one and only one child element (RFC 3920 section 9.2.3)."));
                return replyPacket;
            }
    
            final String namespace = childElement.getNamespaceURI();
            if (namespace == null) {
                replyPacket = IQ.createResultIQ(iq);
                replyPacket.setError(Condition.feature_not_implemented);
                return replyPacket;
            }
    
            if (namespace.equals(NAMESPACE_JABBER_IQ_TOKEN_BIND)) {
                replyPacket = processSetUUID(iq, true);
            } 
            else if (namespace.equals(NAMESPACE_JABBER_IQ_TOKEN_UNBUND)) {
            	replyPacket = processSetUUID(iq, false);
            } 
            else if (namespace.equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)) {
                replyPacket = handleDiscoInfo(iq);
            } 
            else {
                // don't known what to do with this.
                replyPacket = IQ.createResultIQ(iq);
                replyPacket.setError(Condition.feature_not_implemented);
            }
    
            return replyPacket;
        }
    	
        private static IQ handleDiscoInfo(IQ iq) {
            if (iq == null) {
                throw new IllegalArgumentException("Argument 'iq' cannot be null.");
            }
    
            if (!iq.getChildElement().getNamespaceURI().equals(
                    IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)
                    || iq.getType() != Type.get) {
                throw new IllegalArgumentException(
                        "This is not a valid disco#info request.");
            }
    
            final IQ replyPacket = IQ.createResultIQ(iq);
    
            final Element responseElement = replyPacket.setChildElement("query",
                    IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
            responseElement.addElement("identity").addAttribute("category",
                    "directory").addAttribute("type", "user").addAttribute("name",
                    "Offline Push");
            responseElement.addElement("feature").addAttribute("var",
                    NAMESPACE_JABBER_IQ_TOKEN_BIND);
            responseElement.addElement("feature").addAttribute("var",
                    IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
            responseElement.addElement("feature").addAttribute("var",
                    ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
    
            return replyPacket;
        }
        
        private IQ processSetUUID(IQ packet, boolean isSet) {
        	Element rsmElement = null;
            if (!packet.getType().equals(IQ.Type.set)) {
                throw new IllegalArgumentException(
                        "This method only accepts 'set' typed IQ stanzas as an argument.");
            }
    
            final IQ resultIQ;
    
            final Element incomingForm = packet.getChildElement();
    
            rsmElement = incomingForm.element(QName.get("info",
            		NAMESPACE_JABBER_IQ_TOKEN_UNBUND));
            
            if(rsmElement == null) {
            	rsmElement = incomingForm.element(QName.get("info",
                		NAMESPACE_JABBER_IQ_TOKEN_BIND));
            }
    
            resultIQ = IQ.createResultIQ(packet);
            if (rsmElement != null) {
                String osElement = rsmElement.attributeValue("os");
                String jidElement = rsmElement.attributeValue("jid");
                
                String username = new JID(jidElement).getNode();
             
                if (osElement == null || jidElement == null) {
                    resultIQ.setError(Condition.bad_request);
                    return resultIQ;
                }
                if (isSet) {
                	String tokenElement = rsmElement.attributeValue("token");
                	map.put(username, tokenElement);
                	count.put(username, 0);
                	Log.info("set token,username:" + username + " ,token:" + tokenElement);
                }
                else {
                	map.remove(username);
                	count.remove(username);
                	Log.info("remove token,username:" + username );
                }
            } 
            else{
                resultIQ.setError(Condition.bad_request);
            }
    
            return resultIQ;
        }
        
        public String getServiceName() {
            return serviceName;
        }
        
        public void setServiceName(String name) {
            JiveGlobals.setProperty(SERVICENAME, name);
        }
        
        public boolean getServiceEnabled() {
            return serviceEnabled;
        }
        
        public void setServiceEnabled(boolean enabled) {
            serviceEnabled = enabled;
            JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false");
        }
        
        public void propertySet(String property, Map<String, Object> params) {
            if (property.equals(SERVICEENABLED)) {
                this.serviceEnabled = Boolean.parseBoolean((String)params.get("value"));
            }
            if (property.equals("plugin.offlinepush.dcName")) {
                this.dcName = (String)params.get("value");
            }
            else if (property.equals("plugin.offlinepush.enabled")) {
                this.enabled = Boolean.parseBoolean((String)params.get("value"));
            }
            else if (property.equals("plugin.offlinepush.password")) {
            	this.dcPassword = (String)params.get("value");
            }
        }
    
    	/*
    	 * (non-Javadoc)
    	 * 
    	 * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String,
    	 *      java.util.Map)
    	 */
    	public void propertyDeleted(String property, Map<String, Object> params) {
            if (property.equals(SERVICEENABLED)) {
                this.serviceEnabled = true;
            }
            if (property.equals("plugin.offlinepush.dcName")) {
                this.dcName = "delementtest.p12";
            }
            else if (property.equals("plugin.offlinepush.enabled")) {
                this.enabled = false;
            }
            else if (property.equals("plugin.offlinepush.password")) {
            	this.dcPassword = "123456";
            }
        }
        
    	@Override
    	public void initialize(JID jid, ComponentManager componentManager)
    			throws ComponentException {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void start() {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void shutdown() {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void interceptPacket(Packet packet, Session session,
    			boolean incoming, boolean processed) throws PacketRejectedException {
    		if (processed && incoming) {
    			if (packet instanceof Message) {
    				if (((Message) packet).getBody() == null) {
    					return;
    				}
    				JID jid = packet.getTo();
                    //获取用户的设备标志id
    				String uuid = map.get(jid.getNode());
    				if (uuid != null && !"".equals(uuid)) {
    					User user = null;
    					try {
    						user = XMPPServer.getInstance().getUserManager().getUser(jid.getNode());
    					} catch (UserNotFoundException e2) {
    						e2.printStackTrace();
    					}
    					PresenceManager presenceManager = XMPPServer.getInstance().getPresenceManager();
    					org.xmpp.packet.Presence presence = presenceManager.getPresence(user);
    					if (presence == null) {
    						
    						String body = ((Message) packet).getBody();
    						JSONObject jb = null;
    						String msgType = "10015";
    						try {
     
    							jb = new JSONObject(body);
    							msgType = jb.getString("msgType");
    							if ("10012".equals(msgType) || "10001".equals(msgType) || "10002".equals(msgType)) {
    								return;
    							}
    						} catch (JSONException e) {
    							try {
                                  //根据不同的消息类型,发送不通的提示语
    								msgType = jb.getInt("msgType")+"";
    								if ("10012".equals(msgType) || "10001".equals(msgType) || "10002".equals(msgType)) {
    									return;
    								}
    							} catch (JSONException e1) {
    								msgType = "10015";
    							}
    						}
    						if (msgType != null) {
    							//msgType = "offlinepush." + msgType;
    							String pushCont = LocaleUtils.getLocalizedString("offlinepush.10015", "offlinepush");
    							if (!"10000".equals(msgType)) {
    								msgType = "offlinepush." + msgType;
    								pushCont = LocaleUtils.getLocalizedString(msgType, "offlinepush");
    							}
    							else {
    								pushCont = LocaleUtils.getLocalizedString("offlinepush.10000", "offlinepush");
    								String cont = LocaleUtils.getLocalizedString("offlinepush.other", "offlinepush");;
    								String mtype  = "";
    								try {
    									mtype = jb.getString("mtype");
    								} catch (JSONException e) {
    									try {
    										mtype = jb.getInt("mtype") + "";
    									} catch (JSONException e1) {
    										msgType = "10015";
    									}
    								}
    								if ("0".equals(mtype)) {
    									try {
    										cont = jb.getString("Cnt");
    										if (cont.length() > 20) {
    											cont = cont.substring(0, 20);
    											cont += "...";
    										}
    										
    									} catch (JSONException e) {
    									}
    									
    											
    								}
    								else if ("1".equals(mtype)) {
    									cont = LocaleUtils.getLocalizedString("offlinepush.image", "offlinepush"); 
    								}
    								else if ("2".equals(mtype)) {
    									cont = LocaleUtils.getLocalizedString("offlinepush.audio", "offlinepush"); 
    								}
    								else if ("4".equals(mtype)) {
    									cont = LocaleUtils.getLocalizedString("offlinepush.file", "offlinepush"); 
    								}
    								else if ("3".equals(mtype)) {
    									cont = LocaleUtils.getLocalizedString("offlinepush.location", "offlinepush"); 
    								}
    								else if ("6".equals(mtype)) {
    									cont = LocaleUtils.getLocalizedString("offlinepush.video", "offlinepush");
    								}
    								pushCont += cont;
    							}
    							pushOfflineMsg(uuid, pushCont, jid);
    						}
    					}
    				}
    			}
    		}
    		
    	}
    
    	private void pushOfflineMsg(String token, String pushCont, JID jid) {
    			NotificationThreads work = null;
            try {
            	
            	Integer size = count.get(jid.getNode()) + 1; 
            	if (size <= 1000)
            		count.put(jid.getNode(), size);
            	List<PayloadPerDevice> list = new ArrayList<PayloadPerDevice>();
            	PushNotificationPayload payload = new PushNotificationPayload(); 
    			payload.addAlert(pushCont);
    			payload.addSound("default"); 
    	        payload.addBadge(size);
    	        payload.addCustomDictionary("jid", jid.toString());
    	        PayloadPerDevice pay = new PayloadPerDevice(payload, token);
    	        list.add(pay); 
    	        work = new NotificationThreads(appleServer,list,1);
    	        work.setListener(DEBUGGING_PROGRESS_LISTENER);
    	        work.start(); 
    		} catch (JSONException e) {
    			Log.error("JSONException:" + e.getMessage());
    		} catch (InvalidDeviceTokenFormatException e) {
    			Log.error("InvalidDeviceTokenFormatException:" + e.getMessage());
    		}finally{
    			work.destroy();
    			Log.info("push to apple: username: " + jid.getNode() + " ,context" + pushCont);
    		}
    	}
    	
    	public Runnable createTask(final String token, final String msgType, final JID jid) {
    		return new Runnable() {
    			@Override
    			public void run() {
    				PushNotificationPayload payload = new PushNotificationPayload();  
    		        try {
    		        	String pushCont = LocaleUtils.getLocalizedString(msgType, "offlinepush");
    		        	List<PayloadPerDevice> list = new ArrayList<PayloadPerDevice>();
    					payload.addAlert(pushCont);
    					payload.addSound("default"); 
    			        payload.addBadge(1);
    			        payload.addCustomDictionary("jid", jid.toString());
    			        PayloadPerDevice pay = new PayloadPerDevice(payload, token);
    			        list.add(pay); 
    			        NotificationThreads work = new NotificationThreads(appleServer,list,1);
    			        work.setListener(DEBUGGING_PROGRESS_LISTENER);
    			        work.start(); 
    				} catch (JSONException e) {
    					Log.error("JSONException:" + e.getMessage());
    				} catch (InvalidDeviceTokenFormatException e) {
    					Log.error("InvalidDeviceTokenFormatException:" + e.getMessage());
    				} 
    			}
    		};
    		
    	}
    	
    	public static final NotificationProgressListener DEBUGGING_PROGRESS_LISTENER = new NotificationProgressListener() {  
            public void eventThreadStarted(NotificationThread notificationThread) {  
                System.out.println("   [EVENT]: thread #" + notificationThread.getThreadNumber() + " started with " + " devices beginning at message id #" + notificationThread.getFirstMessageIdentifier());  
            }  
            public void eventThreadFinished(NotificationThread thread) {  
                System.out.println("   [EVENT]: thread #" + thread.getThreadNumber() + " finished: pushed messages #" + thread.getFirstMessageIdentifier() + " to " + thread.getLastMessageIdentifier() + " toward "+ " devices");  
            }  
            public void eventConnectionRestarted(NotificationThread thread) {  
                System.out.println("   [EVENT]: connection restarted in thread #" + thread.getThreadNumber() + " because it reached " + thread.getMaxNotificationsPerConnection() + " notifications per connection");  
            }  
            public void eventAllThreadsStarted(NotificationThreads notificationThreads) {  
                System.out.println("   [EVENT]: all threads started: " + notificationThreads.getThreads().size());  
            }  
            public void eventAllThreadsFinished(NotificationThreads notificationThreads) {  
                System.out.println("   [EVENT]: all threads finished: " + notificationThreads.getThreads().size());  
            }  
            public void eventCriticalException(NotificationThread notificationThread, Exception exception) {  
                System.out.println("   [EVENT]: critical exception occurred: " + exception);  
            }  
         }; 
    }
    

    Plugin.xml

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

    资源文件:offlinepush_i18n_zh_CN.properties

    offlinepush.10000=u65B0u6D88u606FuFF1A
    offlinepush.10001=u7528u6237u64CDu4F5C
    offlinepush.image=[u56FEu7247]
    offlinepush.audio=[u8BEDu97F3]
    offlinepush.file=[u6587u4EF6]
    offlinepush.other=[u5176u4ED6]
    offlinepush.location=[u4F4Du7F6E]
    offlinepush.video=[u89C6u9891]
    ...... 
    

    需要的jar包。

    OK啦。

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

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

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



  • 相关阅读:
    centos 7修改时间为中国时间
    颜色表及html代码
    前端日志监控体系
    测试开发必备技能:安全测试漏洞靶场实战
    Jmeter(三)----win10系统下如何修改Jmeter字体大小?
    Jmeter(二) ---Jmeter英文版本修改为中文
    Jmeter(一) ---Windows环境搭建
    跟着高手学复盘_初步理解
    小测试对 HTTP/2 的了解
    软件测试入坑建议
  • 原文地址:https://www.cnblogs.com/huwf/p/4273350.html
Copyright © 2020-2023  润新知