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 098e2ea4 fix(server): 422 on rejected cloud credentials and async file 
logging (#2052)
098e2ea4 is described below

commit 098e2ea4426659ebd84a71b9d1e92755e312a54c
Author: lizhimins <[email protected]>
AuthorDate: Thu Aug 13 11:52:22 2026 +0800

    fix(server): 422 on rejected cloud credentials and async file logging 
(#2052)
    
    Cloud providers now map OpenAPI authentication failures to 422 instead
    of 401: HTTP 401 is reserved for Studio session auth and would make
    the frontend log the user out. Logback gains an async rolling file
    appender under logs/rocketmq-studio (1GB per file, history in
    otherdays/, 10 periods retained) alongside the console. Also move H2
    to test scope, silence the MyBatis-Plus banner, level dev logs to
    INFO, and bypass IDE-injected JVM proxies in the metrics source test.
---
 server/pom.xml                                     |  2 +-
 .../provider/alibaba/AliyunClientFactory.java      |  4 +-
 .../provider/tencent/TencentClientFactory.java     |  4 +-
 server/src/main/resources/application-dev.yml      |  4 +-
 server/src/main/resources/application.yml          |  1 +
 server/src/main/resources/logback-spring.xml       | 17 ++++++++
 .../metrics/MultiBackendMetricsSourceTest.java     | 46 +++++++++++++++++++---
 .../provider/alibaba/AliyunClientFactoryTest.java  |  4 +-
 8 files changed, 70 insertions(+), 12 deletions(-)

diff --git a/server/pom.xml b/server/pom.xml
index c9e1cc12..dbcc5604 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -80,7 +80,7 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <scope>runtime</scope>
+            <scope>test</scope>
         </dependency>
         <!-- RocketMQ Admin Tools (kept from trunk #694) -->
         <dependency>
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
index bb068daf..ea6b1a4a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
@@ -170,7 +170,9 @@ public class AliyunClientFactory {
         int status = statusCode == null ? 0 : statusCode;
         log.warn("Aliyun OpenAPI failure: status={}, errCode={}, message={}", 
status, errCode, message);
         if (status == 401 || "InvalidAccessKeyId".equals(errCode) || 
"SignatureDoesNotMatch".equals(errCode)) {
-            return new BusinessException(401, "Cloud credential is invalid");
+            // 422, not 401: HTTP 401 is reserved for Studio session auth; the 
frontend logs the
+            // user out and redirects on any 401, so a cloud-rejected 
credential must use another code.
+            return new BusinessException(422, "Cloud credential is invalid");
         }
         if (status == 403) {
             return new BusinessException(403, defaultIfBlank(message, "Aliyun 
OpenAPI access denied"));
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
index 96a9b71c..dcf0923a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
@@ -103,7 +103,9 @@ public class TencentClientFactory {
         }
         if (code != null && (code.contains("AuthFailure") || 
code.contains("InvalidCredential")
                 || code.contains("InvalidSecretId") || 
code.contains("SignatureFailure"))) {
-            return new BusinessException(401, "Cloud credential is invalid");
+            // 422, not 401: HTTP 401 is reserved for Studio session auth; the 
frontend logs the
+            // user out and redirects on any 401, so a cloud-rejected 
credential must use another code.
+            return new BusinessException(422, "Cloud credential is invalid");
         }
         if (code != null && (code.contains("NotFound") || 
code.contains("ResourceNotFound"))) {
             return new BusinessException(404, defaultIfBlank(message, "Tencent 
Cloud resource not found"));
diff --git a/server/src/main/resources/application-dev.yml 
b/server/src/main/resources/application-dev.yml
index c6af6056..5b5ef1a8 100644
--- a/server/src/main/resources/application-dev.yml
+++ b/server/src/main/resources/application-dev.yml
@@ -16,5 +16,5 @@ spring:
 
 logging:
   level:
-    org.apache.rocketmq.studio: DEBUG
-    org.springframework.web: DEBUG
+    org.apache.rocketmq.studio: INFO
+    org.springframework.web: INFO
diff --git a/server/src/main/resources/application.yml 
b/server/src/main/resources/application.yml
index 3e6d42d9..5b253cbe 100644
--- a/server/src/main/resources/application.yml
+++ b/server/src/main/resources/application.yml
@@ -17,6 +17,7 @@ mybatis-plus:
     map-underscore-to-camel-case: true
     log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
   global-config:
+    banner: false
     db-config:
       table-prefix: rmq_
       id-type: auto
diff --git a/server/src/main/resources/logback-spring.xml 
b/server/src/main/resources/logback-spring.xml
index 274690af..6342b99e 100644
--- a/server/src/main/resources/logback-spring.xml
+++ b/server/src/main/resources/logback-spring.xml
@@ -7,7 +7,24 @@
 
     <include 
resource="org/springframework/boot/logging/logback/console-appender.xml"/>
 
+    <appender name="FILE" 
class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>logs/rocketmq-studio/studio.log</file>
+        <rollingPolicy 
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            
<fileNamePattern>logs/rocketmq-studio/otherdays/studio.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>1GB</maxFileSize>
+            <maxHistory>10</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="FILE"/>
+    </appender>
+
     <root level="INFO">
         <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="ASYNC_FILE"/>
     </root>
 </configuration>
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
index d2323455..74c69742 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
@@ -20,7 +20,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpServer;
 import org.apache.rocketmq.studio.model.MetricsDataSourceConfig;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -28,8 +30,17 @@ import org.junit.jupiter.params.provider.EnumSource;
 import org.springframework.web.client.RestClient;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -41,6 +52,32 @@ class MultiBackendMetricsSourceTest {
     private final MetricsSourceFactory factory =
             new MetricsSourceFactory(RestClient.builder(), new ObjectMapper());
 
+    private static ProxySelector originalProxySelector;
+
+    @BeforeAll
+    static void bypassJvmProxy() {
+        // The IDE (e.g. IDEA with a PAC proxy) may inject a proxy into the 
test JVM. The embedded
+        // server is bound to a site-local address that is not in 
http.nonProxyHosts, so the request
+        // would be routed through the proxy and time out. Force a direct 
connection for this test.
+        originalProxySelector = ProxySelector.getDefault();
+        ProxySelector.setDefault(new ProxySelector() {
+            @Override
+            public List<Proxy> select(URI uri) {
+                return List.of(Proxy.NO_PROXY);
+            }
+
+            @Override
+            public void connectFailed(URI uri, SocketAddress socketAddress, 
IOException exception) {
+                // Nothing to do; the test never relies on a proxy.
+            }
+        });
+    }
+
+    @AfterAll
+    static void restoreJvmProxy() {
+        ProxySelector.setDefault(originalProxySelector);
+    }
+
     @BeforeEach
     void setUp() throws IOException {
         java.net.InetAddress bindAddress = findSiteLocalAddress();
@@ -128,15 +165,14 @@ class MultiBackendMetricsSourceTest {
     }
 
     private static java.net.InetAddress findSiteLocalAddress() throws 
java.net.SocketException {
-        java.util.Enumeration<java.net.NetworkInterface> interfaces =
-                java.net.NetworkInterface.getNetworkInterfaces();
+        Enumeration<NetworkInterface> interfaces = 
NetworkInterface.getNetworkInterfaces();
         while (interfaces.hasMoreElements()) {
-            java.net.NetworkInterface iface = interfaces.nextElement();
+            NetworkInterface iface = interfaces.nextElement();
             if (!iface.isUp() || iface.isLoopback()) {
                 continue;
             }
-            for (java.net.InterfaceAddress address : 
iface.getInterfaceAddresses()) {
-                java.net.InetAddress inet = address.getAddress();
+            for (InterfaceAddress address : iface.getInterfaceAddresses()) {
+                InetAddress inet = address.getAddress();
                 if (inet instanceof java.net.Inet4Address
                         && inet.isSiteLocalAddress()
                         && !inet.isLoopbackAddress()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
index 3f6c6312..4f0b707a 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
@@ -185,7 +185,7 @@ class AliyunClientFactoryTest {
     }
 
     @Test
-    void callShouldMapInvalidAccessKeyTo401Test() {
+    void callShouldMapInvalidAccessKeyTo422Test() {
         AliyunClientFactory spy = Mockito.spy(factory);
         doReturn(asyncClient).when(spy).client(anyString(), anyString());
         PopServerException error = new PopServerException("bad key");
@@ -198,7 +198,7 @@ class AliyunClientFactoryTest {
                 ListRegionsRequest.builder().build())))
                 .isInstanceOf(BusinessException.class)
                 .extracting("code")
-                .isEqualTo(401);
+                .isEqualTo(422);
     }
 
     @Test

Reply via email to