Bladeren bron

feat:苹果apikey格式修改

刘云鑫 8 uur geleden
bovenliggende
commit
88a05936ca

+ 78 - 8
alien-store/src/main/java/shop/alien/store/service/channel/ApnsPushClient.java

@@ -143,14 +143,25 @@ public class ApnsPushClient {
             }
         }
         if (hasTokenCredential(credential)) {
-            ApnsSigningKey signingKey = ApnsSigningKey.loadFromInputStream(
-                    new ByteArrayInputStream(credential.getString("privateKey").getBytes(StandardCharsets.UTF_8)),
-                    credential.getString("teamId"),
-                    credential.getString("keyId"));
-            return new ApnsClientBuilder()
-                    .setApnsServer(apnsHost)
-                    .setSigningKey(signingKey)
-                    .build();
+            String pem = normalizePrivateKeyPem(credential.getString("privateKey"));
+            if (StringUtils.isBlank(pem)) {
+                log.error("APNs privateKey 为空或格式无法识别");
+                return null;
+            }
+            try {
+                ApnsSigningKey signingKey = ApnsSigningKey.loadFromInputStream(
+                        new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8)),
+                        credential.getString("teamId"),
+                        credential.getString("keyId"));
+                return new ApnsClientBuilder()
+                        .setApnsServer(apnsHost)
+                        .setSigningKey(signingKey)
+                        .build();
+            } catch (Exception e) {
+                log.error("APNs privateKey 加载失败, teamId={}, keyId={}, err={}",
+                        credential.getString("teamId"), credential.getString("keyId"), e.getMessage(), e);
+                return null;
+            }
         }
         log.error("APNs 凭证不完整,需配置 p12Base64+p12Password(证书方式)或 teamId+keyId+privateKey(Token 方式)");
         return null;
@@ -223,6 +234,65 @@ public class ApnsPushClient {
         }
     }
 
+    /**
+     * 将 credential_json 中的 privateKey 规范为 PEM 多行格式,供 {@link ApnsSigningKey#loadFromInputStream} 使用。
+     * <p>支持:JSON 中 {@code \\n} 转义、整行无换行、标准多行 PEM。</p>
+     * <p>格式转换示例(单行 → 多行):</p>
+     * <pre>
+     * 转换前: -----BEGIN PRIVATE KEY-----MIGTAgEA...PCZ6m-----END PRIVATE KEY-----
+     * 转换后:
+     * -----BEGIN PRIVATE KEY-----
+     * MIGTAgEA...(每 64 字符换行)
+     * ...PCZ6m
+     * -----END PRIVATE KEY-----
+     * </pre>
+     */
+    private String normalizePrivateKeyPem(String rawKey) {
+        if (StringUtils.isBlank(rawKey)) {
+            return null;
+        }
+        String key = rawKey.trim()
+                .replace("\\r", "")
+                .replace("\\n", "\n");
+        if (key.contains("\n")) {
+            return key.endsWith("\n") ? key : key + "\n";
+        }
+        if (key.contains(PKCS8_BEGIN)) {
+            return reformatSingleLinePem(key, PKCS8_BEGIN, PKCS8_END);
+        }
+        if (key.contains(EC_PKCS8_BEGIN)) {
+            return reformatSingleLinePem(key, EC_PKCS8_BEGIN, EC_PKCS8_END);
+        }
+        log.warn("APNs privateKey 缺少 PEM 头尾标记 BEGIN/END PRIVATE KEY");
+        return key;
+    }
+
+    private static final String PKCS8_BEGIN = "-----BEGIN PRIVATE KEY-----";
+    private static final String PKCS8_END = "-----END PRIVATE KEY-----";
+    private static final String EC_PKCS8_BEGIN = "-----BEGIN EC PRIVATE KEY-----";
+    private static final String EC_PKCS8_END = "-----END EC PRIVATE KEY-----";
+
+    private String reformatSingleLinePem(String singleLine, String beginMarker, String endMarker) {
+        int beginIdx = singleLine.indexOf(beginMarker);
+        int endIdx = singleLine.indexOf(endMarker);
+        if (beginIdx < 0 || endIdx <= beginIdx) {
+            return singleLine;
+        }
+        String base64Body = singleLine.substring(beginIdx + beginMarker.length(), endIdx)
+                .replaceAll("\\s", "");
+        if (StringUtils.isBlank(base64Body)) {
+            log.warn("APNs privateKey PEM 主体为空");
+            return singleLine;
+        }
+        StringBuilder pem = new StringBuilder();
+        pem.append(beginMarker).append('\n');
+        for (int offset = 0; offset < base64Body.length(); offset += 64) {
+            pem.append(base64Body, offset, Math.min(offset + 64, base64Body.length())).append('\n');
+        }
+        pem.append(endMarker).append('\n');
+        return pem.toString();
+    }
+
     private void closeClient(ApnsClient client) {
         if (client == null) {
             return;

+ 13 - 1
alien-store/src/main/java/shop/alien/store/service/impl/CommonPushSendServiceImpl.java

@@ -461,12 +461,24 @@ public class CommonPushSendServiceImpl implements CommonPushSendService {
             target.setUserId(user.getId() != null ? user.getId().longValue() : null);
             target.setDeviceId(user.getDeviceId());
             // TODO 这个 platform 是根据 deviceId 自动判断的,这里先默认 android 了?怎么判断是 android 还是 ios?
-            target.setPlatform("android");
+            if (isIOSDeviceToken(user.getDeviceId())) {
+                target.setPlatform("ios");
+            } else {
+                target.setPlatform("android");
+            }
             targets.add(target);
         }
         return targets;
     }
 
+    private boolean isIOSDeviceToken(String deviceId) {
+        if (deviceId == null || deviceId.length() != 64) {
+            return false;
+        }
+        // 正则:仅0-9、a-f小写
+        return deviceId.matches("[0-9a-f]{64}");
+    }
+
     /**
      * 按厂商渠道对推送目标分组,供后续逐渠道调用 API。
      * <p>格式转换 targetConfig → preferredChannel:</p>