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=\u65B0\u6D88\u606F\uFF1A
offlinepush.10001=\u7528\u6237\u64CD\u4F5C
offlinepush.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的管理臺做配置。
本人在控制檯配置了三個屬性值:
相關文章
- jQuery原始碼學習之$()jQuery原始碼
- 原始碼學習之EllipsizingTextView原始碼TextView
- Android原始碼學習之handlerAndroid原始碼
- jQuery原始碼學習之eventjQuery原始碼
- 原始碼學習VUE之Observe原始碼Vue
- jQuery原始碼學習之extendjQuery原始碼
- redis原始碼學習之slowlogRedis原始碼
- goFrame 原始碼學習之 ServerGoFrame原始碼Server
- 集合框架原始碼學習之LinkedList框架原始碼
- 集合框架原始碼學習之ArrayList框架原始碼
- PandasTA 原始碼解析(十八)AST原始碼
- iOS實現XMPP通訊(一)搭建OpenfireiOS
- JDK1.8原始碼分析01之學習建議(可以延伸其他原始碼學習)JDK原始碼
- Dubbo原始碼學習之-SPI介紹原始碼
- opencascade原始碼學習之HLRAlgo包 -HLRAlgo原始碼Go
- 原始碼學習之Spring容器建立原理原始碼Spring
- Alink漫談(十八) :原始碼解析 之 多列字串編碼MultiStringIndexer原始碼字串編碼Index
- spark 原始碼分析之十八 -- Spark儲存體系剖析Spark原始碼
- [原始碼解析] 深度學習流水線並行之PopeDream(1)--- Profile階段原始碼深度學習並行
- Dubbo學習系列之十八(Skywalking服務跟蹤)
- iOS開發學習路線iOS
- Vue原始碼學習之雙向繫結Vue原始碼
- SpringCloud原始碼學習之Hystrix熔斷器SpringGCCloud原始碼
- Spring Cloud 原始碼學習之 Hystrix 入門SpringCloud原始碼
- Dubbo原始碼學習之-服務匯出原始碼
- redis原始碼學習之工作流程初探Redis原始碼
- 學習Spring原始碼篇之環境搭建Spring原始碼
- redis原始碼學習之lua執行原理Redis原始碼
- 原始碼學習原始碼
- HTML5學習之離線儲存基礎知識HTML
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- iOS推送系列之Push的工作原理iOS
- Vue原始碼學習(十八):實現元件註冊(一)Vue.component()和Vue.extend()Vue原始碼元件
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼一原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼二原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼三原始碼框架筆記MySql
- 集合框架原始碼學習之HashMap(JDK1.8)框架原始碼HashMapJDK
- Vue原始碼學習之資料初始化Vue原始碼
- 從原始碼學習設計模式之模板方法原始碼設計模式