Merge pull request #50 from captain-miao/master

接入华为 PUSH API 新版本
This commit is contained in:
imndx
2025-09-27 22:37:46 +08:00
committed by GitHub
7 changed files with 235 additions and 40 deletions

View File

@@ -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();

View File

@@ -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<String> 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;
}
}

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -0,0 +1,17 @@
package cn.wildfirechat.push.android.hms.internal;
public class HMSPushClickAction {
/**
* 消息点击行为类型,取值如下:
* <p>
* 1打开应用自定义页面
* 2点击后打开特定URL
* 3点击后打开应用
*/
public int type = 3;
public String intent;
public String url;
public String action;
}

View File

@@ -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<String> token;
}

View File

@@ -0,0 +1,6 @@
package cn.wildfirechat.push.android.hms.internal;
public class HMSPushNotification {
public String title;
public String body;
}