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

来源:互联网 发布:pitta mask 口罩 知乎 编辑:程序博客网 时间:2024/06/06 20:31

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);    }@Overridepublic void xmlPropertySet(String property, Map<String, Object> params) {}@Overridepublic void xmlPropertyDeleted(String property, Map<String, Object> params) {}@Overridepublic 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);}@Overridepublic 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;}@Overridepublic String getName() {return pluginManager.getName(this);}@Overridepublic String getDescription() {return pluginManager.getDescription(this);}@Overridepublic 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";        }    }    @Overridepublic void initialize(JID jid, ComponentManager componentManager)throws ComponentException {// TODO Auto-generated method stub}@Overridepublic void start() {// TODO Auto-generated method stub}@Overridepublic void shutdown() {// TODO Auto-generated method stub}@Overridepublic 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();                //获取用户的设备标志idString 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() {@Overridepublic 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=\u65B0\u6D88\u606F\uFF1Aofflinepush.10001=\u7528\u6237\u64CD\u4F5Cofflinepush.image=[\u56FE\u7247]offlinepush.audio=[\u8BED\u97F3]offlinepush.file=[\u6587\u4EF6]offlinepush.other=[\u5176\u4ED6]offlinepush.location=[\u4F4D\u7F6E]offlinepush.video=[\u89C6\u9891]...... 

需要的jar包。

OK啦。

 

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

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

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



2 0