diff --git a/config/hm.properties b/config/hm.properties new file mode 100644 index 0000000..a069a3d --- /dev/null +++ b/config/hm.properties @@ -0,0 +1,9 @@ +# 请参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/push-server-intro-0000001862404301-V5 +#请在代码中加密私钥,仅获取 '-----BEGIN PRIVATE KEY-----\n' 和 '\n-----END PRIVATE KEY-----\n'之间的字符 +hm.privateKey=a4e5e6a*** +# 请使用服务帐号密钥文件中的sub_account替换 +hm.iss=1002*** +# 请使用服务帐号密钥文件中的key_id替换 +hm.kid=184d3688732245d*** +# 项目 id +hm.projectId=38842184*** \ No newline at end of file diff --git a/pom.xml b/pom.xml index 87ecaab..479fe54 100644 --- a/pom.xml +++ b/pom.xml @@ -99,33 +99,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - org.apache.http httpclient @@ -233,12 +206,16 @@ - - - - - - + + com.auth0 + java-jwt + 3.8.1 + + + commons-codec + commons-codec + 1.16.0 + diff --git a/src/main/java/cn/wildfirechat/push/PushController.java b/src/main/java/cn/wildfirechat/push/PushController.java index 392f02d..aa31686 100644 --- a/src/main/java/cn/wildfirechat/push/PushController.java +++ b/src/main/java/cn/wildfirechat/push/PushController.java @@ -1,6 +1,7 @@ package cn.wildfirechat.push; import cn.wildfirechat.push.android.AndroidPushService; +import cn.wildfirechat.push.hm.HMPushService; import cn.wildfirechat.push.ios.IOSPushService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; @@ -16,13 +17,21 @@ public class PushController { @Autowired private IOSPushService mIOSPushService; - @PostMapping(value = "/android/push", produces = "application/json;charset=UTF-8" ) + @Autowired + private HMPushService hmPushService; + + @PostMapping(value = "/android/push", produces = "application/json;charset=UTF-8") public Object androidPush(@RequestBody PushMessage pushMessage) { return mAndroidPushService.push(pushMessage); } - @PostMapping(value = "/ios/push", produces = "application/json;charset=UTF-8" ) + @PostMapping(value = "/ios/push", produces = "application/json;charset=UTF-8") public Object iOSPush(@RequestBody PushMessage pushMessage) { return mIOSPushService.push(pushMessage); } + + @PostMapping(value = "/hm/push", produces = "application/json;charset=UTF-8") + public Object hmPush(@RequestBody PushMessage pushMessage) { + return hmPushService.push(pushMessage); + } } diff --git a/src/main/java/cn/wildfirechat/push/hm/HMConfig.java b/src/main/java/cn/wildfirechat/push/hm/HMConfig.java new file mode 100644 index 0000000..365a32c --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/HMConfig.java @@ -0,0 +1,47 @@ +package cn.wildfirechat.push.hm; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@ConfigurationProperties(prefix = "hm") +@PropertySource(value = "file:config/hm.properties") +public class HMConfig { + private String privateKey; + private String iss; + private String kid; + private String projectId; + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public String getIss() { + return iss; + } + + public void setIss(String iss) { + this.iss = iss; + } + + public String getKid() { + return kid; + } + + public void setKid(String kid) { + this.kid = kid; + } + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } +} diff --git a/src/main/java/cn/wildfirechat/push/hm/HMPushService.java b/src/main/java/cn/wildfirechat/push/hm/HMPushService.java new file mode 100644 index 0000000..22cc39e --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/HMPushService.java @@ -0,0 +1,7 @@ +package cn.wildfirechat.push.hm; + +import cn.wildfirechat.push.PushMessage; + +public interface HMPushService { + Object push(PushMessage pushMessage); +} diff --git a/src/main/java/cn/wildfirechat/push/hm/HMPushServiceImpl.java b/src/main/java/cn/wildfirechat/push/hm/HMPushServiceImpl.java new file mode 100644 index 0000000..146a008 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/HMPushServiceImpl.java @@ -0,0 +1,154 @@ +package cn.wildfirechat.push.hm; + +import cn.wildfirechat.push.PushMessage; +import cn.wildfirechat.push.PushMessageType; +import cn.wildfirechat.push.Utility; +import cn.wildfirechat.push.hm.payload.AlertPayload; +import cn.wildfirechat.push.hm.payload.VoipPayload; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.text.MessageFormat; +import java.util.List; + +@Service +public class HMPushServiceImpl implements HMPushService { + private static final String AUD = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"; + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + private static final Logger LOG = LoggerFactory.getLogger(HMPushServiceImpl.class); + + @Autowired + HMConfig config; + + private String pushUrl; + + @PostConstruct + void setupPushUrl() { + this.pushUrl = String.format("https://push-api.cloud.huawei.com/v3/%s/messages:send", config.getProjectId()); + } + + private String createJwt() throws NoSuchAlgorithmException, InvalidKeySpecException { + RSAPrivateKey prk = (RSAPrivateKey) getPrivateKey(config.getPrivateKey()); + Algorithm algorithm = Algorithm.RSA256(null, prk); + long iat = System.currentTimeMillis() / 1000; + long exp = iat + 3600; + JWTCreator.Builder builder = + JWT.create() + .withIssuer(config.getIss()) + .withKeyId(config.getKid()) + .withAudience(AUD) + .withClaim("iat", iat) + .withClaim("exp", exp); + String jwt = builder.sign(algorithm); + return jwt; + } + + private PrivateKey getPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodeBase64(key)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + + private byte[] decodeBase64(String key) { + return Base64.decodeBase64(key.getBytes(DEFAULT_CHARSET)); + } + + + @Override + public Object push(PushMessage pushMessage) { + try { + String jwt = null; + jwt = createJwt(); + + if (pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_RECALLED || pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_DELETED) { + //Todo not implement + //撤回或者删除消息,需要更新远程通知,暂未实现 + return null; + } + + if (pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_VOIP_INVITE || pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_VOIP_INVITE) { + // TODO + String data = "TODO"; + VoipPayload voipPayload = VoipPayload.buildAlertPayload(pushMessage.getDeviceToken(), data); + httpPost(this.pushUrl, jwt, 10, voipPayload.toString(), 10000, 10000); + } else { + String[] titleAndContent = Utility.getPushTitleAndContent(pushMessage); + AlertPayload alertPayload = AlertPayload.buildAlertPayload(pushMessage.getDeviceToken(), titleAndContent[0], titleAndContent[1]); + String response = httpPost(this.pushUrl, jwt, 0, alertPayload.toString(), 10000, 10000); + LOG.info("Push to {} response {}", pushMessage.getDeviceToken(), response); + } + + } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) { + e.printStackTrace(); + } + + return null; + } + + private String httpPost(String httpUrl, String jwt, int pushType, String data, int connectTimeout, int readTimeout) throws IOException { + OutputStream outPut = null; + HttpURLConnection urlConnection = null; + InputStream in = null; + + try { + URL url = new URL(httpUrl); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setDoOutput(true); + urlConnection.setDoInput(true); + urlConnection.setRequestProperty("Content-Type", "application/json"); + urlConnection.setRequestProperty("Authorization", "Bearer " + jwt); + urlConnection.setRequestProperty("push-type", pushType + ""); + urlConnection.setConnectTimeout(connectTimeout); + urlConnection.setReadTimeout(readTimeout); + urlConnection.connect(); + + // POST data + outPut = urlConnection.getOutputStream(); + outPut.write(data.getBytes("UTF-8")); + outPut.flush(); + + // read response + if (urlConnection.getResponseCode() < 400) { + in = urlConnection.getInputStream(); + } else { + in = urlConnection.getErrorStream(); + } + + List lines = IOUtils.readLines(in, urlConnection.getContentEncoding()); + StringBuffer strBuf = new StringBuffer(); + for (String line : lines) { + strBuf.append(line); + } +// LOG.info(strBuf.toString()); + return strBuf.toString(); + } finally { + IOUtils.closeQuietly(outPut); + IOUtils.closeQuietly(in); + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } +} diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/AlertPayload.java b/src/main/java/cn/wildfirechat/push/hm/payload/AlertPayload.java new file mode 100644 index 0000000..c382648 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/AlertPayload.java @@ -0,0 +1,41 @@ +package cn.wildfirechat.push.hm.payload; + +import cn.wildfirechat.push.hm.payload.internal.ClickAction; +import cn.wildfirechat.push.hm.payload.internal.Notification; +import cn.wildfirechat.push.hm.payload.internal.Payload; +import cn.wildfirechat.push.hm.payload.internal.Target; +import com.google.gson.Gson; + +import java.util.ArrayList; + + +public class AlertPayload { + Payload payload; + Target target; + + @Override + public String toString() { + return new Gson().toJson(this); + } + + public static AlertPayload buildAlertPayload(String token, String title, String body) { + Notification notification = new Notification(); + notification.title = title; + notification.body = body; + + ClickAction clickAction = new ClickAction(); + notification.clickAction = clickAction; + + Target target = new Target(); + target.token = new ArrayList<>(); + target.token.add(token); + + AlertPayload alertPayload = new AlertPayload(); + alertPayload.payload = new Payload(); + alertPayload.payload.notification = notification; + alertPayload.target = target; + + return alertPayload; + } +} + diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/VoipPayload.java b/src/main/java/cn/wildfirechat/push/hm/payload/VoipPayload.java new file mode 100644 index 0000000..0a894d2 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/VoipPayload.java @@ -0,0 +1,35 @@ +package cn.wildfirechat.push.hm.payload; + +import cn.wildfirechat.push.hm.payload.internal.ClickAction; +import cn.wildfirechat.push.hm.payload.internal.Notification; +import cn.wildfirechat.push.hm.payload.internal.Payload; +import cn.wildfirechat.push.hm.payload.internal.Target; +import com.google.gson.Gson; + +import java.util.ArrayList; + + +public class VoipPayload { + Payload payload; + Target target; + + @Override + public String toString() { + return new Gson().toJson(this); + } + + public static VoipPayload buildAlertPayload(String token, String extraData) { + Target target = new Target(); + target.token = new ArrayList<>(); + target.token.add(token); + + VoipPayload voipPayload = new VoipPayload(); + voipPayload.payload = new Payload(); + voipPayload.payload.extraData = extraData; + voipPayload.target = target; + + return voipPayload; + } +} + + diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/internal/ClickAction.java b/src/main/java/cn/wildfirechat/push/hm/payload/internal/ClickAction.java new file mode 100644 index 0000000..87666ed --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/internal/ClickAction.java @@ -0,0 +1,14 @@ +package cn.wildfirechat.push.hm.payload.internal; + +public class ClickAction { + /** + * 0:打开应用首页 + *

+ * 1:打开应用自定义页面 + */ + public int actionType; + + public String action; + public String uri; + public String data; +} diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/internal/Notification.java b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Notification.java new file mode 100644 index 0000000..71237c6 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Notification.java @@ -0,0 +1,11 @@ +package cn.wildfirechat.push.hm.payload.internal; + +public class Notification { + public String category = "IM"; + public String title; + public String body; + public ClickAction clickAction; + public int style; + public String image; + public Integer notifyId; +} diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/internal/Payload.java b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Payload.java new file mode 100644 index 0000000..54fd429 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Payload.java @@ -0,0 +1,6 @@ +package cn.wildfirechat.push.hm.payload.internal; + +public class Payload { + public Notification notification; + public String extraData; +} diff --git a/src/main/java/cn/wildfirechat/push/hm/payload/internal/Target.java b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Target.java new file mode 100644 index 0000000..aef219a --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/hm/payload/internal/Target.java @@ -0,0 +1,7 @@ +package cn.wildfirechat.push.hm.payload.internal; + +import java.util.ArrayList; + +public class Target { + public ArrayList token; +}