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)
+        );
+    }
 }

Reply via email to