feat(push): 添加推送服务功能支持
- 新增推送相关的类型定义,包括消息类型、聊天类型、推送配置等接口 - 实现 HarmonyOS 推送 SDK,集成 HarmonyOS NEXT Push Kit 服务 - 实现 iOS 推送 SDK,支持 APNS 推送注册和消息接收 - 添加服务器端 APNS 推送提供商,支持 JWT 认证和推送消息发送 - 添加服务器端 HarmonyOS 推送提供商基础框架 - 集成推送配置加载和路由功能,支持多渠道推送分类管理
这个提交包含在:
父节点
824f11c7ea
当前提交
a408b2b39a
@ -58,15 +58,16 @@ public class PushDispatcher {
|
||||
for (DeviceTokenEntity t : targets) {
|
||||
PushProvider provider = providers.get(t.getVendor().name());
|
||||
if (provider != null) {
|
||||
boolean ok = provider.send(appId, t.getToken(), title, body, payload, resolveOptions(appId, payload));
|
||||
boolean ok = provider.send(appId, t.getToken(), title, body, payload, resolveOptions(appId, payload, t.getVendor()));
|
||||
log.info("Push to {}@{} via {} deviceId={}: {}", userId, appId, t.getVendor(), t.getDeviceId(), ok ? "OK" : "FAIL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PushSendOptions resolveOptions(String appId, String payload) {
|
||||
private PushSendOptions resolveOptions(String appId, String payload, DeviceTokenEntity.Vendor vendor) {
|
||||
String routeType = routeType(payload);
|
||||
return pushConfigClient.loadServiceConfig(appId, "ANDROID", "PUSH")
|
||||
String platform = platformForVendor(vendor, payload);
|
||||
return pushConfigClient.loadServiceConfig(appId, platform, "PUSH")
|
||||
.map(config -> {
|
||||
JsonNode route = config.path("routing").path(routeType);
|
||||
String channelKey = route.path("channel").asText("");
|
||||
@ -80,6 +81,29 @@ public class PushDispatcher {
|
||||
.orElseGet(() -> new PushSendOptions(routeType, "", "", ""));
|
||||
}
|
||||
|
||||
private String platformForVendor(DeviceTokenEntity.Vendor vendor, String payload) {
|
||||
if (payload == null || payload.isBlank()) {
|
||||
return defaultPlatformForVendor(vendor);
|
||||
}
|
||||
try {
|
||||
JsonNode node = objectMapper.readTree(payload);
|
||||
String platform = node.path("platform").asText("");
|
||||
if (!platform.isBlank()) {
|
||||
return platform.toUpperCase();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return defaultPlatformForVendor(vendor);
|
||||
}
|
||||
|
||||
private String defaultPlatformForVendor(DeviceTokenEntity.Vendor vendor) {
|
||||
return switch (vendor) {
|
||||
case APNS -> "IOS";
|
||||
case HARMONY -> "HARMONY";
|
||||
default -> "ANDROID";
|
||||
};
|
||||
}
|
||||
|
||||
private String routeType(String payload) {
|
||||
if (payload == null || payload.isBlank()) {
|
||||
return "IM_MESSAGE";
|
||||
|
||||
@ -64,6 +64,11 @@ public class ApnsPushProvider implements PushProvider {
|
||||
|
||||
@Override
|
||||
public boolean send(String appId, String token, String title, String body, String payload) {
|
||||
return send(appId, token, title, body, payload, PushSendOptions.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(String appId, String token, String title, String body, String payload, PushSendOptions options) {
|
||||
String teamId = resolveConfig(appId, "teamId", envTeamId);
|
||||
String keyId = resolveConfig(appId, "keyId", envKeyId);
|
||||
String bundleId = resolveConfig(appId, "bundleId", envBundleId);
|
||||
@ -75,10 +80,20 @@ public class ApnsPushProvider implements PushProvider {
|
||||
try {
|
||||
String authToken = getAuthToken(teamId, keyId, privateKeyPem);
|
||||
String url = (production ? productionPushUrl : sandboxPushUrl).replace("{token}", token);
|
||||
Map<String, Object> aps = Map.of(
|
||||
"alert", Map.of("title", title, "body", body),
|
||||
"sound", "default"
|
||||
);
|
||||
Map<String, Object> aps = new java.util.LinkedHashMap<>();
|
||||
aps.put("alert", Map.of("title", title, "body", body));
|
||||
aps.put("sound", "default");
|
||||
if (options != null) {
|
||||
if (options.category() != null && !options.category().isBlank()) {
|
||||
aps.put("category", options.category());
|
||||
}
|
||||
if (options.routeType() != null && !options.routeType().isBlank()) {
|
||||
aps.put("thread-id", options.routeType());
|
||||
}
|
||||
if ("HIGH".equalsIgnoreCase(options.priority())) {
|
||||
aps.put("interruption-level", "time-sensitive");
|
||||
}
|
||||
}
|
||||
Map<String, Object> message = new java.util.LinkedHashMap<>();
|
||||
message.put("aps", aps);
|
||||
if (payload != null) {
|
||||
@ -91,6 +106,7 @@ public class ApnsPushProvider implements PushProvider {
|
||||
.header("Authorization", "Bearer " + authToken)
|
||||
.header("apns-topic", bundleId)
|
||||
.header("apns-push-type", "alert")
|
||||
.header("apns-priority", options != null && "LOW".equalsIgnoreCase(options.priority()) ? "5" : "10")
|
||||
.header("apns-id", UUID.randomUUID().toString())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
@ -12,6 +12,7 @@ import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -53,6 +54,11 @@ public class HarmonyPushProvider implements PushProvider {
|
||||
|
||||
@Override
|
||||
public boolean send(String appId, String token, String title, String body, String payload) {
|
||||
return send(appId, token, title, body, payload, PushSendOptions.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(String appId, String token, String title, String body, String payload, PushSendOptions options) {
|
||||
String resolvedAppId = resolveConfig(appId, "appId", envAppId);
|
||||
String resolvedAppSecret = resolveConfig(appId, "appSecret", envAppSecret);
|
||||
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
|
||||
@ -62,10 +68,21 @@ public class HarmonyPushProvider implements PushProvider {
|
||||
try {
|
||||
String accessToken = getAccessToken(resolvedAppId, resolvedAppSecret);
|
||||
String url = pushUrl.replace("{appId}", resolvedAppId);
|
||||
Map<String, Object> notification = new LinkedHashMap<>();
|
||||
notification.put("title", title);
|
||||
notification.put("body", body);
|
||||
if (options != null) {
|
||||
if (options.category() != null && !options.category().isBlank()) {
|
||||
notification.put("category", options.category());
|
||||
}
|
||||
if (options.channelId() != null && !options.channelId().isBlank()) {
|
||||
notification.put("channel_id", options.channelId());
|
||||
}
|
||||
}
|
||||
Map<String, Object> message = Map.of(
|
||||
"message", Map.of(
|
||||
"token", new String[]{token},
|
||||
"notification", Map.of("title", title, "body", body),
|
||||
"notification", notification,
|
||||
"data", payload != null ? payload : "{}"
|
||||
)
|
||||
);
|
||||
|
||||
@ -126,6 +126,8 @@ public class FeatureServiceController {
|
||||
req == null ? null : req.honorAppIdValue(),
|
||||
req == null ? null : req.honorClientIdValue(),
|
||||
req == null ? null : req.honorClientSecretValue(),
|
||||
req == null ? null : req.harmonyAppIdValue(),
|
||||
req == null ? null : req.harmonyAppSecretValue(),
|
||||
req == null ? null : req.apnsTeamIdValue(),
|
||||
req == null ? null : req.apnsKeyIdValue(),
|
||||
req == null ? null : req.apnsBundleIdValue(),
|
||||
@ -222,6 +224,8 @@ public class FeatureServiceController {
|
||||
String honorAppId,
|
||||
String honorClientId,
|
||||
String honorClientSecret,
|
||||
String harmonyAppId,
|
||||
String harmonyAppSecret,
|
||||
String apnsTeamId,
|
||||
String apnsKeyId,
|
||||
String apnsBundleId,
|
||||
@ -233,6 +237,7 @@ public class FeatureServiceController {
|
||||
PushVendorConfig oppo,
|
||||
PushVendorConfig vivo,
|
||||
PushVendorConfig honor,
|
||||
PushVendorConfig harmony,
|
||||
PushVendorConfig apns,
|
||||
PushVendorConfig fcm,
|
||||
JsonNode channels,
|
||||
@ -252,6 +257,8 @@ public class FeatureServiceController {
|
||||
public String honorAppIdValue() { return firstText(honorAppId, honor == null ? null : honor.appId()); }
|
||||
public String honorClientIdValue() { return firstText(honorClientId, honor == null ? null : honor.clientId()); }
|
||||
public String honorClientSecretValue() { return firstText(honorClientSecret, honor == null ? null : honor.clientSecret()); }
|
||||
public String harmonyAppIdValue() { return firstText(harmonyAppId, harmony == null ? null : harmony.appId()); }
|
||||
public String harmonyAppSecretValue() { return firstText(harmonyAppSecret, harmony == null ? null : harmony.appSecret()); }
|
||||
public String apnsTeamIdValue() { return firstText(apnsTeamId, apns == null ? null : apns.teamId()); }
|
||||
public String apnsKeyIdValue() { return firstText(apnsKeyId, apns == null ? null : apns.keyId()); }
|
||||
public String apnsBundleIdValue() { return firstText(apnsBundleId, apns == null ? null : apns.bundleId()); }
|
||||
|
||||
@ -491,6 +491,8 @@ public class FeatureServiceManager {
|
||||
String honorAppId,
|
||||
String honorClientId,
|
||||
String honorClientSecret,
|
||||
String harmonyAppId,
|
||||
String harmonyAppSecret,
|
||||
String apnsTeamId,
|
||||
String apnsKeyId,
|
||||
String apnsBundleId,
|
||||
@ -505,6 +507,7 @@ public class FeatureServiceManager {
|
||||
ensureObjectNode(node, "oppo");
|
||||
ensureObjectNode(node, "vivo");
|
||||
ensureObjectNode(node, "honor");
|
||||
ensureObjectNode(node, "harmony");
|
||||
ensureObjectNode(node, "apns");
|
||||
ensureObjectNode(node, "fcm");
|
||||
|
||||
@ -527,6 +530,9 @@ public class FeatureServiceManager {
|
||||
putText(node.with("honor"), "clientId", honorClientId);
|
||||
putText(node.with("honor"), "clientSecret", honorClientSecret);
|
||||
|
||||
putText(node.with("harmony"), "appId", harmonyAppId);
|
||||
putText(node.with("harmony"), "appSecret", harmonyAppSecret);
|
||||
|
||||
putText(node.with("apns"), "teamId", apnsTeamId);
|
||||
putText(node.with("apns"), "keyId", apnsKeyId);
|
||||
putText(node.with("apns"), "bundleId", apnsBundleId);
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户