This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 1fdfe6cbe fix(aliyun): label a binary message body BASE64 instead of 
TEXT (#4857)
1fdfe6cbe is described below

commit 1fdfe6cbec859b882adc688cbfd0fcdf85644566
Author: Wang1rrr <[email protected]>
AuthorDate: Thu Sep 24 18:18:49 2026 +0800

    fix(aliyun): label a binary message body BASE64 instead of TEXT (#4857)
    
    fix(aliyun): label a binary message body BASE64 instead of TEXT
    
    AliyunConverters.toMessageRecord published bodyEncoding="TEXT", a value
    docs/api-spec.md does not define and no other provider emits, and it put
    the raw Base64 into body while doing so. The cause is tryBase64Decode
    collapsing three outcomes into one null: no body, Base64 of a binary
    payload, and a body that is not Base64 at all.
    
    Replace it with a value/encoding pair that keeps them apart, matching the
    shape RocketMQMessageProvider.displayBody already uses:
    
      no body            -> null / null
      Base64 of binary   -> the Base64 / "BASE64"
      not Base64         -> the raw string / "UTF-8"
      Base64 of text     -> the decoded text / "UTF-8"
    
    MessageItem and MessageQueryOutput forward the field verbatim and the
    tool schema declares it as a bare string, so rmq.message.query* was
    telling the model that a Base64 payload was literal text for every binary
    message on an Aliyun instance.
---
 .../studio/provider/alibaba/AliyunConverters.java  | 31 +++++---
 .../alibaba/AliyunConvertersBodyEncodingTest.java  | 90 ++++++++++++++++++++++
 .../alibaba/AliyunInstanceProviderTest.java        |  4 +-
 3 files changed, 114 insertions(+), 11 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
index e94d0fd7a..8209b18fc 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
@@ -232,8 +232,7 @@ final class AliyunConverters {
     }
 
     static MessageRecordVO toMessageRecord(ListMessagesResponseBody.List data) 
{
-        String rawBody = data.getBody();
-        String decodedBody = tryBase64Decode(rawBody);
+        Body body = decodeBody(data.getBody());
         MessageRecordVO.MessageRecordVOBuilder builder = 
MessageRecordVO.builder()
                 .msgId(data.getMessageId())
                 .topic(data.getTopicName())
@@ -244,10 +243,8 @@ final class AliyunConverters {
                 .storeTime(parseTimeMillis(data.getStoreTime()))
                 .properties(data.getUserProperties())
                 .size(data.getBodySize() == null ? 0 : data.getBodySize());
-        if (decodedBody != null) {
-            builder.body(decodedBody).bodyEncoding("UTF-8");
-        } else {
-            builder.body(rawBody).bodyEncoding("TEXT");
+        if (body != null) {
+            builder.body(body.value()).bodyEncoding(body.encoding());
         }
         return builder.build();
     }
@@ -370,7 +367,17 @@ final class AliyunConverters {
                 LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), 
ALIYUN_TIME_ZONE));
     }
 
-    static String tryBase64Decode(String raw) {
+    /**
+     * ListMessages hands the body over Base64-encoded. Resolve it to the 
value/encoding pair the
+     * shared {@code MessageRecordVO} contract publishes - {@code 
docs/api-spec.md} documents
+     * {@code UTF-8} / {@code BASE64}, and {@code 
RocketMQMessageProvider.displayBody} produces
+     * exactly those two. A payload that decodes to text is returned as text; 
a payload that does
+     * not is binary, so the Base64 the API gave us is its faithful display 
form and is labelled
+     * {@code BASE64}. A body that is not Base64 in the first place is already 
literal text and
+     * keeps its own value. No body yields no pair, leaving both fields null 
the way the Apache and
+     * Tencent providers do.
+     */
+    private static Body decodeBody(String raw) {
         if (raw == null || raw.isBlank()) {
             return null;
         }
@@ -378,19 +385,23 @@ final class AliyunConverters {
         try {
             bytes = Base64.getDecoder().decode(raw);
         } catch (IllegalArgumentException ignored) {
-            return null;
+            return new Body(raw, "UTF-8");
         }
         try {
-            return StandardCharsets.UTF_8.newDecoder()
+            String text = StandardCharsets.UTF_8.newDecoder()
                     .onMalformedInput(CodingErrorAction.REPORT)
                     .onUnmappableCharacter(CodingErrorAction.REPORT)
                     .decode(ByteBuffer.wrap(bytes))
                     .toString();
+            return new Body(text, "UTF-8");
         } catch (CharacterCodingException ignored) {
-            return null;
+            return new Body(raw, "BASE64");
         }
     }
 
+    private record Body(String value, String encoding) {
+    }
+
     private static String joinMessageKeys(List<String> keys) {
         return keys == null ? null : joinParts(" ", 
keys.toArray(String[]::new));
     }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
new file mode 100644
index 000000000..be7145463
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.provider.alibaba;
+
+import com.aliyun.sdk.service.rocketmq20220801.models.ListMessagesResponseBody;
+import org.apache.rocketmq.studio.instance.message.MessageRecordVO;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Pins the {@code bodyEncoding} vocabulary of the Aliyun message converter to 
the one the rest of
+ * Studio publishes: {@code UTF-8} for text, {@code BASE64} for a payload that 
is not text, and
+ * {@code null} when the API returned no body at all.
+ */
+class AliyunConvertersBodyEncodingTest {
+
+    private static MessageRecordVO recordWithBody(String body) {
+        return 
AliyunConverters.toMessageRecord(ListMessagesResponseBody.List.builder()
+                .messageId("msg-1")
+                .body(body)
+                .build());
+    }
+
+    @Test
+    void shouldReportUtf8WhenTheBase64BodyDecodesToText() {
+        String encoded = Base64.getEncoder()
+                
.encodeToString("{\"orderId\":42}".getBytes(StandardCharsets.UTF_8));
+
+        MessageRecordVO record = recordWithBody(encoded);
+
+        assertThat(record.getBody()).isEqualTo("{\"orderId\":42}");
+        assertThat(record.getBodyEncoding()).isEqualTo("UTF-8");
+    }
+
+    @Test
+    void shouldReportBase64WhenThePayloadIsBinary() {
+        // 0xFF 0xFE is not valid UTF-8, so the decoder reports a coding error 
and the Base64
+        // the OpenAPI returned is the only faithful way to show the payload.
+        String encoded = Base64.getEncoder().encodeToString(new byte[] {(byte) 
0xFF, (byte) 0xFE});
+
+        MessageRecordVO record = recordWithBody(encoded);
+
+        assertThat(record.getBody()).isEqualTo(encoded);
+        assertThat(record.getBodyEncoding()).isEqualTo("BASE64");
+    }
+
+    @Test
+    void shouldKeepLiteralTextWhenTheBodyIsNotBase64() {
+        MessageRecordVO record = recordWithBody("{\"orderId\":42}");
+
+        assertThat(record.getBody()).isEqualTo("{\"orderId\":42}");
+        assertThat(record.getBodyEncoding()).isEqualTo("UTF-8");
+    }
+
+    @Test
+    void shouldReportNoEncodingWhenTheApiReturnedNoBody() {
+        assertThat(recordWithBody(null).getBodyEncoding()).isNull();
+        assertThat(recordWithBody(null).getBody()).isNull();
+        assertThat(recordWithBody("  ").getBodyEncoding()).isNull();
+    }
+
+    @Test
+    void shouldNeverEmitAnEncodingOutsideThePublishedVocabulary() {
+        String text = 
Base64.getEncoder().encodeToString("plain".getBytes(StandardCharsets.UTF_8));
+        String binary = Base64.getEncoder().encodeToString(new byte[] {(byte) 
0xFF});
+
+        for (String body : Arrays.asList(text, binary, "not base64 at all!!", 
"", null)) {
+            assertThat(recordWithBody(body).getBodyEncoding()).isIn("UTF-8", 
"BASE64", null);
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
index 2edbd0654..a3a167f92 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
@@ -540,8 +540,10 @@ class AliyunInstanceProviderTest {
         assertThat(first.getBornHost()).isEqualTo("10.0.0.1");
         assertThat(first.getProperties()).containsEntry("a", "b");
         MessageRecordVO second = records.get(1);
+        // "{}" is not Base64, so the API handed back literal text; the shared 
contract has no
+        // "TEXT" label and UTF-8 is the one that describes a plain string 
body.
         assertThat(second.getBody()).isEqualTo("{}");
-        assertThat(second.getBodyEncoding()).isEqualTo("TEXT");
+        assertThat(second.getBodyEncoding()).isEqualTo("UTF-8");
 
         List<MessageRecordVO> filtered = 
provider.queryMessages(STUDIO_INSTANCE_ID, "topic-a", null,
                 "tagB", null, null, null);

Reply via email to