You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by li...@apache.org on 2026/08/07 05:51:56 UTC

(rocketmq-dashboard) branch rocketmq-studio updated: refactor: unify multi-vendor architecture and backend code style (#1231)

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 ceef946f refactor: unify multi-vendor architecture and backend code style (#1231)
ceef946f is described below

commit ceef946fa2908cf66469e478c5d84e14b9811253
Author: lizhimins <70...@qq.com>
AuthorDate: Fri Aug 7 13:51:50 2026 +0800

    refactor: unify multi-vendor architecture and backend code style (#1231)
    
    * refactor: unify controller contracts, remove dead and duplicate code, use HttpStatus enums
    
    * refactor: converge multi-vendor architecture onto the instance provider SPI
    
    * refactor: unify backend code style with lombok and consolidate instance-scoped packages
    
    * chore: rename database to rocketmq and document server package structure
---
 deploy/docker-compose.yml                          |   4 +-
 deploy/mysql/init.sql                              |   4 +-
 deploy/mysql/upgrade-cloud-vendor.sql              |   2 +-
 deploy/mysql/upgrade-demo-acl.sql                  |   2 +-
 deploy/mysql/upgrade-demo-alert.sql                |   2 +-
 deploy/mysql/upgrade-demo-instance.sql             |   2 +-
 deploy/mysql/upgrade-query-history-context.sql     |   2 +-
 server/README.md                                   | 108 ++++
 .../studio/cloud/credential/CloudCredentialVO.java | 102 ----
 .../studio/cluster/broker/ClusterService.java      |   2 +-
 .../cluster/k8s/MybatisPlusK8sCertRepository.java  |   7 +-
 .../cluster/metrics/MetricsSourceFactory.java      |   7 +-
 .../cluster/metrics/MetricsSourceSettings.java     | 105 +---
 .../domain/DeleteRequestDTO.java}                  |  14 +-
 .../rocketmq/studio/common/domain/PageResult.java  |   7 +-
 .../rocketmq/studio/common/domain/Result.java      |   7 +-
 .../common/exception/GlobalExceptionHandler.java   |   6 +-
 .../studio/common/util/CredentialUtils.java        |  69 +++
 .../CreateInstanceDTO.java}                        |  58 +--
 .../studio/instance/InstanceController.java        |  20 +-
 .../studio/instance/InstanceRepository.java        |   4 +
 .../rocketmq/studio/instance/InstanceService.java  |  35 +-
 .../instance/MybatisPlusInstanceRepository.java    |  82 ++-
 ...eleteRequestDTO.java => UpdateInstanceDTO.java} |  31 +-
 .../studio/instance/acl/AclController.java         |   5 +-
 .../rocketmq/studio/instance/acl/AclService.java   |  38 +-
 .../instance/acl/MybatisPlusAclRepository.java     |  33 +-
 .../rocketmq/studio/instance/dlq/DLQService.java   |   2 +-
 .../instance/group/ConsumerDiagnosticsService.java |   3 +-
 .../message}/QueryHistoryProperties.java           |   2 +-
 .../message}/QueryHistoryService.java              |   2 +-
 .../instance/topic/CloudMetadataProvider.java      |  67 ---
 .../studio/instance/topic/MetadataService.java     | 103 ++--
 .../studio/instance/topic/NameSrvAdminClient.java  |   1 +
 .../studio/instance/topic/NamespaceController.java |  39 --
 .../apache/rocketmq/studio/model/ACLPolicy.java    | 114 +----
 .../org/apache/rocketmq/studio/model/ACLUser.java  |  82 +--
 .../rocketmq/studio/model/AccessControlList.java   |  91 +---
 .../rocketmq/studio/model/Acl2PolicyContext.java   | 154 +-----
 .../rocketmq/studio/model/ClientInstance.java      | 226 +--------
 .../rocketmq/studio/model/ClusterCapability.java   |  82 +--
 .../rocketmq/studio/model/ClusterTopology.java     | 116 +----
 .../rocketmq/studio/model/ConsumerGroupInfo.java   | 138 +-----
 .../studio/model/ConsumerMonitorConfig.java        |  31 +-
 .../org/apache/rocketmq/studio/model/Entry.java    |  36 +-
 .../rocketmq/studio/model/LiteTopicQuota.java      |  66 +--
 .../rocketmq/studio/model/LiteTopicSession.java    | 139 +-----
 .../rocketmq/studio/model/LiteTopicSummary.java    |  90 +---
 .../apache/rocketmq/studio/model/LoginInfo.java    |  19 +-
 .../apache/rocketmq/studio/model/LoginResult.java  |  26 +-
 .../apache/rocketmq/studio/model/MessageInfo.java  | 108 +---
 .../studio/model/MetricsDataSourceConfig.java      |  93 +---
 .../rocketmq/studio/model/MetricsHealthResult.java |  45 +-
 .../org/apache/rocketmq/studio/model/Policy.java   |  19 +-
 .../rocketmq/studio/model/PolicyRequest.java       |  35 +-
 .../rocketmq/studio/model/SubscriptionInfo.java    |  82 +--
 .../apache/rocketmq/studio/model/TopicInfo.java    | 146 +-----
 .../org/apache/rocketmq/studio/model/User.java     |  47 +-
 .../org/apache/rocketmq/studio/model/UserInfo.java |  45 +-
 .../model/request/ArchitectureSwitchRequest.java   |  29 +-
 .../studio/model/request/MessageQuery.java         |  51 +-
 .../request/MetricsDataSourceQueryRequest.java     |  34 +-
 .../model/request/MetricsDataSourceRequest.java    | 106 +---
 .../studio/model/request/ResetOffsetRequest.java   |  34 +-
 .../model/request/SendTopicMessageRequest.java     |  42 +-
 .../studio/model/request/TopicTypeList.java        |  18 +-
 .../studio/model/request/TopicTypeMeta.java        |  19 +-
 .../studio/model/request/UserCreateRequest.java    |  26 +-
 .../studio/model/request/UserInfoParam.java        |  34 +-
 .../studio/model/request/UserUpdateRequest.java    |  26 +-
 .../studio/model/trace/MessageTraceGraph.java      |  28 +-
 .../rocketmq/studio/model/trace/ProducerNode.java  |  67 +--
 .../studio/model/trace/SubscriptionNode.java       |  19 +-
 .../rocketmq/studio/model/trace/TraceNode.java     |  98 +---
 .../rocketmq/studio/ops/ai/LlmConfigDTO.java       |  51 ++
 .../rocketmq/studio/ops/ai/LlmConfigService.java   |  25 +-
 .../rocketmq/studio/ops/ai/LlmController.java      |  19 +-
 .../rocketmq/studio/ops/ai/tool/ToolCatalog.java   |  16 +-
 .../ops/alert/MybatisPlusAlertRepository.java      |   7 +-
 .../ops/audit/MybatisPlusAuditRepository.java      |   6 +-
 .../persistence/entity/RmqCloudCredential.java     |  66 +--
 .../studio/provider/CloudInstanceDetailVO.java     |  79 +--
 .../studio/provider/CloudInstanceOptionVO.java     |  59 +--
 .../rocketmq/studio/provider/CloudRegionVO.java    |  31 +-
 .../rocketmq/studio/provider/InstanceProvider.java |   4 +
 .../provider/alibaba/AliyunCatalogController.java  |   6 +-
 .../provider/alibaba/AliyunCatalogService.java     |   6 +-
 .../provider/alibaba/AliyunClientFactory.java      |  16 +-
 .../provider/alibaba/AliyunInstanceProvider.java   |  51 +-
 .../topic => provider/apache}/AdminClient.java     |   5 +-
 .../provider/apache/ApacheInstanceProvider.java    |  24 +-
 .../apache}/MetadataProvider.java                  |   5 +-
 .../provider/apache/RocketMQAdminClientImpl.java   | 528 ++++++++++++++++++++
 .../apache}/RocketMQBrokerConfigService.java       |  65 +--
 .../apache}/RocketMQClientProvider.java            |  76 ++-
 .../apache}/RocketMQClusterProvider.java           | 109 ++--
 .../apache}/RocketMQDLQProvider.java               |  16 +-
 .../apache}/RocketMQDashboardProvider.java         |  40 +-
 .../apache}/RocketMQMessageProvider.java           |  24 +-
 .../apache}/RocketMQMetadataProvider.java          | 376 ++++++--------
 .../apache}/RocketMQProperties.java                |   4 +-
 .../apache}/ShortLivedClientName.java              |   2 +-
 .../credential/CloudCredentialController.java      |  11 +-
 .../credential/CloudCredentialRepository.java      |   2 +-
 .../credential/CloudCredentialService.java         |  50 +-
 .../credential/CloudCredentialVO.java}             |  14 +-
 .../credential/CreateCloudCredentialDTO.java       |  44 +-
 .../MybatisPlusCloudCredentialRepository.java      |  34 +-
 .../credential/UpdateCloudCredentialDTO.java}      |  16 +-
 .../provider/tencent/TencentInstanceProvider.java  |  10 +
 .../studio/rocketmq/RocketMQAdminClientImpl.java   | 550 ---------------------
 .../studio/rocketmq/RocketMQAdminConfig.java       |  44 --
 .../DataSourceDTO.java}                            |  35 +-
 .../studio/settings/SettingsController.java        |  15 +-
 server/src/main/resources/application-dev.yml      |   2 +-
 server/src/main/resources/application.yml          |   2 +-
 .../studio/cluster/broker/ClusterServiceTest.java  |   2 +-
 .../studio/instance/InstanceControllerTest.java    |   4 +-
 .../studio/instance/InstanceServiceTest.java       |  53 +-
 .../MybatisPlusInstanceRepositoryTest.java         |  43 +-
 .../message}/QueryHistoryServiceTest.java          |   2 +-
 .../instance/topic/CloudMetadataProviderTest.java  |  43 --
 .../studio/instance/topic/MetadataServiceTest.java |  42 +-
 .../instance/topic/NamespaceControllerTest.java    |  53 --
 .../org/apache/rocketmq/studio/model/UserTest.java |   4 +-
 .../rocketmq/studio/ops/ai/LlmControllerTest.java  |  27 +-
 .../provider/alibaba/AliyunClientFactoryTest.java  |   4 +-
 .../alibaba/AliyunInstanceProviderTest.java        |  39 ++
 .../apache/ApacheInstanceProviderTest.java         |  67 +++
 .../apache}/RocketMQAdminClientImplTest.java       |  11 +-
 .../apache}/RocketMQClientProviderTest.java        |  15 +-
 .../apache}/RocketMQClusterProviderTest.java       |  26 +-
 .../apache}/RocketMQDLQProviderTest.java           |   2 +-
 .../apache}/RocketMQDashboardProviderTest.java     |  22 +-
 .../apache}/RocketMQMessageProviderTest.java       |  11 +-
 .../apache}/RocketMQMetadataProviderTest.java      |  19 +-
 .../apache}/ShortLivedClientNameTest.java          |   2 +-
 .../credential/CloudCredentialServiceTest.java     |   9 +-
 .../tencent/TencentInstanceProviderTest.java       |   4 +
 .../studio/settings/SettingsControllerTest.java    |   4 +-
 web/src/api/llm.test.ts                            |  51 +-
 web/src/api/llm.ts                                 |  16 +-
 142 files changed, 1938 insertions(+), 4820 deletions(-)

diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index e7883c06..9c08c7e0 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -8,7 +8,7 @@ services:
     environment:
       TZ: ${TZ:-Asia/Shanghai}
       MYSQL_ROOT_PASSWORD: studio123
-      MYSQL_DATABASE: rocketmq_studio
+      MYSQL_DATABASE: rocketmq
     ports:
       - "3306:3306"
     volumes:
@@ -41,7 +41,7 @@ services:
       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_URL: jdbc:mysql://mysql:3306/rocketmq?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}
diff --git a/deploy/mysql/init.sql b/deploy/mysql/init.sql
index 51b52ac6..f361c4d3 100644
--- a/deploy/mysql/init.sql
+++ b/deploy/mysql/init.sql
@@ -1,4 +1,4 @@
 SET NAMES utf8mb4;
-CREATE DATABASE IF NOT EXISTS rocketmq_studio DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-USE rocketmq_studio;
+CREATE DATABASE IF NOT EXISTS rocketmq DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE rocketmq;
 SOURCE /docker-entrypoint-initdb.d/schema.sql;
diff --git a/deploy/mysql/upgrade-cloud-vendor.sql b/deploy/mysql/upgrade-cloud-vendor.sql
index 8286a1bb..26684247 100644
--- a/deploy/mysql/upgrade-cloud-vendor.sql
+++ b/deploy/mysql/upgrade-cloud-vendor.sql
@@ -5,7 +5,7 @@
 -- 幂等:可重复执行。
 --
 -- 用法(远程容器内执行):
---   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq_studio < upgrade-cloud-vendor.sql
+--   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq < upgrade-cloud-vendor.sql
 
 -- 固定连接编码,防止 mysql 客户端以 latin1 解释 UTF-8 字节导致中文双重编码
 SET NAMES utf8mb4;
diff --git a/deploy/mysql/upgrade-demo-acl.sql b/deploy/mysql/upgrade-demo-acl.sql
index 75005217..acdbbd25 100644
--- a/deploy/mysql/upgrade-demo-acl.sql
+++ b/deploy/mysql/upgrade-demo-acl.sql
@@ -5,7 +5,7 @@
 -- 幂等:可重复执行。
 --
 -- 用法(远程容器内执行):
---   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq_studio < upgrade-demo-acl.sql
+--   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq < upgrade-demo-acl.sql
 
 -- 固定连接编码,防止 mysql 客户端以 latin1 解释 UTF-8 字节导致中文双重编码
 SET NAMES utf8mb4;
diff --git a/deploy/mysql/upgrade-demo-alert.sql b/deploy/mysql/upgrade-demo-alert.sql
index 904be1f9..de524ade 100644
--- a/deploy/mysql/upgrade-demo-alert.sql
+++ b/deploy/mysql/upgrade-demo-alert.sql
@@ -5,7 +5,7 @@
 -- 幂等:可重复执行。
 --
 -- 用法(远程容器内执行):
---   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq_studio < upgrade-demo-alert.sql
+--   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq < upgrade-demo-alert.sql
 
 -- 固定连接编码,防止 mysql 客户端以 latin1 解释 UTF-8 字节导致中文双重编码
 SET NAMES utf8mb4;
diff --git a/deploy/mysql/upgrade-demo-instance.sql b/deploy/mysql/upgrade-demo-instance.sql
index 0df0a17b..1dc8e585 100644
--- a/deploy/mysql/upgrade-demo-instance.sql
+++ b/deploy/mysql/upgrade-demo-instance.sql
@@ -5,7 +5,7 @@
 -- 幂等:可重复执行。
 --
 -- 用法(远程容器内执行):
---   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq_studio < upgrade-demo-instance.sql
+--   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq < upgrade-demo-instance.sql
 
 -- 固定连接编码,防止 mysql 客户端以 latin1 解释 UTF-8 字节导致中文双重编码
 SET NAMES utf8mb4;
diff --git a/deploy/mysql/upgrade-query-history-context.sql b/deploy/mysql/upgrade-query-history-context.sql
index 8520db44..d7a9ae1e 100644
--- a/deploy/mysql/upgrade-query-history-context.sql
+++ b/deploy/mysql/upgrade-query-history-context.sql
@@ -3,7 +3,7 @@
 -- Fresh volumes already receive these columns from server/src/main/resources/db/schema.sql.
 --
 -- Run once on existing deployments:
---   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq_studio < upgrade-query-history-context.sql
+--   docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123 rocketmq < upgrade-query-history-context.sql
 
 SET @schema_name := DATABASE();
 
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 00000000..ffca74e3
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,108 @@
+# RocketMQ Studio Server — 后端包结构
+
+Spring Boot 3.5 / Java 21 / MyBatis-Plus 单体后端,包根 `org.apache.rocketmq.studio`,
+入口 `StudioApplication`。全部源码约 367 个 Java 文件,按业务域划包,本文档描述各包职责与分层约定。
+
+## 顶层包总览
+
+```
+org.apache.rocketmq.studio
+├── StudioApplication          # Spring Boot 入口
+├── auth                       # 登录认证与会话(10 文件)
+├── audit                      # 操作审计(1 文件)
+├── cluster                    # 集群域:拓扑/集群/Proxy/NameServer/指标/K8s(75 文件)
+├── common                     # 公共基础:domain/exception/util/config(26 文件)
+├── instance                   # 实例域:实例注册 + Topic/Group/消息/DLQ/ACL/查询历史(68 文件)
+├── model                      # 遗留共享模型(admin 客户端交互对象)(41 文件)
+├── ops                        # 运维域:AI 助手/告警/审计/Dashboard(73 文件)
+├── persistence                # MyBatis-Plus 实体与 Mapper(32 文件)
+├── provider                   # 多厂商 SPI、实现与云凭据(apache/alibaba/tencent/credential)(32 文件)
+└── settings                   # 通用设置与数据源(9 文件)
+```
+
+## 各包职责
+
+### auth — 认证
+登录/登出、`AuthInterceptor`(读者/管理员两级权限,凭据 reveal 类 GET 接口强制 admin)、
+`AuthenticatedUserContext` 会话上下文。
+
+### cluster — 集群域
+| 子包 | 职责 |
+|---|---|
+| `cluster.broker` | 集群拓扑、Broker 配置读写、`MqAdminExtFactory`(按 namesrv 地址缓存 admin 客户端)、`RuntimeAdminClientResolver`(按实例 endpoint 解析) |
+| `cluster.nameserver` | NameServer 地址注册表 CRUD |
+| `cluster.proxy` | Proxy 节点管理(列表/重启/地址维护、兼容旧 `.do` 路径) |
+| `cluster.client` | 客户端连接信息 |
+| `cluster.metrics` | Prometheus 指标接入(多数据源、健康检查),`metrics.grafana` 子包提供 Grafana 面板嵌入 |
+| `cluster.k8s` | K8s 证书管理 |
+| `cluster.config` | Broker 配置更新 DTO/VO |
+
+### common — 公共基础
+- `common.domain`:`Result<T>` 统一响应包装、`PageResult`、`BaseEntity`(id/createdAt/updatedAt)、
+  `DeleteRequestDTO`(通用删除入参)、`enums/`(InstanceType、InstanceVendor、TopicType 等 17 个枚举)
+- `common.exception`:`BusinessException(code, msg)` + `GlobalExceptionHandler`(统一转 `Result`)
+- `common.util`:`CredentialUtils` 等共享工具
+- `common.config`:CORS / Web MVC 配置
+
+### instance — 实例域(核心业务)
+实例注册与实例维度的资源管理,REST 基路径 `/api/instances`、`/api/topics`、`/api/groups`、
+`/api/messages`、`/api/dlq`、`/api/instance-acl`:
+
+| 子包 | 职责 |
+|---|---|
+| `instance`(顶层) | 实例 CRUD;vendor 分支创建(APACHE 手填 endpoint / ALIYUN 经云目录选择 / TENCENT 501) |
+| `instance.topic` | Topic CRUD/路由/订阅者、`MetadataService`(按 instanceId 路由到 provider)、消息发送、LiteTopic 子系统 |
+| `instance.group` | 消费组 CRUD/进度/订阅/重置位点/诊断栈 |
+| `instance.message` | 消息查询与轨迹(`MessageProvider` SPI)、查询历史(与实例绑定,随实例上下文记录/回放) |
+| `instance.dlq` | 死信队列列表与重发(`DLQProvider` SPI) |
+| `instance.acl` | 实例 ACL 规则与用户(凭据打码/reveal 的先例实现) |
+
+### provider — 多厂商 SPI
+统一实例操作抽象,按实例 vendor 路由:
+- `InstanceProvider`:topic/group CRUD、进度、订阅、重置位点、消息查询、轨迹(首参均为 Studio 实例 id)
+- `CloudCatalogProvider`:云目录发现(列 region / 列云实例 / 实例详情)
+- `InstanceProviderRegistry`:vendor → provider 路由,`byInstanceId` 查实例分发
+- `provider.apache`:开源实现层——`MetadataProvider`/`AdminClient` 接口为本包内部 SPI,
+  `RocketMQMetadataProvider`/`RocketMQAdminClientImpl` 经 `MqAdminExtFactory`(按 namesrv 地址缓存、
+  懒建连接)执行 admin 操作;`ApacheInstanceProvider` 委托这些 bean,开源行为零差异;
+  `RocketMQMessage/DLQ/Client/Cluster/DashboardProvider` 与 Broker 配置服务也在本包
+- `provider.alibaba`:阿里云 RocketMQ 5.x OpenAPI 完整实现(`AliyunClientFactory` 按
+  credential#region 缓存 AsyncClient、异常统一映射、`AliyunConverters` 集中模型转换、
+  `/api/cloud/aliyun/*` 目录端点)
+- `provider.tencent`:占位实现(全部 `UnsupportedOperationException` → 501)
+- `provider.credential`:云厂商凭据管理(`rmq_cloud_credential` 表 CRUD,`/api/cloud-credentials`):
+  vendor+access_key 唯一键,SK base64 存储,列表打码 + `/{id}/credentials` reveal 接口,
+  编解码与打码统一走 `common.util.CredentialUtils`
+
+### settings / audit
+通用设置(`rmq_settings`)、外部数据源配置与连通性测试、写操作审计。
+
+## 分层与命名约定
+
+- **Controller → Service → Repository**:Controller 只做入参校验与 `Result` 包装;
+  Repository 接口 + `MybatisPlus*` 实现(含 entity↔VO 映射)
+- **入参/出参**:写操作入参统一 `Create/Update/Delete*DTO`(jakarta validation),
+  出参统一 `*VO`;禁止写接口直接收 VO
+- **异常**:`BusinessException(400, msg)` 裸数字码(400/401/403/404/409/500/501/502/503/504),
+  `GlobalExceptionHandler` 统一转 `Result`
+- **返回值**:REST 一律 `Result<T>` 包装
+- **Lombok**:POJO `@Data`/`@Builder`/`@NoArgsConstructor`/`@AllArgsConstructor`,
+  服务 `@RequiredArgsConstructor`,日志 `@Slf4j`
+- **SPI 模式**:跨实现的能力(消息、DLQ、诊断、Dashboard、多厂商)定义 Provider 接口,
+  开源实现统一放 `provider/apache/`(admin 客户端经 `MqAdminExtFactory` 单轨获取),
+  云厂商实现放 `provider/<vendor>/` 保持高内聚
+- **敏感字段**:VO 上 `@ToString.Exclude`;存储 base64(见 `CredentialUtils`);
+  列表打码、reveal 接口 admin-only
+
+## 构建与测试
+
+```bash
+cd server
+mvn -B -ntp package -DskipTests      # 构建(checkstyle 在 validate 阶段强制)
+mvn -B -ntp -T 32 test               # 全量单测(752 个)
+```
+
+依赖纪律:RocketMQ 系依赖(`org.apache.rocketmq:*`)只用 Apache 开源版本(当前基线 5.5.0),
+禁止内部/商业版本号;禁用 `com.aliyun.openservices:ons-client`,客户端收发用开源
+`rocketmq-client`;云厂商管控面走 OpenAPI SDK(`alibabacloud-rocketmq20220801` /
+`tencentcloud-sdk-java-tdmq`)。
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialVO.java b/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialVO.java
deleted file mode 100644
index 275f29b2..00000000
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialVO.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.cloud.credential;
-
-import org.apache.rocketmq.studio.common.domain.BaseEntity;
-import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
-
-import java.util.Objects;
-
-public class CloudCredentialVO extends BaseEntity {
-
-    private String name;
-    private InstanceVendor vendor;
-    private String accessKey;
-    private String secretKey;
-    private String remark;
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public InstanceVendor getVendor() {
-        return vendor;
-    }
-
-    public void setVendor(InstanceVendor vendor) {
-        this.vendor = vendor;
-    }
-
-    public String getAccessKey() {
-        return accessKey;
-    }
-
-    public void setAccessKey(String accessKey) {
-        this.accessKey = accessKey;
-    }
-
-    public String getSecretKey() {
-        return secretKey;
-    }
-
-    public void setSecretKey(String secretKey) {
-        this.secretKey = secretKey;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof CloudCredentialVO)) {
-            return false;
-        }
-        CloudCredentialVO that = (CloudCredentialVO) o;
-        return Objects.equals(getId(), that.getId())
-                && Objects.equals(name, that.name)
-                && vendor == that.vendor
-                && Objects.equals(accessKey, that.accessKey)
-                && Objects.equals(secretKey, that.secretKey)
-                && Objects.equals(remark, that.remark)
-                && Objects.equals(getCreatedAt(), that.getCreatedAt())
-                && Objects.equals(getUpdatedAt(), that.getUpdatedAt());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getId(), name, vendor, accessKey, secretKey, remark,
-                getCreatedAt(), getUpdatedAt());
-    }
-
-    @Override
-    public String toString() {
-        return "CloudCredentialVO{id=" + getId() + ", name=" + name + ", vendor=" + vendor
-                + ", accessKey=" + accessKey + ", secretKey=****}";
-    }
-}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
index a8099614..4b8c76ae 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
@@ -31,7 +31,7 @@ import org.apache.rocketmq.studio.cluster.proxy.RestartProxyDTO;
 import org.apache.rocketmq.studio.common.domain.enums.FlushDiskType;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.ops.audit.AuditService;
-import org.apache.rocketmq.studio.rocketmq.RocketMQBrokerConfigService;
+import org.apache.rocketmq.studio.provider.apache.RocketMQBrokerConfigService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/MybatisPlusK8sCertRepository.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/MybatisPlusK8sCertRepository.java
index ba7be5db..686f4d1e 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/MybatisPlusK8sCertRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/MybatisPlusK8sCertRepository.java
@@ -26,6 +26,7 @@ import org.apache.rocketmq.studio.persistence.entity.RmqK8sCertificate;
 import org.apache.rocketmq.studio.persistence.mapper.RmqK8sCertificateMapper;
 import org.springframework.stereotype.Repository;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
 
 import java.time.LocalDateTime;
 import java.util.List;
@@ -36,17 +37,13 @@ import java.util.stream.Collectors;
  * MySQL-backed K8s certificate repository. The SAN list is stored as a JSON
  * array string.
  */
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusK8sCertRepository implements K8sCertRepository {
 
     private final RmqK8sCertificateMapper certMapper;
     private final ObjectMapper objectMapper;
 
-    public MybatisPlusK8sCertRepository(RmqK8sCertificateMapper certMapper, ObjectMapper objectMapper) {
-        this.certMapper = certMapper;
-        this.objectMapper = objectMapper;
-    }
-
     @Override
     public List<K8sCertVO> findAll() {
         return certMapper.selectList(new QueryWrapper<RmqK8sCertificate>().orderByAsc("name")).stream()
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceFactory.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceFactory.java
index 7e50172e..33fd391f 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceFactory.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceFactory.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.metrics;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestClient;
+import lombok.RequiredArgsConstructor;
 
 import java.time.Duration;
 
@@ -30,17 +31,13 @@ import java.time.Duration;
  * query/parse logic via {@link AbstractPrometheusCompatibleMetricsSource}.
  * </p>
  */
+@RequiredArgsConstructor
 @Component
 public class MetricsSourceFactory {
 
     private final RestClient.Builder restClientBuilder;
     private final ObjectMapper objectMapper;
 
-    public MetricsSourceFactory(RestClient.Builder restClientBuilder, ObjectMapper objectMapper) {
-        this.restClientBuilder = restClientBuilder;
-        this.objectMapper = objectMapper;
-    }
-
     public MetricsSource create(org.apache.rocketmq.studio.model.MetricsDataSourceConfig config) {
         MetricsBackendType backendType = MetricsBackendType.fromProviderType(config.getProviderType());
         MetricsSourceSettings settings = MetricsSourceSettings.builder()
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceSettings.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceSettings.java
index 6e60146f..a38aa280 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceSettings.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/MetricsSourceSettings.java
@@ -16,6 +16,9 @@
  */
 package org.apache.rocketmq.studio.cluster.metrics;
 
+import lombok.Builder;
+import lombok.Getter;
+
 import java.time.Duration;
 
 /**
@@ -26,108 +29,22 @@ import java.time.Duration;
  * produced by {@link MetricsSourceFactory} from a data source configuration.
  * </p>
  */
+@Getter
+@Builder
 public class MetricsSourceSettings {
 
-    private final MetricsBackendType backendType;
+    @Builder.Default
+    private final MetricsBackendType backendType = MetricsBackendType.PROMETHEUS;
     private final String baseUrl;
-    private final Duration connectTimeout;
-    private final Duration readTimeout;
+    @Builder.Default
+    private final Duration connectTimeout = Duration.ofSeconds(3);
+    @Builder.Default
+    private final Duration readTimeout = Duration.ofSeconds(10);
     private final String username;
     private final String password;
     private final String bearerToken;
 
-    private MetricsSourceSettings(Builder builder) {
-        this.backendType = builder.backendType;
-        this.baseUrl = builder.baseUrl;
-        this.connectTimeout = builder.connectTimeout;
-        this.readTimeout = builder.readTimeout;
-        this.username = builder.username;
-        this.password = builder.password;
-        this.bearerToken = builder.bearerToken;
-    }
-
-    public MetricsBackendType getBackendType() {
-        return backendType;
-    }
-
     public String getQueryPath() {
         return backendType.getQueryPath();
     }
-
-    public String getBaseUrl() {
-        return baseUrl;
-    }
-
-    public Duration getConnectTimeout() {
-        return connectTimeout;
-    }
-
-    public Duration getReadTimeout() {
-        return readTimeout;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public String getBearerToken() {
-        return bearerToken;
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    public static class Builder {
-        private MetricsBackendType backendType = MetricsBackendType.PROMETHEUS;
-        private String baseUrl;
-        private Duration connectTimeout = Duration.ofSeconds(3);
-        private Duration readTimeout = Duration.ofSeconds(10);
-        private String username;
-        private String password;
-        private String bearerToken;
-
-        public Builder backendType(MetricsBackendType backendType) {
-            this.backendType = backendType;
-            return this;
-        }
-
-        public Builder baseUrl(String baseUrl) {
-            this.baseUrl = baseUrl;
-            return this;
-        }
-
-        public Builder connectTimeout(Duration connectTimeout) {
-            this.connectTimeout = connectTimeout;
-            return this;
-        }
-
-        public Builder readTimeout(Duration readTimeout) {
-            this.readTimeout = readTimeout;
-            return this;
-        }
-
-        public Builder username(String username) {
-            this.username = username;
-            return this;
-        }
-
-        public Builder password(String password) {
-            this.password = password;
-            return this;
-        }
-
-        public Builder bearerToken(String bearerToken) {
-            this.bearerToken = bearerToken;
-            return this;
-        }
-
-        public MetricsSourceSettings build() {
-            return new MetricsSourceSettings(this);
-        }
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java b/server/src/main/java/org/apache/rocketmq/studio/common/domain/DeleteRequestDTO.java
similarity index 80%
copy from server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
copy to server/src/main/java/org/apache/rocketmq/studio/common/domain/DeleteRequestDTO.java
index 2e051bb1..b01188ba 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/domain/DeleteRequestDTO.java
@@ -14,19 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.instance.acl;
+package org.apache.rocketmq.studio.common.domain;
 
 import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
+/**
+ * Common delete-by-id request body shared by all delete endpoints.
+ */
 @Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class AclDeleteRequestDTO {
+public class DeleteRequestDTO {
+
     @NotBlank(message = "id is required")
     private String id;
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/common/domain/PageResult.java b/server/src/main/java/org/apache/rocketmq/studio/common/domain/PageResult.java
index 5f11e861..ae7507cd 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/common/domain/PageResult.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/domain/PageResult.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.common.domain;
 
+import lombok.Getter;
 import java.util.Collections;
 import java.util.List;
 
+@Getter
 public class PageResult<T> {
     private List<T> items;
     private long total;
@@ -39,9 +41,4 @@ public class PageResult<T> {
     public static <T> PageResult<T> empty(int page, int size) {
         return of(Collections.emptyList(), 0, page, size);
     }
-
-    public List<T> getItems() { return items; }
-    public long getTotal() { return total; }
-    public int getPage() { return page; }
-    public int getSize() { return size; }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/common/domain/Result.java b/server/src/main/java/org/apache/rocketmq/studio/common/domain/Result.java
index d86705fc..2e09bdf2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/common/domain/Result.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/domain/Result.java
@@ -16,6 +16,9 @@
  */
 package org.apache.rocketmq.studio.common.domain;
 
+import lombok.Getter;
+
+@Getter
 public class Result<T> {
     private int code;
     private String message;
@@ -40,8 +43,4 @@ public class Result<T> {
     public static <T> Result<T> error(int code, String message) {
         return new Result<>(code, message, null);
     }
-
-    public int getCode() { return code; }
-    public String getMessage() { return message; }
-    public T getData() { return data; }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandler.java b/server/src/main/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandler.java
index 98297fc5..384c16a6 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandler.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/exception/GlobalExceptionHandler.java
@@ -19,8 +19,6 @@ package org.apache.rocketmq.studio.common.exception;
 import org.apache.rocketmq.studio.cluster.metrics.PrometheusException;
 import org.apache.rocketmq.studio.common.domain.Result;
 import org.apache.rocketmq.studio.ops.ai.LlmGatewayException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.converter.HttpMessageNotReadableException;
@@ -30,12 +28,12 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import lombok.extern.slf4j.Slf4j;
 
+@Slf4j
 @RestControllerAdvice
 public class GlobalExceptionHandler {
 
-    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
-
     @ExceptionHandler(BusinessException.class)
     public ResponseEntity<Result<?>> handleBusinessException(BusinessException ex) {
         log.warn("Business exception: {}", ex.getMessage());
diff --git a/server/src/main/java/org/apache/rocketmq/studio/common/util/CredentialUtils.java b/server/src/main/java/org/apache/rocketmq/studio/common/util/CredentialUtils.java
new file mode 100644
index 00000000..f665f800
--- /dev/null
+++ b/server/src/main/java/org/apache/rocketmq/studio/common/util/CredentialUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.common.util;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * Shared credential helpers: secrets are stored base64-encoded (never plain text) and only
+ * partially shown in list views.
+ */
+public final class CredentialUtils {
+
+    private static final int VISIBLE_CREDENTIAL_CHARS = 4;
+    private static final int MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS = 17;
+    private static final String CREDENTIAL_MASK = "****";
+
+    private CredentialUtils() {
+    }
+
+    public static String encodeBase64(String plainText) {
+        if (plainText == null) {
+            return null;
+        }
+        return Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public static String decodeBase64(String stored) {
+        if (stored == null) {
+            return null;
+        }
+        try {
+            return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8);
+        } catch (IllegalArgumentException ex) {
+            // tolerate legacy values that were stored without encoding
+            return stored;
+        }
+    }
+
+    /**
+     * Keeps the first and last few characters visible, hides everything else; short values are
+     * fully masked.
+     */
+    public static String mask(String value) {
+        if (value == null || value.isEmpty()) {
+            return value;
+        }
+        if (value.length() < MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS) {
+            return CREDENTIAL_MASK;
+        }
+        return value.substring(0, VISIBLE_CREDENTIAL_CHARS)
+                + CREDENTIAL_MASK
+                + value.substring(value.length() - VISIBLE_CREDENTIAL_CHARS);
+    }
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/UpdateCloudCredentialDTO.java b/server/src/main/java/org/apache/rocketmq/studio/instance/CreateInstanceDTO.java
similarity index 50%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/UpdateCloudCredentialDTO.java
rename to server/src/main/java/org/apache/rocketmq/studio/instance/CreateInstanceDTO.java
index 067c58f6..f338dd16 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/UpdateCloudCredentialDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/CreateInstanceDTO.java
@@ -14,50 +14,42 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.instance;
 
-import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
+import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 
-public class UpdateCloudCredentialDTO {
-
-    @NotBlank(message = "credential id is required")
-    private String id;
+@Data
+public class CreateInstanceDTO {
 
     private String name;
 
-    private String secretKey;
-
-    private String remark;
-
-    public String getId() {
-        return id;
-    }
+    private InstanceType type;
 
-    public void setId(String id) {
-        this.id = id;
-    }
+    private String endpoint;
 
-    public String getName() {
-        return name;
-    }
+    private String remark;
 
-    public void setName(String name) {
-        this.name = name;
-    }
+    private InstanceVendor vendor;
 
-    public String getSecretKey() {
-        return secretKey;
-    }
+    private String cloudInstanceId;
 
-    public void setSecretKey(String secretKey) {
-        this.secretKey = secretKey;
-    }
+    private String credentialId;
 
-    public String getRemark() {
-        return remark;
-    }
+    private String regionId;
 
-    public void setRemark(String remark) {
-        this.remark = remark;
+    public InstanceVO toInstanceVO() {
+        InstanceVO vo = InstanceVO.builder()
+                .name(name)
+                .type(type)
+                .endpoint(endpoint)
+                .remark(remark)
+                .vendor(vendor)
+                .cloudInstanceId(cloudInstanceId)
+                .credentialId(credentialId)
+                .regionId(regionId)
+                .build();
+        return vo;
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
index ff9d0e83..ec480a75 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
@@ -17,9 +17,9 @@
 
 package org.apache.rocketmq.studio.instance;
 
+import org.apache.rocketmq.studio.common.domain.DeleteRequestDTO;
 import org.apache.rocketmq.studio.common.domain.Result;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
-import org.apache.rocketmq.studio.common.exception.BusinessException;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -46,26 +46,18 @@ public class InstanceController {
     }
 
     @PostMapping("/create")
-    public Result<InstanceVO> createInstance(@RequestBody(required = false) InstanceVO instance) {
-        requireInstance(instance);
-        return Result.ok(instanceService.createInstance(instance));
+    public Result<InstanceVO> createInstance(@Valid @RequestBody CreateInstanceDTO request) {
+        return Result.ok(instanceService.createInstance(request.toInstanceVO()));
     }
 
     @PostMapping("/update")
-    public Result<InstanceVO> updateInstance(@RequestBody(required = false) InstanceVO instance) {
-        requireInstance(instance);
-        return Result.ok(instanceService.updateInstance(instance));
+    public Result<InstanceVO> updateInstance(@Valid @RequestBody UpdateInstanceDTO request) {
+        return Result.ok(instanceService.updateInstance(request.toInstanceVO()));
     }
 
     @PostMapping("/delete")
-    public Result<Void> deleteInstance(@Valid @RequestBody InstanceDeleteRequestDTO request) {
+    public Result<Void> deleteInstance(@Valid @RequestBody DeleteRequestDTO request) {
         instanceService.deleteInstance(request.getId());
         return Result.ok();
     }
-
-    private void requireInstance(InstanceVO instance) {
-        if (instance == null) {
-            throw new BusinessException(400, "Instance request is required");
-        }
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
index f6c41f4d..546db9b6 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
@@ -38,4 +38,8 @@ public interface InstanceRepository {
     void deleteById(String id);
 
     boolean existsByCredentialId(String credentialId);
+
+    long countTopicsByInstance(String instanceId);
+
+    long countGroupsByInstance(String instanceId);
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
index 18b33e9e..95014da8 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
@@ -17,12 +17,15 @@
 
 package org.apache.rocketmq.studio.instance;
 
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialRepository;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialVO;
+import org.springframework.util.StringUtils;
+
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialRepository;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialVO;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
+import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.apache.rocketmq.studio.provider.InstanceProviderRegistry;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -55,25 +58,22 @@ public class InstanceService {
         } else {
             instances = instanceRepository.findAll();
         }
-        instances.forEach(this::fillCloudCounts);
+        instances.forEach(this::fillCounts);
         return instances;
     }
 
     /**
-     * Cloud instances keep topics/groups on the vendor side, so the local table counts are
-     * always zero; pull live counts through the vendor provider instead.
+     * Resource counts live on the vendor side (cloud APIs) or in the local tables (Apache),
+     * so resolve them uniformly through the vendor provider.
      */
-    private void fillCloudCounts(InstanceVO instance) {
-        if (instance.getVendor() == null || instance.getVendor() == InstanceVendor.APACHE) {
-            return;
-        }
+    private void fillCounts(InstanceVO instance) {
+        InstanceVendor vendor = instance.getVendor() == null ? InstanceVendor.APACHE : instance.getVendor();
         try {
-            org.apache.rocketmq.studio.provider.InstanceProvider provider =
-                    providerRegistry.forVendor(instance.getVendor());
-            instance.setTopicCount(provider.listTopics(instance.getId(), null, null).size());
-            instance.setConsumerGroupCount(provider.listConsumerGroups(instance.getId(), null).size());
+            InstanceProvider provider = providerRegistry.forVendor(vendor);
+            instance.setTopicCount(provider.countTopics(instance.getId()));
+            instance.setConsumerGroupCount(provider.countGroups(instance.getId()));
         } catch (RuntimeException ex) {
-            log.warn("Failed to load cloud resource counts for instance {}: {}",
+            log.warn("Failed to load resource counts for instance {}: {}",
                     instance.getId(), ex.getMessage());
         }
     }
@@ -115,8 +115,8 @@ public class InstanceService {
         if (instance.getEndpoint() != null && !instance.getEndpoint().isBlank()) {
             throw new BusinessException(400, "Commercial instances must be selected from the cloud catalog, endpoint cannot be set manually");
         }
-        if (isBlank(instance.getCredentialId()) || isBlank(instance.getCloudInstanceId())
-                || isBlank(instance.getRegionId())) {
+        if (!StringUtils.hasText(instance.getCredentialId()) || !StringUtils.hasText(instance.getCloudInstanceId())
+                || !StringUtils.hasText(instance.getRegionId())) {
             throw new BusinessException(400, "credentialId, cloudInstanceId and regionId are required for Aliyun instances");
         }
         CloudCredentialVO credential = cloudCredentialRepository.findById(instance.getCredentialId())
@@ -157,9 +157,6 @@ public class InstanceService {
         };
     }
 
-    private static boolean isBlank(String value) {
-        return value == null || value.isBlank();
-    }
 
     public InstanceVO updateInstance(InstanceVO instance) {
         requireInstance(instance);
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
index e903af4f..9d763926 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
@@ -27,13 +27,12 @@ import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqInstanceMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
 import org.springframework.stereotype.Repository;
+import lombok.RequiredArgsConstructor;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusInstanceRepository implements InstanceRepository {
 
@@ -41,47 +40,47 @@ public class MybatisPlusInstanceRepository implements InstanceRepository {
     private final RmqTopicMapper topicMapper;
     private final RmqGroupMapper groupMapper;
 
-    public MybatisPlusInstanceRepository(RmqInstanceMapper instanceMapper,
-                                         RmqTopicMapper topicMapper,
-                                         RmqGroupMapper groupMapper) {
-        this.instanceMapper = instanceMapper;
-        this.topicMapper = topicMapper;
-        this.groupMapper = groupMapper;
-    }
-
     @Override
     public List<InstanceVO> findAll() {
-        return withCounts(instanceMapper.selectList(
-                new QueryWrapper<RmqInstance>().orderByAsc("id")));
+        return instanceMapper.selectList(
+                new QueryWrapper<RmqInstance>().orderByAsc("id")).stream()
+                .map(this::toVO)
+                .toList();
     }
 
     @Override
     public List<InstanceVO> findByType(InstanceType type) {
-        return withCounts(instanceMapper.selectList(
+        return instanceMapper.selectList(
                 new QueryWrapper<RmqInstance>()
                         .eq("type", type.name())
-                        .orderByAsc("id")));
+                        .orderByAsc("id")).stream()
+                .map(this::toVO)
+                .toList();
     }
 
     @Override
     public List<InstanceVO> search(String keyword) {
-        return withCounts(instanceMapper.selectList(
+        return instanceMapper.selectList(
                 new QueryWrapper<RmqInstance>()
                         .and(w -> w.like("name", keyword)
                                 .or().like("endpoint", keyword)
                                 .or().like("remark", keyword))
-                        .orderByAsc("id")));
+                        .orderByAsc("id")).stream()
+                .map(this::toVO)
+                .toList();
     }
 
     @Override
     public List<InstanceVO> findByTypeAndSearch(InstanceType type, String keyword) {
-        return withCounts(instanceMapper.selectList(
+        return instanceMapper.selectList(
                 new QueryWrapper<RmqInstance>()
                         .eq("type", type.name())
                         .and(w -> w.like("name", keyword)
                                 .or().like("endpoint", keyword)
                                 .or().like("remark", keyword))
-                        .orderByAsc("id")));
+                        .orderByAsc("id")).stream()
+                .map(this::toVO)
+                .toList();
     }
 
     @Override
@@ -90,13 +89,7 @@ public class MybatisPlusInstanceRepository implements InstanceRepository {
         if (entity == null) {
             return Optional.empty();
         }
-        Map<String, Long> topicCounts = countByInstance(topicMapper.selectMaps(
-                new QueryWrapper<RmqTopic>().select("instance_id", "COUNT(*) AS total")
-                        .isNotNull("instance_id").eq("instance_id", id).groupBy("instance_id")));
-        Map<String, Long> groupCounts = countByInstance(groupMapper.selectMaps(
-                new QueryWrapper<RmqGroup>().select("instance_id", "COUNT(*) AS total")
-                        .isNotNull("instance_id").eq("instance_id", id).groupBy("instance_id")));
-        return Optional.of(toVO(entity, topicCounts, groupCounts));
+        return Optional.of(toVO(entity));
     }
 
     @Override
@@ -124,36 +117,19 @@ public class MybatisPlusInstanceRepository implements InstanceRepository {
                 new QueryWrapper<RmqInstance>().eq("credential_id", credentialId)) > 0;
     }
 
-    private List<InstanceVO> withCounts(List<RmqInstance> entities) {
-        if (entities.isEmpty()) {
-            return List.of();
-        }
-        Map<String, Long> topicCounts = countByInstance(topicMapper.selectMaps(
-                new QueryWrapper<RmqTopic>().select("instance_id", "COUNT(*) AS total")
-                        .isNotNull("instance_id").groupBy("instance_id")));
-        Map<String, Long> groupCounts = countByInstance(groupMapper.selectMaps(
-                new QueryWrapper<RmqGroup>().select("instance_id", "COUNT(*) AS total")
-                        .isNotNull("instance_id").groupBy("instance_id")));
-        return entities.stream()
-                .map(entity -> toVO(entity, topicCounts, groupCounts))
-                .collect(Collectors.toList());
+    @Override
+    public long countTopicsByInstance(String instanceId) {
+        return topicMapper.selectCount(
+                new QueryWrapper<RmqTopic>().eq("instance_id", instanceId));
     }
 
-    private Map<String, Long> countByInstance(List<Map<String, Object>> rows) {
-        Map<String, Long> counts = new HashMap<>();
-        for (Map<String, Object> row : rows) {
-            Object instanceId = row.get("instance_id");
-            Object total = row.get("total");
-            if (instanceId != null && total != null) {
-                counts.put(instanceId.toString(), ((Number) total).longValue());
-            }
-        }
-        return counts;
+    @Override
+    public long countGroupsByInstance(String instanceId) {
+        return groupMapper.selectCount(
+                new QueryWrapper<RmqGroup>().eq("instance_id", instanceId));
     }
 
-    private InstanceVO toVO(RmqInstance entity,
-                            Map<String, Long> topicCounts,
-                            Map<String, Long> groupCounts) {
+    private InstanceVO toVO(RmqInstance entity) {
         InstanceVO vo = InstanceVO.builder()
                 .name(entity.getName())
                 .remark(entity.getRemark())
@@ -163,8 +139,6 @@ public class MybatisPlusInstanceRepository implements InstanceRepository {
                 .cloudInstanceId(entity.getCloudInstanceId())
                 .credentialId(entity.getCredentialId())
                 .regionId(entity.getRegionId())
-                .topicCount(topicCounts.getOrDefault(entity.getId(), 0L).intValue())
-                .consumerGroupCount(groupCounts.getOrDefault(entity.getId(), 0L).intValue())
                 .build();
         vo.setId(entity.getId());
         vo.setCreatedAt(entity.getCreatedAt());
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java b/server/src/main/java/org/apache/rocketmq/studio/instance/UpdateInstanceDTO.java
similarity index 63%
rename from server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java
rename to server/src/main/java/org/apache/rocketmq/studio/instance/UpdateInstanceDTO.java
index 418bf53f..6b94f840 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/UpdateInstanceDTO.java
@@ -17,16 +17,31 @@
 package org.apache.rocketmq.studio.instance;
 
 import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Data;
-import lombok.NoArgsConstructor;
+import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
 
 @Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class InstanceDeleteRequestDTO {
-    @NotBlank(message = "id is required")
+public class UpdateInstanceDTO {
+
+    @NotBlank(message = "instance id is required")
     private String id;
+
+    private String name;
+
+    private InstanceType type;
+
+    private String endpoint;
+
+    private String remark;
+
+    public InstanceVO toInstanceVO() {
+        InstanceVO vo = InstanceVO.builder()
+                .name(name)
+                .type(type)
+                .endpoint(endpoint)
+                .remark(remark)
+                .build();
+        vo.setId(id);
+        return vo;
+    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
index 4d25bf71..e02e8f10 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.instance.acl;
 
+import org.apache.rocketmq.studio.common.domain.DeleteRequestDTO;
 import org.apache.rocketmq.studio.common.domain.Result;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import jakarta.validation.Valid;
@@ -55,7 +56,7 @@ public class AclController {
     }
 
     @PostMapping("/rules/delete")
-    public Result<Void> deleteRule(@Valid @RequestBody AclDeleteRequestDTO request) {
+    public Result<Void> deleteRule(@Valid @RequestBody DeleteRequestDTO request) {
         aclService.deleteRule(request.getId());
         return Result.ok();
     }
@@ -81,7 +82,7 @@ public class AclController {
     }
 
     @PostMapping("/users/delete")
-    public Result<Void> deleteUser(@Valid @RequestBody AclDeleteRequestDTO request) {
+    public Result<Void> deleteUser(@Valid @RequestBody DeleteRequestDTO request) {
         aclService.deleteUser(request.getId());
         return Result.ok();
     }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
index 65c7995e..c3ada9d7 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
@@ -16,7 +16,10 @@
  */
 package org.apache.rocketmq.studio.instance.acl;
 
+import org.springframework.util.StringUtils;
+
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.util.CredentialUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -30,10 +33,6 @@ import java.util.UUID;
 @RequiredArgsConstructor
 public class AclService {
 
-    private static final int VISIBLE_CREDENTIAL_CHARS = 4;
-    private static final int MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS = 17;
-    private static final String CREDENTIAL_MASK = "****";
-
     private final AclRepository aclRepository;
 
 
@@ -45,10 +44,10 @@ public class AclService {
 
     public AclRuleVO createRule(AclRuleVO rule) {
         log.info("Creating ACL rule for principal={}", rule.getPrincipal());
-        if (isBlank(rule.getPrincipal())) {
+        if (!StringUtils.hasText(rule.getPrincipal())) {
             throw new BusinessException(400, "ACL principal is required");
         }
-        if (isBlank(rule.getResource())) {
+        if (!StringUtils.hasText(rule.getResource())) {
             throw new BusinessException(400, "ACL resource is required");
         }
         rule.setId(UUID.randomUUID().toString());
@@ -57,7 +56,7 @@ public class AclService {
     }
 
     public AclRuleVO updateRule(AclRuleVO rule) {
-        if (isBlank(rule.getId())) {
+        if (!StringUtils.hasText(rule.getId())) {
             throw new BusinessException(400, "ACL rule id is required");
         }
         log.info("Updating ACL rule id={}, principal={}", rule.getId(), rule.getPrincipal());
@@ -83,7 +82,7 @@ public class AclService {
 
     public AclUserVO createUser(AclUserVO user) {
         log.info("Creating ACL user username={}", user.getUsername());
-        if (isBlank(user.getUsername())) {
+        if (!StringUtils.hasText(user.getUsername())) {
             throw new BusinessException(400, "ACL username is required");
         }
         user.setId(UUID.randomUUID().toString());
@@ -94,7 +93,7 @@ public class AclService {
     }
 
     public AclUserVO updateUser(UpdateAclUserDTO user) {
-        if (isBlank(user.getId())) {
+        if (!StringUtils.hasText(user.getId())) {
             throw new BusinessException(400, "ACL user id is required");
         }
         log.info("Updating ACL user id={}, username={}", user.getId(), user.getUsername());
@@ -124,7 +123,7 @@ public class AclService {
      * The secret is stored base64-encoded in the database and decoded here.
      */
     public AclUserVO getUserCredentials(String id) {
-        if (isBlank(id)) {
+        if (!StringUtils.hasText(id)) {
             throw new BusinessException(400, "ACL user id is required");
         }
         log.info("Revealing credentials for ACL user id={}", id);
@@ -132,31 +131,16 @@ public class AclService {
                 .orElseThrow(() -> new BusinessException(404, "ACL user not found: " + id));
     }
 
-    private boolean isBlank(String value) {
-        return value == null || value.trim().isEmpty();
-    }
-
     private AclUserVO maskCredentials(AclUserVO user) {
         return AclUserVO.builder()
                 .id(user.getId())
                 .username(user.getUsername())
-                .accessKey(maskCredential(user.getAccessKey()))
-                .secretKey(maskCredential(user.getSecretKey()))
+                .accessKey(CredentialUtils.mask(user.getAccessKey()))
+                .secretKey(CredentialUtils.mask(user.getSecretKey()))
                 .admin(user.isAdmin())
                 .clusters(user.getClusters() == null ? null : List.copyOf(user.getClusters()))
                 .createdAt(user.getCreatedAt())
                 .build();
     }
 
-    private String maskCredential(String credential) {
-        if (credential == null || credential.isEmpty()) {
-            return credential;
-        }
-        if (credential.length() < MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS) {
-            return CREDENTIAL_MASK;
-        }
-        return credential.substring(0, VISIBLE_CREDENTIAL_CHARS)
-                + CREDENTIAL_MASK
-                + credential.substring(credential.length() - VISIBLE_CREDENTIAL_CHARS);
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
index 318dd6c1..2aeccfa7 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
@@ -17,16 +17,16 @@
 package org.apache.rocketmq.studio.instance.acl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.rocketmq.studio.common.util.CredentialUtils;
 import org.apache.rocketmq.studio.persistence.entity.RmqAclRule;
 import org.apache.rocketmq.studio.persistence.entity.RmqAclUser;
 import org.apache.rocketmq.studio.persistence.mapper.RmqAclRuleMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqAclUserMapper;
 import org.springframework.stereotype.Repository;
+import lombok.RequiredArgsConstructor;
 
-import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -35,17 +35,13 @@ import java.util.stream.Collectors;
  * MySQL-backed ACL repository. User passwords are stored base64-encoded in
  * {@code rmq_acl_user.secret_key} and decoded when read; plain text is never persisted.
  */
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusAclRepository implements AclRepository {
 
     private final RmqAclRuleMapper ruleMapper;
     private final RmqAclUserMapper userMapper;
 
-    public MybatisPlusAclRepository(RmqAclRuleMapper ruleMapper, RmqAclUserMapper userMapper) {
-        this.ruleMapper = ruleMapper;
-        this.userMapper = userMapper;
-    }
-
     @Override
     public List<AclRuleVO> findRules(String clusterId, String principal) {
         QueryWrapper<RmqAclRule> query = new QueryWrapper<RmqAclRule>()
@@ -153,7 +149,7 @@ public class MybatisPlusAclRepository implements AclRepository {
                 .id(entity.getId())
                 .username(entity.getUsername())
                 .accessKey(entity.getAccessKey())
-                .secretKey(decodeBase64(entity.getSecretKey()))
+                .secretKey(CredentialUtils.decodeBase64(entity.getSecretKey()))
                 .admin(Boolean.TRUE.equals(entity.getAdmin()))
                 .clusters(splitCsv(entity.getClusters()))
                 .createdAt(entity.getCreatedAt())
@@ -165,7 +161,7 @@ public class MybatisPlusAclRepository implements AclRepository {
         entity.setId(user.getId());
         entity.setUsername(user.getUsername());
         entity.setAccessKey(user.getAccessKey());
-        entity.setSecretKey(encodeBase64(user.getSecretKey()));
+        entity.setSecretKey(CredentialUtils.encodeBase64(user.getSecretKey()));
         entity.setAdmin(user.isAdmin());
         entity.setClusters(user.getClusters() == null ? null : String.join(",", user.getClusters()));
         entity.setCreatedAt(user.getCreatedAt());
@@ -182,23 +178,4 @@ public class MybatisPlusAclRepository implements AclRepository {
                 .filter(part -> !part.isEmpty())
                 .collect(Collectors.toList());
     }
-
-    private static String encodeBase64(String plainText) {
-        if (plainText == null) {
-            return null;
-        }
-        return Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8));
-    }
-
-    private static String decodeBase64(String stored) {
-        if (stored == null) {
-            return null;
-        }
-        try {
-            return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8);
-        } catch (IllegalArgumentException ex) {
-            // tolerate legacy values that were stored without encoding
-            return stored;
-        }
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
index 674ac107..5923e474 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
@@ -59,7 +59,7 @@ public class DLQService {
 
     private void validateResendRequest(String groupName, Long startTime, Long endTime) {
         if (!StringUtils.hasText(groupName)) {
-            throw new BusinessException(HttpStatus.BAD_REQUEST.value(), "groupName is required");
+            throw new BusinessException(400, "groupName is required");
         }
         if ((startTime == null) != (endTime == null)) {
             throw new BusinessException(
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerDiagnosticsService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerDiagnosticsService.java
index 87e8f1db..7a8699b6 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerDiagnosticsService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerDiagnosticsService.java
@@ -19,7 +19,6 @@ package org.apache.rocketmq.studio.instance.group;
 
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
@@ -37,7 +36,7 @@ public class ConsumerDiagnosticsService {
 
     private String normalizeRequired(String value, String fieldName) {
         if (!StringUtils.hasText(value)) {
-            throw new BusinessException(HttpStatus.BAD_REQUEST.value(), fieldName + " is required");
+            throw new BusinessException(400, fieldName + " is required");
         }
         return value.trim();
     }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryProperties.java b/server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryProperties.java
similarity index 95%
rename from server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryProperties.java
rename to server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryProperties.java
index 63387497..0f081ba4 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryProperties.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryProperties.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.queryhistory;
+package org.apache.rocketmq.studio.instance.message;
 
 import lombok.Getter;
 import lombok.Setter;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryService.java
similarity index 99%
rename from server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryService.java
rename to server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryService.java
index 90bad479..68b56d1c 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/message/QueryHistoryService.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.queryhistory;
+package org.apache.rocketmq.studio.instance.message;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.extern.slf4j.Slf4j;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProvider.java b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProvider.java
deleted file mode 100644
index 7ca6e000..00000000
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProvider.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.instance.topic;
-
-import org.apache.rocketmq.studio.common.exception.BusinessException;
-import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
-import org.apache.rocketmq.studio.instance.group.QueueProgressVO;
-import org.apache.rocketmq.studio.instance.group.SubscriptionEntryVO;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.List;
-
-/**
- * Stub metadata provider. Disabled in favor of RocketMQMetadataProvider.
- */
-@Slf4j
-public class CloudMetadataProvider implements MetadataProvider {
-
-    @Override
-    public List<TopicVO> listTopics(String clusterId, String type, String search) {
-        throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public List<ConsumerGroupVO> listConsumerGroups(String clusterId, String search) {
-        throw consumerGroupProviderUnavailable();
-    }
-
-    @Override
-    public List<BrokerRouteVO> getTopicRoutes(String name) {
-        throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public List<TopicConsumerVO> getTopicConsumers(String name) {
-        throw new UnsupportedOperationException("Not implemented");
-    }
-
-    @Override
-    public List<QueueProgressVO> getGroupProgress(String name) {
-        throw consumerGroupProviderUnavailable();
-    }
-
-    @Override
-    public List<SubscriptionEntryVO> getGroupSubscriptions(String name) {
-        throw consumerGroupProviderUnavailable();
-    }
-
-    private BusinessException consumerGroupProviderUnavailable() {
-        return new BusinessException(501, "Consumer group metadata provider is not configured");
-    }
-}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
index 94093daf..04b80abe 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
@@ -16,6 +16,8 @@
  */
 package org.apache.rocketmq.studio.instance.topic;
 
+import org.apache.rocketmq.studio.provider.apache.AdminClient;
+import org.apache.rocketmq.studio.provider.apache.MetadataProvider;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
@@ -29,7 +31,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
-import java.util.Optional;
 
 @Slf4j
 @Service
@@ -48,28 +49,24 @@ public class MetadataService {
     }
 
     public List<TopicVO> listTopics(String instanceId, String clusterId, String type, String search) {
-        return providerFor(instanceId)
-                .map(provider -> provider.listTopics(instanceId, normalizeFilter(type), normalizeFilter(search)))
-                .orElseGet(() -> metadataProvider.listTopics(
-                        normalizeFilter(clusterId),
-                        normalizeFilter(type),
-                        normalizeFilter(search)));
+        if (isBlank(instanceId) && !isBlank(clusterId)) {
+            // legacy cluster-scoped read kept for AI tool handlers
+            return metadataProvider.listTopics(
+                    normalizeFilter(clusterId), normalizeFilter(type), normalizeFilter(search));
+        }
+        return resolve(instanceId).listTopics(instanceId, normalizeFilter(type), normalizeFilter(search));
     }
 
 
     public TopicVO createTopic(TopicVO topic) {
         requireTopic(topic);
-        return providerFor(topic.getInstanceId())
-                .map(provider -> provider.createTopic(topic.getInstanceId(), topic))
-                .orElseGet(() -> adminClient.createTopic(topic));
+        return resolve(topic.getInstanceId()).createTopic(topic.getInstanceId(), topic);
     }
 
 
     public TopicVO updateTopic(TopicVO topic) {
         requireTopic(topic);
-        return providerFor(topic.getInstanceId())
-                .map(provider -> provider.updateTopic(topic.getInstanceId(), topic))
-                .orElseGet(() -> adminClient.updateTopic(topic));
+        return resolve(topic.getInstanceId()).updateTopic(topic.getInstanceId(), topic);
     }
 
 
@@ -78,12 +75,7 @@ public class MetadataService {
     }
 
     public void deleteTopic(String instanceId, String name) {
-        Optional<InstanceProvider> provider = providerFor(instanceId);
-        if (provider.isPresent()) {
-            provider.get().deleteTopic(instanceId, name);
-        } else {
-            adminClient.deleteTopic(name);
-        }
+        resolve(instanceId).deleteTopic(instanceId, name);
     }
 
 
@@ -92,8 +84,7 @@ public class MetadataService {
     }
 
     public List<BrokerRouteVO> getTopicRoutes(String instanceId, String name) {
-        Optional<InstanceProvider> provider = providerFor(instanceId);
-        if (provider.isPresent() && provider.get().vendor() != InstanceVendor.APACHE) {
+        if (resolve(instanceId).vendor() != InstanceVendor.APACHE) {
             // broker routing does not apply to serverless cloud instances
             return List.of();
         }
@@ -106,19 +97,15 @@ public class MetadataService {
     }
 
     public List<TopicConsumerVO> getTopicConsumers(String instanceId, String name) {
-        return providerFor(instanceId)
-                .map(provider -> provider.getTopicConsumers(instanceId, name))
-                .orElseGet(() -> metadataProvider.getTopicConsumers(name));
+        return resolve(instanceId).getTopicConsumers(instanceId, name);
     }
 
 
     public SendMessageVO sendMessage(SendMessageDTO request) {
         requireSendMessageRequest(request);
-        providerFor(request.getInstanceId()).ifPresent(provider -> {
-            if (provider.vendor() != InstanceVendor.APACHE) {
-                throw new BusinessException(501, "Sending messages is not supported for cloud instances");
-            }
-        });
+        if (resolve(request.getInstanceId()).vendor() != InstanceVendor.APACHE) {
+            throw new BusinessException(501, "Sending messages is not supported for cloud instances");
+        }
         return adminClient.sendMessage(request);
     }
 
@@ -130,10 +117,10 @@ public class MetadataService {
     }
 
     public List<ConsumerGroupVO> listConsumerGroups(String instanceId, String clusterId, String search) {
-        return providerFor(instanceId)
-                .map(provider -> provider.listConsumerGroups(instanceId, normalizeFilter(search)))
-                .orElseGet(() -> metadataProvider.listConsumerGroups(
-                        normalizeFilter(clusterId), normalizeFilter(search)));
+        if (isBlank(instanceId) && !isBlank(clusterId)) {
+            return metadataProvider.listConsumerGroups(normalizeFilter(clusterId), normalizeFilter(search));
+        }
+        return resolve(instanceId).listConsumerGroups(instanceId, normalizeFilter(search));
     }
 
 
@@ -142,11 +129,9 @@ public class MetadataService {
     }
 
     public ConsumerGroupVO getConsumerGroup(String instanceId, String name) {
-        providerFor(instanceId).ifPresent(provider -> {
-            if (provider.vendor() != InstanceVendor.APACHE) {
-                throw new BusinessException(501, "Consumer group detail is not supported for cloud instances");
-            }
-        });
+        if (resolve(instanceId).vendor() != InstanceVendor.APACHE) {
+            throw new BusinessException(501, "Consumer group detail is not supported for cloud instances");
+        }
         return adminClient.getConsumerGroup(name);
     }
 
@@ -156,9 +141,7 @@ public class MetadataService {
     }
 
     public List<QueueProgressVO> getGroupProgress(String instanceId, String name) {
-        return providerFor(instanceId)
-                .map(provider -> provider.getGroupProgress(instanceId, name))
-                .orElseGet(() -> metadataProvider.getGroupProgress(name));
+        return resolve(instanceId).getGroupProgress(instanceId, name);
     }
 
 
@@ -167,16 +150,13 @@ public class MetadataService {
     }
 
     public List<SubscriptionEntryVO> getGroupSubscriptions(String instanceId, String name) {
-        return providerFor(instanceId)
-                .map(provider -> provider.getGroupSubscriptions(instanceId, name))
-                .orElseGet(() -> metadataProvider.getGroupSubscriptions(name));
+        return resolve(instanceId).getGroupSubscriptions(instanceId, name);
     }
 
 
     public ConsumerGroupVO createConsumerGroup(ConsumerGroupVO group) {
-        return providerFor(group == null ? null : group.getInstanceId())
-                .map(provider -> provider.createConsumerGroup(group.getInstanceId(), group))
-                .orElseGet(() -> adminClient.createConsumerGroup(group));
+        String instanceId = group == null ? null : group.getInstanceId();
+        return resolve(instanceId).createConsumerGroup(instanceId, group);
     }
 
 
@@ -185,12 +165,7 @@ public class MetadataService {
     }
 
     public void deleteConsumerGroup(String instanceId, String name) {
-        Optional<InstanceProvider> provider = providerFor(instanceId);
-        if (provider.isPresent()) {
-            provider.get().deleteConsumerGroup(instanceId, name);
-        } else {
-            adminClient.deleteConsumerGroup(name);
-        }
+        resolve(instanceId).deleteConsumerGroup(instanceId, name);
     }
 
 
@@ -199,23 +174,21 @@ public class MetadataService {
     }
 
     public void resetOffset(String instanceId, String name, long timestamp, String topic) {
-        Optional<InstanceProvider> provider = providerFor(instanceId);
-        if (provider.isPresent()) {
-            provider.get().resetOffset(instanceId, name, timestamp, topic);
-        } else {
-            adminClient.resetOffset(name, timestamp, topic);
-        }
+        resolve(instanceId).resetOffset(instanceId, name, timestamp, topic);
     }
 
-    // ── NamespaceVO ───────────────────────────────────────────────────
-
 
-    public List<NamespaceVO> listNamespaces() {
-        throw new BusinessException(501, "Namespace discovery is not implemented by the current metadata provider");
+    /**
+     * Every metadata operation goes through the provider registry; a blank instance id keeps the
+     * legacy global behavior by defaulting to the Apache provider.
+     */
+    private InstanceProvider resolve(String instanceId) {
+        return providerRegistry.byInstanceId(instanceId)
+                .orElseGet(() -> providerRegistry.forVendor(InstanceVendor.APACHE));
     }
 
-    private Optional<InstanceProvider> providerFor(String instanceId) {
-        return providerRegistry.byInstanceId(instanceId);
+    private static boolean isBlank(String value) {
+        return value == null || value.isBlank();
     }
 
     private String normalizeFilter(String value) {
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
index 3fe8c189..f91765b2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.instance.topic;
 
+import org.apache.rocketmq.studio.provider.apache.AdminClient;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceController.java b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceController.java
deleted file mode 100644
index 3cc6cf5f..00000000
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceController.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.instance.topic;
-
-import org.apache.rocketmq.studio.common.domain.Result;
-import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/namespaces")
-@RequiredArgsConstructor
-public class NamespaceController {
-
-    private final MetadataService metadataService;
-
-    @GetMapping
-    public Result<List<NamespaceVO>> listNamespaces() {
-        return Result.ok(metadataService.listNamespaces());
-    }
-}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ACLPolicy.java b/server/src/main/java/org/apache/rocketmq/studio/model/ACLPolicy.java
index 808d5dd9..2c9bde04 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ACLPolicy.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ACLPolicy.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.Set;
 
+@Data
 public class ACLPolicy {
 
     private String policyId;
@@ -82,116 +84,4 @@ public class ACLPolicy {
 
         return "ALLOW".equals(policyType);
     }
-    public String getPolicyId() {
-        return policyId;
-    }
-
-    public void setPolicyId(String policyId) {
-        this.policyId = policyId;
-    }
-
-    public String getPolicyName() {
-        return policyName;
-    }
-
-    public void setPolicyName(String policyName) {
-        this.policyName = policyName;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public Set<String> getUsers() {
-        return users;
-    }
-
-    public void setUsers(Set<String> users) {
-        this.users = users;
-    }
-
-    public Set<String> getResources() {
-        return resources;
-    }
-
-    public void setResources(Set<String> resources) {
-        this.resources = resources;
-    }
-
-    public Set<String> getActions() {
-        return actions;
-    }
-
-    public void setActions(Set<String> actions) {
-        this.actions = actions;
-    }
-
-    public String getPolicyType() {
-        return policyType;
-    }
-
-    public void setPolicyType(String policyType) {
-        this.policyType = policyType;
-    }
-
-    public Set<String> getIpWhiteList() {
-        return ipWhiteList;
-    }
-
-    public void setIpWhiteList(Set<String> ipWhiteList) {
-        this.ipWhiteList = ipWhiteList;
-    }
-
-    public Date getEffectiveTime() {
-        return effectiveTime;
-    }
-
-    public void setEffectiveTime(Date effectiveTime) {
-        this.effectiveTime = effectiveTime;
-    }
-
-    public Date getExpirationTime() {
-        return expirationTime;
-    }
-
-    public void setExpirationTime(Date expirationTime) {
-        this.expirationTime = expirationTime;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Boolean getDefaultPolicy() {
-        return defaultPolicy;
-    }
-
-    public void setDefaultPolicy(Boolean defaultPolicy) {
-        this.defaultPolicy = defaultPolicy;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ACLUser.java b/server/src/main/java/org/apache/rocketmq/studio/model/ACLUser.java
index e7d850c5..8ace2ad1 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ACLUser.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ACLUser.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.Set;
 
+@Data
 public class ACLUser {
 
     private String userName;
@@ -58,84 +60,4 @@ public class ACLUser {
         }
         return ipWhiteList.contains(ip);
     }
-    public String getUserName() {
-        return userName;
-    }
-
-    public void setUserName(String userName) {
-        this.userName = userName;
-    }
-
-    public String getAccessKey() {
-        return accessKey;
-    }
-
-    public void setAccessKey(String accessKey) {
-        this.accessKey = accessKey;
-    }
-
-    public String getUserType() {
-        return userType;
-    }
-
-    public void setUserType(String userType) {
-        this.userType = userType;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
-
-    public Date getLastLoginTime() {
-        return lastLoginTime;
-    }
-
-    public void setLastLoginTime(Date lastLoginTime) {
-        this.lastLoginTime = lastLoginTime;
-    }
-
-    public Set<String> getPolicyIds() {
-        return policyIds;
-    }
-
-    public void setPolicyIds(Set<String> policyIds) {
-        this.policyIds = policyIds;
-    }
-
-    public Set<String> getIpWhiteList() {
-        return ipWhiteList;
-    }
-
-    public void setIpWhiteList(Set<String> ipWhiteList) {
-        this.ipWhiteList = ipWhiteList;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/AccessControlList.java b/server/src/main/java/org/apache/rocketmq/studio/model/AccessControlList.java
index 9e85e9cf..f4e88b02 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/AccessControlList.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/AccessControlList.java
@@ -16,12 +16,14 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.List;
 
 /**
  * Access Control List model for ACL management.
  * Represents the ACL configuration for a broker.
  */
+@Data
 public class AccessControlList {
 
     private String brokerAddr;
@@ -29,41 +31,10 @@ public class AccessControlList {
     private List<AccessControlEntry> entries;
     private long version;
 
-    public String getBrokerAddr() {
-        return brokerAddr;
-    }
-
-    public void setBrokerAddr(String brokerAddr) {
-        this.brokerAddr = brokerAddr;
-    }
-
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public List<AccessControlEntry> getEntries() {
-        return entries;
-    }
-
-    public void setEntries(List<AccessControlEntry> entries) {
-        this.entries = entries;
-    }
-
-    public long getVersion() {
-        return version;
-    }
-
-    public void setVersion(long version) {
-        this.version = version;
-    }
-
     /**
      * Individual access control entry within an ACL.
      */
+    @Data
     public static class AccessControlEntry {
 
         private String accessKey;
@@ -73,61 +44,5 @@ public class AccessControlList {
         private String defaultGroupPerm;
         private List<String> topicPerms;
         private List<String> groupPerms;
-
-        public String getAccessKey() {
-            return accessKey;
-        }
-
-        public void setAccessKey(String accessKey) {
-            this.accessKey = accessKey;
-        }
-
-        public String getSecretKey() {
-            return secretKey;
-        }
-
-        public void setSecretKey(String secretKey) {
-            this.secretKey = secretKey;
-        }
-
-        public String getAdmin() {
-            return admin;
-        }
-
-        public void setAdmin(String admin) {
-            this.admin = admin;
-        }
-
-        public String getDefaultTopicPerm() {
-            return defaultTopicPerm;
-        }
-
-        public void setDefaultTopicPerm(String defaultTopicPerm) {
-            this.defaultTopicPerm = defaultTopicPerm;
-        }
-
-        public String getDefaultGroupPerm() {
-            return defaultGroupPerm;
-        }
-
-        public void setDefaultGroupPerm(String defaultGroupPerm) {
-            this.defaultGroupPerm = defaultGroupPerm;
-        }
-
-        public List<String> getTopicPerms() {
-            return topicPerms;
-        }
-
-        public void setTopicPerms(List<String> topicPerms) {
-            this.topicPerms = topicPerms;
-        }
-
-        public List<String> getGroupPerms() {
-            return groupPerms;
-        }
-
-        public void setGroupPerms(List<String> groupPerms) {
-            this.groupPerms = groupPerms;
-        }
     }
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/Acl2PolicyContext.java b/server/src/main/java/org/apache/rocketmq/studio/model/Acl2PolicyContext.java
index 268b61af..1e0dcbb4 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/Acl2PolicyContext.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/Acl2PolicyContext.java
@@ -16,7 +16,10 @@
  */
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
 import java.util.Date;
 import java.util.List;
 
@@ -30,6 +33,7 @@ import java.util.List;
  *
  * @see ACLPolicy
  */
+@Data
 public class Acl2PolicyContext {
 
     // ========== Base Fields (Compatible with ACL 1.0) ==========
@@ -41,6 +45,8 @@ public class Acl2PolicyContext {
     private String secretKey;
 
     /** Whether this is an admin user */
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
     private boolean isAdmin;
 
     /** IP whitelist patterns */
@@ -82,6 +88,7 @@ public class Acl2PolicyContext {
      * Authorization Rule - defines what actions are allowed/denied on which resources.
      * Supports wildcards in resource patterns following path pattern syntax.
      */
+    @Data
     public static class AuthorizationRule {
 
         /** Resource path pattern supporting wildcards (* matches any single level, ** matches all levels) */
@@ -118,46 +125,6 @@ public class Acl2PolicyContext {
             rule.setPriority(0);
             return rule;
         }
-
-        public String getResourcePattern() {
-            return resourcePattern;
-        }
-
-        public void setResourcePattern(String resourcePattern) {
-            this.resourcePattern = resourcePattern;
-        }
-
-        public List<String> getActions() {
-            return actions;
-        }
-
-        public void setActions(List<String> actions) {
-            this.actions = actions;
-        }
-
-        public String getEffect() {
-            return effect;
-        }
-
-        public void setEffect(String effect) {
-            this.effect = effect;
-        }
-
-        public int getPriority() {
-            return priority;
-        }
-
-        public void setPriority(int priority) {
-            this.priority = priority;
-        }
-
-        public String getDescription() {
-            return description;
-        }
-
-        public void setDescription(String description) {
-            this.description = description;
-        }
     }
 
     /**
@@ -205,22 +172,6 @@ public class Acl2PolicyContext {
     private boolean isValidBoundType(String type) {
         return "USER".equals(type) || "GROUP".equals(type) || "SERVICE_ACCOUNT".equals(type);
     }
-    public String getAccessKey() {
-        return accessKey;
-    }
-
-    public void setAccessKey(String accessKey) {
-        this.accessKey = accessKey;
-    }
-
-    public String getSecretKey() {
-        return secretKey;
-    }
-
-    public void setSecretKey(String secretKey) {
-        this.secretKey = secretKey;
-    }
-
     public boolean isIsAdmin() {
         return isAdmin;
     }
@@ -228,93 +179,4 @@ public class Acl2PolicyContext {
     public void setIsAdmin(boolean isAdmin) {
         this.isAdmin = isAdmin;
     }
-
-    public List<String> getWhiteSet() {
-        return whiteSet;
-    }
-
-    public void setWhiteSet(List<String> whiteSet) {
-        this.whiteSet = whiteSet;
-    }
-
-    public String getPolicyName() {
-        return policyName;
-    }
-
-    public void setPolicyName(String policyName) {
-        this.policyName = policyName;
-    }
-
-    public String getBoundType() {
-        return boundType;
-    }
-
-    public void setBoundType(String boundType) {
-        this.boundType = boundType;
-    }
-
-    public String getBoundEntityId() {
-        return boundEntityId;
-    }
-
-    public void setBoundEntityId(String boundEntityId) {
-        this.boundEntityId = boundEntityId;
-    }
-
-    public List<AuthorizationRule> getRules() {
-        return rules;
-    }
-
-    public void setRules(List<AuthorizationRule> rules) {
-        this.rules = rules;
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public String getBrokerName() {
-        return brokerName;
-    }
-
-    public void setBrokerName(String brokerName) {
-        this.brokerName = brokerName;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ClientInstance.java b/server/src/main/java/org/apache/rocketmq/studio/model/ClientInstance.java
index e608ee81..5714914c 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ClientInstance.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ClientInstance.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.List;
 
+@Data
 public class ClientInstance {
 
     private String clientId;
@@ -146,228 +148,4 @@ public class ClientInstance {
         private String consumptionMode; // PULL / PUSH / POP
         private Boolean orderlyConsume;
     }
-    public String getClientId() {
-        return clientId;
-    }
-
-    public void setClientId(String clientId) {
-        this.clientId = clientId;
-    }
-
-    public String getClientAddress() {
-        return clientAddress;
-    }
-
-    public void setClientAddress(String clientAddress) {
-        this.clientAddress = clientAddress;
-    }
-
-    public ClientType getClientType() {
-        return clientType;
-    }
-
-    public void setClientType(ClientType clientType) {
-        this.clientType = clientType;
-    }
-
-    public List<String> getTopics() {
-        return topics;
-    }
-
-    public void setTopics(List<String> topics) {
-        this.topics = topics;
-    }
-
-    public String getClientSubType() {
-        return clientSubType;
-    }
-
-    public void setClientSubType(String clientSubType) {
-        this.clientSubType = clientSubType;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public void setLanguage(String language) {
-        this.language = language;
-    }
-
-    public String getSdkVersion() {
-        return sdkVersion;
-    }
-
-    public void setSdkVersion(String sdkVersion) {
-        this.sdkVersion = sdkVersion;
-    }
-
-    public ProtocolType getProtocolType() {
-        return protocolType;
-    }
-
-    public void setProtocolType(ProtocolType protocolType) {
-        this.protocolType = protocolType;
-    }
-
-    public String getEndpoint() {
-        return endpoint;
-    }
-
-    public void setEndpoint(String endpoint) {
-        this.endpoint = endpoint;
-    }
-
-    public Date getLastHeartbeatTime() {
-        return lastHeartbeatTime;
-    }
-
-    public void setLastHeartbeatTime(Date lastHeartbeatTime) {
-        this.lastHeartbeatTime = lastHeartbeatTime;
-    }
-
-    public boolean isActive() {
-        return active;
-    }
-
-    public void setActive(boolean active) {
-        this.active = active;
-    }
-
-    public Date getConnectTime() {
-        return connectTime;
-    }
-
-    public void setConnectTime(Date connectTime) {
-        this.connectTime = connectTime;
-    }
-
-    public String getInstanceName() {
-        return instanceName;
-    }
-
-    public void setInstanceName(String instanceName) {
-        this.instanceName = instanceName;
-    }
-
-    public String getConsumerGroup() {
-        return consumerGroup;
-    }
-
-    public void setConsumerGroup(String consumerGroup) {
-        this.consumerGroup = consumerGroup;
-    }
-
-    public String getProducerGroup() {
-        return producerGroup;
-    }
-
-    public void setProducerGroup(String producerGroup) {
-        this.producerGroup = producerGroup;
-    }
-
-    public List<SubscriptionInfo> getSubscriptions() {
-        return subscriptions;
-    }
-
-    public void setSubscriptions(List<SubscriptionInfo> subscriptions) {
-        this.subscriptions = subscriptions;
-    }
-
-    public List<String> getPublishTopics() {
-        return publishTopics;
-    }
-
-    public void setPublishTopics(List<String> publishTopics) {
-        this.publishTopics = publishTopics;
-    }
-
-    public ConsumerProgress getConsumerProgress() {
-        return consumerProgress;
-    }
-
-    public void setConsumerProgress(ConsumerProgress consumerProgress) {
-        this.consumerProgress = consumerProgress;
-    }
-
-    public String getClientVersion() {
-        return clientVersion;
-    }
-
-    public void setClientVersion(String clientVersion) {
-        this.clientVersion = clientVersion;
-    }
-
-    public Boolean getVipChannelEnabled() {
-        return vipChannelEnabled;
-    }
-
-    public void setVipChannelEnabled(Boolean vipChannelEnabled) {
-        this.vipChannelEnabled = vipChannelEnabled;
-    }
-
-    public String getTelemetrySessionId() {
-        return telemetrySessionId;
-    }
-
-    public void setTelemetrySessionId(String telemetrySessionId) {
-        this.telemetrySessionId = telemetrySessionId;
-    }
-
-    public Boolean getLongConnectionActive() {
-        return longConnectionActive;
-    }
-
-    public void setLongConnectionActive(Boolean longConnectionActive) {
-        this.longConnectionActive = longConnectionActive;
-    }
-
-    public String getSettingsVersion() {
-        return settingsVersion;
-    }
-
-    public void setSettingsVersion(String settingsVersion) {
-        this.settingsVersion = settingsVersion;
-    }
-
-    public String getAuthFailureReason() {
-        return authFailureReason;
-    }
-
-    public void setAuthFailureReason(String authFailureReason) {
-        this.authFailureReason = authFailureReason;
-    }
-
-    public Boolean getPopEnabled() {
-        return popEnabled;
-    }
-
-    public void setPopEnabled(Boolean popEnabled) {
-        this.popEnabled = popEnabled;
-    }
-
-    public Integer getPendingAckCount() {
-        return pendingAckCount;
-    }
-
-    public void setPendingAckCount(Integer pendingAckCount) {
-        this.pendingAckCount = pendingAckCount;
-    }
-
-    public Integer getSubscriptionCount() {
-        return subscriptionCount;
-    }
-
-    public void setSubscriptionCount(Integer subscriptionCount) {
-        this.subscriptionCount = subscriptionCount;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ClusterCapability.java b/server/src/main/java/org/apache/rocketmq/studio/model/ClusterCapability.java
index 750dd85c..a24b09a7 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ClusterCapability.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ClusterCapability.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.HashSet;
 import java.util.Set;
 
+@Data
 public class ClusterCapability {
 
     private boolean liteTopicSupported;
@@ -67,84 +69,4 @@ public class ClusterCapability {
         }
         return false;
     }
-    public boolean isLiteTopicSupported() {
-        return liteTopicSupported;
-    }
-
-    public void setLiteTopicSupported(boolean liteTopicSupported) {
-        this.liteTopicSupported = liteTopicSupported;
-    }
-
-    public boolean isPopConsumeSupported() {
-        return popConsumeSupported;
-    }
-
-    public void setPopConsumeSupported(boolean popConsumeSupported) {
-        this.popConsumeSupported = popConsumeSupported;
-    }
-
-    public boolean isAclV2Supported() {
-        return aclV2Supported;
-    }
-
-    public void setAclV2Supported(boolean aclV2Supported) {
-        this.aclV2Supported = aclV2Supported;
-    }
-
-    public boolean isGrpcClientSupported() {
-        return grpcClientSupported;
-    }
-
-    public void setGrpcClientSupported(boolean grpcClientSupported) {
-        this.grpcClientSupported = grpcClientSupported;
-    }
-
-    public boolean isDelayMessageSupported() {
-        return delayMessageSupported;
-    }
-
-    public void setDelayMessageSupported(boolean delayMessageSupported) {
-        this.delayMessageSupported = delayMessageSupported;
-    }
-
-    public boolean isTransactionMessageSupported() {
-        return transactionMessageSupported;
-    }
-
-    public void setTransactionMessageSupported(boolean transactionMessageSupported) {
-        this.transactionMessageSupported = transactionMessageSupported;
-    }
-
-    public boolean isFifoMessageSupported() {
-        return fifoMessageSupported;
-    }
-
-    public void setFifoMessageSupported(boolean fifoMessageSupported) {
-        this.fifoMessageSupported = fifoMessageSupported;
-    }
-
-    public String getArchitectureVersion() {
-        return architectureVersion;
-    }
-
-    public void setArchitectureVersion(String architectureVersion) {
-        this.architectureVersion = architectureVersion;
-    }
-
-    public String getRocketmqVersion() {
-        return rocketmqVersion;
-    }
-
-    public void setRocketmqVersion(String rocketmqVersion) {
-        this.rocketmqVersion = rocketmqVersion;
-    }
-
-    public Set<String> getExtendedCapabilities() {
-        return extendedCapabilities;
-    }
-
-    public void setExtendedCapabilities(Set<String> extendedCapabilities) {
-        this.extendedCapabilities = extendedCapabilities;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ClusterTopology.java b/server/src/main/java/org/apache/rocketmq/studio/model/ClusterTopology.java
index d6474409..7a1d88e1 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ClusterTopology.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ClusterTopology.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -25,6 +26,7 @@ import java.util.Map;
  * Unified cluster topology model
  * Hide differences between different cluster architectures
  */
+@Data
 public class ClusterTopology {
 
     /**
@@ -117,7 +119,7 @@ public class ClusterTopology {
             .count();
     }
 
-
+    @Data
     public static class NodeInfo {
         private String nodeName;
         private Long nodeId;
@@ -140,117 +142,5 @@ public class ClusterTopology {
         public boolean isOnline() {
             return "ONLINE".equals(status);
         }
-
-        public String getNodeName() {
-            return nodeName;
-        }
-
-        public void setNodeName(String nodeName) {
-            this.nodeName = nodeName;
-        }
-
-        public Long getNodeId() {
-            return nodeId;
-        }
-
-        public void setNodeId(Long nodeId) {
-            this.nodeId = nodeId;
-        }
-
-        public String getNodeAddress() {
-            return nodeAddress;
-        }
-
-        public void setNodeAddress(String nodeAddress) {
-            this.nodeAddress = nodeAddress;
-        }
-
-        public String getNodeType() {
-            return nodeType;
-        }
-
-        public void setNodeType(String nodeType) {
-            this.nodeType = nodeType;
-        }
-
-        public String getClusterName() {
-            return clusterName;
-        }
-
-        public void setClusterName(String clusterName) {
-            this.clusterName = clusterName;
-        }
-
-        public String getStatus() {
-            return status;
-        }
-
-        public void setStatus(String status) {
-            this.status = status;
-        }
-
-        public Long getVersion() {
-            return version;
-        }
-
-        public void setVersion(Long version) {
-            this.version = version;
-        }
-
-        public Map<String, Object> getMetadata() {
-            return metadata;
-        }
-
-        public void setMetadata(Map<String, Object> metadata) {
-            this.metadata = metadata;
-        }
-    }
-
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public List<String> getNamesrvAddresses() {
-        return namesrvAddresses;
-    }
-
-    public void setNamesrvAddresses(List<String> namesrvAddresses) {
-        this.namesrvAddresses = namesrvAddresses;
-    }
-
-    public List<NodeInfo> getNamesrvNodes() {
-        return namesrvNodes;
-    }
-
-    public void setNamesrvNodes(List<NodeInfo> namesrvNodes) {
-        this.namesrvNodes = namesrvNodes;
-    }
-
-    public List<NodeInfo> getBrokerNodes() {
-        return brokerNodes;
-    }
-
-    public void setBrokerNodes(List<NodeInfo> brokerNodes) {
-        this.brokerNodes = brokerNodes;
-    }
-
-    public List<NodeInfo> getProxyNodes() {
-        return proxyNodes;
-    }
-
-    public void setProxyNodes(List<NodeInfo> proxyNodes) {
-        this.proxyNodes = proxyNodes;
-    }
-
-    public Map<String, NodeInfo> getNodeMap() {
-        return nodeMap;
-    }
-
-    public void setNodeMap(Map<String, NodeInfo> nodeMap) {
-        this.nodeMap = nodeMap;
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerGroupInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerGroupInfo.java
index db0b7500..61d4ab1e 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerGroupInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerGroupInfo.java
@@ -16,10 +16,12 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.Map;
 import java.util.Set;
 
+@Data
 public class ConsumerGroupInfo {
 
     private String consumerGroupName;
@@ -71,140 +73,4 @@ public class ConsumerGroupInfo {
     public boolean isBroadcastConsume() {
         return Boolean.TRUE.equals(consumeBroadcastEnable);
     }
-    public String getConsumerGroupName() {
-        return consumerGroupName;
-    }
-
-    public void setConsumerGroupName(String consumerGroupName) {
-        this.consumerGroupName = consumerGroupName;
-    }
-
-    public String getConsumeMode() {
-        return consumeMode;
-    }
-
-    public void setConsumeMode(String consumeMode) {
-        this.consumeMode = consumeMode;
-    }
-
-    public Boolean getConsumeMessageOrderly() {
-        return consumeMessageOrderly;
-    }
-
-    public void setConsumeMessageOrderly(Boolean consumeMessageOrderly) {
-        this.consumeMessageOrderly = consumeMessageOrderly;
-    }
-
-    public Boolean getConsumeBroadcastEnable() {
-        return consumeBroadcastEnable;
-    }
-
-    public void setConsumeBroadcastEnable(Boolean consumeBroadcastEnable) {
-        this.consumeBroadcastEnable = consumeBroadcastEnable;
-    }
-
-    public Boolean getConsumeFromMinEnable() {
-        return consumeFromMinEnable;
-    }
-
-    public void setConsumeFromMinEnable(Boolean consumeFromMinEnable) {
-        this.consumeFromMinEnable = consumeFromMinEnable;
-    }
-
-    public Integer getRetryQueueNums() {
-        return retryQueueNums;
-    }
-
-    public void setRetryQueueNums(Integer retryQueueNums) {
-        this.retryQueueNums = retryQueueNums;
-    }
-
-    public Integer getRetryMaxTimes() {
-        return retryMaxTimes;
-    }
-
-    public void setRetryMaxTimes(Integer retryMaxTimes) {
-        this.retryMaxTimes = retryMaxTimes;
-    }
-
-    public Integer getConsumeTimeoutMinute() {
-        return consumeTimeoutMinute;
-    }
-
-    public void setConsumeTimeoutMinute(Integer consumeTimeoutMinute) {
-        this.consumeTimeoutMinute = consumeTimeoutMinute;
-    }
-
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Set<String> getSubscribedTopics() {
-        return subscribedTopics;
-    }
-
-    public void setSubscribedTopics(Set<String> subscribedTopics) {
-        this.subscribedTopics = subscribedTopics;
-    }
-
-    public Integer getOnlineClientCount() {
-        return onlineClientCount;
-    }
-
-    public void setOnlineClientCount(Integer onlineClientCount) {
-        this.onlineClientCount = onlineClientCount;
-    }
-
-    public String getLiteBindTopic() {
-        return liteBindTopic;
-    }
-
-    public void setLiteBindTopic(String liteBindTopic) {
-        this.liteBindTopic = liteBindTopic;
-    }
-
-    public Map<String, String> getAttributes() {
-        return attributes;
-    }
-
-    public void setAttributes(Map<String, String> attributes) {
-        this.attributes = attributes;
-    }
-
-    public Integer getGroupSysFlag() {
-        return groupSysFlag;
-    }
-
-    public void setGroupSysFlag(Integer groupSysFlag) {
-        this.groupSysFlag = groupSysFlag;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerMonitorConfig.java b/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerMonitorConfig.java
index 9bb74c3a..e905f0e6 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerMonitorConfig.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/ConsumerMonitorConfig.java
@@ -16,31 +16,14 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class ConsumerMonitorConfig {
     private int minCount;
     private int maxDiffTotal;
-
-    public ConsumerMonitorConfig() {
-    }
-
-    public ConsumerMonitorConfig(int minCount, int maxDiffTotal) {
-        this.minCount = minCount;
-        this.maxDiffTotal = maxDiffTotal;
-    }
-
-    public int getMinCount() {
-        return minCount;
-    }
-
-    public void setMinCount(int minCount) {
-        this.minCount = minCount;
-    }
-
-    public int getMaxDiffTotal() {
-        return maxDiffTotal;
-    }
-
-    public void setMaxDiffTotal(int maxDiffTotal) {
-        this.maxDiffTotal = maxDiffTotal;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/Entry.java b/server/src/main/java/org/apache/rocketmq/studio/model/Entry.java
index 8b623ede..d8d3b505 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/Entry.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/Entry.java
@@ -17,45 +17,13 @@
 
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class Entry {
     private List<String> resource;
     private List<String> actions;
     private List<String> sourceIps;
     private String decision;
-
-    public List<String> getResource() {
-        return resource;
-    }
-
-    public void setResource(List<String> resource) {
-        this.resource = resource;
-    }
-
-    public List<String> getActions() {
-        return actions;
-    }
-
-    public void setActions(List<String> actions) {
-        this.actions = actions;
-    }
-
-    public List<String> getSourceIps() {
-        return sourceIps;
-    }
-
-    public void setSourceIps(List<String> sourceIps) {
-        this.sourceIps = sourceIps;
-    }
-
-    public String getDecision() {
-        return decision;
-    }
-
-    public void setDecision(String decision) {
-        this.decision = decision;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicQuota.java b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicQuota.java
index daa39b8c..39fd47ab 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicQuota.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicQuota.java
@@ -16,7 +16,9 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 
+@Data
 public class LiteTopicQuota {
 
     private Integer maxTopicCount;
@@ -63,68 +65,4 @@ public class LiteTopicQuota {
         }
         return Math.max(0, maxTopicCount - currentTopicCount);
     }
-    public Integer getMaxTopicCount() {
-        return maxTopicCount;
-    }
-
-    public void setMaxTopicCount(Integer maxTopicCount) {
-        this.maxTopicCount = maxTopicCount;
-    }
-
-    public Integer getCurrentTopicCount() {
-        return currentTopicCount;
-    }
-
-    public void setCurrentTopicCount(Integer currentTopicCount) {
-        this.currentTopicCount = currentTopicCount;
-    }
-
-    public Integer getMaxSessionCount() {
-        return maxSessionCount;
-    }
-
-    public void setMaxSessionCount(Integer maxSessionCount) {
-        this.maxSessionCount = maxSessionCount;
-    }
-
-    public Integer getCurrentSessionCount() {
-        return currentSessionCount;
-    }
-
-    public void setCurrentSessionCount(Integer currentSessionCount) {
-        this.currentSessionCount = currentSessionCount;
-    }
-
-    public Long getDefaultTTL() {
-        return defaultTTL;
-    }
-
-    public void setDefaultTTL(Long defaultTTL) {
-        this.defaultTTL = defaultTTL;
-    }
-
-    public Long getMaxTTL() {
-        return maxTTL;
-    }
-
-    public void setMaxTTL(Long maxTTL) {
-        this.maxTTL = maxTTL;
-    }
-
-    public Double getCurrentCreationRate() {
-        return currentCreationRate;
-    }
-
-    public void setCurrentCreationRate(Double currentCreationRate) {
-        this.currentCreationRate = currentCreationRate;
-    }
-
-    public Double getMaxCreationRate() {
-        return maxCreationRate;
-    }
-
-    public void setMaxCreationRate(Double maxCreationRate) {
-        this.maxCreationRate = maxCreationRate;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSession.java b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSession.java
index f7ea3587..debbb8f4 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSession.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSession.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.Set;
 
+@Data
 public class LiteTopicSession {
 
     /**
@@ -76,6 +78,7 @@ public class LiteTopicSession {
         return (double) consumedMessages / totalMessages * 100.0;
     }
 
+    @Data
     public static class PopConsumeProgress {
         private Integer ackTimeoutSeconds;
         private Integer maxReconsumeTimes;
@@ -84,140 +87,4 @@ public class LiteTopicSession {
         private Integer popCheckpoint;
         private Integer totalPopCount;
     }
-    public String getSessionId() {
-        return sessionId;
-    }
-
-    public void setSessionId(String sessionId) {
-        this.sessionId = sessionId;
-    }
-
-    public String getClientId() {
-        return clientId;
-    }
-
-    public void setClientId(String clientId) {
-        this.clientId = clientId;
-    }
-
-    public String getClientAddress() {
-        return clientAddress;
-    }
-
-    public void setClientAddress(String clientAddress) {
-        this.clientAddress = clientAddress;
-    }
-
-    public Set<String> getLiteTopics() {
-        return liteTopics;
-    }
-
-    public void setLiteTopics(Set<String> liteTopics) {
-        this.liteTopics = liteTopics;
-    }
-
-    public String getParentTopic() {
-        return parentTopic;
-    }
-
-    public void setParentTopic(String parentTopic) {
-        this.parentTopic = parentTopic;
-    }
-
-    public String getConsumerGroup() {
-        return consumerGroup;
-    }
-
-    public void setConsumerGroup(String consumerGroup) {
-        this.consumerGroup = consumerGroup;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getLastActiveTime() {
-        return lastActiveTime;
-    }
-
-    public void setLastActiveTime(Date lastActiveTime) {
-        this.lastActiveTime = lastActiveTime;
-    }
-
-    public Long getTtl() {
-        return ttl;
-    }
-
-    public void setTtl(Long ttl) {
-        this.ttl = ttl;
-    }
-
-    public Long getTtlRemaining() {
-        return ttlRemaining;
-    }
-
-    public void setTtlRemaining(Long ttlRemaining) {
-        this.ttlRemaining = ttlRemaining;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Long getTotalMessages() {
-        return totalMessages;
-    }
-
-    public void setTotalMessages(Long totalMessages) {
-        this.totalMessages = totalMessages;
-    }
-
-    public Long getConsumedMessages() {
-        return consumedMessages;
-    }
-
-    public void setConsumedMessages(Long consumedMessages) {
-        this.consumedMessages = consumedMessages;
-    }
-
-    public Long getPendingMessages() {
-        return pendingMessages;
-    }
-
-    public void setPendingMessages(Long pendingMessages) {
-        this.pendingMessages = pendingMessages;
-    }
-
-    public Double getConsumptionRate() {
-        return consumptionRate;
-    }
-
-    public void setConsumptionRate(Double consumptionRate) {
-        this.consumptionRate = consumptionRate;
-    }
-
-    public PopConsumeProgress getPopProgress() {
-        return popProgress;
-    }
-
-    public void setPopProgress(PopConsumeProgress popProgress) {
-        this.popProgress = popProgress;
-    }
-
-    public Integer getLiteTopicCreationCount() {
-        return liteTopicCreationCount;
-    }
-
-    public void setLiteTopicCreationCount(Integer liteTopicCreationCount) {
-        this.liteTopicCreationCount = liteTopicCreationCount;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSummary.java b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSummary.java
index 49447ac5..925c2dee 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSummary.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/LiteTopicSummary.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.List;
 
+@Data
 public class LiteTopicSummary {
 
     private String topicPattern;
@@ -72,92 +74,4 @@ public class LiteTopicSummary {
     public boolean isEmptyAggregation() {
         return consumerCount == 0 && (totalBacklog == null || totalBacklog == 0);
     }
-    public String getTopicPattern() {
-        return topicPattern;
-    }
-
-    public void setTopicPattern(String topicPattern) {
-        this.topicPattern = topicPattern;
-    }
-
-    public Integer getTopicCount() {
-        return topicCount;
-    }
-
-    public void setTopicCount(Integer topicCount) {
-        this.topicCount = topicCount;
-    }
-
-    public List<String> getSessionIds() {
-        return sessionIds;
-    }
-
-    public void setSessionIds(List<String> sessionIds) {
-        this.sessionIds = sessionIds;
-    }
-
-    public Date getEarliestCreateTime() {
-        return earliestCreateTime;
-    }
-
-    public void setEarliestCreateTime(Date earliestCreateTime) {
-        this.earliestCreateTime = earliestCreateTime;
-    }
-
-    public Date getLastActiveTime() {
-        return lastActiveTime;
-    }
-
-    public void setLastActiveTime(Date lastActiveTime) {
-        this.lastActiveTime = lastActiveTime;
-    }
-
-    public Long getAverageTTL() {
-        return averageTTL;
-    }
-
-    public void setAverageTTL(Long averageTTL) {
-        this.averageTTL = averageTTL;
-    }
-
-    public Long getMinTTL() {
-        return minTTL;
-    }
-
-    public void setMinTTL(Long minTTL) {
-        this.minTTL = minTTL;
-    }
-
-    public Long getMaxTTL() {
-        return maxTTL;
-    }
-
-    public void setMaxTTL(Long maxTTL) {
-        this.maxTTL = maxTTL;
-    }
-
-    public Integer getConsumerCount() {
-        return consumerCount;
-    }
-
-    public void setConsumerCount(Integer consumerCount) {
-        this.consumerCount = consumerCount;
-    }
-
-    public Long getTotalBacklog() {
-        return totalBacklog;
-    }
-
-    public void setTotalBacklog(Long totalBacklog) {
-        this.totalBacklog = totalBacklog;
-    }
-
-    public boolean isActive() {
-        return active;
-    }
-
-    public void setActive(boolean active) {
-        this.active = active;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/LoginInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/LoginInfo.java
index 189c6928..449fe7e5 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/LoginInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/LoginInfo.java
@@ -17,23 +17,10 @@
 
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
+
+@Data
 public class LoginInfo {
     private boolean loginRequired;
     private boolean logined;
-
-    public boolean isLoginRequired() {
-        return loginRequired;
-    }
-
-    public void setLoginRequired(boolean loginRequired) {
-        this.loginRequired = loginRequired;
-    }
-
-    public boolean isLogined() {
-        return logined;
-    }
-
-    public void setLogined(boolean logined) {
-        this.logined = logined;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/LoginResult.java b/server/src/main/java/org/apache/rocketmq/studio/model/LoginResult.java
index 72eccf1f..12be8380 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/LoginResult.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/LoginResult.java
@@ -16,7 +16,9 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 
+@Data
 public class LoginResult {
 
     private String loginUserName;
@@ -27,28 +29,4 @@ public class LoginResult {
     private int loginUserRole;
 
     private String contextPath;
-    public String getLoginUserName() {
-        return loginUserName;
-    }
-
-    public void setLoginUserName(String loginUserName) {
-        this.loginUserName = loginUserName;
-    }
-
-    public int getLoginUserRole() {
-        return loginUserRole;
-    }
-
-    public void setLoginUserRole(int loginUserRole) {
-        this.loginUserRole = loginUserRole;
-    }
-
-    public String getContextPath() {
-        return contextPath;
-    }
-
-    public void setContextPath(String contextPath) {
-        this.contextPath = contextPath;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/MessageInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/MessageInfo.java
index 26c0f42b..91c90f7c 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/MessageInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/MessageInfo.java
@@ -16,12 +16,14 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Map;
 
 /**
  * Message information model for message query and management.
  * Encapsulates key message attributes for dashboard display.
  */
+@Data
 public class MessageInfo {
 
     private String msgId;
@@ -37,108 +39,4 @@ public class MessageInfo {
     private Map<String, String> properties;
     private boolean retryTopic;
     private int reconsumeTimes;
-
-    public String getMsgId() {
-        return msgId;
-    }
-
-    public void setMsgId(String msgId) {
-        this.msgId = msgId;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public String getBrokerName() {
-        return brokerName;
-    }
-
-    public void setBrokerName(String brokerName) {
-        this.brokerName = brokerName;
-    }
-
-    public int getQueueId() {
-        return queueId;
-    }
-
-    public void setQueueId(int queueId) {
-        this.queueId = queueId;
-    }
-
-    public long getQueueOffset() {
-        return queueOffset;
-    }
-
-    public void setQueueOffset(long queueOffset) {
-        this.queueOffset = queueOffset;
-    }
-
-    public long getStoreTimestamp() {
-        return storeTimestamp;
-    }
-
-    public void setStoreTimestamp(long storeTimestamp) {
-        this.storeTimestamp = storeTimestamp;
-    }
-
-    public long getBornTimestamp() {
-        return bornTimestamp;
-    }
-
-    public void setBornTimestamp(long bornTimestamp) {
-        this.bornTimestamp = bornTimestamp;
-    }
-
-    public String getKeys() {
-        return keys;
-    }
-
-    public void setKeys(String keys) {
-        this.keys = keys;
-    }
-
-    public String getTags() {
-        return tags;
-    }
-
-    public void setTags(String tags) {
-        this.tags = tags;
-    }
-
-    public String getBody() {
-        return body;
-    }
-
-    public void setBody(String body) {
-        this.body = body;
-    }
-
-    public Map<String, String> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties = properties;
-    }
-
-    public boolean isRetryTopic() {
-        return retryTopic;
-    }
-
-    public void setRetryTopic(boolean retryTopic) {
-        this.retryTopic = retryTopic;
-    }
-
-    public int getReconsumeTimes() {
-        return reconsumeTimes;
-    }
-
-    public void setReconsumeTimes(int reconsumeTimes) {
-        this.reconsumeTimes = reconsumeTimes;
-    }
-}
\ No newline at end of file
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/MetricsDataSourceConfig.java b/server/src/main/java/org/apache/rocketmq/studio/model/MetricsDataSourceConfig.java
index f15400ae..da45e74e 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/MetricsDataSourceConfig.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/MetricsDataSourceConfig.java
@@ -16,7 +16,7 @@
  */
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.Data;
 import java.io.Serializable;
 import java.util.Map;
 
@@ -29,6 +29,7 @@ import java.util.Map;
  * password / bearerToken should be encrypted at rest in production.
  * </p>
  */
+@Data
 public class MetricsDataSourceConfig implements Serializable {
 
     private static final long serialVersionUID = 1L;
@@ -89,94 +90,4 @@ public class MetricsDataSourceConfig implements Serializable {
      * Whether this data source is currently activated.
      */
     private boolean enabled;
-
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    public String getAuthType() {
-        return authType;
-    }
-
-    public void setAuthType(String authType) {
-        this.authType = authType;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    public String getBearerToken() {
-        return bearerToken;
-    }
-
-    public void setBearerToken(String bearerToken) {
-        this.bearerToken = bearerToken;
-    }
-
-    public String getProviderType() {
-        return providerType;
-    }
-
-    public void setProviderType(String providerType) {
-        this.providerType = providerType;
-    }
-
-    public boolean isTlsEnabled() {
-        return tlsEnabled;
-    }
-
-    public void setTlsEnabled(boolean tlsEnabled) {
-        this.tlsEnabled = tlsEnabled;
-    }
-
-    public Map<String, String> getDefaultLabels() {
-        return defaultLabels;
-    }
-
-    public void setDefaultLabels(Map<String, String> defaultLabels) {
-        this.defaultLabels = defaultLabels;
-    }
-
-    public int getScrapeInterval() {
-        return scrapeInterval;
-    }
-
-    public void setScrapeInterval(int scrapeInterval) {
-        this.scrapeInterval = scrapeInterval;
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/MetricsHealthResult.java b/server/src/main/java/org/apache/rocketmq/studio/model/MetricsHealthResult.java
index 118ff31e..e94ae8da 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/MetricsHealthResult.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/MetricsHealthResult.java
@@ -16,7 +16,7 @@
  */
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.Data;
 import java.io.Serializable;
 import java.util.List;
 
@@ -24,6 +24,7 @@ import java.util.List;
  * Result object returned by data-source health checks.
  * Contains connection status, discovered metric families, and latency.
  */
+@Data
 public class MetricsHealthResult implements Serializable {
 
     private static final long serialVersionUID = 1L;
@@ -42,46 +43,4 @@ public class MetricsHealthResult implements Serializable {
 
     /** Round-trip query latency in milliseconds during the health probe. */
     private long queryLatencyMs;
-
-
-    public boolean isConnected() {
-        return connected;
-    }
-
-    public void setConnected(boolean connected) {
-        this.connected = connected;
-    }
-
-    public String getStatusMessage() {
-        return statusMessage;
-    }
-
-    public void setStatusMessage(String statusMessage) {
-        this.statusMessage = statusMessage;
-    }
-
-    public List<String> getMissingMetricFamilies() {
-        return missingMetricFamilies;
-    }
-
-    public void setMissingMetricFamilies(List<String> missingMetricFamilies) {
-        this.missingMetricFamilies = missingMetricFamilies;
-    }
-
-    public List<String> getAvailableMetricFamilies() {
-        return availableMetricFamilies;
-    }
-
-    public void setAvailableMetricFamilies(List<String> availableMetricFamilies) {
-        this.availableMetricFamilies = availableMetricFamilies;
-    }
-
-    public long getQueryLatencyMs() {
-        return queryLatencyMs;
-    }
-
-    public void setQueryLatencyMs(long queryLatencyMs) {
-        this.queryLatencyMs = queryLatencyMs;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/Policy.java b/server/src/main/java/org/apache/rocketmq/studio/model/Policy.java
index 2483425e..216cf459 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/Policy.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/Policy.java
@@ -17,26 +17,11 @@
 
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class Policy {
     private String policyType;
     private List<Entry> entries;
-    public String getPolicyType() {
-        return policyType;
-    }
-
-    public void setPolicyType(String policyType) {
-        this.policyType = policyType;
-    }
-
-    public List<Entry> getEntries() {
-        return entries;
-    }
-
-    public void setEntries(List<Entry> entries) {
-        this.entries = entries;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/PolicyRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/PolicyRequest.java
index b143812e..d7000909 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/PolicyRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/PolicyRequest.java
@@ -17,44 +17,13 @@
 
 package org.apache.rocketmq.studio.model;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class PolicyRequest {
     private String clusterName;
     private String brokerName;
     private String subject;
     private List<Policy> policies;
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public String getBrokerName() {
-        return brokerName;
-    }
-
-    public void setBrokerName(String brokerName) {
-        this.brokerName = brokerName;
-    }
-
-    public String getSubject() {
-        return subject;
-    }
-
-    public void setSubject(String subject) {
-        this.subject = subject;
-    }
-
-    public List<Policy> getPolicies() {
-        return policies;
-    }
-
-    public void setPolicies(List<Policy> policies) {
-        this.policies = policies;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/SubscriptionInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/SubscriptionInfo.java
index ccc3bd59..8e089f58 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/SubscriptionInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/SubscriptionInfo.java
@@ -16,8 +16,10 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Map;
 
+@Data
 public class SubscriptionInfo {
 
     private String topic;
@@ -47,84 +49,4 @@ public class SubscriptionInfo {
     public boolean isSQL92Subscription() {
         return "SQL92".equals(subscriptionType);
     }
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public String getConsumerGroup() {
-        return consumerGroup;
-    }
-
-    public void setConsumerGroup(String consumerGroup) {
-        this.consumerGroup = consumerGroup;
-    }
-
-    public String getSubExpression() {
-        return subExpression;
-    }
-
-    public void setSubExpression(String subExpression) {
-        this.subExpression = subExpression;
-    }
-
-    public String getSubscriptionType() {
-        return subscriptionType;
-    }
-
-    public void setSubscriptionType(String subscriptionType) {
-        this.subscriptionType = subscriptionType;
-    }
-
-    public Long getVersion() {
-        return version;
-    }
-
-    public void setVersion(Long version) {
-        this.version = version;
-    }
-
-    public Boolean getBroadcast() {
-        return broadcast;
-    }
-
-    public void setBroadcast(Boolean broadcast) {
-        this.broadcast = broadcast;
-    }
-
-    public String getConsumeFromWhere() {
-        return consumeFromWhere;
-    }
-
-    public void setConsumeFromWhere(String consumeFromWhere) {
-        this.consumeFromWhere = consumeFromWhere;
-    }
-
-    public Long getConsumeProgress() {
-        return consumeProgress;
-    }
-
-    public void setConsumeProgress(Long consumeProgress) {
-        this.consumeProgress = consumeProgress;
-    }
-
-    public Long getBacklogCount() {
-        return backlogCount;
-    }
-
-    public void setBacklogCount(Long backlogCount) {
-        this.backlogCount = backlogCount;
-    }
-
-    public Map<String, Object> getAttributes() {
-        return attributes;
-    }
-
-    public void setAttributes(Map<String, Object> attributes) {
-        this.attributes = attributes;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/TopicInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/TopicInfo.java
index bf5082cb..339b9eb2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/TopicInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/TopicInfo.java
@@ -16,9 +16,11 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
 import java.util.Date;
 import java.util.Map;
 
+@Data
 public class TopicInfo {
 
     private String topicName;
@@ -79,148 +81,4 @@ public class TopicInfo {
     public boolean isTransactionTopic() {
         return TopicType.TRANSACTION.equals(topicType);
     }
-    public String getTopicName() {
-        return topicName;
-    }
-
-    public void setTopicName(String topicName) {
-        this.topicName = topicName;
-    }
-
-    public TopicType getTopicType() {
-        return topicType;
-    }
-
-    public void setTopicType(TopicType topicType) {
-        this.topicType = topicType;
-    }
-
-    public Integer getReadQueueNums() {
-        return readQueueNums;
-    }
-
-    public void setReadQueueNums(Integer readQueueNums) {
-        this.readQueueNums = readQueueNums;
-    }
-
-    public Integer getWriteQueueNums() {
-        return writeQueueNums;
-    }
-
-    public void setWriteQueueNums(Integer writeQueueNums) {
-        this.writeQueueNums = writeQueueNums;
-    }
-
-    public Integer getPerm() {
-        return perm;
-    }
-
-    public void setPerm(Integer perm) {
-        this.perm = perm;
-    }
-
-    public Boolean getOrderTopic() {
-        return orderTopic;
-    }
-
-    public void setOrderTopic(Boolean orderTopic) {
-        this.orderTopic = orderTopic;
-    }
-
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
-
-    public String getTopicStatus() {
-        return topicStatus;
-    }
-
-    public void setTopicStatus(String topicStatus) {
-        this.topicStatus = topicStatus;
-    }
-
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public Map<String, String> getAttributes() {
-        return attributes;
-    }
-
-    public void setAttributes(Map<String, String> attributes) {
-        this.attributes = attributes;
-    }
-
-    public Long getFifoTimeoutSeconds() {
-        return fifoTimeoutSeconds;
-    }
-
-    public void setFifoTimeoutSeconds(Long fifoTimeoutSeconds) {
-        this.fifoTimeoutSeconds = fifoTimeoutSeconds;
-    }
-
-    public Long getLiteTopicTTL() {
-        return liteTopicTTL;
-    }
-
-    public void setLiteTopicTTL(Long liteTopicTTL) {
-        this.liteTopicTTL = liteTopicTTL;
-    }
-
-    public String getSessionId() {
-        return sessionId;
-    }
-
-    public void setSessionId(String sessionId) {
-        this.sessionId = sessionId;
-    }
-
-    public String getAutoCreatePattern() {
-        return autoCreatePattern;
-    }
-
-    public void setAutoCreatePattern(String autoCreatePattern) {
-        this.autoCreatePattern = autoCreatePattern;
-    }
-
-    public String getDelayLevel() {
-        return delayLevel;
-    }
-
-    public void setDelayLevel(String delayLevel) {
-        this.delayLevel = delayLevel;
-    }
-
-    public String getTransactionServerAddr() {
-        return transactionServerAddr;
-    }
-
-    public void setTransactionServerAddr(String transactionServerAddr) {
-        this.transactionServerAddr = transactionServerAddr;
-    }
-
-    public Long getTransactionTimeoutSeconds() {
-        return transactionTimeoutSeconds;
-    }
-
-    public void setTransactionTimeoutSeconds(Long transactionTimeoutSeconds) {
-        this.transactionTimeoutSeconds = transactionTimeoutSeconds;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/User.java b/server/src/main/java/org/apache/rocketmq/studio/model/User.java
index 6844d093..4f1772b3 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/User.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/User.java
@@ -17,18 +17,21 @@
 package org.apache.rocketmq.studio.model;
 
 import org.hibernate.validator.constraints.Range;
+import lombok.Data;
+import lombok.ToString;
 
+@Data
 public class User {
     public static final int SUPER = 0;
     public static final int NORMAL = 1;
 
     private long id;
     private String name;
+    @ToString.Exclude
     private String password;
     @Range(min = 0, max = 1)
     private int type = 0;
 
-
     public User(String name, String password, int type) {
         this.name = name;
         this.password = password;
@@ -38,46 +41,4 @@ public class User {
     public User cloneOne() {
         return new User(this.name, this.password, this.type);
     }
-
-    public long getId() {
-        return id;
-    }
-
-    public void setId(long id) {
-        this.id = id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    public int getType() {
-        return type;
-    }
-
-    public void setType(int type) {
-        this.type = type;
-    }
-
-    @Override
-    public String toString() {
-        return "User{" +
-                "id=" + id +
-                ", name='" + name + '\'' +
-                ", password='******'" +
-                ", type=" + type +
-                '}';
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/UserInfo.java b/server/src/main/java/org/apache/rocketmq/studio/model/UserInfo.java
index b5f21236..87d55bd9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/UserInfo.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/UserInfo.java
@@ -16,52 +16,13 @@
  */
 package org.apache.rocketmq.studio.model;
 
+import lombok.Data;
+
+@Data
 public class UserInfo {
     public static final String USER_INFO = "userInfo";
     private User user;
     private long loginTime;
     private String ip;
     private String sessionId;
-
-    public long getLoginTime() {
-        return loginTime;
-    }
-
-    public void setLoginTime(long loginTime) {
-        this.loginTime = loginTime;
-    }
-
-    public String getIp() {
-        return ip;
-    }
-
-    public void setIp(String ip) {
-        this.ip = ip;
-    }
-
-    public User getUser() {
-        return user;
-    }
-
-    public void setUser(User user) {
-        this.user = user;
-    }
-
-    public String getSessionId() {
-        return sessionId;
-    }
-
-    public void setSessionId(String sessionId) {
-        this.sessionId = sessionId;
-    }
-
-    @Override
-    public String toString() {
-        return "UserInfo{" +
-                "user=" + user +
-                ", loginTime=" + loginTime +
-                ", ip='" + ip + '\'' +
-                ", sessionId='" + sessionId + '\'' +
-                '}';
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/ArchitectureSwitchRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/ArchitectureSwitchRequest.java
index 574eb099..f996ee04 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/ArchitectureSwitchRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/ArchitectureSwitchRequest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
+
 /**
  * Request body for architecture switch operation.
  *
@@ -26,6 +28,7 @@ package org.apache.rocketmq.studio.model.request;
  * {@code proxyAddresses} and {@code nameSrvAddress} fields are required.
  * For V4 architecture, only {@code accessType} is needed.</p>
  */
+@Data
 public class ArchitectureSwitchRequest {
 
     /**
@@ -45,28 +48,4 @@ public class ArchitectureSwitchRequest {
      * Required when accessType is V5_PROXY_LOCAL or V5_PROXY_CLUSTER.
      */
     private String nameSrvAddress;
-
-    public String getAccessType() {
-        return accessType;
-    }
-
-    public void setAccessType(String accessType) {
-        this.accessType = accessType;
-    }
-
-    public String[] getProxyAddresses() {
-        return proxyAddresses;
-    }
-
-    public void setProxyAddresses(String[] proxyAddresses) {
-        this.proxyAddresses = proxyAddresses;
-    }
-
-    public String getNameSrvAddress() {
-        return nameSrvAddress;
-    }
-
-    public void setNameSrvAddress(String nameSrvAddress) {
-        this.nameSrvAddress = nameSrvAddress;
-    }
-}
\ No newline at end of file
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/MessageQuery.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/MessageQuery.java
index 141ce421..57d6a78a 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/MessageQuery.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/MessageQuery.java
@@ -16,6 +16,9 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
+
+@Data
 public class MessageQuery {
     /**
      * current page num
@@ -31,52 +34,4 @@ public class MessageQuery {
     private long begin;
 
     private long end;
-
-    public int getPageNum() {
-        return pageNum;
-    }
-
-    public void setPageNum(int pageNum) {
-        this.pageNum = pageNum;
-    }
-
-    public int getPageSize() {
-        return pageSize;
-    }
-
-    public void setPageSize(int pageSize) {
-        this.pageSize = pageSize;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public String getTaskId() {
-        return taskId;
-    }
-
-    public void setTaskId(String taskId) {
-        this.taskId = taskId;
-    }
-
-    public long getBegin() {
-        return begin;
-    }
-
-    public void setBegin(long begin) {
-        this.begin = begin;
-    }
-
-    public long getEnd() {
-        return end;
-    }
-
-    public void setEnd(long end) {
-        this.end = end;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java
index 7144f193..5c4468a0 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.model.request;
 
 import org.apache.rocketmq.studio.cluster.metrics.MetricQueryDTO;
+import lombok.Data;
 
 /**
  * Request to run a PromQL range query against a configured data source.
@@ -26,6 +27,7 @@ import org.apache.rocketmq.studio.cluster.metrics.MetricQueryDTO;
  * never persisted, mirroring the existing {@code /api/settings/datasources/test}
  * flow.</p>
  */
+@Data
 public class MetricsDataSourceQueryRequest {
 
     private MetricQueryDTO query;
@@ -35,36 +37,4 @@ public class MetricsDataSourceQueryRequest {
     private String password;
 
     private String bearerToken;
-
-    public MetricQueryDTO getQuery() {
-        return query;
-    }
-
-    public void setQuery(MetricQueryDTO query) {
-        this.query = query;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    public String getBearerToken() {
-        return bearerToken;
-    }
-
-    public void setBearerToken(String bearerToken) {
-        this.bearerToken = bearerToken;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceRequest.java
index 72846b2b..6e2483f2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceRequest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 
 /**
  * Metrics data source configuration request DTO.
@@ -31,6 +32,7 @@ package org.apache.rocketmq.studio.model.request;
  *   <li><b>CORTEX:</b> Cortex (Prometheus-compatible)</li>
  * </ul>
  */
+@Data
 public class MetricsDataSourceRequest {
 
     /**
@@ -104,108 +106,4 @@ public class MetricsDataSourceRequest {
      * Additional description or notes about this data source.
      */
     private String description;
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public void setType(String type) {
-        this.type = type;
-    }
-
-    public String getProviderType() {
-        return providerType;
-    }
-
-    public void setProviderType(String providerType) {
-        this.providerType = providerType;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    public String getBearerToken() {
-        return bearerToken;
-    }
-
-    public void setBearerToken(String bearerToken) {
-        this.bearerToken = bearerToken;
-    }
-
-    public boolean isIsDefault() {
-        return isDefault;
-    }
-
-    public void setIsDefault(boolean isDefault) {
-        this.isDefault = isDefault;
-    }
-
-    public boolean isReadOnly() {
-        return readOnly;
-    }
-
-    public void setReadOnly(boolean readOnly) {
-        this.readOnly = readOnly;
-    }
-
-    public String getCustomHeaders() {
-        return customHeaders;
-    }
-
-    public void setCustomHeaders(String customHeaders) {
-        this.customHeaders = customHeaders;
-    }
-
-    public Integer getConnectionTimeoutMs() {
-        return connectionTimeoutMs;
-    }
-
-    public void setConnectionTimeoutMs(Integer connectionTimeoutMs) {
-        this.connectionTimeoutMs = connectionTimeoutMs;
-    }
-
-    public Integer getReadTimeoutMs() {
-        return readTimeoutMs;
-    }
-
-    public void setReadTimeoutMs(Integer readTimeoutMs) {
-        this.readTimeoutMs = readTimeoutMs;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
 }
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/ResetOffsetRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/ResetOffsetRequest.java
index f741dd3f..9c8a2bf9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/ResetOffsetRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/ResetOffsetRequest.java
@@ -16,43 +16,13 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class ResetOffsetRequest {
     private List<String> consumerGroupList;
     private String topic;
     private long resetTime;
     private boolean force;
-
-    public List<String> getConsumerGroupList() {
-        return consumerGroupList;
-    }
-
-    public void setConsumerGroupList(List<String> consumerGroupList) {
-        this.consumerGroupList = consumerGroupList;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public long getResetTime() {
-        return resetTime;
-    }
-
-    public void setResetTime(long resetTime) {
-        this.resetTime = resetTime;
-    }
-
-    public boolean isForce() {
-        return force;
-    }
-
-    public void setForce(boolean force) {
-        this.force = force;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/SendTopicMessageRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/SendTopicMessageRequest.java
index 624b739e..ec7a139a 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/SendTopicMessageRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/SendTopicMessageRequest.java
@@ -16,51 +16,13 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 
+@Data
 public class SendTopicMessageRequest {
     private String topic;
     private String key;
     private String tag;
     private String messageBody;
     private boolean traceEnabled;
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getTag() {
-        return tag;
-    }
-
-    public void setTag(String tag) {
-        this.tag = tag;
-    }
-
-    public String getMessageBody() {
-        return messageBody;
-    }
-
-    public void setMessageBody(String messageBody) {
-        this.messageBody = messageBody;
-    }
-
-    public boolean isTraceEnabled() {
-        return traceEnabled;
-    }
-
-    public void setTraceEnabled(boolean traceEnabled) {
-        this.traceEnabled = traceEnabled;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeList.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeList.java
index ec059079..2ce80fd9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeList.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeList.java
@@ -16,28 +16,14 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class TopicTypeList {
     private List<String> topicNameList;
     private List<String> messageTypeList;
 
-    public List<String> getTopicNameList() {
-        return topicNameList;
-    }
-
-    public void setTopicNameList(List<String> topicNameList) {
-        this.topicNameList = topicNameList;
-    }
-
-    public List<String> getMessageTypeList() {
-        return messageTypeList;
-    }
-
-    public void setMessageTypeList(List<String> messageTypeList) {
-        this.messageTypeList = messageTypeList;
-    }
-
     public TopicTypeList(List<String> topicNameList, List<String> messageTypeList) {
         this.topicNameList = topicNameList;
         this.messageTypeList = messageTypeList;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeMeta.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeMeta.java
index 22148962..99660d6a 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeMeta.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/TopicTypeMeta.java
@@ -16,23 +16,10 @@
  */
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
+
+@Data
 public class TopicTypeMeta {
     private String topicName;
     private String messageType;
-
-    public String getTopicName() {
-        return topicName;
-    }
-
-    public void setTopicName(String topicName) {
-        this.topicName = topicName;
-    }
-
-    public String getMessageType() {
-        return messageType;
-    }
-
-    public void setMessageType(String messageType) {
-        this.messageType = messageType;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserCreateRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserCreateRequest.java
index 20a76d53..758e11d9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserCreateRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserCreateRequest.java
@@ -17,33 +17,11 @@
 
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 
+@Data
 public class UserCreateRequest {
     private String clusterName;
     private String brokerName;
     private UserInfoParam userInfo;
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public String getBrokerName() {
-        return brokerName;
-    }
-
-    public void setBrokerName(String brokerName) {
-        this.brokerName = brokerName;
-    }
-
-    public UserInfoParam getUserInfo() {
-        return userInfo;
-    }
-
-    public void setUserInfo(UserInfoParam userInfo) {
-        this.userInfo = userInfo;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserInfoParam.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserInfoParam.java
index f733edbd..0037a545 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserInfoParam.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserInfoParam.java
@@ -17,42 +17,12 @@
 
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 
+@Data
 public class UserInfoParam {
     private String username;
     private String password;
     private String userStatus;
     private String userType;
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    public String getUserStatus() {
-        return userStatus;
-    }
-
-    public void setUserStatus(String userStatus) {
-        this.userStatus = userStatus;
-    }
-
-    public String getUserType() {
-        return userType;
-    }
-
-    public void setUserType(String userType) {
-        this.userType = userType;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserUpdateRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserUpdateRequest.java
index 65e8596d..1027a25b 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/request/UserUpdateRequest.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/UserUpdateRequest.java
@@ -17,33 +17,11 @@
 
 package org.apache.rocketmq.studio.model.request;
 
+import lombok.Data;
 
+@Data
 public class UserUpdateRequest {
     private String clusterName;
     private String brokerName;
     private UserInfoParam userInfo;
-    public String getClusterName() {
-        return clusterName;
-    }
-
-    public void setClusterName(String clusterName) {
-        this.clusterName = clusterName;
-    }
-
-    public String getBrokerName() {
-        return brokerName;
-    }
-
-    public void setBrokerName(String brokerName) {
-        this.brokerName = brokerName;
-    }
-
-    public UserInfoParam getUserInfo() {
-        return userInfo;
-    }
-
-    public void setUserInfo(UserInfoParam userInfo) {
-        this.userInfo = userInfo;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/trace/MessageTraceGraph.java b/server/src/main/java/org/apache/rocketmq/studio/model/trace/MessageTraceGraph.java
index f1a0abcb..6b518aaa 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/trace/MessageTraceGraph.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/trace/MessageTraceGraph.java
@@ -17,36 +17,12 @@
 
 package org.apache.rocketmq.studio.model.trace;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class MessageTraceGraph {
     private ProducerNode producerNode;
     private List<SubscriptionNode> subscriptionNodeList;
     private List<Object> messageTraceViews;
-
-    public ProducerNode getProducerNode() {
-        return producerNode;
-    }
-
-    public void setProducerNode(ProducerNode producerNode) {
-        this.producerNode = producerNode;
-    }
-
-    public List<SubscriptionNode> getSubscriptionNodeList() {
-        return subscriptionNodeList;
-    }
-
-    public void setSubscriptionNodeList(List<SubscriptionNode> subscriptionNodeList) {
-        this.subscriptionNodeList = subscriptionNodeList;
-    }
-
-    public List<Object> getMessageTraceViews() {
-        return messageTraceViews;
-    }
-
-    public void setMessageTraceViews(List<Object> messageTraceViews) {
-        this.messageTraceViews = messageTraceViews;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/trace/ProducerNode.java b/server/src/main/java/org/apache/rocketmq/studio/model/trace/ProducerNode.java
index a3c40225..43f78338 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/trace/ProducerNode.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/trace/ProducerNode.java
@@ -16,9 +16,10 @@
  */
 package org.apache.rocketmq.studio.model.trace;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class ProducerNode {
     private String msgId;
     private String tags;
@@ -28,68 +29,4 @@ public class ProducerNode {
     private String groupName;
     private TraceNode traceNode;
     private List<TraceNode> transactionNodeList;
-    public String getMsgId() {
-        return msgId;
-    }
-
-    public void setMsgId(String msgId) {
-        this.msgId = msgId;
-    }
-
-    public String getTags() {
-        return tags;
-    }
-
-    public void setTags(String tags) {
-        this.tags = tags;
-    }
-
-    public String getKeys() {
-        return keys;
-    }
-
-    public void setKeys(String keys) {
-        this.keys = keys;
-    }
-
-    public String getOffSetMsgId() {
-        return offSetMsgId;
-    }
-
-    public void setOffSetMsgId(String offSetMsgId) {
-        this.offSetMsgId = offSetMsgId;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public void setTopic(String topic) {
-        this.topic = topic;
-    }
-
-    public String getGroupName() {
-        return groupName;
-    }
-
-    public void setGroupName(String groupName) {
-        this.groupName = groupName;
-    }
-
-    public TraceNode getTraceNode() {
-        return traceNode;
-    }
-
-    public void setTraceNode(TraceNode traceNode) {
-        this.traceNode = traceNode;
-    }
-
-    public List<TraceNode> getTransactionNodeList() {
-        return transactionNodeList;
-    }
-
-    public void setTransactionNodeList(List<TraceNode> transactionNodeList) {
-        this.transactionNodeList = transactionNodeList;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/trace/SubscriptionNode.java b/server/src/main/java/org/apache/rocketmq/studio/model/trace/SubscriptionNode.java
index b0239a7f..e6bd72d6 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/trace/SubscriptionNode.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/trace/SubscriptionNode.java
@@ -16,26 +16,11 @@
  */
 package org.apache.rocketmq.studio.model.trace;
 
-
+import lombok.Data;
 import java.util.List;
 
+@Data
 public class SubscriptionNode {
     private String subscriptionGroup;
     private List<TraceNode> consumeNodeList;
-    public String getSubscriptionGroup() {
-        return subscriptionGroup;
-    }
-
-    public void setSubscriptionGroup(String subscriptionGroup) {
-        this.subscriptionGroup = subscriptionGroup;
-    }
-
-    public List<TraceNode> getConsumeNodeList() {
-        return consumeNodeList;
-    }
-
-    public void setConsumeNodeList(List<TraceNode> consumeNodeList) {
-        this.consumeNodeList = consumeNodeList;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/trace/TraceNode.java b/server/src/main/java/org/apache/rocketmq/studio/model/trace/TraceNode.java
index 921364ea..910c6cef 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/model/trace/TraceNode.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/model/trace/TraceNode.java
@@ -17,7 +17,9 @@
 
 package org.apache.rocketmq.studio.model.trace;
 
+import lombok.Data;
 
+@Data
 public class TraceNode {
     private String requestId;
     private String storeHost;
@@ -31,100 +33,4 @@ public class TraceNode {
     private String transactionId;
     private boolean fromTransactionCheck;
     private String msgType;
-    public String getRequestId() {
-        return requestId;
-    }
-
-    public void setRequestId(String requestId) {
-        this.requestId = requestId;
-    }
-
-    public String getStoreHost() {
-        return storeHost;
-    }
-
-    public void setStoreHost(String storeHost) {
-        this.storeHost = storeHost;
-    }
-
-    public String getClientHost() {
-        return clientHost;
-    }
-
-    public void setClientHost(String clientHost) {
-        this.clientHost = clientHost;
-    }
-
-    public int getCostTime() {
-        return costTime;
-    }
-
-    public void setCostTime(int costTime) {
-        this.costTime = costTime;
-    }
-
-    public long getBeginTimestamp() {
-        return beginTimestamp;
-    }
-
-    public void setBeginTimestamp(long beginTimestamp) {
-        this.beginTimestamp = beginTimestamp;
-    }
-
-    public long getEndTimestamp() {
-        return endTimestamp;
-    }
-
-    public void setEndTimestamp(long endTimestamp) {
-        this.endTimestamp = endTimestamp;
-    }
-
-    public int getRetryTimes() {
-        return retryTimes;
-    }
-
-    public void setRetryTimes(int retryTimes) {
-        this.retryTimes = retryTimes;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public String getTransactionState() {
-        return transactionState;
-    }
-
-    public void setTransactionState(String transactionState) {
-        this.transactionState = transactionState;
-    }
-
-    public String getTransactionId() {
-        return transactionId;
-    }
-
-    public void setTransactionId(String transactionId) {
-        this.transactionId = transactionId;
-    }
-
-    public boolean isFromTransactionCheck() {
-        return fromTransactionCheck;
-    }
-
-    public void setFromTransactionCheck(boolean fromTransactionCheck) {
-        this.fromTransactionCheck = fromTransactionCheck;
-    }
-
-    public String getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(String msgType) {
-        this.msgType = msgType;
-    }
-
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigDTO.java b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigDTO.java
new file mode 100644
index 00000000..d2220e6e
--- /dev/null
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigDTO.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ops.ai;
+
+import lombok.Data;
+
+@Data
+public class LlmConfigDTO {
+
+    private String provider;
+    private String engine;
+    private String apiKey;
+    private String apiBase;
+    private String model;
+    private int maxTokens;
+    private double temperature;
+    private boolean enabled;
+    private String deploymentName;
+    private String apiVersion;
+    private String awsRegion;
+
+    public LlmConfigVO toLlmConfigVO() {
+        return LlmConfigVO.builder()
+                .provider(provider)
+                .engine(engine)
+                .apiKey(apiKey)
+                .apiBase(apiBase)
+                .model(model)
+                .maxTokens(maxTokens)
+                .temperature(temperature)
+                .enabled(enabled)
+                .deploymentName(deploymentName)
+                .apiVersion(apiVersion)
+                .awsRegion(awsRegion)
+                .build();
+    }
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
index 75be1dbc..fe7543a5 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
@@ -17,6 +17,8 @@
 
 package org.apache.rocketmq.studio.ops.ai;
 
+import org.springframework.util.StringUtils;
+
 import org.apache.rocketmq.studio.settings.GeneralSettingsVO;
 import org.apache.rocketmq.studio.settings.SettingsService;
 import lombok.RequiredArgsConstructor;
@@ -80,7 +82,7 @@ public class LlmConfigService {
                 ? copy(overrides)
                 : fromGeneralSettings(settingsService.getGeneralSettings());
         String token = envToken();
-        if (!isBlank(token)) {
+        if (!!StringUtils.hasText(token)) {
             config.setApiKey(token.trim());
             config.setEnabled(true);
         }
@@ -95,7 +97,7 @@ public class LlmConfigService {
         }
         GeneralSettingsVO current = settingsService.getGeneralSettings();
         // The env-injected token is authoritative at runtime but must never be persisted.
-        String persistedApiKey = isBlank(envToken())
+        String persistedApiKey = !StringUtils.hasText(envToken())
                 ? normalized.getApiKey()
                 : defaultString(current.getApiKey(), "");
         GeneralSettingsVO updated = GeneralSettingsVO.builder()
@@ -155,19 +157,19 @@ public class LlmConfigService {
         }
         boolean keyRequired = !"ollama".equals(provider)
                 && LlmConfigVO.ENGINE_HTTP.equalsIgnoreCase(normalized.normalizeEngine());
-        if (keyRequired && isBlank(normalized.getApiKey())) {
+        if (keyRequired && !StringUtils.hasText(normalized.getApiKey())) {
             return LlmOperationResultVO.failure(
                     "llm.config.missing_api_key",
                     "LLM API key is required",
                     "Configure an API key for provider " + provider + ", or select ollama for a local provider.");
         }
-        if ("azure".equals(provider) && isBlank(normalized.getDeploymentName())) {
+        if ("azure".equals(provider) && !StringUtils.hasText(normalized.getDeploymentName())) {
             return LlmOperationResultVO.failure(
                     "llm.config.missing_deployment",
                     "Azure OpenAI deployment name is required",
                     "Set the Azure deployment name that maps to the selected model.");
         }
-        if (isBlank(normalized.getModel())) {
+        if (!StringUtils.hasText(normalized.getModel())) {
             return LlmOperationResultVO.failure(
                     "llm.config.missing_model",
                     "LLM model is required",
@@ -215,7 +217,7 @@ public class LlmConfigService {
                 .model(model)
                 .maxTokens(DEFAULT_MAX_TOKENS)
                 .temperature(DEFAULT_TEMPERATURE)
-                .enabled(!requiresApiKey(provider) || !isBlank(apiKey))
+                .enabled(!requiresApiKey(provider) || !!StringUtils.hasText(apiKey))
                 .apiVersion("2024-02-15-preview")
                 .awsRegion("us-east-1")
                 .build();
@@ -253,11 +255,11 @@ public class LlmConfigService {
             normalized.setApiKey("");
             return normalized;
         }
-        if (isBlank(normalized.getApiKey())) {
+        if (!StringUtils.hasText(normalized.getApiKey())) {
             // Fall back to the key stored in settings only; the env-injected token
             // must never be persisted into the settings table.
             String storedKey = settingsService.getGeneralSettings().getApiKey();
-            if (!isBlank(envToken())) {
+            if (!!StringUtils.hasText(envToken())) {
                 storedKey = defaultString(storedKey, envToken());
             }
             normalized.setApiKey(defaultString(storedKey, ""));
@@ -319,7 +321,7 @@ public class LlmConfigService {
         try {
             URI uri = new URI(apiBase);
             String scheme = uri.getScheme() == null ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
-            return ("http".equals(scheme) || "https".equals(scheme)) && !isBlank(uri.getHost())
+            return ("http".equals(scheme) || "https".equals(scheme)) && !!StringUtils.hasText(uri.getHost())
                     && !apiBase.endsWith(CHAT_COMPLETIONS_PATH);
         } catch (URISyntaxException exception) {
             return false;
@@ -327,10 +329,7 @@ public class LlmConfigService {
     }
 
     private String defaultString(String value, String fallback) {
-        return isBlank(value) ? fallback : value.trim();
+        return !StringUtils.hasText(value) ? fallback : value.trim();
     }
 
-    private boolean isBlank(String value) {
-        return value == null || value.trim().isEmpty();
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmController.java b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmController.java
index 1c493c70..3410c813 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmController.java
@@ -18,6 +18,7 @@
 package org.apache.rocketmq.studio.ops.ai;
 
 import lombok.RequiredArgsConstructor;
+import org.apache.rocketmq.studio.common.domain.Result;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -32,23 +33,23 @@ public class LlmController {
     private final LlmConfigService llmConfigService;
 
     @GetMapping("/config")
-    public LlmConfigVO getConfig() {
-        return llmConfigService.getConfig();
+    public Result<LlmConfigVO> getConfig() {
+        return Result.ok(llmConfigService.getConfig());
     }
 
     @PostMapping("/config")
-    public LlmOperationResultVO saveConfig(@RequestBody LlmConfigVO config) {
-        llmConfigService.saveConfig(config);
-        return LlmOperationResultVO.success("saved");
+    public Result<LlmOperationResultVO> saveConfig(@RequestBody LlmConfigDTO config) {
+        llmConfigService.saveConfig(config.toLlmConfigVO());
+        return Result.ok(LlmOperationResultVO.success("saved"));
     }
 
     @PostMapping("/config/test")
-    public LlmOperationResultVO testConfig(@RequestBody LlmConfigVO config) {
-        return llmConfigService.testConfig(config);
+    public Result<LlmOperationResultVO> testConfig(@RequestBody LlmConfigDTO config) {
+        return Result.ok(llmConfigService.testConfig(config.toLlmConfigVO()));
     }
 
     @GetMapping("/models")
-    public LlmModelsResultVO listModels() {
-        return llmConfigService.listModels();
+    public Result<LlmModelsResultVO> listModels() {
+        return Result.ok(llmConfigService.listModels());
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolCatalog.java b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolCatalog.java
index 4f738c7d..d33d2a49 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolCatalog.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolCatalog.java
@@ -23,6 +23,7 @@ import com.networknt.schema.InputFormat;
 import com.networknt.schema.Schema;
 import com.networknt.schema.SchemaRegistry;
 import com.networknt.schema.SpecificationVersion;
+import lombok.Getter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.ResourceLoader;
@@ -49,8 +50,11 @@ public class ToolCatalog {
     private static final String CLUSTER_LIST_TOOL = "rmq.cluster.list";
     private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory());
 
+    @Getter
     private final String version;
+    @Getter
     private final String minimumClientVersion;
+    @Getter
     private final String digest;
     private final List<ToolDefinition> definitions;
     private final Map<String, ToolDefinition> definitionsByName;
@@ -155,18 +159,6 @@ public class ToolCatalog {
         }
     }
 
-    public String getVersion() {
-        return version;
-    }
-
-    public String getMinimumClientVersion() {
-        return minimumClientVersion;
-    }
-
-    public String getDigest() {
-        return digest;
-    }
-
     public List<ToolDefinition> list() {
         return definitions;
     }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertRepository.java b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertRepository.java
index b3aca288..27f31393 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertRepository.java
@@ -24,6 +24,7 @@ import org.apache.rocketmq.studio.persistence.mapper.RmqAlertRuleMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqSystemAlertMapper;
 import org.springframework.stereotype.Repository;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
 
 import java.time.LocalDateTime;
 import java.util.Arrays;
@@ -33,17 +34,13 @@ import java.util.stream.Collectors;
 /**
  * MySQL-backed alert repository for alert rules and system alert events.
  */
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusAlertRepository implements AlertRepository {
 
     private final RmqAlertRuleMapper ruleMapper;
     private final RmqSystemAlertMapper alertMapper;
 
-    public MybatisPlusAlertRepository(RmqAlertRuleMapper ruleMapper, RmqSystemAlertMapper alertMapper) {
-        this.ruleMapper = ruleMapper;
-        this.alertMapper = alertMapper;
-    }
-
     @Override
     public List<AlertRuleVO> findAllRules() {
         return ruleMapper.selectList(new QueryWrapper<RmqAlertRule>().orderByAsc("name")).stream()
diff --git a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/MybatisPlusAuditRepository.java b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/MybatisPlusAuditRepository.java
index 9d2f2451..acdb9015 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/MybatisPlusAuditRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/MybatisPlusAuditRepository.java
@@ -23,21 +23,19 @@ import org.apache.rocketmq.studio.persistence.entity.RmqOperationAudit;
 import org.apache.rocketmq.studio.persistence.mapper.RmqOperationAuditMapper;
 import org.springframework.stereotype.Repository;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
 
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.stream.Collectors;
 
 /** MySQL-backed audit repository (rmq_operation_audit). */
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusAuditRepository implements AuditRepository {
 
     private final RmqOperationAuditMapper auditMapper;
 
-    public MybatisPlusAuditRepository(RmqOperationAuditMapper auditMapper) {
-        this.auditMapper = auditMapper;
-    }
-
     @Override
     public PageResult<AuditRecordVO> findPage(String search, String operationType,
                                               LocalDateTime startDate, LocalDateTime endDate,
diff --git a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqCloudCredential.java b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqCloudCredential.java
index 155e79f5..4aca2975 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqCloudCredential.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqCloudCredential.java
@@ -19,9 +19,11 @@ 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;
 
+@Data
 @TableName("rmq_cloud_credential")
 public class RmqCloudCredential {
 
@@ -41,68 +43,4 @@ public class RmqCloudCredential {
     private LocalDateTime createdAt;
 
     private LocalDateTime updatedAt;
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getVendor() {
-        return vendor;
-    }
-
-    public void setVendor(String vendor) {
-        this.vendor = vendor;
-    }
-
-    public String getAccessKey() {
-        return accessKey;
-    }
-
-    public void setAccessKey(String accessKey) {
-        this.accessKey = accessKey;
-    }
-
-    public String getSecretKey() {
-        return secretKey;
-    }
-
-    public void setSecretKey(String secretKey) {
-        this.secretKey = secretKey;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
-
-    public LocalDateTime getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(LocalDateTime createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public LocalDateTime getUpdatedAt() {
-        return updatedAt;
-    }
-
-    public void setUpdatedAt(LocalDateTime updatedAt) {
-        this.updatedAt = updatedAt;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceDetailVO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceDetailVO.java
index ef736b36..928e4143 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceDetailVO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceDetailVO.java
@@ -16,8 +16,12 @@
  */
 package org.apache.rocketmq.studio.provider;
 
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import java.util.List;
 
+@Data
 public class CloudInstanceDetailVO {
 
     private String instanceId;
@@ -27,80 +31,11 @@ public class CloudInstanceDetailVO {
     private String remark;
     private List<CloudEndpoint> endpoints;
 
-    public String getInstanceId() {
-        return instanceId;
-    }
-
-    public void setInstanceId(String instanceId) {
-        this.instanceId = instanceId;
-    }
-
-    public String getInstanceName() {
-        return instanceName;
-    }
-
-    public void setInstanceName(String instanceName) {
-        this.instanceName = instanceName;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public String getRegionId() {
-        return regionId;
-    }
-
-    public void setRegionId(String regionId) {
-        this.regionId = regionId;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
-
-    public List<CloudEndpoint> getEndpoints() {
-        return endpoints;
-    }
-
-    public void setEndpoints(List<CloudEndpoint> endpoints) {
-        this.endpoints = endpoints;
-    }
-
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
     public static class CloudEndpoint {
         private String endpointType;
         private String endpointUrl;
-
-        public CloudEndpoint() {
-        }
-
-        public CloudEndpoint(String endpointType, String endpointUrl) {
-            this.endpointType = endpointType;
-            this.endpointUrl = endpointUrl;
-        }
-
-        public String getEndpointType() {
-            return endpointType;
-        }
-
-        public void setEndpointType(String endpointType) {
-            this.endpointType = endpointType;
-        }
-
-        public String getEndpointUrl() {
-            return endpointUrl;
-        }
-
-        public void setEndpointUrl(String endpointUrl) {
-            this.endpointUrl = endpointUrl;
-        }
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceOptionVO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceOptionVO.java
index e3d1da28..67d5c933 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceOptionVO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudInstanceOptionVO.java
@@ -16,6 +16,9 @@
  */
 package org.apache.rocketmq.studio.provider;
 
+import lombok.Data;
+
+@Data
 public class CloudInstanceOptionVO {
 
     private String instanceId;
@@ -25,60 +28,4 @@ public class CloudInstanceOptionVO {
     private Integer topicCount;
     private Integer groupCount;
     private String remark;
-
-    public String getInstanceId() {
-        return instanceId;
-    }
-
-    public void setInstanceId(String instanceId) {
-        this.instanceId = instanceId;
-    }
-
-    public String getInstanceName() {
-        return instanceName;
-    }
-
-    public void setInstanceName(String instanceName) {
-        this.instanceName = instanceName;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public String getRegionId() {
-        return regionId;
-    }
-
-    public void setRegionId(String regionId) {
-        this.regionId = regionId;
-    }
-
-    public Integer getTopicCount() {
-        return topicCount;
-    }
-
-    public void setTopicCount(Integer topicCount) {
-        this.topicCount = topicCount;
-    }
-
-    public Integer getGroupCount() {
-        return groupCount;
-    }
-
-    public void setGroupCount(Integer groupCount) {
-        this.groupCount = groupCount;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudRegionVO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudRegionVO.java
index 2a068a4b..7ff7b966 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/CloudRegionVO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/CloudRegionVO.java
@@ -16,32 +16,15 @@
  */
 package org.apache.rocketmq.studio.provider;
 
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class CloudRegionVO {
 
     private String regionId;
     private String regionName;
-
-    public CloudRegionVO() {
-    }
-
-    public CloudRegionVO(String regionId, String regionName) {
-        this.regionId = regionId;
-        this.regionName = regionName;
-    }
-
-    public String getRegionId() {
-        return regionId;
-    }
-
-    public void setRegionId(String regionId) {
-        this.regionId = regionId;
-    }
-
-    public String getRegionName() {
-        return regionName;
-    }
-
-    public void setRegionName(String regionName) {
-        this.regionName = regionName;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/InstanceProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/InstanceProvider.java
index 65a9a945..e3f2fa19 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/InstanceProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/InstanceProvider.java
@@ -36,6 +36,10 @@ public interface InstanceProvider {
 
     InstanceVendor vendor();
 
+    int countTopics(String instanceId);
+
+    int countGroups(String instanceId);
+
     List<TopicVO> listTopics(String instanceId, String type, String search);
 
     TopicVO createTopic(String instanceId, TopicVO topic);
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogController.java b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogController.java
index 25828c94..4fbc8c64 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogController.java
@@ -23,22 +23,20 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
+import lombok.RequiredArgsConstructor;
 
 import java.util.List;
 
 /**
  * REST endpoints for browsing the Aliyun cloud catalog with a stored credential.
  */
+@RequiredArgsConstructor
 @RestController
 @RequestMapping("/api/cloud/aliyun")
 public class AliyunCatalogController {
 
     private final AliyunCatalogService catalogService;
 
-    public AliyunCatalogController(AliyunCatalogService catalogService) {
-        this.catalogService = catalogService;
-    }
-
     @GetMapping("/regions")
     public Result<List<CloudRegionVO>> listRegions(@RequestParam String credentialId) {
         return Result.ok(catalogService.listRegions(credentialId));
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
index cb1d6311..1e6ce14b 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunCatalogService.java
@@ -32,6 +32,7 @@ import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
 import org.apache.rocketmq.studio.provider.CloudInstanceOptionVO;
 import org.apache.rocketmq.studio.provider.CloudRegionVO;
 import org.springframework.stereotype.Component;
+import lombok.RequiredArgsConstructor;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -41,6 +42,7 @@ import java.util.Locale;
 /**
  * Aliyun cloud catalog: regions and commercial RocketMQ 5.x instances visible to a credential.
  */
+@RequiredArgsConstructor
 @Component
 public class AliyunCatalogService implements CloudCatalogProvider {
 
@@ -51,10 +53,6 @@ public class AliyunCatalogService implements CloudCatalogProvider {
 
     private final AliyunClientFactory clientFactory;
 
-    public AliyunCatalogService(AliyunClientFactory clientFactory) {
-        this.clientFactory = clientFactory;
-    }
-
     @Override
     public InstanceVendor vendor() {
         return InstanceVendor.ALIYUN;
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 214a56a8..bac4f3d7 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
@@ -25,12 +25,12 @@ import darabonba.core.client.ClientOverrideConfiguration;
 import darabonba.core.exception.ClientException;
 import darabonba.core.exception.ServerException;
 import jakarta.annotation.PreDestroy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialRepository;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialVO;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialRepository;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialVO;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.springframework.stereotype.Component;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.time.Duration;
 import java.util.Map;
@@ -46,11 +46,11 @@ import java.util.function.Function;
  * Builds and caches Aliyun RocketMQ 5.x OpenAPI async clients per credential#region, and
  * converts SDK failures into {@link BusinessException} with meaningful HTTP-style codes.
  */
+@Slf4j
+@RequiredArgsConstructor
 @Component
 public class AliyunClientFactory {
 
-    private static final Logger log = LoggerFactory.getLogger(AliyunClientFactory.class);
-
     static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10);
     static final Duration RESPONSE_TIMEOUT = Duration.ofSeconds(20);
     static final long DEFAULT_CALL_TIMEOUT_SECONDS = 30L;
@@ -59,10 +59,6 @@ public class AliyunClientFactory {
     private final Map<String, AsyncClient> clients = new ConcurrentHashMap<>();
     private long callTimeoutSeconds = DEFAULT_CALL_TIMEOUT_SECONDS;
 
-    public AliyunClientFactory(CloudCredentialRepository credentialRepository) {
-        this.credentialRepository = credentialRepository;
-    }
-
     public AsyncClient client(String credentialId, String region) {
         String key = cacheKey(credentialId, region);
         return clients.computeIfAbsent(key, ignored -> createClient(credentialId, region));
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
index 04fb8788..927e3bd3 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProvider.java
@@ -43,6 +43,8 @@ import com.aliyun.sdk.service.rocketmq20220801.models.ListTopicsResponse;
 import com.aliyun.sdk.service.rocketmq20220801.models.ListTopicsResponseBody;
 import com.aliyun.sdk.service.rocketmq20220801.models.ResetConsumeOffsetRequest;
 import com.aliyun.sdk.service.rocketmq20220801.models.UpdateTopicRequest;
+import org.springframework.util.StringUtils;
+
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.instance.InstanceRepository;
@@ -56,6 +58,7 @@ import org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
 import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.springframework.stereotype.Component;
+import lombok.RequiredArgsConstructor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,6 +67,7 @@ import java.util.List;
  * Aliyun RocketMQ 5.x implementation of the instance-scoped operations SPI, backed by the
  * OpenAPI async SDK through {@link AliyunClientFactory}.
  */
+@RequiredArgsConstructor
 @Component
 public class AliyunInstanceProvider implements InstanceProvider {
 
@@ -79,16 +83,21 @@ public class AliyunInstanceProvider implements InstanceProvider {
     private final AliyunClientFactory clientFactory;
     private final InstanceRepository instanceRepository;
 
-    public AliyunInstanceProvider(AliyunClientFactory clientFactory, InstanceRepository instanceRepository) {
-        this.clientFactory = clientFactory;
-        this.instanceRepository = instanceRepository;
-    }
-
     @Override
     public InstanceVendor vendor() {
         return InstanceVendor.ALIYUN;
     }
 
+    @Override
+    public int countTopics(String instanceId) {
+        return listTopics(instanceId, null, null).size();
+    }
+
+    @Override
+    public int countGroups(String instanceId) {
+        return listConsumerGroups(instanceId, null).size();
+    }
+
     @Override
     public List<TopicVO> listTopics(String instanceId, String type, String search) {
         Context ctx = resolve(instanceId);
@@ -98,7 +107,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
                     .instanceId(ctx.cloudInstanceId())
                     .pageNumber(page)
                     .pageSize(AliyunConverters.PAGE_SIZE);
-            if (!isBlank(search)) {
+            if (!!StringUtils.hasText(search)) {
                 builder.filter(search);
             }
             ListTopicsRequest request = builder.build();
@@ -128,7 +137,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
     @Override
     public TopicVO createTopic(String instanceId, TopicVO topic) {
         Context ctx = resolve(instanceId);
-        if (topic == null || isBlank(topic.getName())) {
+        if (topic == null || !StringUtils.hasText(topic.getName())) {
             throw new BusinessException(400, "Topic name is required");
         }
         if (topic.getType() == null) {
@@ -150,7 +159,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
     @Override
     public TopicVO updateTopic(String instanceId, TopicVO topic) {
         Context ctx = resolve(instanceId);
-        if (topic == null || isBlank(topic.getName())) {
+        if (topic == null || !StringUtils.hasText(topic.getName())) {
             throw new BusinessException(400, "Topic name is required");
         }
         UpdateTopicRequest request = UpdateTopicRequest.builder()
@@ -203,7 +212,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
                     .instanceId(ctx.cloudInstanceId())
                     .pageNumber(page)
                     .pageSize(AliyunConverters.PAGE_SIZE);
-            if (!isBlank(search)) {
+            if (!!StringUtils.hasText(search)) {
                 builder.filter(search);
             }
             ListConsumerGroupsRequest request = builder.build();
@@ -230,7 +239,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
     @Override
     public ConsumerGroupVO createConsumerGroup(String instanceId, ConsumerGroupVO group) {
         Context ctx = resolve(instanceId);
-        if (group == null || isBlank(group.getName())) {
+        if (group == null || !StringUtils.hasText(group.getName())) {
             throw new BusinessException(400, "Consumer group name is required");
         }
         String deliveryOrderType = normalizeDeliveryOrderType(group.getDeliveryOrderType());
@@ -329,7 +338,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
         ResetConsumeOffsetRequest.Builder builder = ResetConsumeOffsetRequest.builder()
                 .instanceId(ctx.cloudInstanceId())
                 .consumerGroupId(groupName);
-        if (!isBlank(topic)) {
+        if (!!StringUtils.hasText(topic)) {
             builder.topicName(topic);
         }
         if (timestamp > 0L) {
@@ -352,13 +361,13 @@ public class AliyunInstanceProvider implements InstanceProvider {
                     .instanceId(ctx.cloudInstanceId())
                     .pageNumber(page)
                     .pageSize(AliyunConverters.MESSAGE_PAGE_SIZE);
-            if (!isBlank(topic)) {
+            if (!!StringUtils.hasText(topic)) {
                 builder.topicName(topic);
             }
-            if (!isBlank(msgId)) {
+            if (!!StringUtils.hasText(msgId)) {
                 builder.messageId(msgId);
             }
-            if (!isBlank(key)) {
+            if (!!StringUtils.hasText(key)) {
                 builder.messageKey(key);
             }
             if (startTime != null) {
@@ -378,7 +387,7 @@ public class AliyunInstanceProvider implements InstanceProvider {
             }
             for (ListMessagesResponseBody.List item : list) {
                 MessageRecordVO vo = AliyunConverters.toMessageRecord(item);
-                if (isBlank(tag) || tag.equals(vo.getTag())) {
+                if (!StringUtils.hasText(tag) || tag.equals(vo.getTag())) {
                     records.add(vo);
                 }
             }
@@ -407,29 +416,25 @@ public class AliyunInstanceProvider implements InstanceProvider {
     }
 
     private Context resolve(String instanceId) {
-        if (isBlank(instanceId)) {
+        if (!StringUtils.hasText(instanceId)) {
             throw new BusinessException(400, "instanceId is required");
         }
         InstanceVO instance = instanceRepository.findById(instanceId)
                 .orElseThrow(() -> new BusinessException(404, "Instance not found: " + instanceId));
-        if (isBlank(instance.getCloudInstanceId()) || isBlank(instance.getRegionId())
-                || isBlank(instance.getCredentialId())) {
+        if (!StringUtils.hasText(instance.getCloudInstanceId()) || !StringUtils.hasText(instance.getRegionId())
+                || !StringUtils.hasText(instance.getCredentialId())) {
             throw new BusinessException(400, "Instance " + instanceId + " is missing Aliyun cloud binding");
         }
         return new Context(instance.getCloudInstanceId(), instance.getRegionId(), instance.getCredentialId());
     }
 
     private static boolean matchesType(String type, TopicVO vo) {
-        if (isBlank(type)) {
+        if (!StringUtils.hasText(type)) {
             return true;
         }
         return vo.getType() != null && vo.getType().name().equalsIgnoreCase(type.trim());
     }
 
-    private static boolean isBlank(String value) {
-        return value == null || value.isBlank();
-    }
-
     private record Context(String cloudInstanceId, String regionId, String credentialId) {
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/AdminClient.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
similarity index 84%
rename from server/src/main/java/org/apache/rocketmq/studio/instance/topic/AdminClient.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
index 3bd557a4..90e958b9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/AdminClient.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
@@ -14,8 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.instance.topic;
+package org.apache.rocketmq.studio.provider.apache;
 
+import org.apache.rocketmq.studio.instance.topic.SendMessageVO;
+import org.apache.rocketmq.studio.instance.topic.SendMessageDTO;
+import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 
 
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
index 1d351546..a9e4236a 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
@@ -17,18 +17,18 @@
 package org.apache.rocketmq.studio.provider.apache;
 
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
+import org.apache.rocketmq.studio.instance.InstanceRepository;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.group.QueueProgressVO;
 import org.apache.rocketmq.studio.instance.group.SubscriptionEntryVO;
 import org.apache.rocketmq.studio.instance.message.MessageProvider;
 import org.apache.rocketmq.studio.instance.message.MessageRecordVO;
 import org.apache.rocketmq.studio.instance.message.TraceRecordVO;
-import org.apache.rocketmq.studio.instance.topic.AdminClient;
-import org.apache.rocketmq.studio.instance.topic.MetadataProvider;
 import org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
 import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.springframework.stereotype.Component;
+import lombok.RequiredArgsConstructor;
 
 import java.util.List;
 import java.util.Objects;
@@ -37,26 +37,30 @@ import java.util.Objects;
  * Open-source Apache RocketMQ implementation: pure delegation to the existing admin-client
  * beans, keeping behavior identical to the pre-provider code path.
  */
+@RequiredArgsConstructor
 @Component
 public class ApacheInstanceProvider implements InstanceProvider {
 
     private final MetadataProvider metadataProvider;
     private final AdminClient adminClient;
     private final MessageProvider messageProvider;
-
-    public ApacheInstanceProvider(MetadataProvider metadataProvider,
-                                  AdminClient adminClient,
-                                  MessageProvider messageProvider) {
-        this.metadataProvider = metadataProvider;
-        this.adminClient = adminClient;
-        this.messageProvider = messageProvider;
-    }
+    private final InstanceRepository instanceRepository;
 
     @Override
     public InstanceVendor vendor() {
         return InstanceVendor.APACHE;
     }
 
+    @Override
+    public int countTopics(String instanceId) {
+        return (int) instanceRepository.countTopicsByInstance(instanceId);
+    }
+
+    @Override
+    public int countGroups(String instanceId) {
+        return (int) instanceRepository.countGroupsByInstance(instanceId);
+    }
+
     @Override
     public List<TopicVO> listTopics(String instanceId, String type, String search) {
         return metadataProvider.listTopics(null, type, search).stream()
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
similarity index 85%
rename from server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
index fec47fa9..e5ccef5c 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
@@ -14,8 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.instance.topic;
+package org.apache.rocketmq.studio.provider.apache;
 
+import org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
+import org.apache.rocketmq.studio.instance.topic.BrokerRouteVO;
+import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.group.QueueProgressVO;
 import org.apache.rocketmq.studio.instance.group.SubscriptionEntryVO;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
new file mode 100644
index 00000000..10da98ce
--- /dev/null
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
@@ -0,0 +1,528 @@
+/*
+ * 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.provider.apache;
+
+import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.client.producer.DefaultMQProducer;
+import org.apache.rocketmq.client.producer.SendResult;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.message.Message;
+import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
+import org.apache.rocketmq.remoting.protocol.ResponseCode;
+import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
+import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
+import org.apache.rocketmq.studio.instance.topic.SendMessageDTO;
+import org.apache.rocketmq.studio.instance.topic.SendMessageVO;
+import org.apache.rocketmq.studio.instance.topic.TopicVO;
+import org.apache.rocketmq.studio.ops.audit.AuditService;
+import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
+import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
+import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
+import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
+import org.apache.rocketmq.tools.admin.MQAdminExt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Real AdminClient implementation backed by the RocketMQ admin API.
+ * Provides topic CRUD, message sending, consumer group CRUD, and offset reset.
+ */
+@Slf4j
+@Service
+@Primary
+@RequiredArgsConstructor
+public class RocketMQAdminClientImpl implements AdminClient {
+
+    private static final String MESSAGE_SENDER_GROUP_PREFIX = "studio-msg-sender";
+
+    private final MqAdminExtFactory adminFactory;
+    private final RocketMQProperties properties;
+    private final RmqTopicMapper topicMapper;
+    private final RmqGroupMapper groupMapper;
+    private final AuditService auditService;
+
+    @Override
+    public TopicVO getTopic(String name) {
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                var routeData = admin.examineTopicRouteInfo(name);
+                if (routeData == null || routeData.getQueueDatas() == null || routeData.getQueueDatas().isEmpty()) {
+                    throw new BusinessException(404, "Topic not found: " + name);
+                }
+                var qd = routeData.getQueueDatas().get(0);
+                TopicVO vo = new TopicVO();
+                vo.setId(name);
+                vo.setName(name);
+                vo.setWriteQueues(qd.getWriteQueueNums());
+                vo.setReadQueues(qd.getReadQueueNums());
+                return vo;
+            } catch (BusinessException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new BusinessException(500, "Failed to get topic: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public ConsumerGroupVO getConsumerGroup(String name) {
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            ConsumerGroupVO vo = new ConsumerGroupVO();
+            vo.setId(name);
+            vo.setName(name);
+            try {
+                var conn = admin.examineConsumerConnectionInfo(name);
+                if (conn != null) {
+                    if (conn.getConnectionSet() != null) {
+                        vo.setOnlineInstances(conn.getConnectionSet().size());
+                    }
+                    if (conn.getSubscriptionTable() != null) {
+                        vo.setSubscribedTopics(new ArrayList<>(conn.getSubscriptionTable().keySet()));
+                    }
+                }
+            } catch (MQClientException exception) {
+                if (exception.getResponseCode() == ResponseCode.CONSUMER_NOT_ONLINE) {
+                    log.debug("Consumer group {} is offline", name);
+                    return vo;
+                }
+                throw new BusinessException(502, "Failed to get consumer group: " + exception.getMessage());
+            } catch (Exception exception) {
+                throw new BusinessException(502, "Failed to get consumer group: " + exception.getMessage());
+            }
+            return vo;
+        });
+    }
+
+    @Override
+    public TopicVO createTopic(TopicVO topic) {
+        String topicName = topic.getName();
+        int writeQueues = topic.getWriteQueues() > 0 ? topic.getWriteQueues() : 8;
+        int readQueues = topic.getReadQueues() > 0 ? topic.getReadQueues() : 8;
+
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                String clusterName = getClusterName(admin);
+                // Match on the (cluster_id, name) key: the same topic name can exist in several
+                // clusters, and a name-only lookup would blow up with TooManyResultsException.
+                RmqTopic existing = topicMapper.selectOne(
+                        new LambdaQueryWrapper<RmqTopic>()
+                                .eq(RmqTopic::getClusterId, clusterName)
+                                .eq(RmqTopic::getName, topicName));
+                TopicPerm effectivePerm = topic.getPerm() != null
+                        ? topic.getPerm()
+                        : existing == null ? TopicPerm.RW : fromRocketMQPerm(existing.getPerm());
+                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+                if (brokerAddrs.isEmpty()) {
+                    throw new BusinessException(500, "No broker available to create topic");
+                }
+
+                TopicConfig topicConfig = new TopicConfig();
+                topicConfig.setTopicName(topicName);
+                topicConfig.setWriteQueueNums(writeQueues);
+                topicConfig.setReadQueueNums(readQueues);
+                topicConfig.setPerm(toRocketMQPerm(effectivePerm));
+
+                for (String addr : brokerAddrs) {
+                    admin.createAndUpdateTopicConfig(addr, topicConfig);
+                }
+
+                // Persist to DB. Re-creating a topic that already has a record (for example when
+                // rebuilding a broker route from the console) must update it instead of failing on
+                // the unique (cluster_id, name) key.
+                RmqTopic entity = topicMapper.selectOne(new LambdaQueryWrapper<RmqTopic>()
+                        .eq(RmqTopic::getClusterId, clusterName)
+                        .eq(RmqTopic::getName, topicName));
+                boolean isNew = entity == null;
+                if (isNew) {
+                    entity = new RmqTopic();
+                    entity.setName(topicName);
+                    entity.setClusterId(clusterName);
+                    entity.setCreatedAt(LocalDateTime.now());
+                }
+                if (StringUtils.hasText(topic.getInstanceId())) {
+                    entity.setInstanceId(topic.getInstanceId());
+                }
+                entity.setTopicType(topic.getType() != null ? topic.getType().name() : "NORMAL");
+                entity.setReadQueueNums(readQueues);
+                entity.setWriteQueueNums(writeQueues);
+                entity.setPerm(topicConfig.getPerm());
+                if (StringUtils.hasText(topic.getRemark())) {
+                    entity.setRemark(topic.getRemark());
+                }
+                entity.setStatus("ACTIVE");
+                entity.setUpdatedAt(LocalDateTime.now());
+                if (isNew) {
+                    topicMapper.insert(entity);
+                } else {
+                    topicMapper.updateById(entity);
+                }
+
+                auditService.record("CREATE_TOPIC", topicName,
+                        "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
+
+                topic.setId(topicName);
+                topic.setWriteQueues(writeQueues);
+                topic.setReadQueues(readQueues);
+                return topic;
+            } catch (BusinessException e) {
+                auditService.record("CREATE_TOPIC", topicName, e.getMessage(), "FAILED");
+                throw e;
+            } catch (Exception e) {
+                auditService.record("CREATE_TOPIC", topicName, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to create topic: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public TopicVO updateTopic(TopicVO topic) {
+        String topicName = topic.getName();
+        int writeQueues = topic.getWriteQueues() > 0 ? topic.getWriteQueues() : 8;
+        int readQueues = topic.getReadQueues() > 0 ? topic.getReadQueues() : 8;
+
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                // Match on the (cluster_id, name) key to avoid ambiguity when several clusters share
+                // the same topic name (a name-only lookup would throw TooManyResultsException).
+                String clusterName = getClusterName(admin);
+                RmqTopic existing = topicMapper.selectOne(
+                        new LambdaQueryWrapper<RmqTopic>()
+                                .eq(RmqTopic::getClusterId, clusterName)
+                                .eq(RmqTopic::getName, topicName));
+                TopicPerm effectivePerm = topic.getPerm() != null
+                        ? topic.getPerm()
+                        : existing == null ? TopicPerm.RW : fromRocketMQPerm(existing.getPerm());
+                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+                if (brokerAddrs.isEmpty()) {
+                    throw new BusinessException(500, "No broker available to update topic");
+                }
+
+                TopicConfig topicConfig = new TopicConfig();
+                topicConfig.setTopicName(topicName);
+                topicConfig.setWriteQueueNums(writeQueues);
+                topicConfig.setReadQueueNums(readQueues);
+                topicConfig.setPerm(toRocketMQPerm(effectivePerm));
+
+                for (String addr : brokerAddrs) {
+                    admin.createAndUpdateTopicConfig(addr, topicConfig);
+                }
+
+                // Update DB record
+                if (existing != null) {
+                    existing.setWriteQueueNums(writeQueues);
+                    existing.setReadQueueNums(readQueues);
+                    existing.setPerm(topicConfig.getPerm());
+                    existing.setUpdatedAt(LocalDateTime.now());
+                    topicMapper.updateById(existing);
+                }
+
+                auditService.record("UPDATE_TOPIC", topicName,
+                        "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
+
+                topic.setId(topicName);
+                topic.setWriteQueues(writeQueues);
+                topic.setReadQueues(readQueues);
+                return topic;
+            } catch (BusinessException e) {
+                auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), "FAILED");
+                throw e;
+            } catch (Exception e) {
+                auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to update topic: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public void deleteTopic(String name) {
+        String namesrvAddr = namesrvAddr();
+        adminFactory.execute(namesrvAddr, null, admin -> {
+            try {
+                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+
+                // Delete from brokers
+                if (!brokerAddrs.isEmpty()) {
+                    admin.deleteTopicInBroker(brokerAddrs, name);
+                }
+
+                // Delete from nameserver
+                Set<String> nsAddrs = new HashSet<>();
+                for (String addr : namesrvAddr.split("[;,]")) {
+                    String trimmed = addr.trim();
+                    if (!trimmed.isEmpty()) {
+                        nsAddrs.add(trimmed);
+                    }
+                }
+                admin.deleteTopicInNameServer(nsAddrs, getClusterName(admin), name);
+
+                // Delete from DB
+                topicMapper.delete(new LambdaQueryWrapper<RmqTopic>().eq(RmqTopic::getName, name));
+
+                auditService.record("DELETE_TOPIC", name, "", "SUCCESS");
+                return null;
+            } catch (BusinessException e) {
+                auditService.record("DELETE_TOPIC", name, e.getMessage(), "FAILED");
+                throw e;
+            } catch (Exception e) {
+                auditService.record("DELETE_TOPIC", name, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to delete topic: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public SendMessageVO sendMessage(SendMessageDTO request) {
+        String namesrvAddr = namesrvAddr();
+
+        DefaultMQProducer producer = new DefaultMQProducer(nextMessageSenderGroup());
+        producer.setNamesrvAddr(namesrvAddr);
+        producer.setSendMsgTimeout(5000);
+
+        try {
+            producer.start();
+
+            String topic = request.getTopic();
+            String tag = request.getTag() != null ? request.getTag() : "";
+            String key = request.getKey() != null ? request.getKey() : "";
+            String body = request.getBody() != null ? request.getBody() : "";
+
+            String fullTopic = tag.isEmpty() ? topic : topic + ":" + tag;
+            Message msg = new Message(topic, tag, key, body.getBytes(StandardCharsets.UTF_8));
+
+            // Add custom properties
+            if (request.getProperties() != null) {
+                for (Map.Entry<String, String> entry : request.getProperties().entrySet()) {
+                    msg.putUserProperty(entry.getKey(), entry.getValue());
+                }
+            }
+
+            SendResult sendResult = producer.send(msg);
+
+            // The message is already delivered by now; an audit write failure must not turn a
+            // successful send into an error, or callers would retry and duplicate the message.
+            try {
+                auditService.record("SEND_MESSAGE", topic,
+                        "tag=" + tag + ", key=" + key + ", msgId=" + sendResult.getMsgId(), "SUCCESS");
+            } catch (Exception auditFailure) {
+                log.warn("Failed to record send message audit: {}", auditFailure.getMessage());
+            }
+
+            return SendMessageVO.builder()
+                    .msgId(sendResult.getMsgId())
+                    .sendTime(System.currentTimeMillis())
+                    .offsetMsgId(sendResult.getOffsetMsgId())
+                    .build();
+        } catch (Exception e) {
+            auditService.record("SEND_MESSAGE", request.getTopic(), e.getMessage(), "FAILED");
+            throw new BusinessException(500, "Failed to send message: " + e.getMessage());
+        } finally {
+            producer.shutdown();
+        }
+    }
+
+    static String nextMessageSenderGroup() {
+        return ShortLivedClientName.next(MESSAGE_SENDER_GROUP_PREFIX);
+    }
+
+    @Override
+    public ConsumerGroupVO createConsumerGroup(ConsumerGroupVO group) {
+        String groupName = group.getName();
+
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+                if (brokerAddrs.isEmpty()) {
+                    throw new BusinessException(500, "No broker available to create consumer group");
+                }
+
+                SubscriptionGroupConfig config = new SubscriptionGroupConfig();
+                config.setGroupName(groupName);
+                config.setConsumeEnable(true);
+                config.setConsumeBroadcastEnable(true);
+                config.setRetryQueueNums(1);
+                config.setRetryMaxTimes(group.getRetryMaxTimes() > 0 ? group.getRetryMaxTimes() : 16);
+
+                for (String addr : brokerAddrs) {
+                    admin.createAndUpdateSubscriptionGroupConfig(addr, config);
+                }
+
+                // Persist to DB, upserting so re-creating an existing group does not violate the
+                // unique (cluster_id, name) key.
+                String groupClusterName = getClusterName(admin);
+                RmqGroup entity = groupMapper.selectOne(new LambdaQueryWrapper<RmqGroup>()
+                        .eq(RmqGroup::getClusterId, groupClusterName)
+                        .eq(RmqGroup::getName, groupName));
+                boolean isNewGroup = entity == null;
+                if (isNewGroup) {
+                    entity = new RmqGroup();
+                    entity.setName(groupName);
+                    entity.setClusterId(groupClusterName);
+                    entity.setCreatedAt(LocalDateTime.now());
+                }
+                if (StringUtils.hasText(group.getInstanceId())) {
+                    entity.setInstanceId(group.getInstanceId());
+                }
+                entity.setConsumeType(group.getConsumeType() != null ? group.getConsumeType().name() : "CLUSTERING");
+                entity.setMessageModel(group.getSubscriptionMode() != null ? group.getSubscriptionMode().name() : "Push");
+                entity.setMaxRetry(config.getRetryMaxTimes());
+                entity.setStatus("ACTIVE");
+                entity.setUpdatedAt(LocalDateTime.now());
+                if (isNewGroup) {
+                    groupMapper.insert(entity);
+                } else {
+                    groupMapper.updateById(entity);
+                }
+
+                auditService.record("CREATE_GROUP", groupName,
+                        "retryMaxTimes=" + config.getRetryMaxTimes(), "SUCCESS");
+
+                group.setId(groupName);
+                return group;
+            } catch (BusinessException e) {
+                auditService.record("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
+                throw e;
+            } catch (Exception e) {
+                auditService.record("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to create consumer group: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public void deleteConsumerGroup(String name) {
+        adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+
+                for (String addr : brokerAddrs) {
+                    admin.deleteSubscriptionGroup(addr, name, true);
+                }
+
+                // Delete from DB
+                groupMapper.delete(new LambdaQueryWrapper<RmqGroup>().eq(RmqGroup::getName, name));
+
+                auditService.record("DELETE_GROUP", name, "", "SUCCESS");
+                return null;
+            } catch (BusinessException e) {
+                auditService.record("DELETE_GROUP", name, e.getMessage(), "FAILED");
+                throw e;
+            } catch (Exception e) {
+                auditService.record("DELETE_GROUP", name, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to delete consumer group: " + e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public void resetOffset(String name, long timestamp, String topic) {
+        adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                admin.resetOffsetByTimestamp(getClusterName(admin), topic, name, timestamp, false);
+                auditService.record("RESET_OFFSET", name,
+                        "topic=" + topic + ", timestamp=" + timestamp, "SUCCESS");
+                return null;
+            } catch (Exception e) {
+                auditService.record("RESET_OFFSET", name, e.getMessage(), "FAILED");
+                throw new BusinessException(500, "Failed to reset offset: " + e.getMessage());
+            }
+        });
+    }
+
+    // ── Helper methods ──────────────────────────────────────────────────
+
+    /**
+     * Returns the configured default NameServer address, failing fast when the studio has no
+     * RocketMQ endpoint configured (equivalent to the former absent admin bean).
+     */
+    private String namesrvAddr() {
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
+            throw new BusinessException(503, "RocketMQ admin not connected");
+        }
+        return namesrvAddr;
+    }
+
+    private Set<String> getAllMasterBrokerAddrs(MQAdminExt admin) throws Exception {
+        Set<String> addrs = new HashSet<>();
+        ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
+        if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null) {
+            return addrs;
+        }
+
+        for (BrokerData brokerData : clusterInfo.getBrokerAddrTable().values()) {
+            if (brokerData.getBrokerAddrs() == null) {
+                continue;
+            }
+            // Use master address (brokerId = 0) preferentially
+            String masterAddr = brokerData.getBrokerAddrs().get(0L);
+            if (masterAddr == null && !brokerData.getBrokerAddrs().isEmpty()) {
+                masterAddr = brokerData.getBrokerAddrs().values().iterator().next();
+            }
+            if (masterAddr != null) {
+                addrs.add(masterAddr);
+            }
+        }
+        return addrs;
+    }
+
+    private String getClusterName(MQAdminExt admin) {
+        try {
+            ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
+            if (clusterInfo != null && clusterInfo.getClusterAddrTable() != null
+                    && !clusterInfo.getClusterAddrTable().isEmpty()) {
+                return clusterInfo.getClusterAddrTable().keySet().iterator().next();
+            }
+        } catch (Exception ignored) {
+        }
+        return "DefaultCluster";
+    }
+
+    private int toRocketMQPerm(TopicPerm perm) {
+        if (perm == TopicPerm.RO) {
+            return 4;
+        }
+        if (perm == TopicPerm.WO) {
+            return 2;
+        }
+        return 6;
+    }
+
+    private TopicPerm fromRocketMQPerm(Integer perm) {
+        if (perm != null && perm == 4) {
+            return TopicPerm.RO;
+        }
+        if (perm != null && perm == 2) {
+            return TopicPerm.WO;
+        }
+        return TopicPerm.RW;
+    }
+}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQBrokerConfigService.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
similarity index 65%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQBrokerConfigService.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
index 67415674..15efe4d2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQBrokerConfigService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
@@ -14,61 +14,62 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.cluster.config.ClusterConfigVO;
 import org.apache.rocketmq.studio.common.domain.enums.FlushDiskType;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.ops.audit.AuditService;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.util.Properties;
 
 @Slf4j
 @Service
+@RequiredArgsConstructor
 public class RocketMQBrokerConfigService {
 
-    private final DefaultMQAdminExt adminExt;
+    private final MqAdminExtFactory adminFactory;
+    private final RocketMQProperties properties;
     private final AuditService auditService;
 
-    public RocketMQBrokerConfigService(@Nullable DefaultMQAdminExt adminExt, AuditService auditService) {
-        this.adminExt = adminExt;
-        this.auditService = auditService;
-    }
-
     /**
      * Read broker config from the live broker via admin API.
      */
     public ClusterConfigVO getBrokerConfig(String brokerAddr) {
-        requireAdmin();
-        try {
-            Properties props = adminExt.getBrokerConfig(brokerAddr);
-            return mapToClusterConfigVO(props);
-        } catch (Exception e) {
-            log.error("Failed to get broker config from {}", brokerAddr, e);
-            throw new BusinessException(500, "Failed to get broker config: " + e.getMessage());
-        }
+        return adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                Properties props = admin.getBrokerConfig(brokerAddr);
+                return mapToClusterConfigVO(props);
+            } catch (Exception e) {
+                log.error("Failed to get broker config from {}", brokerAddr, e);
+                throw new BusinessException(500, "Failed to get broker config: " + e.getMessage());
+            }
+        });
     }
 
     /**
      * Update broker config on the live broker via admin API, then record audit.
      */
     public void updateBrokerConfig(String brokerAddr, String clusterId, Properties newConfig) {
-        requireAdmin();
-        try {
-            adminExt.updateBrokerConfig(brokerAddr, newConfig);
-            String detail = "brokerAddr=" + brokerAddr + ", config=" + newConfig;
-            auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + clusterId, detail, "SUCCESS");
-            log.info("Broker config updated successfully: {}", brokerAddr);
-        } catch (Exception e) {
-            log.error("Failed to update broker config at {}", brokerAddr, e);
-            String detail = "brokerAddr=" + brokerAddr + ", error=" + e.getMessage();
-            auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + clusterId, detail, "FAILURE");
-            throw new BusinessException(500, "Failed to update broker config: " + e.getMessage());
-        }
+        adminFactory.execute(namesrvAddr(), null, admin -> {
+            try {
+                admin.updateBrokerConfig(brokerAddr, newConfig);
+                String detail = "brokerAddr=" + brokerAddr + ", config=" + newConfig;
+                auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + clusterId, detail, "SUCCESS");
+                log.info("Broker config updated successfully: {}", brokerAddr);
+                return null;
+            } catch (Exception e) {
+                log.error("Failed to update broker config at {}", brokerAddr, e);
+                String detail = "brokerAddr=" + brokerAddr + ", error=" + e.getMessage();
+                auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + clusterId, detail, "FAILURE");
+                throw new BusinessException(500, "Failed to update broker config: " + e.getMessage());
+            }
+        });
     }
 
     private ClusterConfigVO mapToClusterConfigVO(Properties props) {
@@ -105,9 +106,11 @@ public class RocketMQBrokerConfigService {
         }
     }
 
-    private void requireAdmin() {
-        if (adminExt == null) {
+    private String namesrvAddr() {
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
             throw new BusinessException(503, "RocketMQ admin is not configured. Set studio.rocketmq.namesrv-addr.");
         }
+        return namesrvAddr;
     }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClientProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClientProvider.java
similarity index 87%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClientProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClientProvider.java
index 48760cf6..574f0d3f 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClientProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClientProvider.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.remoting.protocol.LanguageCode;
@@ -29,17 +29,18 @@ import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import org.apache.rocketmq.studio.cluster.client.ClientConnectionVO;
 import org.apache.rocketmq.studio.cluster.client.ClientProvider;
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.cluster.broker.RuntimeAdminClientResolver;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.common.domain.enums.ClientLanguage;
 import org.apache.rocketmq.studio.common.domain.enums.ClientType;
 import org.apache.rocketmq.studio.common.domain.enums.Protocol;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
 import org.apache.rocketmq.tools.admin.MQAdminExt;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -58,18 +59,14 @@ import java.util.Set;
 @Slf4j
 @Service
 @Primary
+@RequiredArgsConstructor
 public class RocketMQClientProvider implements ClientProvider {
 
     private static final long SUBSCRIPTION_GROUP_TIMEOUT_MILLIS = 5000L;
 
-    private final ObjectProvider<DefaultMQAdminExt> adminExtProvider;
+    private final MqAdminExtFactory adminFactory;
     private final RuntimeAdminClientResolver runtimeAdminClientResolver;
-
-    public RocketMQClientProvider(ObjectProvider<DefaultMQAdminExt> adminExtProvider,
-                                  RuntimeAdminClientResolver runtimeAdminClientResolver) {
-        this.adminExtProvider = adminExtProvider;
-        this.runtimeAdminClientResolver = runtimeAdminClientResolver;
-    }
+    private final RocketMQProperties properties;
 
     @Override
     public List<ClientConnectionVO> findConnections(String instanceId, String clusterId, String type) {
@@ -90,30 +87,35 @@ public class RocketMQClientProvider implements ClientProvider {
 
     @Override
     public List<ClientConnectionVO> findProducerConnections(String topic, String producerGroup) {
-        DefaultMQAdminExt adminExt = requireAdminExt();
-        try {
-            ProducerConnection producerConnection =
-                    adminExt.examineProducerConnectionInfo(producerGroup, topic);
-            if (producerConnection == null || producerConnection.getConnectionSet() == null) {
-                return List.of();
-            }
-            return producerConnection.getConnectionSet().stream()
-                    .filter(Objects::nonNull)
-                    .map(connection -> toConnectionVO(
-                            connection, ClientType.Producer, topic, producerGroup, null))
-                    .toList();
-        } catch (MQClientException e) {
-            if (isTopicNotExist(e)) {
-                // A non-existent topic is a normal "nothing here" outcome — the client
-                // page should show an empty list, not a 502 that looks like a failure.
-                return List.of();
-            }
-            throw new BusinessException(502,
-                    "Failed to query producer connections: " + rootMessage(e));
-        } catch (Exception e) {
-            throw new BusinessException(502,
-                    "Failed to query producer connections: " + rootMessage(e));
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
+            throw new BusinessException(501, "Client connection provider is not configured");
         }
+        return adminFactory.execute(namesrvAddr, null, admin -> {
+            try {
+                ProducerConnection producerConnection =
+                        admin.examineProducerConnectionInfo(producerGroup, topic);
+                if (producerConnection == null || producerConnection.getConnectionSet() == null) {
+                    return List.<ClientConnectionVO>of();
+                }
+                return producerConnection.getConnectionSet().stream()
+                        .filter(Objects::nonNull)
+                        .map(connection -> toConnectionVO(
+                                connection, ClientType.Producer, topic, producerGroup, null))
+                        .toList();
+            } catch (MQClientException e) {
+                if (isTopicNotExist(e)) {
+                    // A non-existent topic is a normal "nothing here" outcome — the client
+                    // page should show an empty list, not a 502 that looks like a failure.
+                    return List.<ClientConnectionVO>of();
+                }
+                throw new BusinessException(502,
+                        "Failed to query producer connections: " + rootMessage(e));
+            } catch (Exception e) {
+                throw new BusinessException(502,
+                        "Failed to query producer connections: " + rootMessage(e));
+            }
+        });
     }
 
     private boolean isTopicNotExist(MQClientException e) {
@@ -336,14 +338,6 @@ public class RocketMQClientProvider implements ClientProvider {
                 || group.startsWith("CID_HOUSEKEEPING");
     }
 
-    private DefaultMQAdminExt requireAdminExt() {
-        DefaultMQAdminExt adminExt = adminExtProvider.getIfAvailable();
-        if (adminExt == null) {
-            throw new BusinessException(501, "Client connection provider is not configured");
-        }
-        return adminExt;
-    }
-
     private String rootMessage(Throwable error) {
         Throwable current = error;
         while (current.getCause() != null) {
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
similarity index 68%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
index 7d5c7efd..bdf0990b 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
 import org.apache.rocketmq.remoting.protocol.body.KVTable;
@@ -22,15 +22,16 @@ import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import org.apache.rocketmq.studio.cluster.broker.BrokerVO;
 import org.apache.rocketmq.studio.cluster.broker.ClusterProvider;
 import org.apache.rocketmq.studio.cluster.broker.ClusterVO;
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.cluster.nameserver.NameServerVO;
 import org.apache.rocketmq.studio.common.domain.enums.BrokerStatus;
 import org.apache.rocketmq.studio.common.domain.enums.ClusterStatus;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.rocketmq.tools.admin.MQAdminExt;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -39,54 +40,49 @@ import java.util.Map;
 import java.util.Set;
 
 /**
- * Real cluster discovery implementation using DefaultMQAdminExt.
- * Falls back to returning empty results when adminExt is not configured or connection fails.
+ * Real cluster discovery implementation using the RocketMQ admin API.
+ * Falls back to returning empty results when no NameServer is configured or connection fails.
  */
+@Slf4j
 @Service
 @Primary
+@RequiredArgsConstructor
 public class RocketMQClusterProvider implements ClusterProvider {
 
-    private static final Logger log = LoggerFactory.getLogger(RocketMQClusterProvider.class);
-
-    private final DefaultMQAdminExt adminExt;
+    private final MqAdminExtFactory adminFactory;
     private final RocketMQProperties properties;
 
-    @Autowired
-    public RocketMQClusterProvider(
-            @Autowired(required = false) DefaultMQAdminExt adminExt,
-            RocketMQProperties properties) {
-        this.adminExt = adminExt;
-        this.properties = properties;
-    }
-
     @Override
     public List<ClusterVO> discoverClusters() {
-        if (adminExt == null) {
-            log.debug("DefaultMQAdminExt not configured, returning empty cluster list");
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
+            log.debug("NameServer address not configured, returning empty cluster list");
             return Collections.emptyList();
         }
 
         try {
-            ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
-            if (clusterInfo == null || clusterInfo.getClusterAddrTable() == null) {
-                return Collections.emptyList();
-            }
+            return adminFactory.execute(namesrvAddr, null, admin -> {
+                ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
+                if (clusterInfo == null || clusterInfo.getClusterAddrTable() == null) {
+                    return Collections.<ClusterVO>emptyList();
+                }
 
-            Map<String, Set<String>> clusterAddrTable = clusterInfo.getClusterAddrTable();
-            Map<String, BrokerData> brokerAddrTable = clusterInfo.getBrokerAddrTable();
+                Map<String, Set<String>> clusterAddrTable = clusterInfo.getClusterAddrTable();
+                Map<String, BrokerData> brokerAddrTable = clusterInfo.getBrokerAddrTable();
 
-            List<ClusterVO> clusters = new ArrayList<>();
-            for (Map.Entry<String, Set<String>> entry : clusterAddrTable.entrySet()) {
-                String clusterName = entry.getKey();
-                Set<String> brokerNames = entry.getValue();
+                List<ClusterVO> clusters = new ArrayList<>();
+                for (Map.Entry<String, Set<String>> entry : clusterAddrTable.entrySet()) {
+                    String clusterName = entry.getKey();
+                    Set<String> brokerNames = entry.getValue();
 
-                List<BrokerVO> brokers = buildBrokerList(brokerNames, brokerAddrTable);
-                List<NameServerVO> nameServers = buildNameServerList();
+                    List<BrokerVO> brokers = buildBrokerList(admin, brokerNames, brokerAddrTable);
+                    List<NameServerVO> nameServers = buildNameServerList();
 
-                ClusterVO cluster = buildClusterVO(clusterName, brokers, nameServers);
-                clusters.add(cluster);
-            }
-            return clusters;
+                    ClusterVO cluster = buildClusterVO(clusterName, brokers, nameServers);
+                    clusters.add(cluster);
+                }
+                return clusters;
+            });
         } catch (Exception e) {
             log.warn("Failed to discover clusters via NameServer: {}", e.getMessage());
             return Collections.emptyList();
@@ -95,29 +91,32 @@ public class RocketMQClusterProvider implements ClusterProvider {
 
     @Override
     public ClusterVO refreshClusterDetail(String clusterId) {
-        if (adminExt == null) {
-            log.debug("DefaultMQAdminExt not configured, cannot refresh cluster detail");
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
+            log.debug("NameServer address not configured, cannot refresh cluster detail");
             return null;
         }
 
         try {
-            ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
-            if (clusterInfo == null || clusterInfo.getClusterAddrTable() == null) {
-                return null;
-            }
+            return adminFactory.execute(namesrvAddr, null, admin -> {
+                ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
+                if (clusterInfo == null || clusterInfo.getClusterAddrTable() == null) {
+                    return null;
+                }
 
-            Map<String, Set<String>> clusterAddrTable = clusterInfo.getClusterAddrTable();
-            Map<String, BrokerData> brokerAddrTable = clusterInfo.getBrokerAddrTable();
+                Map<String, Set<String>> clusterAddrTable = clusterInfo.getClusterAddrTable();
+                Map<String, BrokerData> brokerAddrTable = clusterInfo.getBrokerAddrTable();
 
-            Set<String> brokerNames = clusterAddrTable.get(clusterId);
-            if (brokerNames == null) {
-                return null;
-            }
+                Set<String> brokerNames = clusterAddrTable.get(clusterId);
+                if (brokerNames == null) {
+                    return null;
+                }
 
-            List<BrokerVO> brokers = buildBrokerList(brokerNames, brokerAddrTable);
-            List<NameServerVO> nameServers = buildNameServerList();
+                List<BrokerVO> brokers = buildBrokerList(admin, brokerNames, brokerAddrTable);
+                List<NameServerVO> nameServers = buildNameServerList();
 
-            return buildClusterVO(clusterId, brokers, nameServers);
+                return buildClusterVO(clusterId, brokers, nameServers);
+            });
         } catch (Exception e) {
             log.warn("Failed to refresh cluster detail for {}: {}", clusterId, e.getMessage());
             return null;
@@ -144,7 +143,7 @@ public class RocketMQClusterProvider implements ClusterProvider {
         return cluster;
     }
 
-    private List<BrokerVO> buildBrokerList(Set<String> brokerNames,
+    private List<BrokerVO> buildBrokerList(MQAdminExt admin, Set<String> brokerNames,
                                            Map<String, BrokerData> brokerAddrTable) {
         List<BrokerVO> brokers = new ArrayList<>();
         if (brokerNames == null || brokerAddrTable == null) {
@@ -172,16 +171,16 @@ public class RocketMQClusterProvider implements ClusterProvider {
                     .status(BrokerStatus.running);
 
             // Try to get runtime info for version and TPS
-            enrichBrokerWithRuntimeInfo(builder, masterAddr);
+            enrichBrokerWithRuntimeInfo(admin, builder, masterAddr);
 
             brokers.add(builder.build());
         }
         return brokers;
     }
 
-    private void enrichBrokerWithRuntimeInfo(BrokerVO.BrokerVOBuilder builder, String brokerAddr) {
+    private void enrichBrokerWithRuntimeInfo(MQAdminExt admin, BrokerVO.BrokerVOBuilder builder, String brokerAddr) {
         try {
-            KVTable runtimeInfo = adminExt.fetchBrokerRuntimeStats(brokerAddr);
+            KVTable runtimeInfo = admin.fetchBrokerRuntimeStats(brokerAddr);
             if (runtimeInfo == null || runtimeInfo.getTable() == null) {
                 return;
             }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDLQProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDLQProvider.java
similarity index 96%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDLQProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDLQProvider.java
index bb49f96f..138df1f5 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDLQProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDLQProvider.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
 import org.apache.rocketmq.client.consumer.PullResult;
@@ -35,11 +35,11 @@ import org.apache.rocketmq.studio.instance.dlq.DLQProvider;
 import org.apache.rocketmq.studio.instance.dlq.DLQResendResultVO;
 import org.apache.rocketmq.studio.ops.audit.AuditService;
 import org.apache.rocketmq.tools.admin.MQAdminExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -54,24 +54,18 @@ import java.util.Set;
  * Real {@link DLQProvider} backed by the RocketMQ admin API. Lists dead-letter groups by scanning
  * {@code %DLQ%} topics and resends dead-letter messages back to a target topic.
  */
+@Slf4j
+@RequiredArgsConstructor
 @Service
 @Primary
 public class RocketMQDLQProvider implements DLQProvider {
 
-    private static final Logger log = LoggerFactory.getLogger(RocketMQDLQProvider.class);
-
     private static final long ONE_HOUR_MILLIS = 3600_000L;
     private static final int RESEND_HARD_CAP = 5000;
 
     private final RuntimeAdminClientResolver runtimeAdminClientResolver;
     private final AuditService auditService;
 
-    public RocketMQDLQProvider(RuntimeAdminClientResolver runtimeAdminClientResolver,
-                               AuditService auditService) {
-        this.runtimeAdminClientResolver = runtimeAdminClientResolver;
-        this.auditService = auditService;
-    }
-
     @Override
     public List<DLQGroupVO> listDLQGroups(String instanceId) {
         return runtimeAdminClientResolver.execute(instanceId, this::listDLQGroups);
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDashboardProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
similarity index 91%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDashboardProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
index 0b7214de..6ffd6270 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQDashboardProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -28,44 +28,46 @@ import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
 import org.apache.rocketmq.remoting.protocol.body.TopicList;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.common.domain.enums.ClusterStatus;
 import org.apache.rocketmq.studio.common.domain.enums.ClusterType;
 import org.apache.rocketmq.studio.ops.dashboard.ClusterOverviewVO;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardDataVO;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardProvider;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardStatsVO;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.rocketmq.tools.admin.MQAdminExt;
 import org.springframework.context.annotation.Primary;
-import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
+@Slf4j
+@RequiredArgsConstructor
 @Service
 @Primary
 public class RocketMQDashboardProvider implements DashboardProvider {
 
-    private static final Logger log = LoggerFactory.getLogger(RocketMQDashboardProvider.class);
-
     private static final Set<String> SYSTEM_TOPIC_PREFIXES = Set.of(
             "rmq_sys_", "SCHEDULE_TOPIC_", "RMQ_SYS_", "broker_", "%RETRY%", "%DLQ%",
             "TBW102", "SELF_TEST_TOPIC", "BenchmarkTest", "OFFSET_MOVED_EVENT",
             "DefaultCluster", "broker-a", "broker-b"
     );
 
-    private final DefaultMQAdminExt adminExt;
-
-    public RocketMQDashboardProvider(@Nullable DefaultMQAdminExt adminExt) {
-        this.adminExt = adminExt;
-    }
+    private final MqAdminExtFactory adminFactory;
+    private final RocketMQProperties properties;
 
     @Override
     public DashboardDataVO getDashboardData() {
-        if (adminExt == null) {
-            log.warn("DefaultMQAdminExt not available, returning empty dashboard");
+        String namesrvAddr = properties.getNamesrvAddr();
+        if (!StringUtils.hasText(namesrvAddr)) {
+            log.warn("NameServer address not configured, returning empty dashboard");
             return emptyDashboard();
         }
+        return adminFactory.execute(namesrvAddr, null, this::collectDashboardData);
+    }
 
+    private DashboardDataVO collectDashboardData(MQAdminExt admin) {
         int totalClusters = 0;
         int totalBrokers = 0;
         int totalTopics = 0;
@@ -76,7 +78,7 @@ public class RocketMQDashboardProvider implements DashboardProvider {
         List<ClusterOverviewVO> clusters = new ArrayList<>();
 
         try {
-            ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
+            ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
             if (clusterInfo == null) {
                 log.warn("NameServer returned no cluster topology, returning empty dashboard");
                 return emptyDashboard();
@@ -112,7 +114,7 @@ public class RocketMQDashboardProvider implements DashboardProvider {
 
             // Count topics
             try {
-                TopicList topicList = adminExt.fetchAllTopicList();
+                TopicList topicList = admin.fetchAllTopicList();
                 Set<String> topics = topicList.getTopicList();
                 totalTopics = (int) topics.stream()
                         .filter(t -> !isSystemTopic(t))
@@ -125,7 +127,7 @@ public class RocketMQDashboardProvider implements DashboardProvider {
             Set<String> allGroups = new HashSet<>();
             for (String brokerAddr : masterAddrs) {
                 try {
-                    SubscriptionGroupWrapper subscriptionGroupWrapper = adminExt.getAllSubscriptionGroup(brokerAddr, 5000);
+                    SubscriptionGroupWrapper subscriptionGroupWrapper = admin.getAllSubscriptionGroup(brokerAddr, 5000);
                     if (subscriptionGroupWrapper != null && subscriptionGroupWrapper.getSubscriptionGroupTable() != null) {
                         for (Map.Entry<String, SubscriptionGroupConfig> entry :
                                 subscriptionGroupWrapper.getSubscriptionGroupTable().entrySet()) {
@@ -141,7 +143,7 @@ public class RocketMQDashboardProvider implements DashboardProvider {
 
                 // Get runtime stats for TPS
                 try {
-                    KVTable runtimeInfo = adminExt.fetchBrokerRuntimeStats(brokerAddr);
+                    KVTable runtimeInfo = admin.fetchBrokerRuntimeStats(brokerAddr);
                     if (runtimeInfo != null && runtimeInfo.getTable() != null) {
                         Map<String, String> table = runtimeInfo.getTable();
                         tpsIn += parseTps(table.get("putTps"));
@@ -177,7 +179,7 @@ public class RocketMQDashboardProvider implements DashboardProvider {
                         String masterAddr = brokerData.getBrokerAddrs().get(0L);
                         if (masterAddr != null) {
                             try {
-                                KVTable rt = adminExt.fetchBrokerRuntimeStats(masterAddr);
+                                KVTable rt = admin.fetchBrokerRuntimeStats(masterAddr);
                                 if (rt != null && rt.getTable() != null) {
                                     clusterTpsIn += (int) parseTps(rt.getTable().get("putTps"));
                                     clusterTpsOut += (int) parseTps(rt.getTable().get("getTransferedTps"));
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMessageProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMessageProvider.java
similarity index 96%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMessageProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMessageProvider.java
index f23d7b7e..3dee1e9f 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMessageProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMessageProvider.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import org.apache.rocketmq.client.QueryResult;
 import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
@@ -31,16 +31,15 @@ import org.apache.rocketmq.studio.instance.message.MessageProvider;
 import org.apache.rocketmq.studio.instance.message.MessageRecordVO;
 import org.apache.rocketmq.studio.instance.message.TraceNodeVO;
 import org.apache.rocketmq.studio.instance.message.TraceRecordVO;
-import org.apache.rocketmq.studio.queryhistory.QueryHistoryService;
+import org.apache.rocketmq.studio.instance.message.QueryHistoryService;
 import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
 import org.apache.rocketmq.tools.admin.api.MessageTrack;
 import org.apache.rocketmq.tools.admin.api.TrackType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
@@ -62,12 +61,12 @@ import java.util.Set;
  * message id, by business key and by topic + time range, as well as message trace retrieval.
  * Falls back to empty results when adminExt is not configured or a query fails.
  */
+@Slf4j
+@RequiredArgsConstructor
 @Service
 @Primary
 public class RocketMQMessageProvider implements MessageProvider {
 
-    private static final Logger log = LoggerFactory.getLogger(RocketMQMessageProvider.class);
-
     private static final String TRACE_TOPIC = "RMQ_SYS_TRACE_TOPIC";
     private static final char FIELD_SEPARATOR = '\u0001';
     private static final int KEY_QUERY_MAX = 64;
@@ -81,21 +80,10 @@ public class RocketMQMessageProvider implements MessageProvider {
     private static final long ONE_HOUR_MILLIS = 3600_000L;
     private static final long ONE_DAY_MILLIS = 24 * ONE_HOUR_MILLIS;
 
-    private final ObjectProvider<DefaultMQAdminExt> adminExtProvider;
     private final RuntimeAdminClientResolver runtimeAdminClientResolver;
     private final QueryHistoryService queryHistoryService;
     private final RocketMQProperties properties;
 
-    public RocketMQMessageProvider(ObjectProvider<DefaultMQAdminExt> adminExtProvider,
-                                   RuntimeAdminClientResolver runtimeAdminClientResolver,
-                                   QueryHistoryService queryHistoryService,
-                                   RocketMQProperties properties) {
-        this.adminExtProvider = adminExtProvider;
-        this.runtimeAdminClientResolver = runtimeAdminClientResolver;
-        this.queryHistoryService = queryHistoryService;
-        this.properties = properties;
-    }
-
     @Override
     public List<MessageRecordVO> queryMessages(String instanceId, String topic, String msgId, String tag, String key,
                                                Long startTime, Long endTime) {
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMetadataProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
similarity index 52%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMetadataProvider.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
index 75f83344..90233979 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQMetadataProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
@@ -14,20 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.rocketmq.common.message.MessageQueue;
 import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
 import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper;
-import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
 import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
 import org.apache.rocketmq.remoting.protocol.body.GroupList;
 import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import org.apache.rocketmq.remoting.protocol.route.QueueData;
 import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.common.domain.enums.ConsumeType;
 import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
 import org.apache.rocketmq.studio.common.domain.enums.TopicType;
@@ -35,20 +35,17 @@ import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.group.QueueProgressVO;
 import org.apache.rocketmq.studio.instance.group.SubscriptionEntryVO;
 import org.apache.rocketmq.studio.instance.topic.BrokerRouteVO;
-import org.apache.rocketmq.studio.instance.topic.MetadataProvider;
 import org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
 import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
 import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
 import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -63,15 +60,15 @@ import java.util.Set;
  *
  * <p>Topic and consumer group listings are served from the studio metadata database, which is the
  * source of record: creation writes there and reads never fall back to the broker. Route,
- * consumer, progress and subscription queries stay live through DefaultMQAdminExt, so a record
- * without a broker route surfaces as an empty route list instead of being hidden.
+ * consumer, progress and subscription queries stay live through the RocketMQ admin API, so a
+ * record without a broker route surfaces as an empty route list instead of being hidden.
  */
+@Slf4j
 @Service
 @Primary
+@RequiredArgsConstructor
 public class RocketMQMetadataProvider implements MetadataProvider {
 
-    private static final Logger log = LoggerFactory.getLogger(RocketMQMetadataProvider.class);
-
     private static final Set<String> SYSTEM_TOPIC_PREFIXES = Set.of(
             "RMQ_SYS_", "SCHEDULE_TOPIC_", "%RETRY%", "%DLQ%", "CID_"
     );
@@ -82,17 +79,18 @@ public class RocketMQMetadataProvider implements MetadataProvider {
             "RMQ_SYS_TRACE_TOPIC", "RMQ_SYS_TRANS_OP_HALF_TOPIC"
     );
 
-    private final DefaultMQAdminExt adminExt;
+    private final MqAdminExtFactory adminFactory;
+    private final RocketMQProperties properties;
     private final RmqTopicMapper topicMapper;
     private final RmqGroupMapper groupMapper;
 
-    @Autowired
-    public RocketMQMetadataProvider(@Autowired(required = false) DefaultMQAdminExt adminExt,
-                                    RmqTopicMapper topicMapper,
-                                    RmqGroupMapper groupMapper) {
-        this.adminExt = adminExt;
-        this.topicMapper = topicMapper;
-        this.groupMapper = groupMapper;
+    /** Whether a default NameServer is configured and live queries are therefore possible. */
+    private boolean hasAdmin() {
+        return StringUtils.hasText(properties.getNamesrvAddr());
+    }
+
+    private <T> T adminExecute(MqAdminExtFactory.AdminAction<T> action) {
+        return adminFactory.execute(properties.getNamesrvAddr(), null, action);
     }
 
     @Override
@@ -173,7 +171,7 @@ public class RocketMQMetadataProvider implements MetadataProvider {
             vo.setCreatedAt(entity.getCreatedAt());
             vo.setUpdatedAt(entity.getUpdatedAt());
 
-            if (adminExt != null) {
+            if (hasAdmin()) {
                 enrichGroupWithConnectionInfo(vo, entity.getName());
             }
             result.add(vo);
@@ -195,120 +193,124 @@ public class RocketMQMetadataProvider implements MetadataProvider {
 
     @Override
     public List<BrokerRouteVO> getTopicRoutes(String name) {
-        if (adminExt == null) {
+        if (!hasAdmin()) {
             return Collections.emptyList();
         }
 
-        try {
-            TopicRouteData routeData = adminExt.examineTopicRouteInfo(name);
-            if (routeData == null) {
-                return Collections.emptyList();
-            }
+        return adminExecute(admin -> {
+            try {
+                TopicRouteData routeData = admin.examineTopicRouteInfo(name);
+                if (routeData == null) {
+                    return Collections.emptyList();
+                }
 
-            Map<String, BrokerData> brokerDataMap = new HashMap<>();
-            if (routeData.getBrokerDatas() != null) {
-                for (BrokerData bd : routeData.getBrokerDatas()) {
-                    brokerDataMap.put(bd.getBrokerName(), bd);
+                Map<String, BrokerData> brokerDataMap = new HashMap<>();
+                if (routeData.getBrokerDatas() != null) {
+                    for (BrokerData bd : routeData.getBrokerDatas()) {
+                        brokerDataMap.put(bd.getBrokerName(), bd);
+                    }
                 }
-            }
 
-            List<BrokerRouteVO> routes = new ArrayList<>();
-            if (routeData.getQueueDatas() != null) {
-                for (QueueData qd : routeData.getQueueDatas()) {
-                    BrokerData bd = brokerDataMap.get(qd.getBrokerName());
-                    String brokerAddr = "";
-                    if (bd != null && bd.getBrokerAddrs() != null && !bd.getBrokerAddrs().isEmpty()) {
-                        brokerAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
-                        if (brokerAddr == null) {
-                            brokerAddr = bd.getBrokerAddrs().values().iterator().next();
+                List<BrokerRouteVO> routes = new ArrayList<>();
+                if (routeData.getQueueDatas() != null) {
+                    for (QueueData qd : routeData.getQueueDatas()) {
+                        BrokerData bd = brokerDataMap.get(qd.getBrokerName());
+                        String brokerAddr = "";
+                        if (bd != null && bd.getBrokerAddrs() != null && !bd.getBrokerAddrs().isEmpty()) {
+                            brokerAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
+                            if (brokerAddr == null) {
+                                brokerAddr = bd.getBrokerAddrs().values().iterator().next();
+                            }
                         }
-                    }
 
-                    routes.add(BrokerRouteVO.builder()
-                            .brokerName(qd.getBrokerName())
-                            .brokerAddr(brokerAddr)
-                            .writeQueues(qd.getWriteQueueNums())
-                            .readQueues(qd.getReadQueueNums())
-                            .perm(mapPerm(qd.getPerm()))
-                            .build());
+                        routes.add(BrokerRouteVO.builder()
+                                .brokerName(qd.getBrokerName())
+                                .brokerAddr(brokerAddr)
+                                .writeQueues(qd.getWriteQueueNums())
+                                .readQueues(qd.getReadQueueNums())
+                                .perm(mapPerm(qd.getPerm()))
+                                .build());
+                    }
                 }
+                return routes;
+            } catch (Exception e) {
+                log.warn("Failed to get routes for topic {}: {}", name, e.getMessage());
+                return Collections.emptyList();
             }
-            return routes;
-        } catch (Exception e) {
-            log.warn("Failed to get routes for topic {}: {}", name, e.getMessage());
-            return Collections.emptyList();
-        }
+        });
     }
 
     @Override
     public List<TopicConsumerVO> getTopicConsumers(String name) {
-        if (adminExt == null) {
+        if (!hasAdmin()) {
             return Collections.emptyList();
         }
 
-        try {
-            // Ask the broker who consumes this topic instead of scanning every subscription
-            // group, which floods the result with system groups.
-            GroupList groupList = adminExt.queryTopicConsumeByWho(name);
-            Set<String> subscribingGroups = new HashSet<>();
-            if (groupList != null && groupList.getGroupList() != null) {
-                for (String group : groupList.getGroupList()) {
-                    if (!isSystemConsumerGroup(group)) {
-                        subscribingGroups.add(group);
+        return adminExecute(admin -> {
+            try {
+                // Ask the broker who consumes this topic instead of scanning every subscription
+                // group, which floods the result with system groups.
+                GroupList groupList = admin.queryTopicConsumeByWho(name);
+                Set<String> subscribingGroups = new HashSet<>();
+                if (groupList != null && groupList.getGroupList() != null) {
+                    for (String group : groupList.getGroupList()) {
+                        if (!isSystemConsumerGroup(group)) {
+                            subscribingGroups.add(group);
+                        }
                     }
                 }
-            }
 
-            List<TopicConsumerVO> consumers = new ArrayList<>();
-            for (String group : subscribingGroups) {
-                try {
-                    ConsumeStats stats = adminExt.examineConsumeStats(group, name);
-                    long diffTotal = 0;
-                    double consumeTps = 0;
-                    if (stats != null && stats.getOffsetTable() != null) {
-                        for (Map.Entry<MessageQueue, OffsetWrapper> entry : stats.getOffsetTable().entrySet()) {
-                            OffsetWrapper ow = entry.getValue();
-                            diffTotal += Math.max(0, ow.getBrokerOffset() - ow.getConsumerOffset());
+                List<TopicConsumerVO> consumers = new ArrayList<>();
+                for (String group : subscribingGroups) {
+                    try {
+                        ConsumeStats stats = admin.examineConsumeStats(group, name);
+                        long diffTotal = 0;
+                        double consumeTps = 0;
+                        if (stats != null && stats.getOffsetTable() != null) {
+                            for (Map.Entry<MessageQueue, OffsetWrapper> entry : stats.getOffsetTable().entrySet()) {
+                                OffsetWrapper ow = entry.getValue();
+                                diffTotal += Math.max(0, ow.getBrokerOffset() - ow.getConsumerOffset());
+                            }
+                            consumeTps = stats.getConsumeTps();
                         }
-                        consumeTps = stats.getConsumeTps();
-                    }
 
-                    ConsumeType consumeType = ConsumeType.CLUSTERING;
-                    String messageModel = "CLUSTERING";
-                    try {
-                        ConsumerConnection conn = adminExt.examineConsumerConnectionInfo(group);
-                        if (conn != null && conn.getMessageModel() != null) {
-                            messageModel = conn.getMessageModel().name();
-                            consumeType = parseConsumeType(messageModel);
+                        ConsumeType consumeType = ConsumeType.CLUSTERING;
+                        String messageModel = "CLUSTERING";
+                        try {
+                            ConsumerConnection conn = admin.examineConsumerConnectionInfo(group);
+                            if (conn != null && conn.getMessageModel() != null) {
+                                messageModel = conn.getMessageModel().name();
+                                consumeType = parseConsumeType(messageModel);
+                            }
+                        } catch (Exception ignored) {
+                            // group may be offline
                         }
+
+                        consumers.add(TopicConsumerVO.builder()
+                                .group(group)
+                                .consumeType(consumeType)
+                                .messageModel(messageModel)
+                                .consumeTps(consumeTps)
+                                .diffTotal(diffTotal)
+                                .build());
                     } catch (Exception ignored) {
-                        // group may be offline
+                        // stats unavailable for this group, still list it below without numbers
+                        consumers.add(TopicConsumerVO.builder()
+                                .group(group)
+                                .consumeType(ConsumeType.CLUSTERING)
+                                .messageModel("CLUSTERING")
+                                .consumeTps(0)
+                                .diffTotal(0)
+                                .build());
                     }
-
-                    consumers.add(TopicConsumerVO.builder()
-                            .group(group)
-                            .consumeType(consumeType)
-                            .messageModel(messageModel)
-                            .consumeTps(consumeTps)
-                            .diffTotal(diffTotal)
-                            .build());
-                } catch (Exception ignored) {
-                    // stats unavailable for this group, still list it below without numbers
-                    consumers.add(TopicConsumerVO.builder()
-                            .group(group)
-                            .consumeType(ConsumeType.CLUSTERING)
-                            .messageModel("CLUSTERING")
-                            .consumeTps(0)
-                            .diffTotal(0)
-                            .build());
                 }
+                consumers.sort((a, b) -> a.getGroup().compareToIgnoreCase(b.getGroup()));
+                return consumers;
+            } catch (Exception e) {
+                log.warn("Failed to get consumers for topic {}: {}", name, e.getMessage());
+                return Collections.emptyList();
             }
-            consumers.sort((a, b) -> a.getGroup().compareToIgnoreCase(b.getGroup()));
-            return consumers;
-        } catch (Exception e) {
-            log.warn("Failed to get consumers for topic {}: {}", name, e.getMessage());
-            return Collections.emptyList();
-        }
+        });
     }
 
     private boolean isSystemConsumerGroup(String group) {
@@ -325,105 +327,80 @@ public class RocketMQMetadataProvider implements MetadataProvider {
 
     @Override
     public List<QueueProgressVO> getGroupProgress(String name) {
-        if (adminExt == null) {
+        if (!hasAdmin()) {
             return Collections.emptyList();
         }
 
-        try {
-            ConsumeStats stats = adminExt.examineConsumeStats(name);
-            if (stats == null || stats.getOffsetTable() == null) {
-                return Collections.emptyList();
-            }
+        return adminExecute(admin -> {
+            try {
+                ConsumeStats stats = admin.examineConsumeStats(name);
+                if (stats == null || stats.getOffsetTable() == null) {
+                    return Collections.emptyList();
+                }
 
-            List<QueueProgressVO> progress = new ArrayList<>();
-            for (Map.Entry<MessageQueue, OffsetWrapper> entry : stats.getOffsetTable().entrySet()) {
-                MessageQueue mq = entry.getKey();
-                OffsetWrapper ow = entry.getValue();
-                long diff = Math.max(0, ow.getBrokerOffset() - ow.getConsumerOffset());
-
-                progress.add(QueueProgressVO.builder()
-                        .broker(mq.getBrokerName())
-                        .queueId(mq.getQueueId())
-                        .brokerOffset(ow.getBrokerOffset())
-                        .consumerOffset(ow.getConsumerOffset())
-                        .diffTotal(diff)
-                        .build());
-            }
+                List<QueueProgressVO> progress = new ArrayList<>();
+                for (Map.Entry<MessageQueue, OffsetWrapper> entry : stats.getOffsetTable().entrySet()) {
+                    MessageQueue mq = entry.getKey();
+                    OffsetWrapper ow = entry.getValue();
+                    long diff = Math.max(0, ow.getBrokerOffset() - ow.getConsumerOffset());
+
+                    progress.add(QueueProgressVO.builder()
+                            .broker(mq.getBrokerName())
+                            .queueId(mq.getQueueId())
+                            .brokerOffset(ow.getBrokerOffset())
+                            .consumerOffset(ow.getConsumerOffset())
+                            .diffTotal(diff)
+                            .build());
+                }
 
-            progress.sort((a, b) -> {
-                int cmp = a.getBroker().compareToIgnoreCase(b.getBroker());
-                return cmp != 0 ? cmp : Integer.compare(a.getQueueId(), b.getQueueId());
-            });
-            return progress;
-        } catch (Exception e) {
-            log.warn("Failed to get progress for group {}: {}", name, e.getMessage());
-            return Collections.emptyList();
-        }
+                progress.sort((a, b) -> {
+                    int cmp = a.getBroker().compareToIgnoreCase(b.getBroker());
+                    return cmp != 0 ? cmp : Integer.compare(a.getQueueId(), b.getQueueId());
+                });
+                return progress;
+            } catch (Exception e) {
+                log.warn("Failed to get progress for group {}: {}", name, e.getMessage());
+                return Collections.emptyList();
+            }
+        });
     }
 
     @Override
     public List<SubscriptionEntryVO> getGroupSubscriptions(String name) {
-        if (adminExt == null) {
+        if (!hasAdmin()) {
             return Collections.emptyList();
         }
 
-        try {
-            ConsumerConnection conn = adminExt.examineConsumerConnectionInfo(name);
-            if (conn == null || conn.getSubscriptionTable() == null) {
-                return Collections.emptyList();
-            }
+        return adminExecute(admin -> {
+            try {
+                ConsumerConnection conn = admin.examineConsumerConnectionInfo(name);
+                if (conn == null || conn.getSubscriptionTable() == null) {
+                    return Collections.emptyList();
+                }
 
-            List<SubscriptionEntryVO> subscriptions = new ArrayList<>();
-            for (Map.Entry<String, SubscriptionData> entry : conn.getSubscriptionTable().entrySet()) {
-                SubscriptionData sd = entry.getValue();
-                subscriptions.add(SubscriptionEntryVO.builder()
-                        .topic(sd.getTopic())
-                        .expression(sd.getSubString())
-                        .type(sd.getExpressionType())
-                        .filterMode(filterMode(sd.getExpressionType()))
-                        .build());
+                List<SubscriptionEntryVO> subscriptions = new ArrayList<>();
+                for (Map.Entry<String, SubscriptionData> entry : conn.getSubscriptionTable().entrySet()) {
+                    SubscriptionData sd = entry.getValue();
+                    subscriptions.add(SubscriptionEntryVO.builder()
+                            .topic(sd.getTopic())
+                            .expression(sd.getSubString())
+                            .type(sd.getExpressionType())
+                            .filterMode(filterMode(sd.getExpressionType()))
+                            .build());
+                }
+                return subscriptions;
+            } catch (Exception e) {
+                log.warn("Failed to get subscriptions for group {}: {}", name, e.getMessage());
+                return Collections.emptyList();
             }
-            return subscriptions;
-        } catch (Exception e) {
-            log.warn("Failed to get subscriptions for group {}: {}", name, e.getMessage());
-            return Collections.emptyList();
-        }
+        });
     }
 
     // ── Helper methods ──────────────────────────────────────────────────
 
-    private TopicVO buildTopicVO(String topicName) {
-        try {
-            TopicRouteData routeData = adminExt.examineTopicRouteInfo(topicName);
-            if (routeData == null || routeData.getQueueDatas() == null || routeData.getQueueDatas().isEmpty()) {
-                // Topic exists in nameserver but has no route data
-                TopicVO vo = new TopicVO();
-                vo.setId(topicName);
-                vo.setName(topicName);
-                return vo;
-            }
-
-            QueueData firstQd = routeData.getQueueDatas().get(0);
-            TopicVO vo = new TopicVO();
-            vo.setId(topicName);
-            vo.setName(topicName);
-            vo.setWriteQueues(firstQd.getWriteQueueNums());
-            vo.setReadQueues(firstQd.getReadQueueNums());
-            vo.setPerm(mapPerm(firstQd.getPerm()));
-            vo.setType(inferTopicType(topicName));
-            return vo;
-        } catch (Exception e) {
-            log.debug("Failed to get route for topic {}: {}", topicName, e.getMessage());
-            TopicVO vo = new TopicVO();
-            vo.setId(topicName);
-            vo.setName(topicName);
-            return vo;
-        }
-    }
-
     private void enrichGroupWithConnectionInfo(ConsumerGroupVO vo, String groupName) {
         try {
-            ConsumerConnection conn = adminExt.examineConsumerConnectionInfo(groupName);
+            ConsumerConnection conn = adminExecute(admin -> admin.examineConsumerConnectionInfo(groupName));
             if (conn != null) {
                 if (conn.getConnectionSet() != null) {
                     vo.setOnlineInstances(conn.getConnectionSet().size());
@@ -438,7 +415,7 @@ public class RocketMQMetadataProvider implements MetadataProvider {
 
         // Try to get lag info
         try {
-            ConsumeStats stats = adminExt.examineConsumeStats(groupName);
+            ConsumeStats stats = adminExecute(admin -> admin.examineConsumeStats(groupName));
             if (stats != null && stats.getOffsetTable() != null) {
                 long totalLag = 0;
                 for (OffsetWrapper ow : stats.getOffsetTable().values()) {
@@ -461,18 +438,6 @@ public class RocketMQMetadataProvider implements MetadataProvider {
         return "TAG";
     }
 
-    private Set<String> getBrokerNames() {
-        Set<String> names = new HashSet<>();
-        try {
-            ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
-            if (clusterInfo != null && clusterInfo.getBrokerAddrTable() != null) {
-                names.addAll(clusterInfo.getBrokerAddrTable().keySet());
-            }
-        } catch (Exception ignored) {
-        }
-        return names;
-    }
-
     private boolean isSystemTopic(String topicName, Set<String> brokerNames) {
         if (SYSTEM_TOPICS.contains(topicName)) {
             return true;
@@ -497,17 +462,4 @@ public class RocketMQMetadataProvider implements MetadataProvider {
         }
         return TopicPerm.RW;
     }
-
-    private org.apache.rocketmq.studio.common.domain.enums.TopicType inferTopicType(String topicName) {
-        if (topicName.contains("TRANS") || topicName.contains("trans")) {
-            return org.apache.rocketmq.studio.common.domain.enums.TopicType.TRANSACTION;
-        }
-        if (topicName.contains("DELAY") || topicName.contains("delay") || topicName.contains("SCHEDULE")) {
-            return org.apache.rocketmq.studio.common.domain.enums.TopicType.DELAY;
-        }
-        if (topicName.contains("FIFO") || topicName.contains("fifo") || topicName.contains("ORDER")) {
-            return org.apache.rocketmq.studio.common.domain.enums.TopicType.FIFO;
-        }
-        return org.apache.rocketmq.studio.common.domain.enums.TopicType.NORMAL;
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQProperties.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQProperties.java
similarity index 90%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQProperties.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQProperties.java
index 5c4bbaac..78aafb5d 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQProperties.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQProperties.java
@@ -14,12 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
 
 @Data
+@Component
 @ConfigurationProperties(prefix = "studio.rocketmq")
 public class RocketMQProperties {
     private String namesrvAddr;
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/ShortLivedClientName.java b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ShortLivedClientName.java
similarity index 95%
rename from server/src/main/java/org/apache/rocketmq/studio/rocketmq/ShortLivedClientName.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/apache/ShortLivedClientName.java
index b2ee385d..3259856d 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/ShortLivedClientName.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ShortLivedClientName.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.rocketmq;
+package org.apache.rocketmq.studio.provider.apache;
 
 import java.util.UUID;
 
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialController.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialController.java
similarity index 90%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialController.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialController.java
index 540cf116..40f99a74 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialController.java
@@ -14,9 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
 
 import jakarta.validation.Valid;
+import org.apache.rocketmq.studio.common.domain.DeleteRequestDTO;
 import org.apache.rocketmq.studio.common.domain.Result;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -25,19 +26,17 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import lombok.RequiredArgsConstructor;
 
 import java.util.List;
 
+@RequiredArgsConstructor
 @RestController
 @RequestMapping("/api/cloud-credentials")
 public class CloudCredentialController {
 
     private final CloudCredentialService credentialService;
 
-    public CloudCredentialController(CloudCredentialService credentialService) {
-        this.credentialService = credentialService;
-    }
-
     @GetMapping
     public Result<List<CloudCredentialVO>> listCredentials() {
         return Result.ok(credentialService.listMasked());
@@ -62,7 +61,7 @@ public class CloudCredentialController {
     }
 
     @PostMapping("/delete")
-    public Result<Void> deleteCredential(@Valid @RequestBody CloudCredentialDeleteRequestDTO request) {
+    public Result<Void> deleteCredential(@Valid @RequestBody DeleteRequestDTO request) {
         credentialService.delete(request.getId());
         return Result.ok();
     }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialRepository.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialRepository.java
similarity index 95%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialRepository.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialRepository.java
index 94e5d4fd..b6f12ae2 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialRepository.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
 
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialService.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialService.java
similarity index 77%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialService.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialService.java
index c1418344..503b9385 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialService.java
@@ -14,36 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
+
+import org.springframework.util.StringUtils;
 
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.util.CredentialUtils;
 import org.apache.rocketmq.studio.instance.InstanceRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.UUID;
 
+@Slf4j
+@RequiredArgsConstructor
 @Service
 public class CloudCredentialService {
 
-    private static final Logger log = LoggerFactory.getLogger(CloudCredentialService.class);
-
-    private static final int VISIBLE_CREDENTIAL_CHARS = 4;
-    private static final int MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS = 17;
-    private static final String CREDENTIAL_MASK = "****";
-
     private final CloudCredentialRepository credentialRepository;
     private final InstanceRepository instanceRepository;
 
-    public CloudCredentialService(CloudCredentialRepository credentialRepository,
-                                  InstanceRepository instanceRepository) {
-        this.credentialRepository = credentialRepository;
-        this.instanceRepository = instanceRepository;
-    }
-
     public List<CloudCredentialVO> listMasked() {
         log.info("Listing cloud credentials (masked)");
         return credentialRepository.findAll().stream()
@@ -55,20 +48,20 @@ public class CloudCredentialService {
         if (credential == null) {
             throw new BusinessException(400, "Cloud credential request is required");
         }
-        if (isBlank(credential.getName())) {
+        if (!StringUtils.hasText(credential.getName())) {
             throw new BusinessException(400, "Cloud credential name is required");
         }
         if (credential.getVendor() == null || credential.getVendor() == org.apache.rocketmq.studio.common.domain.enums.InstanceVendor.APACHE) {
             throw new BusinessException(400, "Cloud credential vendor must be ALIYUN or TENCENT");
         }
-        if (isBlank(credential.getAccessKey()) || isBlank(credential.getSecretKey())) {
+        if (!StringUtils.hasText(credential.getAccessKey()) || !StringUtils.hasText(credential.getSecretKey())) {
             throw new BusinessException(400, "Cloud credential accessKey and secretKey are required");
         }
         credentialRepository.findByVendorAndAccessKey(credential.getVendor(), credential.getAccessKey())
                 .ifPresent(existing -> {
                     throw new BusinessException(400,
                             "Cloud credential already exists for vendor " + credential.getVendor()
-                                    + " and accessKey " + maskCredential(credential.getAccessKey()));
+                                    + " and accessKey " + CredentialUtils.mask(credential.getAccessKey()));
                 });
         log.info("Creating cloud credential name={}, vendor={}", credential.getName(), credential.getVendor());
         credential.setId(UUID.randomUUID().toString());
@@ -78,7 +71,7 @@ public class CloudCredentialService {
     }
 
     public CloudCredentialVO update(UpdateCloudCredentialDTO request) {
-        if (request == null || isBlank(request.getId())) {
+        if (request == null || !StringUtils.hasText(request.getId())) {
             throw new BusinessException(400, "Cloud credential id is required");
         }
         log.info("Updating cloud credential id={}", request.getId());
@@ -101,7 +94,7 @@ public class CloudCredentialService {
     }
 
     public void delete(String id) {
-        if (isBlank(id)) {
+        if (!StringUtils.hasText(id)) {
             throw new BusinessException(400, "Cloud credential id is required");
         }
         log.info("Deleting cloud credential id={}", id);
@@ -114,7 +107,7 @@ public class CloudCredentialService {
     }
 
     public CloudCredentialVO reveal(String id) {
-        if (isBlank(id)) {
+        if (!StringUtils.hasText(id)) {
             throw new BusinessException(400, "Cloud credential id is required");
         }
         return credentialRepository.findById(id)
@@ -126,24 +119,11 @@ public class CloudCredentialService {
         masked.setId(credential.getId());
         masked.setName(credential.getName());
         masked.setVendor(credential.getVendor());
-        masked.setAccessKey(maskCredential(credential.getAccessKey()));
+        masked.setAccessKey(CredentialUtils.mask(credential.getAccessKey()));
         masked.setSecretKey(null);
         masked.setRemark(credential.getRemark());
         masked.setCreatedAt(credential.getCreatedAt());
         masked.setUpdatedAt(credential.getUpdatedAt());
         return masked;
     }
-
-    static String maskCredential(String value) {
-        if (value == null || value.length() < MIN_PARTIALLY_MASKED_CREDENTIAL_CHARS) {
-            return CREDENTIAL_MASK;
-        }
-        return value.substring(0, VISIBLE_CREDENTIAL_CHARS)
-                + CREDENTIAL_MASK
-                + value.substring(value.length() - VISIBLE_CREDENTIAL_CHARS);
-    }
-
-    private static boolean isBlank(String value) {
-        return value == null || value.isBlank();
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceVO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialVO.java
similarity index 74%
rename from server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceVO.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialVO.java
index 56a67154..98133b49 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NamespaceVO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CloudCredentialVO.java
@@ -14,16 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.rocketmq.studio.instance.topic;
+package org.apache.rocketmq.studio.provider.credential;
 
 import org.apache.rocketmq.studio.common.domain.BaseEntity;
+import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.ToString;
 
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class NamespaceVO extends BaseEntity {
+public class CloudCredentialVO extends BaseEntity {
+
     private String name;
-    private String clusterId;
+    private InstanceVendor vendor;
+    private String accessKey;
+    @ToString.Exclude
+    private String secretKey;
+    private String remark;
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CreateCloudCredentialDTO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CreateCloudCredentialDTO.java
similarity index 71%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CreateCloudCredentialDTO.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/CreateCloudCredentialDTO.java
index 79856bb8..92ad4ac4 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CreateCloudCredentialDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/CreateCloudCredentialDTO.java
@@ -14,11 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
 
 import jakarta.validation.constraints.NotBlank;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
+import lombok.Data;
 
+@Data
 public class CreateCloudCredentialDTO {
 
     @NotBlank(message = "credential name is required")
@@ -35,46 +37,6 @@ public class CreateCloudCredentialDTO {
 
     private String remark;
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getVendor() {
-        return vendor;
-    }
-
-    public void setVendor(String vendor) {
-        this.vendor = vendor;
-    }
-
-    public String getAccessKey() {
-        return accessKey;
-    }
-
-    public void setAccessKey(String accessKey) {
-        this.accessKey = accessKey;
-    }
-
-    public String getSecretKey() {
-        return secretKey;
-    }
-
-    public void setSecretKey(String secretKey) {
-        this.secretKey = secretKey;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
-
     public CloudCredentialVO toCloudCredentialVO() {
         CloudCredentialVO vo = new CloudCredentialVO();
         vo.setName(name);
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/MybatisPlusCloudCredentialRepository.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/MybatisPlusCloudCredentialRepository.java
similarity index 82%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/MybatisPlusCloudCredentialRepository.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/MybatisPlusCloudCredentialRepository.java
index c83b7352..137131c0 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/MybatisPlusCloudCredentialRepository.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/MybatisPlusCloudCredentialRepository.java
@@ -14,17 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
+import org.apache.rocketmq.studio.common.util.CredentialUtils;
 import org.apache.rocketmq.studio.persistence.entity.RmqCloudCredential;
 import org.apache.rocketmq.studio.persistence.mapper.RmqCloudCredentialMapper;
 import org.springframework.stereotype.Repository;
+import lombok.RequiredArgsConstructor;
 
-import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
-import java.util.Base64;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -33,15 +33,12 @@ import java.util.stream.Collectors;
  * MySQL-backed cloud credential repository. Secret keys are stored base64-encoded in
  * {@code rmq_cloud_credential.secret_key} and decoded when read; plain text is never persisted.
  */
+@RequiredArgsConstructor
 @Repository
 public class MybatisPlusCloudCredentialRepository implements CloudCredentialRepository {
 
     private final RmqCloudCredentialMapper credentialMapper;
 
-    public MybatisPlusCloudCredentialRepository(RmqCloudCredentialMapper credentialMapper) {
-        this.credentialMapper = credentialMapper;
-    }
-
     @Override
     public List<CloudCredentialVO> findAll() {
         return credentialMapper.selectList(
@@ -90,7 +87,7 @@ public class MybatisPlusCloudCredentialRepository implements CloudCredentialRepo
         vo.setName(entity.getName());
         vo.setVendor(parseVendor(entity.getVendor()));
         vo.setAccessKey(entity.getAccessKey());
-        vo.setSecretKey(decodeBase64(entity.getSecretKey()));
+        vo.setSecretKey(CredentialUtils.decodeBase64(entity.getSecretKey()));
         vo.setRemark(entity.getRemark());
         vo.setCreatedAt(entity.getCreatedAt());
         vo.setUpdatedAt(entity.getUpdatedAt());
@@ -103,7 +100,7 @@ public class MybatisPlusCloudCredentialRepository implements CloudCredentialRepo
         entity.setName(vo.getName());
         entity.setVendor(vo.getVendor() == null ? null : vo.getVendor().name());
         entity.setAccessKey(vo.getAccessKey());
-        entity.setSecretKey(encodeBase64(vo.getSecretKey()));
+        entity.setSecretKey(CredentialUtils.encodeBase64(vo.getSecretKey()));
         entity.setRemark(vo.getRemark());
         entity.setCreatedAt(vo.getCreatedAt());
         entity.setUpdatedAt(vo.getUpdatedAt() == null ? LocalDateTime.now() : vo.getUpdatedAt());
@@ -117,23 +114,4 @@ public class MybatisPlusCloudCredentialRepository implements CloudCredentialRepo
             return null;
         }
     }
-
-    static String encodeBase64(String plainText) {
-        if (plainText == null) {
-            return null;
-        }
-        return Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8));
-    }
-
-    static String decodeBase64(String stored) {
-        if (stored == null) {
-            return null;
-        }
-        try {
-            return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8);
-        } catch (IllegalArgumentException ex) {
-            // tolerate legacy values that were stored without encoding
-            return stored;
-        }
-    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialDeleteRequestDTO.java b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/UpdateCloudCredentialDTO.java
similarity index 81%
rename from server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialDeleteRequestDTO.java
rename to server/src/main/java/org/apache/rocketmq/studio/provider/credential/UpdateCloudCredentialDTO.java
index d4513327..6219c5bc 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/cloud/credential/CloudCredentialDeleteRequestDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/credential/UpdateCloudCredentialDTO.java
@@ -14,20 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.cloud.credential;
+package org.apache.rocketmq.studio.provider.credential;
 
 import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
 
-public class CloudCredentialDeleteRequestDTO {
+@Data
+public class UpdateCloudCredentialDTO {
 
     @NotBlank(message = "credential id is required")
     private String id;
 
-    public String getId() {
-        return id;
-    }
+    private String name;
 
-    public void setId(String id) {
-        this.id = id;
-    }
+    private String secretKey;
+
+    private String remark;
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
index ca4df5f4..62b9f8c0 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
@@ -42,6 +42,16 @@ public class TencentInstanceProvider implements InstanceProvider {
         return InstanceVendor.TENCENT;
     }
 
+    @Override
+    public int countTopics(String instanceId) {
+        throw new UnsupportedOperationException(NOT_IMPLEMENTED);
+    }
+
+    @Override
+    public int countGroups(String instanceId) {
+        throw new UnsupportedOperationException(NOT_IMPLEMENTED);
+    }
+
     @Override
     public List<TopicVO> listTopics(String instanceId, String type, String search) {
         throw new UnsupportedOperationException(NOT_IMPLEMENTED);
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminClientImpl.java b/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminClientImpl.java
deleted file mode 100644
index ceec6bf6..00000000
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminClientImpl.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * 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.rocketmq;
-
-import org.apache.rocketmq.client.exception.MQClientException;
-import org.apache.rocketmq.client.producer.DefaultMQProducer;
-import org.apache.rocketmq.client.producer.SendResult;
-import org.apache.rocketmq.common.TopicConfig;
-import org.apache.rocketmq.common.message.Message;
-import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
-import org.apache.rocketmq.remoting.protocol.ResponseCode;
-import org.apache.rocketmq.remoting.protocol.route.BrokerData;
-import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
-import org.apache.rocketmq.studio.common.exception.BusinessException;
-import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
-import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
-import org.apache.rocketmq.studio.instance.topic.AdminClient;
-import org.apache.rocketmq.studio.instance.topic.SendMessageDTO;
-import org.apache.rocketmq.studio.instance.topic.SendMessageVO;
-import org.apache.rocketmq.studio.instance.topic.TopicVO;
-import org.apache.rocketmq.studio.ops.audit.AuditService;
-import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
-import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
-import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
-import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Primary;
-import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-
-import java.nio.charset.StandardCharsets;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Real AdminClient implementation backed by DefaultMQAdminExt.
- * Provides topic CRUD, message sending, consumer group CRUD, and offset reset.
- */
-@Service
-@Primary
-public class RocketMQAdminClientImpl implements AdminClient {
-
-    private static final Logger log = LoggerFactory.getLogger(RocketMQAdminClientImpl.class);
-    private static final String MESSAGE_SENDER_GROUP_PREFIX = "studio-msg-sender";
-
-    private final DefaultMQAdminExt adminExt;
-    private final RocketMQProperties properties;
-    private final RmqTopicMapper topicMapper;
-    private final RmqGroupMapper groupMapper;
-    private final AuditService auditService;
-
-    @Autowired
-    public RocketMQAdminClientImpl(
-            @Autowired(required = false) DefaultMQAdminExt adminExt,
-            RocketMQProperties properties,
-            RmqTopicMapper topicMapper,
-            RmqGroupMapper groupMapper,
-            AuditService auditService) {
-        this.adminExt = adminExt;
-        this.properties = properties;
-        this.topicMapper = topicMapper;
-        this.groupMapper = groupMapper;
-        this.auditService = auditService;
-    }
-
-    @Override
-    public TopicVO getTopic(String name) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-        try {
-            var routeData = adminExt.examineTopicRouteInfo(name);
-            if (routeData == null || routeData.getQueueDatas() == null || routeData.getQueueDatas().isEmpty()) {
-                throw new BusinessException(404, "Topic not found: " + name);
-            }
-            var qd = routeData.getQueueDatas().get(0);
-            TopicVO vo = new TopicVO();
-            vo.setId(name);
-            vo.setName(name);
-            vo.setWriteQueues(qd.getWriteQueueNums());
-            vo.setReadQueues(qd.getReadQueueNums());
-            return vo;
-        } catch (BusinessException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new BusinessException(500, "Failed to get topic: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public ConsumerGroupVO getConsumerGroup(String name) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-        ConsumerGroupVO vo = new ConsumerGroupVO();
-        vo.setId(name);
-        vo.setName(name);
-        try {
-            var conn = adminExt.examineConsumerConnectionInfo(name);
-            if (conn != null) {
-                if (conn.getConnectionSet() != null) {
-                    vo.setOnlineInstances(conn.getConnectionSet().size());
-                }
-                if (conn.getSubscriptionTable() != null) {
-                    vo.setSubscribedTopics(new ArrayList<>(conn.getSubscriptionTable().keySet()));
-                }
-            }
-        } catch (MQClientException exception) {
-            if (exception.getResponseCode() == ResponseCode.CONSUMER_NOT_ONLINE) {
-                log.debug("Consumer group {} is offline", name);
-                return vo;
-            }
-            throw new BusinessException(502, "Failed to get consumer group: " + exception.getMessage());
-        } catch (Exception exception) {
-            throw new BusinessException(502, "Failed to get consumer group: " + exception.getMessage());
-        }
-        return vo;
-    }
-
-    @Override
-    public TopicVO createTopic(TopicVO topic) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        String topicName = topic.getName();
-        int writeQueues = topic.getWriteQueues() > 0 ? topic.getWriteQueues() : 8;
-        int readQueues = topic.getReadQueues() > 0 ? topic.getReadQueues() : 8;
-
-        try {
-            String clusterName = getClusterName();
-            // Match on the (cluster_id, name) key: the same topic name can exist in several
-            // clusters, and a name-only lookup would blow up with TooManyResultsException.
-            RmqTopic existing = topicMapper.selectOne(
-                    new LambdaQueryWrapper<RmqTopic>()
-                            .eq(RmqTopic::getClusterId, clusterName)
-                            .eq(RmqTopic::getName, topicName));
-            TopicPerm effectivePerm = topic.getPerm() != null
-                    ? topic.getPerm()
-                    : existing == null ? TopicPerm.RW : fromRocketMQPerm(existing.getPerm());
-            Set<String> brokerAddrs = getAllMasterBrokerAddrs();
-            if (brokerAddrs.isEmpty()) {
-                throw new BusinessException(500, "No broker available to create topic");
-            }
-
-            TopicConfig topicConfig = new TopicConfig();
-            topicConfig.setTopicName(topicName);
-            topicConfig.setWriteQueueNums(writeQueues);
-            topicConfig.setReadQueueNums(readQueues);
-            topicConfig.setPerm(toRocketMQPerm(effectivePerm));
-
-            for (String addr : brokerAddrs) {
-                adminExt.createAndUpdateTopicConfig(addr, topicConfig);
-            }
-
-            // Persist to DB. Re-creating a topic that already has a record (for example when
-            // rebuilding a broker route from the console) must update it instead of failing on
-            // the unique (cluster_id, name) key.
-            RmqTopic entity = topicMapper.selectOne(new LambdaQueryWrapper<RmqTopic>()
-                    .eq(RmqTopic::getClusterId, clusterName)
-                    .eq(RmqTopic::getName, topicName));
-            boolean isNew = entity == null;
-            if (isNew) {
-                entity = new RmqTopic();
-                entity.setName(topicName);
-                entity.setClusterId(clusterName);
-                entity.setCreatedAt(LocalDateTime.now());
-            }
-            if (StringUtils.hasText(topic.getInstanceId())) {
-                entity.setInstanceId(topic.getInstanceId());
-            }
-            entity.setTopicType(topic.getType() != null ? topic.getType().name() : "NORMAL");
-            entity.setReadQueueNums(readQueues);
-            entity.setWriteQueueNums(writeQueues);
-            entity.setPerm(topicConfig.getPerm());
-            if (StringUtils.hasText(topic.getRemark())) {
-                entity.setRemark(topic.getRemark());
-            }
-            entity.setStatus("ACTIVE");
-            entity.setUpdatedAt(LocalDateTime.now());
-            if (isNew) {
-                topicMapper.insert(entity);
-            } else {
-                topicMapper.updateById(entity);
-            }
-
-            auditService.record("CREATE_TOPIC", topicName,
-                    "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
-
-            topic.setId(topicName);
-            topic.setWriteQueues(writeQueues);
-            topic.setReadQueues(readQueues);
-            return topic;
-        } catch (BusinessException e) {
-            auditService.record("CREATE_TOPIC", topicName, e.getMessage(), "FAILED");
-            throw e;
-        } catch (Exception e) {
-            auditService.record("CREATE_TOPIC", topicName, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to create topic: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public TopicVO updateTopic(TopicVO topic) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        String topicName = topic.getName();
-        int writeQueues = topic.getWriteQueues() > 0 ? topic.getWriteQueues() : 8;
-        int readQueues = topic.getReadQueues() > 0 ? topic.getReadQueues() : 8;
-
-        try {
-            // Match on the (cluster_id, name) key to avoid ambiguity when several clusters share
-            // the same topic name (a name-only lookup would throw TooManyResultsException).
-            String clusterName = getClusterName();
-            RmqTopic existing = topicMapper.selectOne(
-                    new LambdaQueryWrapper<RmqTopic>()
-                            .eq(RmqTopic::getClusterId, clusterName)
-                            .eq(RmqTopic::getName, topicName));
-            TopicPerm effectivePerm = topic.getPerm() != null
-                    ? topic.getPerm()
-                    : existing == null ? TopicPerm.RW : fromRocketMQPerm(existing.getPerm());
-            Set<String> brokerAddrs = getAllMasterBrokerAddrs();
-            if (brokerAddrs.isEmpty()) {
-                throw new BusinessException(500, "No broker available to update topic");
-            }
-
-            TopicConfig topicConfig = new TopicConfig();
-            topicConfig.setTopicName(topicName);
-            topicConfig.setWriteQueueNums(writeQueues);
-            topicConfig.setReadQueueNums(readQueues);
-            topicConfig.setPerm(toRocketMQPerm(effectivePerm));
-
-            for (String addr : brokerAddrs) {
-                adminExt.createAndUpdateTopicConfig(addr, topicConfig);
-            }
-
-            // Update DB record
-            if (existing != null) {
-                existing.setWriteQueueNums(writeQueues);
-                existing.setReadQueueNums(readQueues);
-                existing.setPerm(topicConfig.getPerm());
-                existing.setUpdatedAt(LocalDateTime.now());
-                topicMapper.updateById(existing);
-            }
-
-            auditService.record("UPDATE_TOPIC", topicName,
-                    "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
-
-            topic.setId(topicName);
-            topic.setWriteQueues(writeQueues);
-            topic.setReadQueues(readQueues);
-            return topic;
-        } catch (BusinessException e) {
-            auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), "FAILED");
-            throw e;
-        } catch (Exception e) {
-            auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to update topic: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public void deleteTopic(String name) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        try {
-            Set<String> brokerAddrs = getAllMasterBrokerAddrs();
-
-            // Delete from brokers
-            if (!brokerAddrs.isEmpty()) {
-                adminExt.deleteTopicInBroker(brokerAddrs, name);
-            }
-
-            // Delete from nameserver
-            String namesrvAddr = properties.getNamesrvAddr();
-            if (namesrvAddr != null && !namesrvAddr.isEmpty()) {
-                Set<String> nsAddrs = new HashSet<>();
-                for (String addr : namesrvAddr.split("[;,]")) {
-                    String trimmed = addr.trim();
-                    if (!trimmed.isEmpty()) {
-                        nsAddrs.add(trimmed);
-                    }
-                }
-                adminExt.deleteTopicInNameServer(nsAddrs, getClusterName(), name);
-            }
-
-            // Delete from DB
-            topicMapper.delete(new LambdaQueryWrapper<RmqTopic>().eq(RmqTopic::getName, name));
-
-            auditService.record("DELETE_TOPIC", name, "", "SUCCESS");
-        } catch (BusinessException e) {
-            auditService.record("DELETE_TOPIC", name, e.getMessage(), "FAILED");
-            throw e;
-        } catch (Exception e) {
-            auditService.record("DELETE_TOPIC", name, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to delete topic: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public SendMessageVO sendMessage(SendMessageDTO request) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        String namesrvAddr = properties.getNamesrvAddr();
-        if (namesrvAddr == null || namesrvAddr.isEmpty()) {
-            throw new BusinessException(500, "Nameserver address not configured");
-        }
-
-        DefaultMQProducer producer = new DefaultMQProducer(nextMessageSenderGroup());
-        producer.setNamesrvAddr(namesrvAddr);
-        producer.setSendMsgTimeout(5000);
-
-        try {
-            producer.start();
-
-            String topic = request.getTopic();
-            String tag = request.getTag() != null ? request.getTag() : "";
-            String key = request.getKey() != null ? request.getKey() : "";
-            String body = request.getBody() != null ? request.getBody() : "";
-
-            String fullTopic = tag.isEmpty() ? topic : topic + ":" + tag;
-            Message msg = new Message(topic, tag, key, body.getBytes(StandardCharsets.UTF_8));
-
-            // Add custom properties
-            if (request.getProperties() != null) {
-                for (Map.Entry<String, String> entry : request.getProperties().entrySet()) {
-                    msg.putUserProperty(entry.getKey(), entry.getValue());
-                }
-            }
-
-            SendResult sendResult = producer.send(msg);
-
-            // The message is already delivered by now; an audit write failure must not turn a
-            // successful send into an error, or callers would retry and duplicate the message.
-            try {
-                auditService.record("SEND_MESSAGE", topic,
-                        "tag=" + tag + ", key=" + key + ", msgId=" + sendResult.getMsgId(), "SUCCESS");
-            } catch (Exception auditFailure) {
-                log.warn("Failed to record send message audit: {}", auditFailure.getMessage());
-            }
-
-            return SendMessageVO.builder()
-                    .msgId(sendResult.getMsgId())
-                    .sendTime(System.currentTimeMillis())
-                    .offsetMsgId(sendResult.getOffsetMsgId())
-                    .build();
-        } catch (Exception e) {
-            auditService.record("SEND_MESSAGE", request.getTopic(), e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to send message: " + e.getMessage());
-        } finally {
-            producer.shutdown();
-        }
-    }
-
-    static String nextMessageSenderGroup() {
-        return ShortLivedClientName.next(MESSAGE_SENDER_GROUP_PREFIX);
-    }
-
-    @Override
-    public ConsumerGroupVO createConsumerGroup(ConsumerGroupVO group) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        String groupName = group.getName();
-
-        try {
-            Set<String> brokerAddrs = getAllMasterBrokerAddrs();
-            if (brokerAddrs.isEmpty()) {
-                throw new BusinessException(500, "No broker available to create consumer group");
-            }
-
-            SubscriptionGroupConfig config = new SubscriptionGroupConfig();
-            config.setGroupName(groupName);
-            config.setConsumeEnable(true);
-            config.setConsumeBroadcastEnable(true);
-            config.setRetryQueueNums(1);
-            config.setRetryMaxTimes(group.getRetryMaxTimes() > 0 ? group.getRetryMaxTimes() : 16);
-
-            for (String addr : brokerAddrs) {
-                adminExt.createAndUpdateSubscriptionGroupConfig(addr, config);
-            }
-
-            // Persist to DB, upserting so re-creating an existing group does not violate the
-            // unique (cluster_id, name) key.
-            String groupClusterName = getClusterName();
-            RmqGroup entity = groupMapper.selectOne(new LambdaQueryWrapper<RmqGroup>()
-                    .eq(RmqGroup::getClusterId, groupClusterName)
-                    .eq(RmqGroup::getName, groupName));
-            boolean isNewGroup = entity == null;
-            if (isNewGroup) {
-                entity = new RmqGroup();
-                entity.setName(groupName);
-                entity.setClusterId(groupClusterName);
-                entity.setCreatedAt(LocalDateTime.now());
-            }
-            if (StringUtils.hasText(group.getInstanceId())) {
-                entity.setInstanceId(group.getInstanceId());
-            }
-            entity.setConsumeType(group.getConsumeType() != null ? group.getConsumeType().name() : "CLUSTERING");
-            entity.setMessageModel(group.getSubscriptionMode() != null ? group.getSubscriptionMode().name() : "Push");
-            entity.setMaxRetry(config.getRetryMaxTimes());
-            entity.setStatus("ACTIVE");
-            entity.setUpdatedAt(LocalDateTime.now());
-            if (isNewGroup) {
-                groupMapper.insert(entity);
-            } else {
-                groupMapper.updateById(entity);
-            }
-
-            auditService.record("CREATE_GROUP", groupName,
-                    "retryMaxTimes=" + config.getRetryMaxTimes(), "SUCCESS");
-
-            group.setId(groupName);
-            return group;
-        } catch (BusinessException e) {
-            auditService.record("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
-            throw e;
-        } catch (Exception e) {
-            auditService.record("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to create consumer group: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public void deleteConsumerGroup(String name) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        try {
-            Set<String> brokerAddrs = getAllMasterBrokerAddrs();
-
-            for (String addr : brokerAddrs) {
-                adminExt.deleteSubscriptionGroup(addr, name, true);
-            }
-
-            // Delete from DB
-            groupMapper.delete(new LambdaQueryWrapper<RmqGroup>().eq(RmqGroup::getName, name));
-
-            auditService.record("DELETE_GROUP", name, "", "SUCCESS");
-        } catch (BusinessException e) {
-            auditService.record("DELETE_GROUP", name, e.getMessage(), "FAILED");
-            throw e;
-        } catch (Exception e) {
-            auditService.record("DELETE_GROUP", name, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to delete consumer group: " + e.getMessage());
-        }
-    }
-
-    @Override
-    public void resetOffset(String name, long timestamp, String topic) {
-        if (adminExt == null) {
-            throw new BusinessException(503, "RocketMQ admin not connected");
-        }
-
-        try {
-            adminExt.resetOffsetByTimestamp(getClusterName(), topic, name, timestamp, false);
-            auditService.record("RESET_OFFSET", name,
-                    "topic=" + topic + ", timestamp=" + timestamp, "SUCCESS");
-        } catch (Exception e) {
-            auditService.record("RESET_OFFSET", name, e.getMessage(), "FAILED");
-            throw new BusinessException(500, "Failed to reset offset: " + e.getMessage());
-        }
-    }
-
-    // ── Helper methods ──────────────────────────────────────────────────
-
-    private Set<String> getAllMasterBrokerAddrs() throws Exception {
-        Set<String> addrs = new HashSet<>();
-        ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
-        if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null) {
-            return addrs;
-        }
-
-        for (BrokerData brokerData : clusterInfo.getBrokerAddrTable().values()) {
-            if (brokerData.getBrokerAddrs() == null) {
-                continue;
-            }
-            // Use master address (brokerId = 0) preferentially
-            String masterAddr = brokerData.getBrokerAddrs().get(0L);
-            if (masterAddr == null && !brokerData.getBrokerAddrs().isEmpty()) {
-                masterAddr = brokerData.getBrokerAddrs().values().iterator().next();
-            }
-            if (masterAddr != null) {
-                addrs.add(masterAddr);
-            }
-        }
-        return addrs;
-    }
-
-    private String getClusterName() {
-        try {
-            ClusterInfo clusterInfo = adminExt.examineBrokerClusterInfo();
-            if (clusterInfo != null && clusterInfo.getClusterAddrTable() != null
-                    && !clusterInfo.getClusterAddrTable().isEmpty()) {
-                return clusterInfo.getClusterAddrTable().keySet().iterator().next();
-            }
-        } catch (Exception ignored) {
-        }
-        return "DefaultCluster";
-    }
-
-    private int toRocketMQPerm(TopicPerm perm) {
-        if (perm == TopicPerm.RO) {
-            return 4;
-        }
-        if (perm == TopicPerm.WO) {
-            return 2;
-        }
-        return 6;
-    }
-
-    private TopicPerm fromRocketMQPerm(Integer perm) {
-        if (perm != null && perm == 4) {
-            return TopicPerm.RO;
-        }
-        if (perm != null && perm == 2) {
-            return TopicPerm.WO;
-        }
-        return TopicPerm.RW;
-    }
-}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminConfig.java b/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminConfig.java
deleted file mode 100644
index 8cf4e7f5..00000000
--- a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQAdminConfig.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.rocketmq;
-
-import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@EnableConfigurationProperties(RocketMQProperties.class)
-public class RocketMQAdminConfig {
-
-    private static final Logger log = LoggerFactory.getLogger(RocketMQAdminConfig.class);
-
-    @Bean(initMethod = "start", destroyMethod = "shutdown")
-    @ConditionalOnProperty(prefix = "studio.rocketmq", name = "namesrv-addr")
-    public DefaultMQAdminExt defaultMQAdminExt(
-            @Value("${studio.rocketmq.namesrv-addr:}") String namesrvAddr) {
-        log.info("Initializing DefaultMQAdminExt with namesrvAddr=[{}]", namesrvAddr);
-        DefaultMQAdminExt adminExt = new DefaultMQAdminExt();
-        adminExt.setNamesrvAddr(namesrvAddr);
-        adminExt.setInstanceName("studio-admin-" + System.currentTimeMillis());
-        return adminExt;
-    }
-}
diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java b/server/src/main/java/org/apache/rocketmq/studio/settings/DataSourceDTO.java
similarity index 60%
rename from server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
rename to server/src/main/java/org/apache/rocketmq/studio/settings/DataSourceDTO.java
index 2e051bb1..61626ad9 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/settings/DataSourceDTO.java
@@ -14,19 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.instance.acl;
+package org.apache.rocketmq.studio.settings;
 
 import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
 @Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class AclDeleteRequestDTO {
-    @NotBlank(message = "id is required")
-    private String id;
+public class DataSourceDTO {
+
+    private String key;
+
+    @NotBlank(message = "name is required")
+    private String name;
+
+    @NotBlank(message = "type is required")
+    private String type;
+
+    @NotBlank(message = "url is required")
+    private String url;
+
+    private String auth;
+
+    public DataSourceVO toDataSourceVO() {
+        return DataSourceVO.builder()
+                .key(key)
+                .name(name)
+                .type(type)
+                .url(url)
+                .auth(auth)
+                .build();
+    }
 }
diff --git a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
index 40d79332..935a6da4 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
@@ -17,7 +17,6 @@
 package org.apache.rocketmq.studio.settings;
 
 import org.apache.rocketmq.studio.common.domain.Result;
-import org.apache.rocketmq.studio.common.exception.BusinessException;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -53,13 +52,13 @@ public class SettingsController {
     }
 
     @PostMapping("/datasources/create")
-    public Result<DataSourceVO> createDataSource(@Valid @RequestBody(required = false) DataSourceVO dataSource) {
-        return Result.ok(settingsService.createDataSource(requireDataSource(dataSource)));
+    public Result<DataSourceVO> createDataSource(@Valid @RequestBody DataSourceDTO request) {
+        return Result.ok(settingsService.createDataSource(request.toDataSourceVO()));
     }
 
     @PostMapping("/datasources/update")
-    public Result<DataSourceVO> updateDataSource(@Valid @RequestBody(required = false) DataSourceVO dataSource) {
-        return Result.ok(settingsService.updateDataSource(requireDataSource(dataSource)));
+    public Result<DataSourceVO> updateDataSource(@Valid @RequestBody DataSourceDTO request) {
+        return Result.ok(settingsService.updateDataSource(request.toDataSourceVO()));
     }
 
     @PostMapping("/datasources/delete")
@@ -73,10 +72,4 @@ public class SettingsController {
         return Result.ok(settingsService.testDataSource(request));
     }
 
-    private DataSourceVO requireDataSource(DataSourceVO dataSource) {
-        if (dataSource == null) {
-            throw new BusinessException(400, "Data source request is required");
-        }
-        return dataSource;
-    }
 }
diff --git a/server/src/main/resources/application-dev.yml b/server/src/main/resources/application-dev.yml
index 401febe3..f7efb59e 100644
--- a/server/src/main/resources/application-dev.yml
+++ b/server/src/main/resources/application-dev.yml
@@ -1,7 +1,7 @@
 # Development profile
 spring:
   datasource:
-    url: jdbc:h2:mem:rocketmq_studio;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
+    url: jdbc:h2:mem:rocketmq;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
     username: sa
     password:
     driver-class-name: org.h2.Driver
diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml
index ddd36def..9db5ce44 100644
--- a/server/src/main/resources/application.yml
+++ b/server/src/main/resources/application.yml
@@ -7,7 +7,7 @@ spring:
   profiles:
     active: dev
   datasource:
-    url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/rocketmq_studio?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC}
+    url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/rocketmq?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC}
     username: ${SPRING_DATASOURCE_USERNAME:root}
     password: ${SPRING_DATASOURCE_PASSWORD:studio123}
     driver-class-name: com.mysql.cj.jdbc.Driver
diff --git a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
index 6100d4a9..1e6bcb9c 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
@@ -33,7 +33,7 @@ import org.apache.rocketmq.studio.common.domain.enums.ClusterType;
 import org.apache.rocketmq.studio.common.domain.enums.FlushDiskType;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.ops.audit.AuditService;
-import org.apache.rocketmq.studio.rocketmq.RocketMQBrokerConfigService;
+import org.apache.rocketmq.studio.provider.apache.RocketMQBrokerConfigService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
index c10b9fae..8f3ff7e9 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
@@ -140,7 +140,7 @@ class InstanceControllerTest {
                         .content("null"))
                 .andExpect(status().isBadRequest())
                 .andExpect(jsonPath("$.code").value(400))
-                .andExpect(jsonPath("$.message").value("Instance request is required"));
+                .andExpect(jsonPath("$.message").value("Invalid request body"));
 
         verifyNoInteractions(instanceService);
     }
@@ -179,7 +179,7 @@ class InstanceControllerTest {
                         .content("null"))
                 .andExpect(status().isBadRequest())
                 .andExpect(jsonPath("$.code").value(400))
-                .andExpect(jsonPath("$.message").value("Instance request is required"));
+                .andExpect(jsonPath("$.message").value("Invalid request body"));
 
         verifyNoInteractions(instanceService);
     }
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
index 096339c9..b998643e 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
@@ -17,13 +17,14 @@
 
 package org.apache.rocketmq.studio.instance;
 
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialRepository;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialVO;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialRepository;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialVO;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.provider.CloudCatalogProvider;
 import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
+import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.apache.rocketmq.studio.provider.InstanceProviderRegistry;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -55,6 +56,9 @@ class InstanceServiceTest {
     @Mock
     private InstanceProviderRegistry providerRegistry;
 
+    @Mock
+    private InstanceProvider instanceProvider;
+
     @InjectMocks
     private InstanceService instanceService;
 
@@ -65,6 +69,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("inst-2").build()
         );
         when(instanceRepository.findAll()).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(null, null);
 
@@ -78,6 +83,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("proxy-1").type(InstanceType.PROXY).build()
         );
         when(instanceRepository.findByType(InstanceType.PROXY)).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(InstanceType.PROXY, null);
 
@@ -91,6 +97,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("production").build()
         );
         when(instanceRepository.search("prod")).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(null, "prod");
 
@@ -104,6 +111,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("production").build()
         );
         when(instanceRepository.search("prod")).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(null, " prod ");
 
@@ -117,6 +125,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("production-proxy").type(InstanceType.PROXY).build()
         );
         when(instanceRepository.findByTypeAndSearch(InstanceType.PROXY, "prod")).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(InstanceType.PROXY, "prod");
 
@@ -130,6 +139,7 @@ class InstanceServiceTest {
                 InstanceVO.builder().name("production-proxy").type(InstanceType.PROXY).build()
         );
         when(instanceRepository.findByTypeAndSearch(InstanceType.PROXY, "prod")).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(InstanceType.PROXY, " prod ");
 
@@ -141,6 +151,7 @@ class InstanceServiceTest {
     void listInstancesShouldIgnoreBlankSearch() {
         List<InstanceVO> instances = List.of(InstanceVO.builder().name("inst").build());
         when(instanceRepository.findAll()).thenReturn(instances);
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
         List<InstanceVO> result = instanceService.listInstances(null, "   ");
 
@@ -148,6 +159,44 @@ class InstanceServiceTest {
         verify(instanceRepository).findAll();
     }
 
+    @Test
+    void listInstancesShouldFillCountsThroughVendorProviderTest() {
+        InstanceVO apache = InstanceVO.builder().name("apache").build();
+        apache.setId("inst-apache");
+        InstanceVO aliyun = InstanceVO.builder().name("aliyun").vendor(InstanceVendor.ALIYUN).build();
+        aliyun.setId("inst-aliyun");
+        InstanceProvider aliyunProvider = org.mockito.Mockito.mock(InstanceProvider.class);
+        when(instanceRepository.findAll()).thenReturn(List.of(apache, aliyun));
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
+        when(providerRegistry.forVendor(InstanceVendor.ALIYUN)).thenReturn(aliyunProvider);
+        when(instanceProvider.countTopics("inst-apache")).thenReturn(3);
+        when(instanceProvider.countGroups("inst-apache")).thenReturn(2);
+        when(aliyunProvider.countTopics("inst-aliyun")).thenReturn(5);
+        when(aliyunProvider.countGroups("inst-aliyun")).thenReturn(4);
+
+        List<InstanceVO> result = instanceService.listInstances(null, null);
+
+        assertThat(result.get(0).getTopicCount()).isEqualTo(3);
+        assertThat(result.get(0).getConsumerGroupCount()).isEqualTo(2);
+        assertThat(result.get(1).getTopicCount()).isEqualTo(5);
+        assertThat(result.get(1).getConsumerGroupCount()).isEqualTo(4);
+    }
+
+    @Test
+    void listInstancesShouldKeepZeroCountsWhenProviderFailsTest() {
+        InstanceVO instance = InstanceVO.builder().name("broken").build();
+        instance.setId("inst-broken");
+        when(instanceRepository.findAll()).thenReturn(List.of(instance));
+        when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
+        when(instanceProvider.countTopics("inst-broken"))
+                .thenThrow(new IllegalStateException("admin unavailable"));
+
+        List<InstanceVO> result = instanceService.listInstances(null, null);
+
+        assertThat(result.get(0).getTopicCount()).isZero();
+        assertThat(result.get(0).getConsumerGroupCount()).isZero();
+    }
+
     @Test
     void createInstanceShouldThrowWhenRequestIsNull() {
         assertThatThrownBy(() -> instanceService.createInstance(null))
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
index eba043f8..f796564b 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
@@ -31,13 +31,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
@@ -56,15 +56,10 @@ class MybatisPlusInstanceRepositoryTest {
     private MybatisPlusInstanceRepository repository;
 
     @Test
-    void findAllShouldPopulateCountsGroupedByInstance() {
+    void findAllShouldMapEntitiesWithoutCountsTest() {
         when(instanceMapper.selectList(any(QueryWrapper.class)))
                 .thenReturn(List.of(entity("instance-direct-1", InstanceType.DIRECT),
                         entity("instance-proxy-1", InstanceType.PROXY)));
-        when(topicMapper.selectMaps(any(QueryWrapper.class)))
-                .thenReturn(List.of(Map.of("instance_id", "instance-proxy-1", "total", 3L)));
-        when(groupMapper.selectMaps(any(QueryWrapper.class)))
-                .thenReturn(List.of(Map.of("instance_id", "instance-proxy-1", "total", 2L),
-                        Map.of("instance_id", "instance-direct-1", "total", 1L)));
 
         List<InstanceVO> result = repository.findAll();
 
@@ -74,9 +69,10 @@ class MybatisPlusInstanceRepositoryTest {
         InstanceVO proxy = result.stream()
                 .filter(i -> "instance-proxy-1".equals(i.getId())).findFirst().orElseThrow();
         assertThat(direct.getTopicCount()).isZero();
-        assertThat(direct.getConsumerGroupCount()).isEqualTo(1);
-        assertThat(proxy.getTopicCount()).isEqualTo(3);
-        assertThat(proxy.getConsumerGroupCount()).isEqualTo(2);
+        assertThat(direct.getConsumerGroupCount()).isZero();
+        assertThat(proxy.getTopicCount()).isZero();
+        assertThat(proxy.getConsumerGroupCount()).isZero();
+        verifyNoInteractions(topicMapper, groupMapper);
     }
 
     @Test
@@ -92,8 +88,7 @@ class MybatisPlusInstanceRepositoryTest {
         when(instanceMapper.selectList(any(QueryWrapper.class))).thenReturn(List.of());
 
         assertThat(repository.findAll()).isEmpty();
-        verify(topicMapper, never()).selectMaps(any(QueryWrapper.class));
-        verify(groupMapper, never()).selectMaps(any(QueryWrapper.class));
+        verifyNoInteractions(topicMapper, groupMapper);
     }
 
     @Test
@@ -104,17 +99,31 @@ class MybatisPlusInstanceRepositoryTest {
     }
 
     @Test
-    void findByIdShouldPopulateCountsForSingleInstance() {
+    void findByIdShouldNotComputeCountsTest() {
         when(instanceMapper.selectById("instance-proxy-1")).thenReturn(entity("instance-proxy-1", InstanceType.PROXY));
-        when(topicMapper.selectMaps(any(QueryWrapper.class)))
-                .thenReturn(List.of(Map.of("instance_id", "instance-proxy-1", "total", 5L)));
-        when(groupMapper.selectMaps(any(QueryWrapper.class))).thenReturn(List.of());
 
         Optional<InstanceVO> result = repository.findById("instance-proxy-1");
 
         assertThat(result).isPresent();
-        assertThat(result.get().getTopicCount()).isEqualTo(5);
+        assertThat(result.get().getTopicCount()).isZero();
         assertThat(result.get().getConsumerGroupCount()).isZero();
+        verifyNoInteractions(topicMapper, groupMapper);
+    }
+
+    @Test
+    void countTopicsByInstanceShouldDelegateToTopicMapperTest() {
+        when(topicMapper.selectCount(any(QueryWrapper.class))).thenReturn(5L);
+
+        assertThat(repository.countTopicsByInstance("instance-proxy-1")).isEqualTo(5L);
+        verify(topicMapper).selectCount(any(QueryWrapper.class));
+    }
+
+    @Test
+    void countGroupsByInstanceShouldDelegateToGroupMapperTest() {
+        when(groupMapper.selectCount(any(QueryWrapper.class))).thenReturn(2L);
+
+        assertThat(repository.countGroupsByInstance("instance-proxy-1")).isEqualTo(2L);
+        verify(groupMapper).selectCount(any(QueryWrapper.class));
     }
 
     @Test
diff --git a/server/src/test/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryServiceTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/message/QueryHistoryServiceTest.java
similarity index 98%
rename from server/src/test/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryServiceTest.java
rename to server/src/test/java/org/apache/rocketmq/studio/instance/message/QueryHistoryServiceTest.java
index 609d4aef..b9108bc4 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/queryhistory/QueryHistoryServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/instance/message/QueryHistoryServiceTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.studio.queryhistory;
+package org.apache.rocketmq.studio.instance.message;
 
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import org.apache.rocketmq.studio.auth.AuthenticatedUserContext;
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProviderTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProviderTest.java
deleted file mode 100644
index 81b82889..00000000
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/CloudMetadataProviderTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.instance.topic;
-
-import org.apache.rocketmq.studio.common.exception.BusinessException;
-import org.assertj.core.api.ThrowableAssert;
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-class CloudMetadataProviderTest {
-
-    private final CloudMetadataProvider provider = new CloudMetadataProvider();
-
-    @Test
-    void consumerGroupQueriesShouldReturnProviderUnavailable() {
-        assertUnavailable(() -> provider.listConsumerGroups("cluster-1", "order"));
-        assertUnavailable(() -> provider.getGroupProgress("cg-order"));
-        assertUnavailable(() -> provider.getGroupSubscriptions("cg-order"));
-    }
-
-    private void assertUnavailable(ThrowableAssert.ThrowingCallable callable) {
-        assertThatThrownBy(callable)
-                .isInstanceOf(BusinessException.class)
-                .hasMessage("Consumer group metadata provider is not configured")
-                .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(501));
-    }
-}
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
index 7ed2013b..caa06436 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/MetadataServiceTest.java
@@ -17,9 +17,14 @@
 
 package org.apache.rocketmq.studio.instance.topic;
 
+import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
+import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.apache.rocketmq.studio.provider.InstanceProviderRegistry;
+import org.apache.rocketmq.studio.provider.apache.AdminClient;
+import org.apache.rocketmq.studio.provider.apache.MetadataProvider;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
@@ -31,6 +36,7 @@ import java.util.List;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
@@ -47,9 +53,18 @@ class MetadataServiceTest {
     @Mock
     private InstanceProviderRegistry providerRegistry;
 
+    @Mock
+    private InstanceProvider apacheProvider;
+
     @InjectMocks
     private MetadataService metadataService;
 
+    @BeforeEach
+    void routeBlankInstanceIdsToApacheProvider() {
+        lenient().when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(apacheProvider);
+        lenient().when(apacheProvider.vendor()).thenReturn(InstanceVendor.APACHE);
+    }
+
     @Test
     void listTopicsShouldReturnTopicsFromProvider() {
         TopicVO topic = new TopicVO();
@@ -68,7 +83,7 @@ class MetadataServiceTest {
 
     @Test
     void listTopicsShouldReturnEmptyWhenNone() {
-        when(metadataProvider.listTopics(null, null, "nonexistent")).thenReturn(List.of());
+        when(apacheProvider.listTopics(null, null, "nonexistent")).thenReturn(List.of());
 
         List<TopicVO> result = metadataService.listTopics(null, null, "nonexistent");
 
@@ -89,12 +104,12 @@ class MetadataServiceTest {
 
     @Test
     void listTopicsShouldTreatBlankFiltersAsUnspecified() {
-        when(metadataProvider.listTopics(null, null, null)).thenReturn(List.of());
+        when(apacheProvider.listTopics(null, null, null)).thenReturn(List.of());
 
         List<TopicVO> result = metadataService.listTopics(" ", "\t", "");
 
         assertThat(result).isEmpty();
-        verify(metadataProvider).listTopics(null, null, null);
+        verify(apacheProvider).listTopics(null, null, null);
     }
 
     @Test
@@ -116,7 +131,7 @@ class MetadataServiceTest {
     }
 
     @Test
-    void createTopicShouldDelegateToAdminClient() {
+    void createTopicShouldDelegateToApacheProvider() {
         TopicVO input = new TopicVO();
         input.setName("new-topic");
         input.setWriteQueues(8);
@@ -127,19 +142,19 @@ class MetadataServiceTest {
         created.setWriteQueues(8);
         created.setReadQueues(8);
 
-        when(adminClient.createTopic(any(TopicVO.class))).thenReturn(created);
+        when(apacheProvider.createTopic(any(), any(TopicVO.class))).thenReturn(created);
 
         TopicVO result = metadataService.createTopic(input);
 
         assertThat(result.getName()).isEqualTo("new-topic");
-        verify(adminClient).createTopic(input);
+        verify(apacheProvider).createTopic(null, input);
     }
 
     @Test
-    void deleteTopicShouldDelegateToAdminClient() {
+    void deleteTopicShouldDelegateToApacheProvider() {
         metadataService.deleteTopic("topic-to-delete");
 
-        verify(adminClient).deleteTopic("topic-to-delete");
+        verify(apacheProvider).deleteTopic(null, "topic-to-delete");
     }
 
     @Test
@@ -181,12 +196,12 @@ class MetadataServiceTest {
 
     @Test
     void listConsumerGroupsShouldPassSearchFilter() {
-        when(metadataProvider.listConsumerGroups(null, "order")).thenReturn(List.of());
+        when(apacheProvider.listConsumerGroups(null, "order")).thenReturn(List.of());
 
         List<ConsumerGroupVO> result = metadataService.listConsumerGroups(null, "order");
 
         assertThat(result).isEmpty();
-        verify(metadataProvider).listConsumerGroups(null, "order");
+        verify(apacheProvider).listConsumerGroups(null, "order");
     }
 
     @Test
@@ -201,11 +216,4 @@ class MetadataServiceTest {
         verify(metadataProvider).listConsumerGroups("cluster-1", "order");
     }
 
-    @Test
-    void listNamespacesShouldReportUnsupportedProviderCapability() {
-        assertThatThrownBy(() -> metadataService.listNamespaces())
-                .isInstanceOf(BusinessException.class)
-                .hasMessage("Namespace discovery is not implemented by the current metadata provider")
-                .satisfies(exception -> assertThat(((BusinessException) exception).getCode()).isEqualTo(501));
-    }
 }
diff --git a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NamespaceControllerTest.java b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NamespaceControllerTest.java
deleted file mode 100644
index c052adca..00000000
--- a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NamespaceControllerTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.instance.topic;
-
-import org.apache.rocketmq.studio.common.exception.BusinessException;
-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.test.web.servlet.MockMvc;
-
-import static org.mockito.Mockito.when;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@WebMvcTest(NamespaceController.class)
-@AutoConfigureMockMvc(addFilters = false)
-class NamespaceControllerTest {
-
-    @Autowired
-    private MockMvc mockMvc;
-
-    @MockBean
-    private MetadataService metadataService;
-
-    @Test
-    void listNamespacesShouldReturnStructuredUnsupportedCapabilityError() throws Exception {
-        when(metadataService.listNamespaces()).thenThrow(
-                new BusinessException(501, "Namespace discovery is not implemented by the current metadata provider"));
-
-        mockMvc.perform(get("/api/namespaces"))
-                .andExpect(status().isNotImplemented())
-                .andExpect(jsonPath("$.code").value(501))
-                .andExpect(jsonPath("$.message")
-                        .value("Namespace discovery is not implemented by the current metadata provider"));
-    }
-}
diff --git a/server/src/test/java/org/apache/rocketmq/studio/model/UserTest.java b/server/src/test/java/org/apache/rocketmq/studio/model/UserTest.java
index 37a0d522..b992d10f 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/model/UserTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/model/UserTest.java
@@ -28,8 +28,8 @@ class UserTest {
 
         String value = user.toString();
 
-        assertThat(value).contains("name='admin'");
-        assertThat(value).contains("password='******'");
+        assertThat(value).contains("name=admin");
+        assertThat(value).doesNotContain("password");
         assertThat(value).doesNotContain("plain-secret");
     }
 }
diff --git a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
index 0b6a0a3e..d84ccd44 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmControllerTest.java
@@ -63,12 +63,13 @@ class LlmControllerTest {
 
         mockMvc.perform(get("/api/llm/config"))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.provider").value("openai"))
-                .andExpect(jsonPath("$.apiKey").doesNotExist())
-                .andExpect(jsonPath("$.apiKeyConfigured").value(true))
-                .andExpect(jsonPath("$.apiBase").value("https://api.openai.com/v1"))
-                .andExpect(jsonPath("$.model").value("gpt-4o"))
-                .andExpect(jsonPath("$.enabled").value(true));
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.provider").value("openai"))
+                .andExpect(jsonPath("$.data.apiKey").doesNotExist())
+                .andExpect(jsonPath("$.data.apiKeyConfigured").value(true))
+                .andExpect(jsonPath("$.data.apiBase").value("https://api.openai.com/v1"))
+                .andExpect(jsonPath("$.data.model").value("gpt-4o"))
+                .andExpect(jsonPath("$.data.enabled").value(true));
     }
 
     @Test
@@ -84,8 +85,8 @@ class LlmControllerTest {
                         .contentType(MediaType.APPLICATION_JSON)
                         .content(objectMapper.writeValueAsString(config)))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.status").value(0))
-                .andExpect(jsonPath("$.msg").value("saved"));
+                .andExpect(jsonPath("$.data.status").value(0))
+                .andExpect(jsonPath("$.data.msg").value("saved"));
 
         verify(llmConfigService).saveConfig(any(LlmConfigVO.class));
     }
@@ -102,8 +103,8 @@ class LlmControllerTest {
                                 .model("llama3")
                                 .build())))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.status").value(0))
-                .andExpect(jsonPath("$.msg").value("Configuration accepted"));
+                .andExpect(jsonPath("$.data.status").value(0))
+                .andExpect(jsonPath("$.data.msg").value("Configuration accepted"));
     }
 
     @Test
@@ -114,8 +115,8 @@ class LlmControllerTest {
 
         mockMvc.perform(get("/api/llm/models"))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.status").value(0))
-                .andExpect(jsonPath("$.data[0].id").value("gpt-4o"))
-                .andExpect(jsonPath("$.data[1].name").value("GPT-4"));
+                .andExpect(jsonPath("$.data.status").value(0))
+                .andExpect(jsonPath("$.data.data[0].id").value("gpt-4o"))
+                .andExpect(jsonPath("$.data.data[1].name").value("GPT-4"));
     }
 }
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 766072a4..05aa00a3 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
@@ -21,8 +21,8 @@ import com.aliyun.sdk.service.rocketmq20220801.AsyncClient;
 import com.aliyun.sdk.service.rocketmq20220801.models.ListRegionsRequest;
 import com.aliyun.sdk.service.rocketmq20220801.models.ListRegionsResponse;
 import com.aliyun.sdk.service.rocketmq20220801.models.ListRegionsResponseBody;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialRepository;
-import org.apache.rocketmq.studio.cloud.credential.CloudCredentialVO;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialRepository;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialVO;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.BeforeEach;
diff --git a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
index 330f1bcd..645d9537 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
@@ -426,6 +426,45 @@ class AliyunInstanceProviderTest {
                 .build();
     }
 
+    @Test
+    void countTopicsShouldReuseListTopicsSizeTest() {
+        stubInstance();
+        stubCallThrough();
+        when(asyncClient.listTopics(any(ListTopicsRequest.class)))
+                .thenReturn(CompletableFuture.completedFuture(topicsResponse(
+                        topicRow("topic-a", "NORMAL"),
+                        topicRow("topic-b", "FIFO"))));
+
+        assertThat(provider.countTopics(STUDIO_INSTANCE_ID)).isEqualTo(2);
+    }
+
+    @Test
+    void countGroupsShouldReuseListConsumerGroupsSizeTest() {
+        stubInstance();
+        stubCallThrough();
+        ListConsumerGroupsResponse response = ListConsumerGroupsResponse.create().toBuilder()
+                .statusCode(200)
+                .body(ListConsumerGroupsResponseBody.builder()
+                        .data(ListConsumerGroupsResponseBody.Data.builder()
+                                .list(List.of(
+                                        ListConsumerGroupsResponseBody.List.builder()
+                                                .consumerGroupId("GID_one")
+                                                .build(),
+                                        ListConsumerGroupsResponseBody.List.builder()
+                                                .consumerGroupId("GID_two")
+                                                .build()))
+                                .pageNumber(1L)
+                                .pageSize(100L)
+                                .totalCount(2L)
+                                .build())
+                        .build())
+                .build();
+        when(asyncClient.listConsumerGroups(any()))
+                .thenReturn(CompletableFuture.completedFuture(response));
+
+        assertThat(provider.countGroups(STUDIO_INSTANCE_ID)).isEqualTo(2);
+    }
+
     @Test
     void normalizeDeliveryOrderTypeShouldMapFifoToOrderlyTest() {
         org.junit.jupiter.api.Assertions.assertEquals("Orderly",
diff --git a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
new file mode 100644
index 00000000..62db28a8
--- /dev/null
+++ b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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
... 697 lines suppressed ...