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

commit cc81e4feaba9f2df1640b3f9c3b831b7e8e48be4
Author: aias00 <[email protected]>
AuthorDate: Thu Jul 23 22:51:25 2026 -0700

    feat: add proxy address endpoints (#505)
    
    Add proxy address list and add-address endpoints supporting Studio frontend 
form submission.
---
 .../studio/cluster/proxy/ProxyAddressService.java  | 58 +++++++++++++++++
 .../cluster/proxy/ProxyCompatController.java       | 46 ++++++++++++++
 .../rocketmq/studio/cluster/proxy/ProxyHomeVO.java | 30 +++++++++
 .../cluster/proxy/ProxyAddressServiceTest.java     | 55 ++++++++++++++++
 .../cluster/proxy/ProxyCompatControllerTest.java   | 73 ++++++++++++++++++++++
 5 files changed, 262 insertions(+)

diff --git 
a/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyAddressService.java
 
b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyAddressService.java
new file mode 100644
index 00000000..d2e30a56
--- /dev/null
+++ 
b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyAddressService.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.rocketmq.studio.cluster.proxy;
+
+import com.rocketmq.studio.common.exception.BusinessException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+@Slf4j
+@Service
+public class ProxyAddressService {
+
+    private final Set<String> proxyAddrs = new 
LinkedHashSet<>(List.of("127.0.0.1:8081"));
+    private String currentProxyAddr = "127.0.0.1:8081";
+
+    public synchronized ProxyHomeVO getHomePage() {
+        return ProxyHomeVO.builder()
+                .proxyAddrList(new ArrayList<>(proxyAddrs))
+                .currentProxyAddr(currentProxyAddr)
+                .build();
+    }
+
+    public synchronized void addProxyAddr(String newProxyAddr) {
+        String normalized = normalizeProxyAddr(newProxyAddr);
+        proxyAddrs.add(normalized);
+        if (currentProxyAddr == null || currentProxyAddr.isBlank()) {
+            currentProxyAddr = normalized;
+        }
+        log.info("Added Proxy address {}", normalized);
+    }
+
+    private String normalizeProxyAddr(String proxyAddr) {
+        if (proxyAddr == null || proxyAddr.trim().isEmpty()) {
+            throw new BusinessException(400, "newProxyAddr is required");
+        }
+        return proxyAddr.trim();
+    }
+}
diff --git 
a/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyCompatController.java
 
b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyCompatController.java
new file mode 100644
index 00000000..48c15445
--- /dev/null
+++ 
b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyCompatController.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.rocketmq.studio.cluster.proxy;
+
+import com.rocketmq.studio.common.domain.Result;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/proxy")
+@RequiredArgsConstructor
+public class ProxyCompatController {
+
+    private final ProxyAddressService proxyAddressService;
+
+    @GetMapping("/homePage.query")
+    public Result<ProxyHomeVO> homePage() {
+        return Result.ok(proxyAddressService.getHomePage());
+    }
+
+    @PostMapping(value = "/addProxyAddr.do", consumes = 
MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+    public Result<Void> addProxyAddr(@RequestParam String newProxyAddr) {
+        proxyAddressService.addProxyAddr(newProxyAddr);
+        return Result.ok();
+    }
+}
diff --git 
a/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyHomeVO.java 
b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyHomeVO.java
new file mode 100644
index 00000000..426f65e1
--- /dev/null
+++ b/server/src/main/java/com/rocketmq/studio/cluster/proxy/ProxyHomeVO.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.rocketmq.studio.cluster.proxy;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class ProxyHomeVO {
+    private List<String> proxyAddrList;
+    private String currentProxyAddr;
+}
diff --git 
a/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyAddressServiceTest.java
 
b/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyAddressServiceTest.java
new file mode 100644
index 00000000..44b7e72e
--- /dev/null
+++ 
b/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyAddressServiceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.rocketmq.studio.cluster.proxy;
+
+import com.rocketmq.studio.common.exception.BusinessException;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ProxyAddressServiceTest {
+
+    private final ProxyAddressService proxyAddressService = new 
ProxyAddressService();
+
+    @Test
+    void homePageShouldReturnDefaultProxyAddress() {
+        ProxyHomeVO home = proxyAddressService.getHomePage();
+
+        assertThat(home.getProxyAddrList()).containsExactly("127.0.0.1:8081");
+        assertThat(home.getCurrentProxyAddr()).isEqualTo("127.0.0.1:8081");
+    }
+
+    @Test
+    void addProxyAddrShouldTrimAndKeepUniqueAddresses() {
+        proxyAddressService.addProxyAddr(" 10.0.0.1:8081 ");
+        proxyAddressService.addProxyAddr("10.0.0.1:8081");
+
+        ProxyHomeVO home = proxyAddressService.getHomePage();
+        assertThat(home.getProxyAddrList()).containsExactly("127.0.0.1:8081", 
"10.0.0.1:8081");
+        assertThat(home.getCurrentProxyAddr()).isEqualTo("127.0.0.1:8081");
+    }
+
+    @Test
+    void addProxyAddrShouldRejectBlankAddress() {
+        assertThatThrownBy(() -> proxyAddressService.addProxyAddr(" "))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("newProxyAddr is required")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400));
+    }
+}
diff --git 
a/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyCompatControllerTest.java
 
b/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyCompatControllerTest.java
new file mode 100644
index 00000000..1268fbb9
--- /dev/null
+++ 
b/server/src/test/java/com/rocketmq/studio/cluster/proxy/ProxyCompatControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.rocketmq.studio.cluster.proxy;
+
+import org.junit.jupiter.api.Test;
+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;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(ProxyCompatController.class)
+@AutoConfigureMockMvc(addFilters = false)
+class ProxyCompatControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    private ProxyAddressService proxyAddressService;
+
+    @Test
+    void homePageShouldReturnProxyAddressState() throws Exception {
+        ProxyHomeVO home = ProxyHomeVO.builder()
+                .proxyAddrList(List.of("127.0.0.1:8081", "10.0.0.1:8081"))
+                .currentProxyAddr("127.0.0.1:8081")
+                .build();
+        when(proxyAddressService.getHomePage()).thenReturn(home);
+
+        mockMvc.perform(get("/api/proxy/homePage.query"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                
.andExpect(jsonPath("$.data.proxyAddrList[0]").value("127.0.0.1:8081"))
+                
.andExpect(jsonPath("$.data.currentProxyAddr").value("127.0.0.1:8081"));
+    }
+
+    @Test
+    void addProxyAddrShouldAcceptFormEncodedPayload() throws Exception {
+        mockMvc.perform(post("/api/proxy/addProxyAddr.do")
+                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+                        .param("newProxyAddr", "10.0.0.1:8081"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200));
+
+        verify(proxyAddressService).addProxyAddr(eq("10.0.0.1:8081"));
+    }
+}

Reply via email to