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 958a75990 fix(dlq): keep the redelivery count in exports and reject a
zero-length window (#4718)
958a75990 is described below
commit 958a75990337806fc06df86ce753fbadb15f2a14
Author: btlqql <[email protected]>
AuthorDate: Mon Sep 21 20:16:39 2026 +0800
fix(dlq): keep the redelivery count in exports and reject a zero-length
window (#4718)
Two DLQ fixes from the same author, consolidated into one change.
1. `DLQService.validateTimeRange` only rejected a reversed window, so
`start == end` was accepted and each queue ran a 32-message pull that could
return messages outside the intended window. All four DLQ actions now reject a
zero-length window with `BusinessException(400)`.
2. The dead-letter Excel export dropped the redelivery count:
`DLQMessageExcelRow` gains a `Reconsume Times` column, filled from
`DLQMessageVO` in `from()`.
Consolidates #4702 and #4718 (same author, same domain).
---
.../studio/instance/dlq/DLQMessageExcelRow.java | 3 +
.../rocketmq/studio/instance/dlq/DLQService.java | 8 +++
.../instance/dlq/DLQMessageExcelRowTest.java | 71 ++++++++++++++++++++++
.../studio/instance/dlq/DLQServiceTest.java | 31 ++++++++++
4 files changed, 113 insertions(+)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRow.java
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRow.java
index 0b8e7de26..6d06c2542 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRow.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRow.java
@@ -43,6 +43,8 @@ public class DLQMessageExcelRow {
private long offset;
@ExcelProperty("Store Time")
private String storeTime;
+ @ExcelProperty("Reconsume Times")
+ private int reconsumeTimes;
@ExcelProperty("Keys")
private String keys;
@ExcelProperty("Body")
@@ -56,6 +58,7 @@ public class DLQMessageExcelRow {
row.setOffset(vo.getOffset());
row.setStoreTime(LocalDateTime.ofInstant(
Instant.ofEpochMilli(vo.getStoreTime()),
ZoneId.systemDefault()).format(STORE_TIME_FORMAT));
+ row.setReconsumeTimes(vo.getReconsumeTimes());
row.setKeys(vo.getKeys());
row.setBody(vo.getBody());
return row;
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
index 8217622aa..11babd853 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
@@ -160,5 +160,13 @@ public class DLQService {
if (endTime < startTime) {
throw new BusinessException(400, "endTime must not be earlier than
startTime");
}
+ // A zero-length window cannot describe a range. The DLQ message list
and resend paths
+ // reject it in the provider ("... start time must be before end
time"), but the two
+ // export paths do not check it at all: the scan they run returns at
most the single
+ // message sitting on that instant, so the caller received a 200 with
a meaningless
+ // export instead of the 400 the other two actions return.
+ if (endTime.equals(startTime)) {
+ throw new BusinessException(400, "startTime must be before
endTime");
+ }
}
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRowTest.java
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRowTest.java
new file mode 100644
index 000000000..cbb5d866f
--- /dev/null
+++
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQMessageExcelRowTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.instance.dlq;
+
+import com.alibaba.excel.EasyExcel;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class DLQMessageExcelRowTest {
+
+ @Test
+ void shouldExportTheMessageRedeliveryCountTest() {
+ DLQMessageVO message = DLQMessageVO.builder()
+ .msgId("msg-1")
+ .topic("%DLQ%group-1")
+ .queueId(7)
+ .offset(17L)
+ .storeTime(1_700_000_000_000L)
+ .reconsumeTimes(3)
+ .keys("order-1")
+ .body("payload")
+ .build();
+
+ Map<String, String> cells = firstDataRowByColumnName(List.of(message));
+
+ assertThat(cells)
+ .containsEntry("Message ID", "msg-1")
+ .containsEntry("Reconsume Times", "3");
+ }
+
+ private static Map<String, String>
firstDataRowByColumnName(List<DLQMessageVO> messages) {
+ List<Map<Integer, String>> rows = readBack(messages);
+ Map<Integer, String> header = rows.get(0);
+ Map<Integer, String> values = rows.get(1);
+ Map<String, String> cells = new HashMap<>();
+ header.forEach((column, name) -> cells.put(name, values.get(column)));
+ return cells;
+ }
+
+ private static List<Map<Integer, String>> readBack(List<DLQMessageVO>
messages) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ EasyExcel.write(output, DLQMessageExcelRow.class)
+ .sheet("dlq")
+
.doWrite(messages.stream().map(DLQMessageExcelRow::from).toList());
+ return EasyExcel.read(new ByteArrayInputStream(output.toByteArray()))
+ .sheet()
+ .headRowNumber(0)
+ .doReadSync();
+ }
+}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
index cf3b16917..29964cfab 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
@@ -228,4 +228,35 @@ class DLQServiceTest {
verifyNoInteractions(dlqProvider);
}
+
+ @Test
+ void everyActionShouldRejectAnEmptyTimeWindowTest() {
+ assertThatThrownBy(() -> dlqService.resendMessages("instance-1",
"group-1", 5000L, 5000L, null))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("startTime must be before endTime");
+ assertThatThrownBy(() -> dlqService.listMessages("instance-1",
"group-1", 5000L, 5000L, 1, 20))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("startTime must be before endTime");
+ assertThatThrownBy(() -> dlqService.exportMessages("instance-1",
"group-1", 5000L, 5000L, 100))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("startTime must be before endTime");
+ assertThatThrownBy(() -> dlqService.exportExcel("instance-1",
"group-1", 5000L, 5000L, null))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("startTime must be before endTime");
+
+ verifyNoInteractions(dlqProvider);
+ }
+
+ @Test
+ void everyActionShouldStillAcceptAOneMillisecondTimeWindowTest() {
+ dlqService.resendMessages("instance-1", "group-1", 5000L, 5001L, null);
+ dlqService.listMessages("instance-1", "group-1", 5000L, 5001L, 1, 20);
+ dlqService.exportMessages("instance-1", "group-1", 5000L, 5001L, 100);
+ dlqService.exportExcel("instance-1", "group-1", 5000L, 5001L, null);
+
+ verify(dlqProvider).resendMessages("instance-1", "group-1", 5000L,
5001L, null);
+ verify(dlqProvider).listMessages("instance-1", "group-1", 5000L,
5001L, 1, 20);
+ verify(dlqProvider).exportMessages("instance-1", "group-1", 5000L,
5001L, 100);
+ verify(dlqProvider).exportExcel("instance-1", "group-1", 5000L, 5001L,
null);
+ }
}