From e84d6a6d32647ab72435ab5bc493279a23b320a4 Mon Sep 17 00:00:00 2001 From: YanLu Date: Sat, 27 Sep 2025 17:52:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=8D=8E=E4=B8=BA=20PUSH=20A?= =?UTF-8?q?PI=20=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../push/android/hms/HMSPush.java | 143 +++++++++++++----- .../push/android/hms/HMSPushPayload.java | 76 ++++++++++ .../hms/internal/HMSMessageNotification.java | 17 +++ .../hms/internal/HMSPushAndroidInfo.java | 7 + .../hms/internal/HMSPushClickAction.java | 17 +++ .../android/hms/internal/HMSPushMessage.java | 9 ++ .../hms/internal/HMSPushNotification.java | 6 + 7 files changed, 235 insertions(+), 40 deletions(-) create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/HMSPushPayload.java create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/internal/HMSMessageNotification.java create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushAndroidInfo.java create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushClickAction.java create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushMessage.java create mode 100644 src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushNotification.java diff --git a/src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java b/src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java index db9f34c..6302166 100644 --- a/src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java +++ b/src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java @@ -3,10 +3,13 @@ package cn.wildfirechat.push.android.hms; import cn.wildfirechat.push.PushMessage; import cn.wildfirechat.push.PushMessageType; +import cn.wildfirechat.push.hm.payload.AlertPayload; + import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -24,75 +27,128 @@ import java.util.List; @Component public class HMSPush { private static final Logger LOG = LoggerFactory.getLogger(HMSPush.class); - private static final String tokenUrl = "https://login.vmall.com/oauth2/token"; //获取认证Token的URL - private static final String apiUrl = "https://api.push.hicloud.com/pushsend.do"; //应用级消息下发API + private static final String tokenUrl = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"; //获取认证Token的URL + private static final String apiUrl = "https://push-api.cloud.huawei.com/v1/%s/messages:send"; //应用级消息下发API private String accessToken;//下发通知消息的认证Token - private long tokenExpiredTime; //accessToken的过期时间 + private long tokenExpiredTime = 0; //accessToken的过期时间,初始化为0 @Autowired private HMSConfig mConfig; + /** + * 检查token是否有效 + * @return true if token is valid and not expired + */ + private boolean isTokenValid() { + if (StringUtils.isEmpty(accessToken)) { + LOG.debug("HMS token is empty"); + return false; + } + + long currentTime = System.currentTimeMillis(); + if (tokenExpiredTime <= currentTime) { + LOG.debug("HMS token expired. Current: {}, Expired: {}", currentTime, tokenExpiredTime); + return false; + } + + long remainingTime = (tokenExpiredTime - currentTime) / 1000; + LOG.debug("HMS token is valid, remaining {} seconds", remainingTime); + return true; + } + //获取下发通知消息的认证Token private void refreshToken() throws IOException { LOG.info("hms refresh token"); String msgBody = MessageFormat.format( "grant_type=client_credentials&client_secret={0}&client_id={1}", URLEncoder.encode(mConfig.getAppSecret(), "UTF-8"), mConfig.getAppId()); - String response = httpPost(tokenUrl, msgBody, 5000, 5000); + String response = httpPost(tokenUrl, "", msgBody, 5000, 5000); JSONObject obj = JSONObject.parseObject(response); - accessToken = obj.getString("access_token"); - tokenExpiredTime = System.currentTimeMillis() + obj.getLong("expires_in") - 5*60*1000; - LOG.info("hms refresh token with result {}", response); + + if (obj.containsKey("access_token")) { + accessToken = obj.getString("access_token"); + // 设置过期时间,提前5分钟刷新 + long expiresIn = obj.getLong("expires_in") * 1000; // 转换为毫秒 + tokenExpiredTime = System.currentTimeMillis() + expiresIn - 5*60*1000; + LOG.info("hms token refreshed successfully, expires in {} seconds", obj.getLong("expires_in")); + } else { + LOG.error("Failed to get access_token from response: {}", response); + throw new IOException("Failed to get access_token from hms auth response"); + } } //发送Push消息 public void push(PushMessage pushMessage) { - if (tokenExpiredTime <= System.currentTimeMillis()) { + // 检查token是否有效,无效则刷新 + if (!isTokenValid()) { try { refreshToken(); } catch (IOException e) { - e.printStackTrace(); + LOG.error("Failed to refresh hms token", e); + return; // token刷新失败,直接返回 } } /*PushManager.requestToken为客户端申请token的方法,可以调用多次以防止申请token失败*/ /*PushToken不支持手动编写,需使用客户端的onToken方法获取*/ - JSONArray deviceTokens = new JSONArray();//目标设备Token - deviceTokens.add(pushMessage.getDeviceToken()); - - - JSONObject msg = new JSONObject(); - msg.put("type", 1);//3: 通知栏消息,异步透传消息请根据接口文档设置 - String token = pushMessage.getDeviceToken(); - pushMessage.deviceToken = null; - msg.put("body", new Gson().toJson(pushMessage));//通知栏消息body内容 - - JSONObject hps = new JSONObject();//华为PUSH消息总结构体 - hps.put("msg", msg); - - JSONObject payload = new JSONObject(); - payload.put("hps", hps); - - LOG.info("send push to HMS {}", payload); +// JSONArray deviceTokens = new JSONArray();//目标设备Token +// deviceTokens.add(pushMessage.getDeviceToken()); +// +// JSONObject param = new JSONObject(); +// param.put("appPkgName", pushMessage.packageName);//定义需要打开的appPkgName +// JSONObject action = new JSONObject(); +// action.put("type", 3);//类型3为打开APP,其他行为请参考接口文档设置 +// action.put("param", param);//消息点击动作参数 +// +// +// JSONObject msg = new JSONObject(); +// // 透传消息 +// msg.put("type", 3);//3: 通知栏消息,异步透传消息请根据接口文档设置 +// msg.put("action", action);//消息点击动作 add by liguangyu +// +// String token = pushMessage.getDeviceToken(); +// pushMessage.deviceToken = null; +//// msg.put("body", new Gson().toJson(pushMessage));//通知栏消息body内容 +// +// JSONObject body = new JSONObject();//仅通知栏消息需要设置标题和内容,透传消息key和value为用户自定义 +// body.put("title", pushMessage.senderName);//消息标题 +// body.put("content", pushMessage.pushContent);//消息内容体 +// //body.put("info", new Gson().toJson(pushMessage));//消息内容体 +// msg.put("body", body);//通知栏消息body内容示例代码 +// LOG.info("liguangyu test body: {} pushMessage{}",body,new Gson().toJson(pushMessage) ); +// +// // 华为消息分类 +// msg.put("importance", "NORMAL"); +// msg.put("category", "IM"); +// +// JSONObject hps = new JSONObject();//华为PUSH消息总结构体 +// hps.put("msg", msg); +// +// JSONObject payload = new JSONObject(); +// payload.put("hps", hps); +// +// LOG.info("send push to HMS {}", payload); try { - String postBody = MessageFormat.format( - "access_token={0}&nsp_svc={1}&nsp_ts={2}&device_token_list={3}&payload={4}", - URLEncoder.encode(accessToken,"UTF-8"), - URLEncoder.encode("openpush.message.api.send","UTF-8"), - URLEncoder.encode(String.valueOf(System.currentTimeMillis() / 1000),"UTF-8"), - URLEncoder.encode(deviceTokens.toString(),"UTF-8"), - URLEncoder.encode(payload.toString(),"UTF-8")); - - String postUrl = apiUrl + "?nsp_ctx=" + URLEncoder.encode("{\"ver\":\"1\", \"appId\":\"" + mConfig.getAppId() + "\"}", "UTF-8"); - String response = httpPost(postUrl, postBody, 5000, 5000); - LOG.info("Push to {} response {}", token, response); +// String postBody = MessageFormat.format( +// "access_token={0}&nsp_svc={1}&nsp_ts={2}&device_token_list={3}&payload={4}", +// URLEncoder.encode(accessToken,"UTF-8"), +// URLEncoder.encode("openpush.message.api.send","UTF-8"), +// URLEncoder.encode(String.valueOf(System.currentTimeMillis() / 1000),"UTF-8"), +// URLEncoder.encode(deviceTokens.toString(),"UTF-8"), +// URLEncoder.encode(payload.toString(),"UTF-8")); + HMSPushPayload alertPayload = HMSPushPayload.buildAlertPayload(pushMessage, mConfig.getAppId()); + LOG.info("Push message {}", alertPayload); + //String postUrl = apiUrl + "?nsp_ctx=" + URLEncoder.encode("{\"ver\":\"1\", \"appId\":\"" + mConfig.getAppId() + "\"}", "UTF-8"); + String postUrl = String.format(apiUrl, mConfig.getAppId()); + String response = httpPost(postUrl, accessToken, alertPayload.toString(), 8000, 8000); + LOG.info("Push to {} response {}", pushMessage.getDeviceToken(), response); } catch (IOException e) { e.printStackTrace(); - LOG.info("Push to {} with exception", token, e); + LOG.info("Push to {} with exception", pushMessage.getDeviceToken(), e); } } - public String httpPost(String httpUrl, String data, int connectTimeout, int readTimeout) throws IOException { + public String httpPost(String httpUrl, String jwt, String data, int connectTimeout, int readTimeout) throws IOException { OutputStream outPut = null; HttpURLConnection urlConnection = null; InputStream in = null; @@ -103,7 +159,14 @@ public class HMSPush { urlConnection.setRequestMethod("POST"); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); - urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + if (StringUtils.isNotEmpty(jwt)) { + urlConnection.setRequestProperty("Content-Type", "application/json"); + urlConnection.setRequestProperty("Authorization", "Bearer " + jwt); + //urlConnection.setRequestProperty("push-type", pushType + ""); + } else { + urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + } + urlConnection.setConnectTimeout(connectTimeout); urlConnection.setReadTimeout(readTimeout); urlConnection.connect(); diff --git a/src/main/java/cn/wildfirechat/push/android/hms/HMSPushPayload.java b/src/main/java/cn/wildfirechat/push/android/hms/HMSPushPayload.java new file mode 100644 index 0000000..07fac08 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/HMSPushPayload.java @@ -0,0 +1,76 @@ +package cn.wildfirechat.push.android.hms; + +import com.google.gson.Gson; + +import org.json.simple.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import cn.wildfirechat.push.PushMessage; +import cn.wildfirechat.push.PushMessageType; +import cn.wildfirechat.push.Utility; +import cn.wildfirechat.push.android.hms.internal.HMSMessageNotification; +import cn.wildfirechat.push.android.hms.internal.HMSPushAndroidInfo; +import cn.wildfirechat.push.android.hms.internal.HMSPushClickAction; +import cn.wildfirechat.push.android.hms.internal.HMSPushMessage; +import cn.wildfirechat.push.android.hms.internal.HMSPushNotification; +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; + +// https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/rest-sample-code-0000001050040242 +public class HMSPushPayload { + // 华为推送长度限制:title最大40字符,body最大256字符 + private static final int HMS_PUSH_MAX_TITLE = 40; + private static final int HMS_PUSH_MAX_BODY = 256; + + public boolean validate_only = false; + public HMSPushMessage message; + + + @Override + public String toString() { + return new Gson().toJson(this); + } + + public static HMSPushPayload buildAlertPayload(PushMessage pushMessage, String appId) { + HMSPushMessage hmsMessage = new HMSPushMessage(); + + hmsMessage.notification = new HMSPushNotification(); + String[] titleAndBody = Utility.getPushTitleAndContent(pushMessage); + String title = titleAndBody[0]; + String body = titleAndBody[1]; + + // 处理华为推送的长度限制 + if (title != null && title.length() > HMS_PUSH_MAX_TITLE) { + title = title.substring(0, HMS_PUSH_MAX_TITLE - 3) + "..."; + } + + if (body != null && body.length() > HMS_PUSH_MAX_BODY) { + body = body.substring(0, HMS_PUSH_MAX_BODY - 3) + "..."; + } + + hmsMessage.notification.title = title; + hmsMessage.notification.body = body; + + List tokens = new ArrayList<>(); + tokens.add(pushMessage.deviceToken); + hmsMessage.token = tokens; + + hmsMessage.android = new HMSPushAndroidInfo(); + hmsMessage.android.notification = new HMSMessageNotification(); + hmsMessage.android.notification.channel_id = "channel_offline_" + appId; + + hmsMessage.android.notification.clickAction = new HMSPushClickAction(); + hmsMessage.android.notification.clickAction.type = 3; + + HMSPushPayload alertPayload = new HMSPushPayload(); + alertPayload.validate_only = false; + alertPayload.message = hmsMessage; + + return alertPayload; + } +} + diff --git a/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSMessageNotification.java b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSMessageNotification.java new file mode 100644 index 0000000..c6e7fb5 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSMessageNotification.java @@ -0,0 +1,17 @@ +package cn.wildfirechat.push.android.hms.internal; + +import com.google.gson.annotations.SerializedName; + +public class HMSMessageNotification { + public String importance = "NORMAL"; + @SerializedName("click_action") + public HMSPushClickAction clickAction; +// public String ticker; +// public String notify_summary; +// + public String channel_id; +// public int style = 0; +// public int notify_id; +// public String visibility; +// public String when; +} diff --git a/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushAndroidInfo.java b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushAndroidInfo.java new file mode 100644 index 0000000..64f7426 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushAndroidInfo.java @@ -0,0 +1,7 @@ +package cn.wildfirechat.push.android.hms.internal; + +public class HMSPushAndroidInfo { + public String category = "IM"; + public HMSMessageNotification notification; + public String ttl = "86400s"; +} diff --git a/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushClickAction.java b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushClickAction.java new file mode 100644 index 0000000..4be3483 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushClickAction.java @@ -0,0 +1,17 @@ +package cn.wildfirechat.push.android.hms.internal; + +public class HMSPushClickAction { + /** + * 消息点击行为类型,取值如下: + *

+ * 1:打开应用自定义页面 + * 2:点击后打开特定URL + * 3:点击后打开应用 + */ + + public int type = 3; + + public String intent; + public String url; + public String action; +} diff --git a/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushMessage.java b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushMessage.java new file mode 100644 index 0000000..d0509db --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushMessage.java @@ -0,0 +1,9 @@ +package cn.wildfirechat.push.android.hms.internal; + +import java.util.List; + +public class HMSPushMessage { + public HMSPushNotification notification; + public HMSPushAndroidInfo android; + public List token; +} diff --git a/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushNotification.java b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushNotification.java new file mode 100644 index 0000000..3df6f43 --- /dev/null +++ b/src/main/java/cn/wildfirechat/push/android/hms/internal/HMSPushNotification.java @@ -0,0 +1,6 @@ +package cn.wildfirechat.push.android.hms.internal; + +public class HMSPushNotification { + public String title; + public String body; +}