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 ec433ca1 feat: add MySQL persistence and the deployment it needs (#796)
ec433ca1 is described below

commit ec433ca19b3e7f36c25538f9ce023110fb6b1021
Author: lizhimins <[email protected]>
AuthorDate: Mon Aug 3 14:32:08 2026 +0800

    feat: add MySQL persistence and the deployment it needs (#796)
---
 deploy/docker-compose.yml                          |  51 ++++++
 deploy/mysql/init.sql                              |   3 +
 deploy/nginx.conf                                  |  12 +-
 deploy/rocketmq/Dockerfile                         |   3 +-
 deploy/rocketmq/docker-compose.yml                 |  26 ++-
 server/pom.xml                                     |  17 ++
 .../studio/audit/OperationAuditService.java        |  50 ++++++
 .../rocketmq/studio/ops/audit/AuditRepository.java |   2 +
 .../rocketmq/studio/ops/audit/AuditService.java    |  12 ++
 .../studio/ops/audit/InMemoryAuditRepository.java  |  12 ++
 .../MyBatisConfig.java}                            |  16 +-
 .../persistence/MybatisPlusSettingsRepository.java | 175 +++++++++++++++++++++
 .../entity/RmqDataSource.java}                     |  22 ++-
 .../entity/RmqGroup.java}                          |  35 ++++-
 .../entity/RmqK8sCertificate.java}                 |  37 ++++-
 .../entity/RmqMessageQuery.java}                   |  37 ++++-
 .../entity/RmqNameserver.java}                     |  31 +++-
 .../entity/RmqOperationAudit.java}                 |  35 ++++-
 .../entity/RmqSettings.java}                       |  21 ++-
 .../entity/RmqTopic.java}                          |  39 ++++-
 .../entity/RmqTraceQuery.java}                     |  29 +++-
 .../mapper/RmqDataSourceMapper.java}               |  14 +-
 .../mapper/RmqGroupMapper.java}                    |  14 +-
 .../mapper/RmqK8sCertificateMapper.java}           |  14 +-
 .../mapper/RmqMessageQueryMapper.java}             |  14 +-
 .../mapper/RmqNameserverMapper.java}               |  14 +-
 .../mapper/RmqOperationAuditMapper.java}           |  14 +-
 .../mapper/RmqSettingsMapper.java}                 |  14 +-
 .../mapper/RmqTopicMapper.java}                    |  14 +-
 .../mapper/RmqTraceQueryMapper.java}               |  14 +-
 .../settings/InMemorySettingsRepository.java       |   3 +-
 .../rocketmq/studio/settings/SettingsService.java  |   7 +-
 server/src/main/resources/application-dev.yml      |  11 ++
 server/src/main/resources/application.yml          |  16 ++
 server/src/main/resources/db/schema.sql            | 167 ++++++++++++++++++++
 .../studio/settings/SettingsServiceTest.java       |  14 +-
 web/nginx.conf                                     |  12 +-
 37 files changed, 839 insertions(+), 182 deletions(-)

diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index a707a007..e4079e8c 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -1,6 +1,29 @@
 name: rocketmq-studio
 
 services:
+  mysql:
+    image: mysql:8.0
+    container_name: rocketmq-studio-mysql
+    restart: unless-stopped
+    environment:
+      TZ: ${TZ:-Asia/Shanghai}
+      MYSQL_ROOT_PASSWORD: studio123
+      MYSQL_DATABASE: rocketmq_studio
+    ports:
+      - "3306:3306"
+    volumes:
+      - ./mysql/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
+      - 
../server/src/main/resources/db/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
+      - mysql-data:/var/lib/mysql
+    networks:
+      - studio-net
+      - rocketmq
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", 
"-pstudio123"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+
   rocketmq-server:
     build:
       context: ../server
@@ -9,6 +32,7 @@ services:
     container_name: rocketmq-server
     restart: unless-stopped
     environment:
+      TZ: ${TZ:-Asia/Shanghai}
       STUDIO_AUTH_LOGIN_REQUIRED: ${STUDIO_AUTH_LOGIN_REQUIRED:-false}
       STUDIO_AUTH_ADMIN_USERNAME: ${STUDIO_AUTH_ADMIN_USERNAME:-}
       STUDIO_AUTH_ADMIN_PASSWORD: ${STUDIO_AUTH_ADMIN_PASSWORD:-}
@@ -16,8 +40,23 @@ services:
       STUDIO_METRICS_PROMETHEUS_USERNAME: 
${STUDIO_METRICS_PROMETHEUS_USERNAME:-}
       STUDIO_METRICS_PROMETHEUS_PASSWORD: 
${STUDIO_METRICS_PROMETHEUS_PASSWORD:-}
       STUDIO_METRICS_PROMETHEUS_BEARER_TOKEN: 
${STUDIO_METRICS_PROMETHEUS_BEARER_TOKEN:-}
+      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
+      SPRING_DATASOURCE_URL: 
jdbc:mysql://mysql:3306/rocketmq_studio?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci
+      SPRING_DATASOURCE_USERNAME: root
+      SPRING_DATASOURCE_PASSWORD: studio123
+      STUDIO_ROCKETMQ_NAMESRV_ADDR: 
${STUDIO_ROCKETMQ_NAMESRV_ADDR:-nameserver:9876}
     expose:
       - "8888"
+    extra_hosts:
+      - "nameserver:10.89.2.10"
+      - "broker-0:10.89.2.11"
+      - "broker-1:10.89.2.12"
+    networks:
+      - studio-net
+      - rocketmq
+    depends_on:
+      mysql:
+        condition: service_healthy
 
   rocketmq-web:
     build:
@@ -30,3 +69,15 @@ services:
       - "6789:80"
     depends_on:
       - rocketmq-server
+    networks:
+      - studio-net
+
+networks:
+  studio-net:
+    driver: bridge
+  rocketmq:
+    external: true
+    name: rocketmq_default
+
+volumes:
+  mysql-data:
diff --git a/deploy/mysql/init.sql b/deploy/mysql/init.sql
new file mode 100644
index 00000000..c50e5727
--- /dev/null
+++ b/deploy/mysql/init.sql
@@ -0,0 +1,3 @@
+CREATE DATABASE IF NOT EXISTS rocketmq_studio DEFAULT CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;
+USE rocketmq_studio;
+SOURCE /docker-entrypoint-initdb.d/schema.sql;
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index 165c0f93..0979fe79 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -9,9 +9,15 @@ server {
     }
 
     location /api/ {
-        resolver 10.89.0.1 valid=10s;
-        set $backend http://rocketmq-server:8888;
-        proxy_pass $backend;
+        proxy_pass http://rocketmq-server:8888;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+
+    location /actuator/ {
+        proxy_pass http://rocketmq-server:8888;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/deploy/rocketmq/Dockerfile b/deploy/rocketmq/Dockerfile
index 9b603ed5..9fd7758c 100644
--- a/deploy/rocketmq/Dockerfile
+++ b/deploy/rocketmq/Dockerfile
@@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y 
--no-install-recommends git \
 RUN git clone --depth 1 -b ${ROCKETMQ_BRANCH} ${ROCKETMQ_REPO} /build/rocketmq
 
 WORKDIR /build/rocketmq
-RUN mvn -Prelease-all -DskipTests -Dspotbugs.skip=true -Dcheckstyle.skip=true \
+# 容器默认 nofile ulimit 过低,并行(-T 2C)构建时 javac 会报 "Too many open files",先调高
+RUN ulimit -n 65535; mvn -Prelease-all -Dmaven.test.skip=true 
-Dspotbugs.skip=true -Dcheckstyle.skip=true \
         -Dmaven.javadoc.skip=true -T 2C clean install \
     && mkdir /dist \
     && cp -r distribution/target/rocketmq-*/rocketmq-*/* /dist/
diff --git a/deploy/rocketmq/docker-compose.yml 
b/deploy/rocketmq/docker-compose.yml
index 76889124..1393c6f8 100644
--- a/deploy/rocketmq/docker-compose.yml
+++ b/deploy/rocketmq/docker-compose.yml
@@ -13,6 +13,9 @@ services:
       JAVA_OPT_EXT: -Xms512m -Xmx512m -Xmn256m
     ports:
       - "9876:9876"
+    networks:
+      default:
+        ipv4_address: 10.89.2.10
 
   broker-0:
     image: apache-rocketmq:develop
@@ -31,6 +34,9 @@ services:
       - "10909:10909"
       - "10911:10911"
       - "10912:10912"
+    networks:
+      default:
+        ipv4_address: 10.89.2.11
     depends_on:
       - nameserver
 
@@ -52,6 +58,9 @@ services:
       - "20909:20909"
       - "20911:20911"
       - "20912:20912"
+    networks:
+      default:
+        ipv4_address: 10.89.2.12
     depends_on:
       - nameserver
 
@@ -80,14 +89,15 @@ services:
       sh -c "javac -encoding UTF-8 -cp 'lib/*' -d /tmp/classes 
/clients/TraceProducer.java
       && java -cp 'lib/*:/tmp/classes' TraceProducer"
     environment:
-      # 指向 proxy remoting 端口,流量经 proxy 转发(proxy 会以自身地址应答路由)
-      NAMESRV_ADDR: proxy:8080
+      # 直连 NameServer,broker 端可见 producer 容器真实连接(供 Studio 客户端连接视图展示)
+      NAMESRV_ADDR: nameserver:9876
       TOPIC: StudioTest
       SEND_INTERVAL_MS: "1000"
     volumes:
       - ./clients:/clients:ro
     depends_on:
-      - proxy
+      - broker-0
+      - broker-1
     restart: on-failure
 
   consumer:
@@ -97,14 +107,20 @@ services:
       sh -c "javac -encoding UTF-8 -cp 'lib/*' -d /tmp/classes 
/clients/TraceConsumer.java
       && java -cp 'lib/*:/tmp/classes' TraceConsumer"
     environment:
-      NAMESRV_ADDR: proxy:8080
+      # 直连 NameServer,broker 端可见 consumer 容器真实连接(供 Studio 客户端连接视图展示)
+      NAMESRV_ADDR: nameserver:9876
       TOPIC: StudioTest
     volumes:
       - ./clients:/clients:ro
     depends_on:
-      - proxy
+      - broker-0
+      - broker-1
     restart: on-failure
 
+networks:
+  default:
+    name: rocketmq_default
+
 volumes:
   broker-0-store:
   broker-0-logs:
diff --git a/server/pom.xml b/server/pom.xml
index 1f2dccf3..1c79eabd 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -66,6 +66,23 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- MyBatis-Plus + MySQL -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+            <version>3.5.7</version>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <!-- RocketMQ Admin Tools (kept from trunk #694) -->
         <dependency>
             <groupId>org.apache.rocketmq</groupId>
             <artifactId>rocketmq-tools</artifactId>
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/audit/OperationAuditService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/audit/OperationAuditService.java
new file mode 100644
index 00000000..97b9b305
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/audit/OperationAuditService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.audit;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.studio.persistence.entity.RmqOperationAudit;
+import org.apache.rocketmq.studio.persistence.mapper.RmqOperationAuditMapper;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+@Slf4j
+@Service
+public class OperationAuditService {
+
+    private final RmqOperationAuditMapper auditMapper;
+
+    public OperationAuditService(RmqOperationAuditMapper auditMapper) {
+        this.auditMapper = auditMapper;
+    }
+
+    public void record(String operation, String resourceType, String 
resourceName,
+                       String clusterId, String detail, String result, String 
errorMessage) {
+        RmqOperationAudit audit = new RmqOperationAudit();
+        audit.setOperation(operation);
+        audit.setResourceType(resourceType);
+        audit.setResourceName(resourceName);
+        audit.setClusterId(clusterId);
+        audit.setDetail(detail);
+        audit.setResult(result);
+        audit.setErrorMessage(errorMessage);
+        audit.setOperatedAt(LocalDateTime.now());
+        auditMapper.insert(audit);
+        log.debug("Audit recorded: {} {} {}", operation, resourceType, 
resourceName);
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
index 597df17e..efb36d66 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
@@ -25,5 +25,7 @@ public interface AuditRepository {
                               LocalDateTime startDate, LocalDateTime endDate,
                               String result);
 
+    void save(AuditRecordVO record);
+
     int deleteBefore(LocalDateTime cutoff);
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
index 3595c90b..4a1f057d 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
@@ -76,6 +76,18 @@ public class AuditService {
     }
 
 
+    public void record(String operationType, String target, String detail, 
String result) {
+        AuditRecordVO record = AuditRecordVO.builder()
+                .timestamp(LocalDateTime.now())
+                .operationType(operationType)
+                .target(target)
+                .detail(detail)
+                .result(result)
+                .build();
+        auditRepository.save(record);
+        log.info("Audit recorded: {} on {} -> {}", operationType, target, 
result);
+    }
+
     public int cleanupLogs(int beforeDays) {
         if (beforeDays <= 0) {
             throw new BusinessException(400, "beforeDays must be greater than 
0");
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
index 02740375..0b1ee895 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
@@ -66,6 +66,18 @@ public class InMemoryAuditRepository implements 
AuditRepository {
         return value != null && value.toLowerCase().contains(normalizedSearch);
     }
 
+    @Override
+    public void save(AuditRecordVO record) {
+        if (record.getId() == null) {
+            record.setId(java.util.UUID.randomUUID().toString());
+        }
+        if (record.getTimestamp() == null) {
+            record.setTimestamp(LocalDateTime.now());
+        }
+        records.put(record.getId(), record);
+        log.debug("Saved audit record: {} - {}", record.getOperationType(), 
record.getTarget());
+    }
+
     @Override
     public int deleteBefore(LocalDateTime cutoff) {
         List<String> toRemove = records.values().stream()
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MyBatisConfig.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/MyBatisConfig.java
index 597df17e..002a5e8b 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MyBatisConfig.java
@@ -14,16 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence;
 
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Configuration;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+@Configuration
+@MapperScan("org.apache.rocketmq.studio.persistence.mapper")
+public class MyBatisConfig {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
new file mode 100644
index 00000000..62c30896
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
@@ -0,0 +1,175 @@
+/*
+ * 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.persistence;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.studio.persistence.entity.RmqDataSource;
+import org.apache.rocketmq.studio.persistence.entity.RmqSettings;
+import org.apache.rocketmq.studio.persistence.mapper.RmqDataSourceMapper;
+import org.apache.rocketmq.studio.persistence.mapper.RmqSettingsMapper;
+import org.apache.rocketmq.studio.settings.DataSourceVO;
+import org.apache.rocketmq.studio.settings.GeneralSettingsVO;
+import org.apache.rocketmq.studio.settings.SettingsRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Repository
+public class MybatisPlusSettingsRepository implements SettingsRepository {
+
+    private static final String SETTINGS_ID = "singleton";
+
+    private final RmqSettingsMapper settingsMapper;
+    private final RmqDataSourceMapper dataSourceMapper;
+    private final ObjectMapper objectMapper;
+
+    public MybatisPlusSettingsRepository(RmqSettingsMapper settingsMapper,
+                                         RmqDataSourceMapper dataSourceMapper,
+                                         ObjectMapper objectMapper) {
+        this.settingsMapper = settingsMapper;
+        this.dataSourceMapper = dataSourceMapper;
+        this.objectMapper = objectMapper;
+    }
+
+    @Override
+    public GeneralSettingsVO loadGeneralSettings() {
+        RmqSettings entity = settingsMapper.selectById(SETTINGS_ID);
+        if (entity == null || entity.getJson() == null) {
+            return GeneralSettingsVO.builder()
+                    .theme("system")
+                    .compact(false)
+                    .desktopNotify(true)
+                    .notifySound(false)
+                    .sessionTimeout(30)
+                    .requireLogin(false)
+                    .llmProvider("openai")
+                    .apiKey("")
+                    .model("gpt-4")
+                    .baseUrl("")
+                    .build();
+        }
+        try {
+            return objectMapper.readValue(entity.getJson(), 
GeneralSettingsVO.class);
+        } catch (JsonProcessingException e) {
+            log.error("Failed to deserialize general settings", e);
+            return GeneralSettingsVO.builder()
+                    .theme("system")
+                    .compact(false)
+                    .desktopNotify(true)
+                    .notifySound(false)
+                    .sessionTimeout(30)
+                    .requireLogin(false)
+                    .llmProvider("openai")
+                    .apiKey("")
+                    .model("gpt-4")
+                    .baseUrl("")
+                    .build();
+        }
+    }
+
+    @Override
+    public void saveGeneralSettings(GeneralSettingsVO settings) {
+        try {
+            String json = objectMapper.writeValueAsString(settings);
+            RmqSettings entity = settingsMapper.selectById(SETTINGS_ID);
+            if (entity == null) {
+                entity = new RmqSettings();
+                entity.setId(SETTINGS_ID);
+                entity.setJson(json);
+                entity.setUpdatedAt(LocalDateTime.now());
+                settingsMapper.insert(entity);
+            } else {
+                entity.setJson(json);
+                entity.setUpdatedAt(LocalDateTime.now());
+                settingsMapper.updateById(entity);
+            }
+        } catch (JsonProcessingException e) {
+            log.error("Failed to serialize general settings", e);
+            throw new RuntimeException("Failed to save settings", e);
+        }
+    }
+
+    @Override
+    public List<DataSourceVO> findAllDataSources() {
+        return dataSourceMapper.selectList(null).stream()
+                .map(this::toDataSourceVO)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public DataSourceVO saveDataSource(DataSourceVO dataSource) {
+        RmqDataSource entity = new RmqDataSource();
+        entity.setDsKey(dataSource.getKey());
+        entity.setJson(toJson(dataSource));
+        entity.setCreatedAt(LocalDateTime.now());
+        entity.setUpdatedAt(LocalDateTime.now());
+        dataSourceMapper.insert(entity);
+        return dataSource;
+    }
+
+    @Override
+    public boolean replaceDataSource(DataSourceVO dataSource) {
+        RmqDataSource existing = 
dataSourceMapper.selectById(dataSource.getKey());
+        if (existing == null) {
+            return false;
+        }
+        existing.setJson(toJson(dataSource));
+        existing.setUpdatedAt(LocalDateTime.now());
+        dataSourceMapper.updateById(existing);
+        return true;
+    }
+
+    @Override
+    public boolean deleteDataSource(String key) {
+        return dataSourceMapper.deleteById(key) > 0;
+    }
+
+    @Override
+    public Optional<DataSourceVO> findDataSourceByKey(String key) {
+        RmqDataSource entity = dataSourceMapper.selectById(key);
+        if (entity == null) {
+            return Optional.empty();
+        }
+        return Optional.of(toDataSourceVO(entity));
+    }
+
+    private DataSourceVO toDataSourceVO(RmqDataSource entity) {
+        try {
+            DataSourceVO vo = objectMapper.readValue(entity.getJson(), 
DataSourceVO.class);
+            vo.setKey(entity.getDsKey());
+            return vo;
+        } catch (JsonProcessingException e) {
+            log.error("Failed to deserialize data source: {}", 
entity.getDsKey(), e);
+            return DataSourceVO.builder().key(entity.getDsKey()).build();
+        }
+    }
+
+    private String toJson(DataSourceVO dataSource) {
+        try {
+            return objectMapper.writeValueAsString(dataSource);
+        } catch (JsonProcessingException e) {
+            log.error("Failed to serialize data source: {}", 
dataSource.getKey(), e);
+            throw new RuntimeException("Failed to serialize data source", e);
+        }
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqDataSource.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqDataSource.java
index 597df17e..ddc9fd8e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqDataSource.java
@@ -14,16 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_data_source")
+public class RmqDataSource {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId
+    private String dsKey;
+
+    private String json;
+
+    private LocalDateTime createdAt;
+
+    private LocalDateTime updatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqGroup.java
similarity index 57%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqGroup.java
index 597df17e..6d6cc0a1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqGroup.java
@@ -14,16 +14,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_group")
+public class RmqGroup {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String clusterId;
+
+    private String name;
+
+    private String consumeType;
+
+    private String messageModel;
+
+    private Integer maxRetry;
+
+    private String status;
+
+    private String createdBy;
+
+    private LocalDateTime createdAt;
+
+    private LocalDateTime updatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
similarity index 55%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
index 597df17e..69307dff 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
@@ -14,16 +14,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_k8s_certificate")
+public class RmqK8sCertificate {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String id;
+
+    private String name;
+
+    private String namespace;
+
+    private String cluster;
+
+    private String certType;
+
+    private String issuer;
+
+    private LocalDateTime notBefore;
+
+    private LocalDateTime notAfter;
+
+    private String status;
+
+    private Integer daysRemaining;
+
+    private String san;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqMessageQuery.java
similarity index 56%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqMessageQuery.java
index 597df17e..449a8178 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqMessageQuery.java
@@ -14,16 +14,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_message_query")
+public class RmqMessageQuery {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String queryType;
+
+    private String topic;
+
+    private String msgId;
+
+    private String tag;
+
+    private String messageKey;
+
+    private Long startTime;
+
+    private Long endTime;
+
+    private Integer resultCount;
+
+    private String queriedBy;
+
+    private LocalDateTime queriedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqNameserver.java
similarity index 58%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqNameserver.java
index 597df17e..3ef35533 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqNameserver.java
@@ -14,16 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_nameserver")
+public class RmqNameserver {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String id;
+
+    private String name;
+
+    private String namesrvAddr;
+
+    private String clusterType;
+
+    private String status;
+
+    private String description;
+
+    private LocalDateTime createdAt;
+
+    private LocalDateTime updatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqOperationAudit.java
similarity index 56%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqOperationAudit.java
index 597df17e..085d1d9a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqOperationAudit.java
@@ -14,16 +14,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_operation_audit")
+public class RmqOperationAudit {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String operation;
+
+    private String resourceType;
+
+    private String resourceName;
+
+    private String clusterId;
+
+    private String detail;
+
+    private String result;
+
+    private String errorMessage;
+
+    private String operator;
+
+    private LocalDateTime operatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSettings.java
similarity index 66%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSettings.java
index 597df17e..696f7d73 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSettings.java
@@ -14,16 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_settings")
+public class RmqSettings {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String id;
+
+    private String json;
+
+    private LocalDateTime updatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTopic.java
similarity index 54%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTopic.java
index 597df17e..d6511d17 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTopic.java
@@ -14,16 +14,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_topic")
+public class RmqTopic {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String clusterId;
+
+    private String name;
+
+    private String topicType;
+
+    private Integer readQueueNums;
+
+    private Integer writeQueueNums;
+
+    private Integer perm;
+
+    private String remark;
+
+    private String status;
+
+    private String createdBy;
+
+    private LocalDateTime createdAt;
+
+    private LocalDateTime updatedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTraceQuery.java
similarity index 60%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTraceQuery.java
index 597df17e..2dc124be 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqTraceQuery.java
@@ -14,16 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.entity;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 import java.time.LocalDateTime;
-import java.util.List;
 
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
+@Data
+@TableName("rmq_trace_query")
+public class RmqTraceQuery {
 
-    int deleteBefore(LocalDateTime cutoff);
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String msgId;
+
+    private String topic;
+
+    private Integer nodeCount;
+
+    private Integer consumerCount;
+
+    private String queriedBy;
+
+    private LocalDateTime queriedAt;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqDataSourceMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqDataSourceMapper.java
index 597df17e..70734bb2 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqDataSourceMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqDataSource;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqDataSourceMapper extends BaseMapper<RmqDataSource> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqGroupMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqGroupMapper.java
index 597df17e..50880324 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqGroupMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqGroupMapper extends BaseMapper<RmqGroup> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqK8sCertificateMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqK8sCertificateMapper.java
index 597df17e..2004af99 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqK8sCertificateMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqK8sCertificate;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqK8sCertificateMapper extends BaseMapper<RmqK8sCertificate> 
{
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqMessageQueryMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqMessageQueryMapper.java
index 597df17e..c363bf7b 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqMessageQueryMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqMessageQuery;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqMessageQueryMapper extends BaseMapper<RmqMessageQuery> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqNameserverMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqNameserverMapper.java
index 597df17e..7d6b9cc1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqNameserverMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqNameserver;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqNameserverMapper extends BaseMapper<RmqNameserver> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqOperationAuditMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqOperationAuditMapper.java
index 597df17e..cd225ace 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqOperationAuditMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqOperationAudit;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqOperationAuditMapper extends BaseMapper<RmqOperationAudit> 
{
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSettingsMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSettingsMapper.java
index 597df17e..c159887d 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSettingsMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqSettings;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqSettingsMapper extends BaseMapper<RmqSettings> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTopicMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTopicMapper.java
index 597df17e..9bcc8119 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTopicMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqTopicMapper extends BaseMapper<RmqTopic> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTraceQueryMapper.java
similarity index 68%
copy from 
server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTraceQueryMapper.java
index 597df17e..fec3cde9 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqTraceQueryMapper.java
@@ -14,16 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.ops.audit;
+package org.apache.rocketmq.studio.persistence.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqTraceQuery;
 
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface AuditRepository {
-    List<AuditRecordVO> findAll(String search, String operationType,
-                              LocalDateTime startDate, LocalDateTime endDate,
-                              String result);
-
-    int deleteBefore(LocalDateTime cutoff);
+public interface RmqTraceQueryMapper extends BaseMapper<RmqTraceQuery> {
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
index 59f0da33..692068fe 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
@@ -17,7 +17,6 @@
 package org.apache.rocketmq.studio.settings;
 
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -26,7 +25,7 @@ import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 @Slf4j
-@Component
+// @Component — replaced by MybatisPlusSettingsRepository
 public class InMemorySettingsRepository implements SettingsRepository {
 
     private GeneralSettingsVO generalSettings = GeneralSettingsVO.builder()
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
index 4ca38f44..a4bd5068 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.settings;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.studio.audit.OperationAuditService;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -56,15 +57,17 @@ public class SettingsService {
     private final SettingsRepository settingsRepository;
     private final RestClient restClient;
     private final ObjectMapper objectMapper;
+    private final OperationAuditService operationAuditService;
 
     public SettingsService(SettingsRepository settingsRepository, 
RestClient.Builder restClientBuilder,
-                           ObjectMapper objectMapper) {
+                           ObjectMapper objectMapper, OperationAuditService 
operationAuditService) {
         SimpleClientHttpRequestFactory requestFactory = new 
SimpleClientHttpRequestFactory();
         requestFactory.setConnectTimeout(DATA_SOURCE_TEST_CONNECT_TIMEOUT);
         requestFactory.setReadTimeout(DATA_SOURCE_TEST_READ_TIMEOUT);
         this.settingsRepository = settingsRepository;
         this.restClient = 
restClientBuilder.requestFactory(requestFactory).build();
         this.objectMapper = objectMapper;
+        this.operationAuditService = operationAuditService;
     }
 
 
@@ -84,6 +87,8 @@ public class SettingsService {
         }
         settings.setClearApiKey(false);
         settingsRepository.saveGeneralSettings(settings);
+        operationAuditService.record("UPDATE_SETTINGS", "SETTINGS", "general",
+                null, "General settings updated", "SUCCESS", null);
     }
 
 
diff --git a/server/src/main/resources/application-dev.yml 
b/server/src/main/resources/application-dev.yml
index 2abe64cb..401febe3 100644
--- a/server/src/main/resources/application-dev.yml
+++ b/server/src/main/resources/application-dev.yml
@@ -1,4 +1,15 @@
 # Development profile
+spring:
+  datasource:
+    url: 
jdbc:h2:mem:rocketmq_studio;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
+    username: sa
+    password:
+    driver-class-name: org.h2.Driver
+  h2:
+    console:
+      enabled: true
+      path: /h2-console
+
 logging:
   level:
     org.apache.rocketmq.studio: DEBUG
diff --git a/server/src/main/resources/application.yml 
b/server/src/main/resources/application.yml
index 131f0f27..bb6f7b69 100644
--- a/server/src/main/resources/application.yml
+++ b/server/src/main/resources/application.yml
@@ -6,6 +6,20 @@ spring:
     name: rocketmq-studio
   profiles:
     active: dev
+  datasource:
+    url: 
${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/rocketmq_studio?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC}
+    username: ${SPRING_DATASOURCE_USERNAME:root}
+    password: ${SPRING_DATASOURCE_PASSWORD:studio123}
+    driver-class-name: com.mysql.cj.jdbc.Driver
+
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+  global-config:
+    db-config:
+      table-prefix: rmq_
+      id-type: auto
 
 springdoc:
   api-docs:
@@ -28,3 +42,5 @@ studio:
       username: ${STUDIO_METRICS_PROMETHEUS_USERNAME:}
       password: ${STUDIO_METRICS_PROMETHEUS_PASSWORD:}
       bearer-token: ${STUDIO_METRICS_PROMETHEUS_BEARER_TOKEN:}
+  rocketmq:
+    namesrv-addr: ${STUDIO_ROCKETMQ_NAMESRV_ADDR:}
diff --git a/server/src/main/resources/db/schema.sql 
b/server/src/main/resources/db/schema.sql
new file mode 100644
index 00000000..f335b632
--- /dev/null
+++ b/server/src/main/resources/db/schema.sql
@@ -0,0 +1,167 @@
+-- server/src/main/resources/db/schema.sql
+-- RocketMQ Studio 数据库 Schema(MySQL 8.0)
+-- 此文件为唯一权威 DDL 来源,MyBatis-Plus Entity 与此保持同步
+-- 注意:MyBatis-Plus 不自动建表,需通过此 SQL 初始化(docker-compose 挂载执行)
+
+-- 1. NameServer / 集群地址注册表
+CREATE TABLE IF NOT EXISTS rmq_nameserver (
+  id VARCHAR(64) PRIMARY KEY,
+  name VARCHAR(128) NOT NULL,
+  namesrv_addr VARCHAR(512) NOT NULL COMMENT 'NameServer 地址,逗号分隔',
+  cluster_type VARCHAR(32) DEFAULT 'V5_PROXY_CLUSTER',
+  status VARCHAR(32) DEFAULT 'healthy',
+  description TEXT,
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 2. Topic 管理记录(通过 Studio 创建/管理的 Topic 元数据)
+CREATE TABLE IF NOT EXISTS rmq_topic (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  cluster_id VARCHAR(64) NOT NULL,
+  name VARCHAR(255) NOT NULL,
+  topic_type VARCHAR(32) DEFAULT 'NORMAL',
+  read_queue_nums INT DEFAULT 8,
+  write_queue_nums INT DEFAULT 8,
+  perm INT DEFAULT 6,
+  remark VARCHAR(255) COMMENT '业务用途备注',
+  status VARCHAR(32) DEFAULT 'ACTIVE',
+  created_by VARCHAR(64),
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  UNIQUE KEY uk_cluster_topic (cluster_id, name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 3. Consumer Group 管理记录
+CREATE TABLE IF NOT EXISTS rmq_group (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  cluster_id VARCHAR(64) NOT NULL,
+  name VARCHAR(255) NOT NULL,
+  consume_type VARCHAR(32) DEFAULT 'CONCURRENTLY',
+  message_model VARCHAR(32) DEFAULT 'CLUSTERING',
+  max_retry INT DEFAULT 16,
+  status VARCHAR(32) DEFAULT 'ACTIVE',
+  created_by VARCHAR(64),
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  UNIQUE KEY uk_cluster_group (cluster_id, name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 4. K8s 证书管理
+CREATE TABLE IF NOT EXISTS rmq_k8s_certificate (
+  id VARCHAR(64) PRIMARY KEY,
+  name VARCHAR(128) NOT NULL,
+  namespace VARCHAR(128),
+  cluster VARCHAR(128),
+  cert_type VARCHAR(32) DEFAULT 'TLS',
+  issuer VARCHAR(256),
+  not_before DATETIME,
+  not_after DATETIME,
+  status VARCHAR(32) DEFAULT 'valid',
+  days_remaining INT,
+  san TEXT COMMENT 'JSON array of SANs',
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 5. 消息查询记录
+CREATE TABLE IF NOT EXISTS rmq_message_query (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  query_type VARCHAR(32) NOT NULL COMMENT 'TOPIC/KEY/MSG_ID',
+  topic VARCHAR(255),
+  msg_id VARCHAR(128),
+  tag VARCHAR(128),
+  message_key VARCHAR(255),
+  start_time BIGINT,
+  end_time BIGINT,
+  result_count INT DEFAULT 0,
+  queried_by VARCHAR(64),
+  queried_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  INDEX idx_queried_at (queried_at),
+  INDEX idx_topic (topic)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 6. 消息轨迹查询记录
+CREATE TABLE IF NOT EXISTS rmq_trace_query (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  msg_id VARCHAR(128) NOT NULL,
+  topic VARCHAR(255),
+  node_count INT DEFAULT 0,
+  consumer_count INT DEFAULT 0,
+  queried_by VARCHAR(64),
+  queried_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  INDEX idx_msg_id (msg_id),
+  INDEX idx_queried_at (queried_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 7. 操作审计日志(所有写操作)
+CREATE TABLE IF NOT EXISTS rmq_operation_audit (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  operation VARCHAR(64) NOT NULL COMMENT 
'CREATE_TOPIC/DELETE_TOPIC/CREATE_GROUP/RESET_OFFSET/SEND_MESSAGE/UPDATE_CONFIG/...',
+  resource_type VARCHAR(64) NOT NULL COMMENT 
'TOPIC/GROUP/CLUSTER/CERT/SETTINGS',
+  resource_name VARCHAR(255),
+  cluster_id VARCHAR(64),
+  detail TEXT COMMENT 'JSON: 操作详情/变更内容',
+  result VARCHAR(16) DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAILED',
+  error_message TEXT,
+  operator VARCHAR(64),
+  operated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  INDEX idx_operated_at (operated_at),
+  INDEX idx_resource (resource_type, resource_name),
+  INDEX idx_operation (operation)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 8. 通用设置(单行)
+CREATE TABLE IF NOT EXISTS rmq_settings (
+  id VARCHAR(16) PRIMARY KEY DEFAULT 'singleton',
+  json TEXT NOT NULL COMMENT 'GeneralSettingsVO JSON',
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 9. 数据源配置
+CREATE TABLE IF NOT EXISTS rmq_data_source (
+  ds_key VARCHAR(64) PRIMARY KEY,
+  json TEXT NOT NULL COMMENT 'DataSourceVO JSON',
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ============================================================
+-- 样例数据(幂等):topic / group 列表以本库为准,创建时写库、读取时读库。
+-- 以电商交易链路为例:下单 -> 支付 -> 库存 -> 履约 -> 物流 -> 结算。
+-- cluster_id 需与 NameServer 上报的集群名一致,否则页面按集群过滤时查不到。
+-- ============================================================
+
+INSERT IGNORE INTO rmq_topic
+  (cluster_id, name, topic_type, read_queue_nums, write_queue_nums, perm, 
remark, status, created_by)
+VALUES
+  ('rocketmq-studio', 'order_create_event',        'NORMAL',      8,  8, 6,
+   '下单成功事件,履约、营销、风控多方订阅', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'order_status_change',       'FIFO',        4,  4, 6,
+   '订单状态流转,按订单号分区保证同单有序', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'order_timeout_cancel',      'DELAY',       8,  8, 6,
+   '未支付订单超时关单,延迟 30 分钟投递', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'payment_result_notify',     'TRANSACTION', 8,  8, 6,
+   '支付结果通知,与支付流水落库同事务', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'inventory_deduct_command',  'NORMAL',     16, 16, 6,
+   '库存扣减指令,大促期间扩容至 16 队列', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'logistics_tracking_update', 'NORMAL',      8,  8, 6,
+   '物流轨迹更新,承运商回调后投递', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'marketing_coupon_issue',    'NORMAL',      4,  4, 6,
+   '营销发券,活动期间异步发放优惠券', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'settlement_daily_archive',  'NORMAL',      2,  2, 4,
+   '日结账单归档,已停止写入仅供回溯消费', 'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'risk_control_audit',        'NORMAL',      4,  4, 2,
+   '风控审计流水,仅生产侧写入,消费方待接入', 'ACTIVE', 'seed');
+
+INSERT IGNORE INTO rmq_group
+  (cluster_id, name, consume_type, message_model, max_retry, status, 
created_by)
+VALUES
+  ('rocketmq-studio', 'GID_fulfillment_order',     'PUSH', 'CLUSTERING',   16, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_inventory_deduct',      'PUSH', 'CLUSTERING',   16, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_payment_result',        'PUSH', 'CLUSTERING',   16, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_logistics_tracking',    'PUSH', 'CLUSTERING',    5, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_marketing_coupon',      'PUSH', 'CLUSTERING',    3, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_settlement_archive',    'PULL', 'CLUSTERING',    3, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'GID_bi_realtime_report',    'PUSH', 'BROADCASTING',  1, 
'ACTIVE', 'seed'),
+  ('rocketmq-studio', 'studio-trace-consumer',     'PUSH', 'CLUSTERING',   16, 
'ACTIVE', 'seed');
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
index 7ff2a724..aaa343e8 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.settings;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpServer;
+import org.apache.rocketmq.studio.audit.OperationAuditService;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -49,6 +50,9 @@ class SettingsServiceTest {
     @Mock
     private SettingsRepository settingsRepository;
 
+    @Mock
+    private OperationAuditService operationAuditService;
+
     private SettingsService settingsService;
 
     private HttpServer prometheusServer;
@@ -56,7 +60,7 @@ class SettingsServiceTest {
 
     @BeforeEach
     void setUp() throws IOException {
-        settingsService = new SettingsService(settingsRepository, 
RestClient.builder(), new ObjectMapper());
+        settingsService = new SettingsService(settingsRepository, 
RestClient.builder(), new ObjectMapper(), operationAuditService);
         prometheusServer = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
         prometheusBaseUrl = "http://127.0.0.1:"; + 
prometheusServer.getAddress().getPort();
         prometheusServer.start();
@@ -225,7 +229,7 @@ class SettingsServiceTest {
 
     @Test
     void updateDataSourceShouldRejectUnknownKey() {
-        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper());
+        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(), 
operationAuditService);
         DataSourceVO input = 
DataSourceVO.builder().key("missing").name("Unexpected DS").type("rocketmq")
                 .url("unexpected-host:9876").build();
 
@@ -239,7 +243,8 @@ class SettingsServiceTest {
 
     @Test
     void updateDataSourceShouldRejectBlankKey() {
-        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper());
+        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(),
+                operationAuditService);
         DataSourceVO input = DataSourceVO.builder().key(" ").name("Unexpected 
DS").type("rocketmq")
                 .url("unexpected-host:9876").build();
 
@@ -262,7 +267,8 @@ class SettingsServiceTest {
 
     @Test
     void deleteDataSourceShouldRejectUnknownKey() {
-        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper());
+        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(),
+                operationAuditService);
 
         assertThatThrownBy(() -> service.deleteDataSource("missing"))
                 .isInstanceOf(BusinessException.class)
diff --git a/web/nginx.conf b/web/nginx.conf
index 65016a0a..0979fe79 100644
--- a/web/nginx.conf
+++ b/web/nginx.conf
@@ -9,9 +9,15 @@ server {
     }
 
     location /api/ {
-        resolver ${NGINX_LOCAL_RESOLVERS} valid=10s;
-        set $backend http://rocketmq-server:8888;
-        proxy_pass $backend;
+        proxy_pass http://rocketmq-server:8888;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+
+    location /actuator/ {
+        proxy_pass http://rocketmq-server:8888;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Reply via email to