Bläddra i källkod

fix(push): classify native failures and set rider FCM channel

qmj 2 veckor sedan
förälder
incheckning
6366a8680f

+ 22 - 5
ruoyi-admin/src/main/java/com/ruoyi/app/push/BusinessPushService.java

@@ -30,6 +30,7 @@ public class BusinessPushService {
 
     public void send(RecipientApp app, Long userId, String cid,
                      String title, String content, String payload) {
+        String platform = "unknown";
         try {
             if (properties.getMode() == BusinessPushProperties.Mode.UNI) {
                 if (cid == null || cid.isBlank()) {
@@ -40,9 +41,16 @@ public class BusinessPushService {
                 return;
             }
             InfoUser current = userId == null ? null : users.getById(userId);
-            String platform = platform(current);
-            if (platform == null || current.getDeviceToken() == null || current.getDeviceToken().isBlank()) {
-                log.warn("Native push address missing or platform invalid, app={}, userId={}", app, userId);
+            String resolvedPlatform = platform(current);
+            if (resolvedPlatform == null) {
+                log.warn("Native push skipped, app={}, userId={}, platform={}, category=PLATFORM_INVALID_OR_USER_MISSING",
+                        app, userId, platform);
+                return;
+            }
+            platform = resolvedPlatform;
+            if (current.getDeviceToken() == null || current.getDeviceToken().isBlank()) {
+                log.warn("Native push skipped, app={}, userId={}, platform={}, category=TOKEN_MISSING",
+                        app, userId, platform);
                 return;
             }
             if ("android".equals(platform)) {
@@ -51,9 +59,18 @@ public class BusinessPushService {
                 apns.send(app, current.getDeviceToken(), title, content, payload);
             }
         } catch (RuntimeException exception) {
-            log.warn("Business push failed, app={}, userId={}, mode={}, category={}",
-                    app, userId, properties.getMode(), exception.getClass().getSimpleName());
+            log.warn("Business push failed, app={}, userId={}, mode={}, platform={}, category={}",
+                    app, userId, properties.getMode(), platform, failureCategory(exception));
+        }
+    }
+
+    private static String failureCategory(RuntimeException exception) {
+        String category = exception.getMessage();
+        if (exception instanceof IllegalStateException && category != null
+                && category.matches("(?:FCM|APNS)_[A-Za-z0-9_]{1,64}")) {
+            return category;
         }
+        return exception.getClass().getSimpleName();
     }
 
     public String addressKey(InfoUser recipient) {

+ 9 - 3
ruoyi-admin/src/main/java/com/ruoyi/app/push/FcmPushSender.java

@@ -7,6 +7,7 @@ import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.messaging.AndroidConfig;
+import com.google.firebase.messaging.AndroidNotification;
 import com.google.firebase.messaging.FirebaseMessaging;
 import com.google.firebase.messaging.FirebaseMessagingException;
 import com.google.firebase.messaging.Message;
@@ -93,7 +94,7 @@ public class FcmPushSender {
             throw new IllegalStateException("FCM_TOKEN_MISSING");
         }
         try {
-            client.send(buildMessage(token, title, content, payload));
+            client.send(buildMessage(app, token, title, content, payload));
         } catch (FirebaseMessagingException exception) {
             MessagingErrorCode code = exception.getMessagingErrorCode();
             String category = code == null ? "UNKNOWN" : code.name();
@@ -102,13 +103,18 @@ public class FcmPushSender {
         }
     }
 
-    static Message buildMessage(String token, String title, String content, String payload) {
+    static Message buildMessage(RecipientApp app, String token, String title, String content, String payload) {
         String safeTitle = title == null ? "" : title;
         String safeContent = content == null ? "" : content;
+        AndroidNotification.Builder androidNotification = AndroidNotification.builder().setDefaultSound(true);
+        if (app == RecipientApp.RIDER) {
+            androidNotification.setChannelId("qsRing");
+        }
         Message.Builder message = Message.builder()
                 .setToken(token)
                 .setNotification(Notification.builder().setTitle(safeTitle).setBody(safeContent).build())
-                .setAndroidConfig(AndroidConfig.builder().setPriority(AndroidConfig.Priority.HIGH).build())
+                .setAndroidConfig(AndroidConfig.builder().setPriority(AndroidConfig.Priority.HIGH)
+                        .setNotification(androidNotification.build()).build())
                 .putData("title", safeTitle)
                 .putData("body", safeContent);
         if (payload != null) {

+ 28 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/push/BusinessPushServiceTest.java

@@ -1,5 +1,8 @@
 package com.ruoyi.app.push;
 
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
 import com.ruoyi.app.utils.PayPush;
 import com.ruoyi.app.utils.event.PushEventService;
 import com.ruoyi.common.utils.spring.SpringUtils;
@@ -7,10 +10,12 @@ import com.ruoyi.system.domain.InfoUser;
 import com.ruoyi.system.service.IInfoUserService;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
+import org.slf4j.LoggerFactory;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -102,6 +107,29 @@ class BusinessPushServiceTest {
         verify(uni, never()).apppush(any(), any(), any(), any());
     }
 
+    @Test
+    void nativeLocalFailureLogIdentifiesPlatformAndSafeCategoryWithoutToken() {
+        properties.setMode(BusinessPushProperties.Mode.NATIVE);
+        when(users.getById(9L)).thenReturn(user(9L, "ios", "secret-device-token"));
+        doThrow(new IllegalStateException("APNS_TOKEN_INVALID"))
+                .when(apns).send(eq(RecipientApp.USER), eq("secret-device-token"), any(), any(), any());
+        Logger logger = (Logger) LoggerFactory.getLogger(BusinessPushService.class);
+        ListAppender<ILoggingEvent> captured = new ListAppender<>();
+        captured.start();
+        logger.addAppender(captured);
+        try {
+            service().send(RecipientApp.USER, 9L, null, "title", "body", "{}");
+        } finally {
+            logger.detachAppender(captured);
+            captured.stop();
+        }
+
+        assertTrue(captured.list.stream().map(ILoggingEvent::getFormattedMessage)
+                .anyMatch(message -> message.contains("platform=ios")
+                        && message.contains("category=APNS_TOKEN_INVALID")
+                        && !message.contains("secret-device-token")));
+    }
+
     @Test
     void payPushWrapperPublishesOneMessageBeforeRouting() {
         BusinessPushService router = mock(BusinessPushService.class);

+ 4 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/push/FcmPushSenderTest.java

@@ -51,6 +51,10 @@ class FcmPushSenderTest {
             assertEquals("order", data.get("pushType"));
             assertTrue(data.get("payload").contains("\"ddId\":123"));
             assertFalse(data.values().contains("uni-cid"));
+            Object androidNotification = field(field(message, "androidConfig"), "notification");
+            assertEquals(Boolean.TRUE, field(androidNotification, "defaultSound"));
+            assertEquals(app == RecipientApp.RIDER ? "qsRing" : null,
+                    field(androidNotification, "channelId"));
         }
     }