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 ea628e07 fix: reject out-of-range broker configuration values (#679)
ea628e07 is described below
commit ea628e07a29c9b27572b7befe1e46c3e6cb1ff7e
Author: Rui <[email protected]>
AuthorDate: Fri Jul 31 16:20:39 2026 +0800
fix: reject out-of-range broker configuration values (#679)
---
docs/api-spec.md | 20 +++--
.../studio/cluster/config/UpdateConfigDTO.java | 17 ++++
.../cluster/broker/ClusterControllerTest.java | 97 ++++++++++++++++++++++
3 files changed, 125 insertions(+), 9 deletions(-)
diff --git a/docs/api-spec.md b/docs/api-spec.md
index 782ad796..cb010f8a 100644
--- a/docs/api-spec.md
+++ b/docs/api-spec.md
@@ -432,16 +432,18 @@ POST /api/clusters/config/update
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `id` | `string` | 是 | 集群 ID |
-| `flushDiskType` | `string` | 是 | `ASYNC_FLUSH` / `SYNC_FLUSH` |
-| `autoCreateTopicEnable` | `boolean` | 是 | 自动创建 Topic |
-| `autoCreateSubscriptionGroup` | `boolean` | 是 | 自动创建订阅组 |
-| `maxMessageSize` | `number` | 是 | 最大消息大小(字节) |
-| `fileReservedTime` | `number` | 是 | 文件保留时间(小时,1-720) |
-| `writeQueueNums` | `number` | 是 | 写队列数(1-256) |
-| `readQueueNums` | `number` | 是 | 读队列数(1-256) |
-| `brokerPermission` | `number` | 是 | Broker 权限(0-7) |
+| `flushDiskType` | `string` | 否 | `ASYNC_FLUSH` / `SYNC_FLUSH` |
+| `autoCreateTopicEnable` | `boolean` | 否 | 自动创建 Topic |
+| `autoCreateSubscriptionGroup` | `boolean` | 否 | 自动创建订阅组 |
+| `maxMessageSize` | `number` | 否 | 最大消息大小(字节,1,048,576-134,217,728) |
+| `fileReservedTime` | `number` | 否 | 文件保留时间(小时,1-720) |
+| `writeQueueNums` | `number` | 否 | 写队列数(1-256) |
+| `readQueueNums` | `number` | 否 | 读队列数(1-256) |
+| `brokerPermission` | `number` | 否 | Broker 权限(0-7) |
-**Response `data`:** `null`
+未提供的可选字段保持原配置不变。
+
+**Response `data`:** `ClusterInfo`(同 4.1 的单条记录)
### 4.4 重启 Broker
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
index 229653c1..a23d0c67 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
@@ -16,6 +16,8 @@
*/
package org.apache.rocketmq.studio.cluster.config;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -33,9 +35,24 @@ public class UpdateConfigDTO {
private String flushDiskType;
private Boolean autoCreateTopicEnable;
private Boolean autoCreateSubscriptionGroup;
+
+ @Min(value = 1_048_576, message = "maxMessageSize must be between 1048576
and 134217728")
+ @Max(value = 134_217_728, message = "maxMessageSize must be between
1048576 and 134217728")
private Integer maxMessageSize;
+
+ @Min(value = 1, message = "fileReservedTime must be between 1 and 720")
+ @Max(value = 720, message = "fileReservedTime must be between 1 and 720")
private Integer fileReservedTime;
+
+ @Min(value = 1, message = "writeQueueNums must be between 1 and 256")
+ @Max(value = 256, message = "writeQueueNums must be between 1 and 256")
private Integer writeQueueNums;
+
+ @Min(value = 1, message = "readQueueNums must be between 1 and 256")
+ @Max(value = 256, message = "readQueueNums must be between 1 and 256")
private Integer readQueueNums;
+
+ @Min(value = 0, message = "brokerPermission must be between 0 and 7")
+ @Max(value = 7, message = "brokerPermission must be between 0 and 7")
private Integer brokerPermission;
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
index 5279f617..1c23fcdb 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
@@ -20,10 +20,14 @@ import
org.apache.rocketmq.studio.cluster.config.ClusterConfigVO;
import org.apache.rocketmq.studio.cluster.config.UpdateConfigDTO;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.rocketmq.studio.common.domain.enums.ClusterStatus;
import org.apache.rocketmq.studio.common.domain.enums.ClusterType;
import org.apache.rocketmq.studio.common.domain.enums.FlushDiskType;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -33,8 +37,10 @@ import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.Collections;
+import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -170,6 +176,57 @@ class ClusterControllerTest {
verifyNoInteractions(clusterService);
}
+ @ParameterizedTest(name = "{0}={1} should be rejected")
+ @MethodSource("outOfRangeConfigValues")
+ void updateConfigShouldRejectOutOfRangeValues(String field, int value,
String expectedMessage)
+ throws Exception {
+ ObjectNode command = objectMapper.createObjectNode()
+ .put("id", "cluster-1")
+ .put(field, value);
+
+ mockMvc.perform(post("/api/clusters/config/update")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(command)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value(400))
+ .andExpect(jsonPath("$.message").value(expectedMessage));
+
+ verifyNoInteractions(clusterService);
+ }
+
+ @ParameterizedTest(name = "{0}={1} should be accepted")
+ @MethodSource("boundaryConfigValues")
+ void updateConfigShouldAcceptBoundaryValues(String field, int value)
throws Exception {
+ when(clusterService.updateClusterConfig(any(UpdateConfigDTO.class)))
+ .thenReturn(buildCluster("cluster-1", "production-cluster",
ClusterStatus.healthy));
+ ObjectNode command = objectMapper.createObjectNode()
+ .put("id", "cluster-1")
+ .put(field, value);
+
+ mockMvc.perform(post("/api/clusters/config/update")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(command)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value(200));
+
+ verify(clusterService).updateClusterConfig(any(UpdateConfigDTO.class));
+ }
+
+ @Test
+ void updateConfigShouldAcceptIdOnlyPartialRequest() throws Exception {
+ when(clusterService.updateClusterConfig(any(UpdateConfigDTO.class)))
+ .thenReturn(buildCluster("cluster-1", "production-cluster",
ClusterStatus.healthy));
+ ObjectNode command = objectMapper.createObjectNode().put("id",
"cluster-1");
+
+ mockMvc.perform(post("/api/clusters/config/update")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(command)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value(200));
+
+ verify(clusterService).updateClusterConfig(any(UpdateConfigDTO.class));
+ }
+
@Test
void restartBrokerShouldReturnSuccess() throws Exception {
when(clusterService.restartBroker("cluster-1",
"broker-0")).thenReturn(true);
@@ -197,4 +254,44 @@ class ClusterControllerTest {
cluster.setId(id);
return cluster;
}
+
+ private static Stream<Arguments> outOfRangeConfigValues() {
+ return Stream.of(
+ Arguments.of("maxMessageSize", 1_048_575,
+ "maxMessageSize must be between 1048576 and
134217728"),
+ Arguments.of("maxMessageSize", 134_217_729,
+ "maxMessageSize must be between 1048576 and
134217728"),
+ Arguments.of("fileReservedTime", 0,
+ "fileReservedTime must be between 1 and 720"),
+ Arguments.of("fileReservedTime", 721,
+ "fileReservedTime must be between 1 and 720"),
+ Arguments.of("writeQueueNums", 0,
+ "writeQueueNums must be between 1 and 256"),
+ Arguments.of("writeQueueNums", 257,
+ "writeQueueNums must be between 1 and 256"),
+ Arguments.of("readQueueNums", 0,
+ "readQueueNums must be between 1 and 256"),
+ Arguments.of("readQueueNums", 257,
+ "readQueueNums must be between 1 and 256"),
+ Arguments.of("brokerPermission", -1,
+ "brokerPermission must be between 0 and 7"),
+ Arguments.of("brokerPermission", 8,
+ "brokerPermission must be between 0 and 7")
+ );
+ }
+
+ private static Stream<Arguments> boundaryConfigValues() {
+ return Stream.of(
+ Arguments.of("maxMessageSize", 1_048_576),
+ Arguments.of("maxMessageSize", 134_217_728),
+ Arguments.of("fileReservedTime", 1),
+ Arguments.of("fileReservedTime", 720),
+ Arguments.of("writeQueueNums", 1),
+ Arguments.of("writeQueueNums", 256),
+ Arguments.of("readQueueNums", 1),
+ Arguments.of("readQueueNums", 256),
+ Arguments.of("brokerPermission", 0),
+ Arguments.of("brokerPermission", 7)
+ );
+ }
}