diff --git a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java index e1b8647..78cfe2e 100644 --- a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java +++ b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java @@ -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"; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java index fc1e476..1a88d58 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java @@ -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 aps = Map.of( - "alert", Map.of("title", title, "body", body), - "sound", "default" - ); + Map 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 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(); diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java index 6d10158..78efeea 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java @@ -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 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 message = Map.of( "message", Map.of( "token", new String[]{token}, - "notification", Map.of("title", title, "body", body), + "notification", notification, "data", payload != null ? payload : "{}" ) ); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java index b243dc7..2d8033d 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java @@ -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()); } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java index 41d2e22..2bfa672 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java @@ -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);