diff --git a/src-plugin/com/wentch/redkale/service/apns/ApnsMessage.java b/src-plugin/com/wentch/redkale/service/apns/ApnsMessage.java new file mode 100644 index 000000000..808860576 --- /dev/null +++ b/src-plugin/com/wentch/redkale/service/apns/ApnsMessage.java @@ -0,0 +1,93 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.wentch.redkale.service.apns; + +import com.wentch.redkale.convert.json.*; + +/** + * + * @author zhangjx + */ +public class ApnsMessage { + + public static final int PRIORITY_IMMEDIATELY = 10; + + public static final int PRIORITY_A_TIME = 5; + + private ApnsPayload payload; + + private int expiredate; + + private int priority = PRIORITY_IMMEDIATELY; + + private int identifier; + + private String token; + + public ApnsMessage() { + } + + public ApnsMessage(String token, ApnsPayload payload) { + this(token, payload, 0); + } + + public ApnsMessage(String token, ApnsPayload payload, int expiredate) { + this(token, payload, expiredate, PRIORITY_IMMEDIATELY); + } + + public ApnsMessage(String token, ApnsPayload payload, int expiredate, int priority) { + this.token = token; + this.payload = payload; + this.expiredate = expiredate; + this.priority = priority; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public int getExpiredate() { + return expiredate; + } + + public void setExpiredate(int expiredate) { + this.expiredate = expiredate; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public ApnsPayload getPayload() { + return payload; + } + + public void setPayload(ApnsPayload payload) { + this.payload = payload; + } + + public int getIdentifier() { + return identifier; + } + + public void setIdentifier(int identifier) { + this.identifier = identifier; + } + + @Override + public String toString() { + return JsonFactory.root().getConvert().convertTo(this); + } + +} diff --git a/src-plugin/com/wentch/redkale/service/apns/ApnsPayload.java b/src-plugin/com/wentch/redkale/service/apns/ApnsPayload.java new file mode 100644 index 000000000..3d294568d --- /dev/null +++ b/src-plugin/com/wentch/redkale/service/apns/ApnsPayload.java @@ -0,0 +1,247 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.wentch.redkale.service.apns; + +import com.wentch.redkale.convert.json.*; +import java.util.*; +import java.util.regex.*; + +/** + * + * @author zhangjx + */ +public class ApnsPayload { + + private static final Pattern regex = Pattern.compile("\""); + + //----------------------- alert --------------------------------- + private String alertTitle; + + private String alertBody; + + private String alertTitleLocKey; + + private String[] alertTitleLocArgs; + + private String alertActionLocKey; + + private String alertLocKey; + + private String[] alertLocArgs; + + private String alertLaunchImage; + + //-------------------------------------------------------- + private int contentAvailable; + + private String alert; + + private int badge; + + private String sound; + + private final Map attributes = new HashMap<>(); + + public ApnsPayload() { + } + + public ApnsPayload(String alert, int badge) { + this.alert = alert; + this.badge = badge; + } + + public ApnsPayload(String title, String body, int badge) { + this.alertTitle = title; + this.alertBody = body; + this.badge = badge; + } + + public void putAttribute(String name, Object value) { + attributes.put(name, value); + } + + public void removeAttribute(String name) { + attributes.remove(name); + } + + public T getAttribute(String name) { + return (T) attributes.get(name); + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map map) { + if (map != null) attributes.putAll(map); + } + + @Override + public String toString() { + StringBuilder alertsb = new StringBuilder(); + if (alert != null) { + alertsb.append('"').append(regex.matcher(alert).replaceAll("\\\"")).append('"'); + } else { + alertsb.append('{'); + if (alertTitle != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"title\":\"").append(regex.matcher(alertTitle).replaceAll("\\\"")).append('"'); + } + if (alertBody != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"body\":\"").append(regex.matcher(alertBody).replaceAll("\\\"")).append('"'); + } + if (alertTitleLocKey != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"title-loc-key\":\"").append(regex.matcher(alertTitleLocKey).replaceAll("\\\"")).append('"'); + } + if (alertTitleLocArgs != null && alertTitleLocArgs.length > 0) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"title-loc-args\":["); + boolean first = true; + for (String str : alertTitleLocArgs) { + if (!first) alertsb.append(','); + alertsb.append('"').append(regex.matcher(str).replaceAll("\\\"")).append('"'); + first = false; + } + alertsb.append(']'); + } + if (alertActionLocKey != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"action-loc-key\":\"").append(regex.matcher(alertActionLocKey).replaceAll("\\\"")).append('"'); + } + if (alertLocKey != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"loc-key\":\"").append(regex.matcher(alertLocKey).replaceAll("\\\"")).append('"'); + } + if (alertLocArgs != null && alertLocArgs.length > 0) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"loc-args\":["); + boolean first = true; + for (String str : alertLocArgs) { + if (!first) alertsb.append(','); + alertsb.append('"').append(regex.matcher(str).replaceAll("\\\"")).append('"'); + first = false; + } + alertsb.append(']'); + } + if (alertLaunchImage != null) { + if (alertsb.length() > 1) alertsb.append(','); + alertsb.append("\"launch-image\":\"").append(regex.matcher(alertLaunchImage).replaceAll("\\\"")).append('"'); + } + alertsb.append('}'); + } + final StringBuilder sb = new StringBuilder(); + sb.append("{\"aps\":{\"alert\":").append(alertsb); + if (badge > 0) sb.append(",\"badge\":").append(badge); + if (contentAvailable > 0) sb.append(",\"content-available\":").append(contentAvailable); + if (sound != null) sb.append(",\"sound\":\"").append(sound).append('"'); + sb.append("}"); + if (attributes.isEmpty()) { + sb.append('}'); + } else { + sb.append(',').append(JsonFactory.root().getConvert().convertTo(attributes).substring(1)); + } + return sb.toString(); + } + + public String getAlertTitle() { + return alertTitle; + } + + public void setAlertTitle(String alertTitle) { + this.alertTitle = alertTitle; + } + + public String getAlertBody() { + return alertBody; + } + + public void setAlertBody(String alertBody) { + this.alertBody = alertBody; + } + + public String getAlertTitleLocKey() { + return alertTitleLocKey; + } + + public void setAlertTitleLocKey(String alertTitleLocKey) { + this.alertTitleLocKey = alertTitleLocKey; + } + + public String[] getAlertTitleLocArgs() { + return alertTitleLocArgs; + } + + public void setAlertTitleLocArgs(String[] alertTitleLocArgs) { + this.alertTitleLocArgs = alertTitleLocArgs; + } + + public String getAlertActionLocKey() { + return alertActionLocKey; + } + + public void setAlertActionLocKey(String alertActionLocKey) { + this.alertActionLocKey = alertActionLocKey; + } + + public String getAlertLocKey() { + return alertLocKey; + } + + public void setAlertLocKey(String alertLocKey) { + this.alertLocKey = alertLocKey; + } + + public String[] getAlertLocArgs() { + return alertLocArgs; + } + + public void setAlertLocArgs(String[] alertLocArgs) { + this.alertLocArgs = alertLocArgs; + } + + public String getAlertLaunchImage() { + return alertLaunchImage; + } + + public void setAlertLaunchImage(String alertLaunchImage) { + this.alertLaunchImage = alertLaunchImage; + } + + public int getContentAvailable() { + return contentAvailable; + } + + public void setContentAvailable(int contentAvailable) { + this.contentAvailable = contentAvailable; + } + + public String getAlert() { + return alert; + } + + public void setAlert(String alert) { + this.alert = alert; + } + + public int getBadge() { + return badge; + } + + public void setBadge(int badge) { + this.badge = badge; + } + + public String getSound() { + return sound; + } + + public void setSound(String sound) { + this.sound = sound; + } + +} diff --git a/src-plugin/com/wentch/redkale/service/apns/ApnsService.java b/src-plugin/com/wentch/redkale/service/apns/ApnsService.java new file mode 100644 index 000000000..c693ece04 --- /dev/null +++ b/src-plugin/com/wentch/redkale/service/apns/ApnsService.java @@ -0,0 +1,147 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.wentch.redkale.service.apns; + +import com.wentch.redkale.convert.json.*; +import com.wentch.redkale.service.*; +import com.wentch.redkale.util.*; +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.charset.*; +import java.security.*; +import java.util.logging.*; +import javax.annotation.*; +import javax.net.ssl.*; + +/** + * + * @author zhangjx + */ +public class ApnsService implements Service { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + @Resource + protected JsonConvert convert; + + @Resource(name = "property.apns.certpwd") + protected String apnscertpwd = "1"; //证书的密码 + + @Resource(name = "property.apns.certpath") //用来加载证书用 + protected String apnscertpath = "apnspushdev_cert.p12"; + + @Resource(name = "property.apns.pushaddr") // + protected String apnspushaddr = "gateway.sandbox.push.apple.com"; + + @Resource(name = "property.apns.pushport") // + protected int apnspushport = 2195; + + @Resource(name = "property.apns.buffersize") // + protected int apnsbuffersize = 4096; + + private final Object socketlock = new Object(); + + private SSLSocketFactory sslFactory; + + private Socket pushSocket; + + @Override + public void init(AnyValue conf) { + try { + final String path = "/" + this.getClass().getPackage().getName().replace('.', '/') + "/" + apnscertpath; + KeyStore ks = KeyStore.getInstance("PKCS12"); + InputStream in = ApnsService.class.getResourceAsStream(path); + ks.load(in, apnscertpwd.toCharArray()); + in.close(); + KeyManagerFactory kf = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + kf.init(ks, apnscertpwd.toCharArray()); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(kf.getKeyManagers(), tmf.getTrustManagers(), null); + this.sslFactory = context.getSocketFactory(); + } catch (Exception e) { + logger.log(Level.SEVERE, this.getClass().getSimpleName() + " init SSLContext error", e); + } + } + + @Override + public void destroy(AnyValue conf) { + try { + if (pushSocket != null) pushSocket.close(); + } catch (Exception e) { + } + } + + private Socket getPushSocket() throws IOException { + if (this.sslFactory == null) return null; + if (pushSocket == null || pushSocket.isClosed()) { + synchronized (socketlock) { + if (pushSocket == null || pushSocket.isClosed()) { + pushSocket = sslFactory.createSocket(apnspushaddr, apnspushport); + pushSocket.setTcpNoDelay(true); + } + } + } + return pushSocket; + } + + public void pushApnsMessage(ApnsMessage message) throws IOException { + final byte[] tokens = Utility.hexToBin(message.getToken().replaceAll("\\s+", "")); + ByteBuffer buffer = ByteBuffer.allocate(apnsbuffersize); + buffer.put((byte) 2); //固定命令号 + buffer.putInt(0); //下面数据的长度 + + buffer.put((byte) 1); //token + buffer.putShort((short) tokens.length); + buffer.put(tokens); + + buffer.put((byte) 2); //payload + final byte[] payload = message.getPayload().toString().getBytes(UTF8); + buffer.putShort((short) payload.length); + buffer.put(payload); + + if (message.getIdentifier() > 0) { + buffer.put((byte) 3); //Notification identifier + buffer.putShort((short) 4); + buffer.putInt(message.getIdentifier()); + } + if (message.getExpiredate() > 0) { + buffer.put((byte) 4); //Expiration date + buffer.putShort((short) 4); + buffer.putInt(message.getExpiredate()); + } + buffer.put((byte) 5); //Priority + buffer.putShort((short) 1); + buffer.put((byte) message.getPriority()); + + final int pos = buffer.position(); + buffer.position(1); + buffer.putInt(pos - 5); + buffer.position(pos); + buffer.flip(); + + Socket socket = getPushSocket(); + Channels.newChannel(socket.getOutputStream()).write(buffer); + } + + public static void main(String[] args) throws Exception { + ApnsService service = new ApnsService(); + service.convert = JsonFactory.root().getConvert(); + service.init(null); + + final String token = "01727b19 b9f8abf4 0891e31d 3446479d a43902e1 819edc44 a073d951 b8b7db90"; + ApnsPayload payload = new ApnsPayload("您有新的消息", "这是消息内容", 1); + System.out.println(payload); + service.pushApnsMessage(new ApnsMessage(token, payload)); + } + +}