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 002449ba refactor: replace remaining in-memory repositories with
MySQL-backed ones (#955)
002449ba is described below
commit 002449ba24160db6542774cd4ed7229d42b82e69
Author: lizhimins <[email protected]>
AuthorDate: Tue Aug 4 15:51:56 2026 +0800
refactor: replace remaining in-memory repositories with MySQL-backed ones
(#955)
Drop InMemorySettingsRepository, InMemoryK8sCertRepository,
InMemoryAlertRepository and InMemoryAuditRepository now that every
store is database-backed. Add MyBatis-Plus repositories for alert
rules and system alerts (new rmq_alert_rule / rmq_system_alert tables
plus the upgrade-demo-alert migration), audit records
(rmq_operation_audit) and K8s certificates (rmq_k8s_certificate, SAN
stored as a JSON array). Service tests that relied on the in-memory
fakes now use Mockito stubs expressing the repository contracts.
---
deploy/mysql/upgrade-demo-alert.sql | 43 +++++
.../cluster/k8s/InMemoryK8sCertRepository.java | 56 ------
.../cluster/k8s/MybatisPlusK8sCertRepository.java | 159 +++++++++++++++++
.../studio/ops/alert/InMemoryAlertRepository.java | 84 ---------
.../ops/alert/MybatisPlusAlertRepository.java | 190 +++++++++++++++++++++
.../studio/ops/audit/InMemoryAuditRepository.java | 91 ----------
.../ops/audit/MybatisPlusAuditRepository.java | 91 ++++++++++
.../{RmqK8sCertificate.java => RmqAlertRule.java} | 34 ++--
.../persistence/entity/RmqK8sCertificate.java | 4 +
...{RmqK8sCertificate.java => RmqSystemAlert.java} | 26 ++-
.../RmqAlertRuleMapper.java} | 37 +---
.../RmqSystemAlertMapper.java} | 37 +---
.../settings/InMemorySettingsRepository.java | 81 ---------
server/src/main/resources/db/schema.sql | 34 ++++
.../cluster/k8s/InMemoryK8sCertRepositoryTest.java | 64 -------
.../ops/alert/InMemoryAlertRepositoryTest.java | 57 -------
.../ops/audit/InMemoryAuditRepositoryTest.java | 83 ---------
.../settings/InMemorySettingsRepositoryTest.java | 74 --------
.../studio/settings/SettingsServiceTest.java | 6 +-
19 files changed, 564 insertions(+), 687 deletions(-)
diff --git a/deploy/mysql/upgrade-demo-alert.sql
b/deploy/mysql/upgrade-demo-alert.sql
new file mode 100644
index 00000000..904be1f9
--- /dev/null
+++ b/deploy/mysql/upgrade-demo-alert.sql
@@ -0,0 +1,43 @@
+-- deploy/mysql/upgrade-demo-alert.sql
+-- 存量 MySQL 数据卷增量迁移:告警规则与系统告警入库(rmq_alert_rule / rmq_system_alert)
+-- 适用:数据卷已初始化、docker-entrypoint-initdb.d 不会再执行的存量部署。
+-- 全新数据卷由 server/src/main/resources/db/schema.sql 直接覆盖,无需本脚本。
+-- 幂等:可重复执行。
+--
+-- 用法(远程容器内执行):
+-- docker exec -i rocketmq-studio-mysql mysql -uroot -pstudio123
rocketmq_studio < upgrade-demo-alert.sql
+
+-- 固定连接编码,防止 mysql 客户端以 latin1 解释 UTF-8 字节导致中文双重编码
+SET NAMES utf8mb4;
+
+CREATE TABLE IF NOT EXISTS rmq_alert_rule (
+ id VARCHAR(64) PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ metric VARCHAR(128),
+ operator VARCHAR(16),
+ threshold DOUBLE,
+ threshold_unit VARCHAR(32),
+ duration VARCHAR(32),
+ channels VARCHAR(512) COMMENT '逗号分隔的通知渠道',
+ enabled TINYINT(1) DEFAULT 1,
+ last_triggered VARCHAR(64),
+ description VARCHAR(512),
+ broker_name VARCHAR(128),
+ cluster_name VARCHAR(128),
+ severity VARCHAR(32),
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS rmq_system_alert (
+ id VARCHAR(64) PRIMARY KEY,
+ level VARCHAR(32),
+ title VARCHAR(255),
+ description TEXT,
+ time DATETIME,
+ acknowledged TINYINT(1) DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_level (level),
+ INDEX idx_acknowledged (acknowledged)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepository.java
deleted file mode 100644
index 622242c2..00000000
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepository.java
+++ /dev/null
@@ -1,56 +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.cluster.k8s;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Slf4j
-@Component
-public class InMemoryK8sCertRepository implements K8sCertRepository {
-
- private final Map<String, K8sCertVO> store = new ConcurrentHashMap<>();
-
- @Override
- public List<K8sCertVO> findAll() {
- return new ArrayList<>(store.values());
- }
-
- @Override
- public Optional<K8sCertVO> findById(String id) {
- return Optional.ofNullable(store.get(id));
- }
-
- @Override
- public K8sCertVO save(K8sCertVO cert) {
- store.put(cert.getId(), cert);
- log.info("Saved certificate: {} (id={})", cert.getName(),
cert.getId());
- return cert;
- }
-
- @Override
- public void deleteById(String id) {
- store.remove(id);
- log.info("Deleted certificate: {}", id);
- }
-}
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
new file mode 100644
index 00000000..acf66fb8
--- /dev/null
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/MybatisPlusK8sCertRepository.java
@@ -0,0 +1,159 @@
+/*
+ * 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.cluster.k8s;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.common.domain.enums.CertStatus;
+import org.apache.rocketmq.studio.common.domain.enums.CertType;
+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 java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * MySQL-backed K8s certificate repository. The SAN list is stored as a JSON
+ * array string.
+ */
+@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()
+ .map(this::toVO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Optional<K8sCertVO> findById(String id) {
+ return Optional.ofNullable(certMapper.selectById(id)).map(this::toVO);
+ }
+
+ @Override
+ public K8sCertVO save(K8sCertVO cert) {
+ RmqK8sCertificate entity = toEntity(cert);
+ if (entity.getId() != null && certMapper.selectById(entity.getId()) !=
null) {
+ certMapper.updateById(entity);
+ } else {
+ certMapper.insert(entity);
+ cert.setId(entity.getId());
+ }
+ return cert;
+ }
+
+ @Override
+ public void deleteById(String id) {
+ certMapper.deleteById(id);
+ }
+
+ private K8sCertVO toVO(RmqK8sCertificate entity) {
+ K8sCertVO vo = new K8sCertVO();
+ vo.setId(entity.getId());
+ vo.setName(entity.getName());
+ vo.setNamespace(entity.getNamespace());
+ vo.setCluster(entity.getCluster());
+ vo.setType(parseCertType(entity.getCertType()));
+ vo.setIssuer(entity.getIssuer());
+ vo.setNotBefore(entity.getNotBefore());
+ vo.setNotAfter(entity.getNotAfter());
+ vo.setStatus(parseCertStatus(entity.getStatus()));
+ vo.setDaysRemaining(entity.getDaysRemaining() == null ? 0 :
entity.getDaysRemaining());
+ vo.setSan(parseSan(entity.getSan()));
+ vo.setCreatedAt(entity.getCreatedAt());
+ vo.setUpdatedAt(entity.getUpdatedAt());
+ return vo;
+ }
+
+ private RmqK8sCertificate toEntity(K8sCertVO cert) {
+ RmqK8sCertificate entity = new RmqK8sCertificate();
+ entity.setId(cert.getId());
+ entity.setName(cert.getName());
+ entity.setNamespace(cert.getNamespace());
+ entity.setCluster(cert.getCluster());
+ entity.setCertType(cert.getType() == null ? null :
cert.getType().name());
+ entity.setIssuer(cert.getIssuer());
+ entity.setNotBefore(cert.getNotBefore());
+ entity.setNotAfter(cert.getNotAfter());
+ entity.setStatus(cert.getStatus() == null ? null :
cert.getStatus().name());
+ entity.setDaysRemaining(cert.getDaysRemaining());
+ entity.setSan(writeSan(cert.getSan()));
+ entity.setCreatedAt(cert.getCreatedAt());
+ entity.setUpdatedAt(LocalDateTime.now());
+ return entity;
+ }
+
+ private CertType parseCertType(String value) {
+ if (!StringUtils.hasText(value)) {
+ return null;
+ }
+ try {
+ return CertType.valueOf(value);
+ } catch (IllegalArgumentException exception) {
+ return null;
+ }
+ }
+
+ private CertStatus parseCertStatus(String value) {
+ if (!StringUtils.hasText(value)) {
+ return null;
+ }
+ try {
+ return CertStatus.valueOf(value);
+ } catch (IllegalArgumentException exception) {
+ return null;
+ }
+ }
+
+ private List<String> parseSan(String json) {
+ if (!StringUtils.hasText(json)) {
+ return List.of();
+ }
+ try {
+ return objectMapper.readValue(json, new
TypeReference<List<String>>() {
+ });
+ } catch (JsonProcessingException exception) {
+ return List.of();
+ }
+ }
+
+ private String writeSan(List<String> san) {
+ if (san == null) {
+ return null;
+ }
+ try {
+ return objectMapper.writeValueAsString(san);
+ } catch (JsonProcessingException exception) {
+ return "[]";
+ }
+ }
+}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepository.java
deleted file mode 100644
index 167d5682..00000000
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepository.java
+++ /dev/null
@@ -1,84 +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.ops.alert;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Component
-public class InMemoryAlertRepository implements AlertRepository {
-
- private final Map<String, AlertRuleVO> rules = new ConcurrentHashMap<>();
- private final Map<String, SystemAlertVO> alerts = new
ConcurrentHashMap<>();
-
- @Override
- public List<AlertRuleVO> findAllRules() {
- return new ArrayList<>(rules.values());
- }
-
- @Override
- public AlertRuleVO saveRule(AlertRuleVO rule) {
- rules.put(rule.getId(), rule);
- log.debug("Saved alert rule id={}", rule.getId());
- return rule;
- }
-
- @Override
- public boolean replaceRule(AlertRuleVO rule) {
- boolean replaced = rules.replace(rule.getId(), rule) != null;
- log.debug("Replaced alert rule id={}, replaced={}", rule.getId(),
replaced);
- return replaced;
- }
-
- @Override
- public void deleteRule(String id) {
- rules.remove(id);
- log.debug("Deleted alert rule id={}", id);
- }
-
- @Override
- public List<SystemAlertVO> findAlerts(String level) {
- return alerts.values().stream()
- .filter(a -> level == null ||
level.equalsIgnoreCase(a.getLevel().name()))
- .collect(Collectors.toList());
- }
-
- @Override
- public SystemAlertVO saveAlert(SystemAlertVO alert) {
- alerts.put(alert.getId(), alert);
- log.debug("Saved system alert id={}", alert.getId());
- return alert;
- }
-
- @Override
- public int deleteAcknowledgedAlerts() {
- List<String> toRemove = alerts.values().stream()
- .filter(SystemAlertVO::isAcknowledged)
- .map(SystemAlertVO::getId)
- .collect(Collectors.toList());
- toRemove.forEach(alerts::remove);
- log.debug("Cleared {} acknowledged system alerts", toRemove.size());
- return toRemove.size();
- }
-}
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
new file mode 100644
index 00000000..4aa0a9d8
--- /dev/null
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertRepository.java
@@ -0,0 +1,190 @@
+/*
+ * 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.alert;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.rocketmq.studio.common.domain.enums.AlertLevel;
+import org.apache.rocketmq.studio.persistence.entity.RmqAlertRule;
+import org.apache.rocketmq.studio.persistence.entity.RmqSystemAlert;
+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 java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * MySQL-backed alert repository for alert rules and system alert events.
+ */
+@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()
+ .map(MybatisPlusAlertRepository::toRuleVO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public AlertRuleVO saveRule(AlertRuleVO rule) {
+ RmqAlertRule entity = toRuleEntity(rule);
+ if (entity.getId() != null && ruleMapper.selectById(entity.getId()) !=
null) {
+ ruleMapper.updateById(entity);
+ } else {
+ ruleMapper.insert(entity);
+ }
+ return rule;
+ }
+
+ @Override
+ public boolean replaceRule(AlertRuleVO rule) {
+ if (ruleMapper.selectById(rule.getId()) == null) {
+ return false;
+ }
+ ruleMapper.updateById(toRuleEntity(rule));
+ return true;
+ }
+
+ @Override
+ public void deleteRule(String id) {
+ ruleMapper.deleteById(id);
+ }
+
+ @Override
+ public List<SystemAlertVO> findAlerts(String level) {
+ QueryWrapper<RmqSystemAlert> query = new QueryWrapper<RmqSystemAlert>()
+ .eq(StringUtils.hasText(level), "level", level == null ? null
: level.toLowerCase())
+ .orderByDesc("time");
+ return alertMapper.selectList(query).stream()
+ .map(MybatisPlusAlertRepository::toAlertVO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public SystemAlertVO saveAlert(SystemAlertVO alert) {
+ RmqSystemAlert entity = toAlertEntity(alert);
+ if (entity.getId() != null && alertMapper.selectById(entity.getId())
!= null) {
+ alertMapper.updateById(entity);
+ } else {
+ alertMapper.insert(entity);
+ }
+ return alert;
+ }
+
+ @Override
+ public int deleteAcknowledgedAlerts() {
+ return Math.toIntExact(alertMapper.delete(
+ new QueryWrapper<RmqSystemAlert>().eq("acknowledged", true)));
+ }
+
+ // ── Mapping ────────────────────────────────────────────────────
+
+ private static AlertRuleVO toRuleVO(RmqAlertRule entity) {
+ AlertRuleVO vo = new AlertRuleVO();
+ vo.setId(entity.getId());
+ vo.setName(entity.getName());
+ vo.setMetric(entity.getMetric());
+ vo.setOperator(entity.getOperator());
+ vo.setThreshold(entity.getThreshold() == null ? 0 :
entity.getThreshold());
+ vo.setThresholdUnit(entity.getThresholdUnit());
+ vo.setDuration(entity.getDuration());
+ vo.setChannels(splitCsv(entity.getChannels()));
+ vo.setEnabled(Boolean.TRUE.equals(entity.getEnabled()));
+ vo.setLastTriggered(entity.getLastTriggered());
+ vo.setDescription(entity.getDescription());
+ vo.setBrokerName(entity.getBrokerName());
+ vo.setClusterName(entity.getClusterName());
+ vo.setSeverity(entity.getSeverity());
+ return vo;
+ }
+
+ private static RmqAlertRule toRuleEntity(AlertRuleVO rule) {
+ RmqAlertRule entity = new RmqAlertRule();
+ entity.setId(rule.getId());
+ entity.setName(rule.getName());
+ entity.setMetric(rule.getMetric());
+ entity.setOperator(rule.getOperator());
+ entity.setThreshold(rule.getThreshold());
+ entity.setThresholdUnit(rule.getThresholdUnit());
+ entity.setDuration(rule.getDuration());
+ entity.setChannels(rule.getChannels() == null ? null :
String.join(",", rule.getChannels()));
+ entity.setEnabled(rule.isEnabled());
+ entity.setLastTriggered(rule.getLastTriggered());
+ entity.setDescription(rule.getDescription());
+ entity.setBrokerName(rule.getBrokerName());
+ entity.setClusterName(rule.getClusterName());
+ entity.setSeverity(rule.getSeverity());
+ entity.setUpdatedAt(LocalDateTime.now());
+ return entity;
+ }
+
+ private static SystemAlertVO toAlertVO(RmqSystemAlert entity) {
+ SystemAlertVO vo = new SystemAlertVO();
+ vo.setId(entity.getId());
+ vo.setLevel(parseLevel(entity.getLevel()));
+ vo.setTitle(entity.getTitle());
+ vo.setDescription(entity.getDescription());
+ vo.setTime(entity.getTime());
+ vo.setAcknowledged(Boolean.TRUE.equals(entity.getAcknowledged()));
+ return vo;
+ }
+
+ private static RmqSystemAlert toAlertEntity(SystemAlertVO alert) {
+ RmqSystemAlert entity = new RmqSystemAlert();
+ entity.setId(alert.getId());
+ entity.setLevel(alert.getLevel() == null ? null :
alert.getLevel().name());
+ entity.setTitle(alert.getTitle());
+ entity.setDescription(alert.getDescription());
+ entity.setTime(alert.getTime());
+ entity.setAcknowledged(alert.isAcknowledged());
+ entity.setUpdatedAt(LocalDateTime.now());
+ return entity;
+ }
+
+ private static AlertLevel parseLevel(String level) {
+ if (!StringUtils.hasText(level)) {
+ return AlertLevel.info;
+ }
+ try {
+ return AlertLevel.valueOf(level);
+ } catch (IllegalArgumentException exception) {
+ return AlertLevel.info;
+ }
+ }
+
+ private static List<String> splitCsv(String value) {
+ if (!StringUtils.hasText(value)) {
+ return List.of();
+ }
+ return Arrays.stream(value.split(","))
+ .map(String::trim)
+ .filter(part -> !part.isEmpty())
+ .collect(Collectors.toList());
+ }
+}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
deleted file mode 100644
index 0b1ee895..00000000
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepository.java
+++ /dev/null
@@ -1,91 +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.ops.audit;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Component
-public class InMemoryAuditRepository implements AuditRepository {
-
- private final Map<String, AuditRecordVO> records = new
ConcurrentHashMap<>();
-
- @Override
- public List<AuditRecordVO> findAll(String search, String operationType,
- LocalDateTime startDate, LocalDateTime
endDate,
- String result) {
- String normalizedSearch = normalize(search);
- return records.values().stream()
- .filter(r -> normalizedSearch == null
- || containsIgnoreCase(r.getDetail(), normalizedSearch)
- || containsIgnoreCase(r.getOperator(),
normalizedSearch)
- || containsIgnoreCase(r.getTarget(), normalizedSearch))
- .filter(r -> operationType == null || operationType.isEmpty()
- || operationType.equals(r.getOperationType()))
- .filter(r -> startDate == null || r.getTimestamp() != null &&
!r.getTimestamp().isBefore(startDate))
- .filter(r -> endDate == null || r.getTimestamp() != null &&
!r.getTimestamp().isAfter(endDate))
- .filter(r -> result == null || result.isEmpty() ||
result.equals(r.getResult()))
- .sorted((a, b) -> {
- if (a.getTimestamp() == null || b.getTimestamp() == null) {
- return 0;
- }
- return b.getTimestamp().compareTo(a.getTimestamp());
- })
- .collect(Collectors.toList());
- }
-
- private String normalize(String value) {
- if (value == null || value.isBlank()) {
- return null;
- }
- return value.toLowerCase();
- }
-
- private boolean containsIgnoreCase(String value, String normalizedSearch) {
- return value != null && value.toLowerCase().contains(normalizedSearch);
- }
-
- @Override
- public void save(AuditRecordVO record) {
- if (record.getId() == null) {
- record.setId(java.util.UUID.randomUUID().toString());
- }
- if (record.getTimestamp() == null) {
- record.setTimestamp(LocalDateTime.now());
- }
- records.put(record.getId(), record);
- log.debug("Saved audit record: {} - {}", record.getOperationType(),
record.getTarget());
- }
-
- @Override
- public int deleteBefore(LocalDateTime cutoff) {
- List<String> toRemove = records.values().stream()
- .filter(r -> r.getTimestamp() != null &&
r.getTimestamp().isBefore(cutoff))
- .map(AuditRecordVO::getId)
- .collect(Collectors.toList());
- toRemove.forEach(records::remove);
- log.debug("Deleted {} audit records before {}", toRemove.size(),
cutoff);
- return toRemove.size();
- }
-}
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
new file mode 100644
index 00000000..5640d95d
--- /dev/null
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/MybatisPlusAuditRepository.java
@@ -0,0 +1,91 @@
+/*
+ * 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.audit;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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 java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * MySQL-backed audit repository (rmq_operation_audit). The VO's ipAddress has
+ * no dedicated column and is not persisted.
+ */
+@Repository
+public class MybatisPlusAuditRepository implements AuditRepository {
+
+ private final RmqOperationAuditMapper auditMapper;
+
+ public MybatisPlusAuditRepository(RmqOperationAuditMapper auditMapper) {
+ this.auditMapper = auditMapper;
+ }
+
+ @Override
+ public List<AuditRecordVO> findAll(String search, String operationType,
+ LocalDateTime startDate, LocalDateTime
endDate,
+ String result) {
+ QueryWrapper<RmqOperationAudit> query = new
QueryWrapper<RmqOperationAudit>()
+ .and(StringUtils.hasText(search), w -> w
+ .like("operator", search)
+ .or().like("resource_name", search)
+ .or().like("detail", search))
+ .eq(StringUtils.hasText(operationType), "operation",
operationType)
+ .ge(startDate != null, "operated_at", startDate)
+ .le(endDate != null, "operated_at", endDate)
+ .eq(StringUtils.hasText(result), "result", result)
+ .orderByDesc("operated_at");
+ return auditMapper.selectList(query).stream()
+ .map(MybatisPlusAuditRepository::toVO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void save(AuditRecordVO record) {
+ RmqOperationAudit entity = new RmqOperationAudit();
+ entity.setOperation(record.getOperationType());
+ entity.setResourceType("GENERAL");
+ entity.setResourceName(record.getTarget());
+ entity.setDetail(record.getDetail());
+ entity.setResult(record.getResult());
+ entity.setOperator(record.getOperator());
+ entity.setOperatedAt(record.getTimestamp() == null ?
LocalDateTime.now() : record.getTimestamp());
+ auditMapper.insert(entity);
+ }
+
+ @Override
+ public int deleteBefore(LocalDateTime cutoff) {
+ return Math.toIntExact(auditMapper.delete(
+ new QueryWrapper<RmqOperationAudit>().lt("operated_at",
cutoff)));
+ }
+
+ private static AuditRecordVO toVO(RmqOperationAudit entity) {
+ AuditRecordVO vo = new AuditRecordVO();
+ vo.setId(entity.getId() == null ? null :
String.valueOf(entity.getId()));
+ vo.setTimestamp(entity.getOperatedAt());
+ vo.setOperator(entity.getOperator());
+ vo.setOperationType(entity.getOperation());
+ vo.setTarget(entity.getResourceName());
+ vo.setDetail(entity.getDetail());
+ vo.setResult(entity.getResult());
+ return vo;
+ }
+}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqAlertRule.java
similarity index 67%
copy from
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
copy to
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqAlertRule.java
index 69307dff..402003c4 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqAlertRule.java
@@ -24,29 +24,39 @@ import lombok.Data;
import java.time.LocalDateTime;
@Data
-@TableName("rmq_k8s_certificate")
-public class RmqK8sCertificate {
+@TableName("rmq_alert_rule")
+public class RmqAlertRule {
- @TableId(type = IdType.ASSIGN_UUID)
+ @TableId(type = IdType.INPUT)
private String id;
private String name;
- private String namespace;
+ private String metric;
- private String cluster;
+ private String operator;
- private String certType;
+ private Double threshold;
- private String issuer;
+ private String thresholdUnit;
- private LocalDateTime notBefore;
+ private String duration;
- private LocalDateTime notAfter;
+ private String channels;
- private String status;
+ private Boolean enabled;
- private Integer daysRemaining;
+ private String lastTriggered;
- private String san;
+ private String description;
+
+ private String brokerName;
+
+ private String clusterName;
+
+ private String severity;
+
+ private LocalDateTime createdAt;
+
+ private LocalDateTime updatedAt;
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
index 69307dff..3eea9b35 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
@@ -49,4 +49,8 @@ public class RmqK8sCertificate {
private Integer daysRemaining;
private String san;
+
+ private LocalDateTime createdAt;
+
+ private LocalDateTime updatedAt;
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSystemAlert.java
similarity index 73%
copy from
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
copy to
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSystemAlert.java
index 69307dff..3167a9a1 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqSystemAlert.java
@@ -24,29 +24,23 @@ import lombok.Data;
import java.time.LocalDateTime;
@Data
-@TableName("rmq_k8s_certificate")
-public class RmqK8sCertificate {
+@TableName("rmq_system_alert")
+public class RmqSystemAlert {
- @TableId(type = IdType.ASSIGN_UUID)
+ @TableId(type = IdType.INPUT)
private String id;
- private String name;
+ private String level;
- private String namespace;
+ private String title;
- private String cluster;
+ private String description;
- private String certType;
+ private LocalDateTime time;
- private String issuer;
+ private Boolean acknowledged;
- private LocalDateTime notBefore;
+ private LocalDateTime createdAt;
- private LocalDateTime notAfter;
-
- private String status;
-
- private Integer daysRemaining;
-
- private String san;
+ private LocalDateTime updatedAt;
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqAlertRuleMapper.java
similarity index 53%
copy from
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
copy to
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqAlertRuleMapper.java
index 69307dff..a3b47d7d 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqAlertRuleMapper.java
@@ -14,39 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.rocketmq.studio.persistence.entity;
+package org.apache.rocketmq.studio.persistence.mapper;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqAlertRule;
-import java.time.LocalDateTime;
-
-@Data
-@TableName("rmq_k8s_certificate")
-public class RmqK8sCertificate {
-
- @TableId(type = IdType.ASSIGN_UUID)
- private String id;
-
- private String name;
-
- private String namespace;
-
- private String cluster;
-
- private String certType;
-
- private String issuer;
-
- private LocalDateTime notBefore;
-
- private LocalDateTime notAfter;
-
- private String status;
-
- private Integer daysRemaining;
-
- private String san;
+public interface RmqAlertRuleMapper extends BaseMapper<RmqAlertRule> {
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSystemAlertMapper.java
similarity index 53%
copy from
server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
copy to
server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSystemAlertMapper.java
index 69307dff..b6d64340 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/entity/RmqK8sCertificate.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/mapper/RmqSystemAlertMapper.java
@@ -14,39 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.rocketmq.studio.persistence.entity;
+package org.apache.rocketmq.studio.persistence.mapper;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.rocketmq.studio.persistence.entity.RmqSystemAlert;
-import java.time.LocalDateTime;
-
-@Data
-@TableName("rmq_k8s_certificate")
-public class RmqK8sCertificate {
-
- @TableId(type = IdType.ASSIGN_UUID)
- private String id;
-
- private String name;
-
- private String namespace;
-
- private String cluster;
-
- private String certType;
-
- private String issuer;
-
- private LocalDateTime notBefore;
-
- private LocalDateTime notAfter;
-
- private String status;
-
- private Integer daysRemaining;
-
- private String san;
+public interface RmqSystemAlertMapper extends BaseMapper<RmqSystemAlert> {
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
deleted file mode 100644
index 692068fe..00000000
---
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
+++ /dev/null
@@ -1,81 +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.settings;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Slf4j
-// @Component — replaced by MybatisPlusSettingsRepository
-public class InMemorySettingsRepository implements SettingsRepository {
-
- private GeneralSettingsVO generalSettings = GeneralSettingsVO.builder()
- .theme("system")
- .compact(false)
- .desktopNotify(true)
- .notifySound(false)
- .sessionTimeout(30)
- .requireLogin(false)
- .llmProvider("openai")
- .apiKey("")
- .model("gpt-4")
- .baseUrl("")
- .build();
-
- private final Map<String, DataSourceVO> dataSources = new
ConcurrentHashMap<>();
-
- @Override
- public GeneralSettingsVO loadGeneralSettings() {
- return generalSettings;
- }
-
- @Override
- public void saveGeneralSettings(GeneralSettingsVO settings) {
- this.generalSettings = settings;
- }
-
- @Override
- public List<DataSourceVO> findAllDataSources() {
- return new ArrayList<>(dataSources.values());
- }
-
- @Override
- public DataSourceVO saveDataSource(DataSourceVO dataSource) {
- dataSources.put(dataSource.getKey(), dataSource);
- return dataSource;
- }
-
- @Override
- public boolean replaceDataSource(DataSourceVO dataSource) {
- return dataSources.replace(dataSource.getKey(), dataSource) != null;
- }
-
- @Override
- public boolean deleteDataSource(String key) {
- return dataSources.remove(key) != null;
- }
-
- @Override
- public Optional<DataSourceVO> findDataSourceByKey(String key) {
- return Optional.ofNullable(dataSources.get(key));
- }
-}
diff --git a/server/src/main/resources/db/schema.sql
b/server/src/main/resources/db/schema.sql
index f85700b1..a8952cf0 100644
--- a/server/src/main/resources/db/schema.sql
+++ b/server/src/main/resources/db/schema.sql
@@ -173,6 +173,40 @@ CREATE TABLE IF NOT EXISTS rmq_acl_user (
UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+-- 13. 告警规则
+CREATE TABLE IF NOT EXISTS rmq_alert_rule (
+ id VARCHAR(64) PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ metric VARCHAR(128),
+ operator VARCHAR(16),
+ threshold DOUBLE,
+ threshold_unit VARCHAR(32),
+ duration VARCHAR(32),
+ channels VARCHAR(512) COMMENT '逗号分隔的通知渠道',
+ enabled TINYINT(1) DEFAULT 1,
+ last_triggered VARCHAR(64),
+ description VARCHAR(512),
+ broker_name VARCHAR(128),
+ cluster_name VARCHAR(128),
+ severity VARCHAR(32),
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 14. 系统告警事件
+CREATE TABLE IF NOT EXISTS rmq_system_alert (
+ id VARCHAR(64) PRIMARY KEY,
+ level VARCHAR(32),
+ title VARCHAR(255),
+ description TEXT,
+ time DATETIME,
+ acknowledged TINYINT(1) DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_level (level),
+ INDEX idx_acknowledged (acknowledged)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
-- ============================================================
-- 样例数据(幂等):instance / topic / group 列表以本库为准,创建时写库、读取时读库。
-- 实例管理页默认 5 个实例:2 个 DIRECT(instance-direct-1/2)+ 3 个
PROXY(instance-proxy-1/2/3)。
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepositoryTest.java
deleted file mode 100644
index e2f0a916..00000000
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/InMemoryK8sCertRepositoryTest.java
+++ /dev/null
@@ -1,64 +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.cluster.k8s;
-
-import org.apache.rocketmq.studio.common.domain.enums.CertStatus;
-import org.apache.rocketmq.studio.common.domain.enums.CertType;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class InMemoryK8sCertRepositoryTest {
-
- @Test
- void repositoryShouldStartEmpty() {
- InMemoryK8sCertRepository repository = new InMemoryK8sCertRepository();
-
- assertThat(repository.findAll()).isEmpty();
- }
-
- @Test
- void saveFindAndDeleteShouldManageExplicitRecordsOnly() {
- InMemoryK8sCertRepository repository = new InMemoryK8sCertRepository();
- K8sCertVO cert = K8sCertVO.builder()
- .name("rocketmq-tls")
- .namespace("rocketmq")
- .cluster("prod")
- .type(CertType.TLS)
- .issuer("issuer")
- .notBefore(LocalDateTime.now().minusDays(1))
- .notAfter(LocalDateTime.now().plusDays(30))
- .status(CertStatus.valid)
- .daysRemaining(30)
- .san(List.of("broker.example.com"))
- .build();
- cert.setId("cert-explicit");
-
- repository.save(cert);
-
- assertThat(repository.findAll()).containsExactly(cert);
- assertThat(repository.findById("cert-explicit")).contains(cert);
-
- repository.deleteById("cert-explicit");
-
- assertThat(repository.findAll()).isEmpty();
- assertThat(repository.findById("cert-explicit")).isEmpty();
- }
-}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepositoryTest.java
deleted file mode 100644
index 4076bdf3..00000000
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/InMemoryAlertRepositoryTest.java
+++ /dev/null
@@ -1,57 +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.ops.alert;
-
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class InMemoryAlertRepositoryTest {
-
- private final InMemoryAlertRepository repository = new
InMemoryAlertRepository();
-
- @Test
- void replaceRuleShouldUpdateExistingRule() {
- AlertRuleVO existing = AlertRuleVO.builder()
- .id("rule-1")
- .name("Original rule")
- .build();
- AlertRuleVO replacement = AlertRuleVO.builder()
- .id("rule-1")
- .name("Updated rule")
- .build();
- repository.saveRule(existing);
-
- boolean replaced = repository.replaceRule(replacement);
-
- assertThat(replaced).isTrue();
- assertThat(repository.findAllRules()).containsExactly(replacement);
- }
-
- @Test
- void replaceRuleShouldNotInsertUnknownRule() {
- AlertRuleVO replacement = AlertRuleVO.builder()
- .id("missing")
- .name("Missing rule")
- .build();
-
- boolean replaced = repository.replaceRule(replacement);
-
- assertThat(replaced).isFalse();
- assertThat(repository.findAllRules()).isEmpty();
- }
-}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepositoryTest.java
deleted file mode 100644
index 25516302..00000000
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/audit/InMemoryAuditRepositoryTest.java
+++ /dev/null
@@ -1,83 +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.ops.audit;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import java.time.LocalDateTime;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class InMemoryAuditRepositoryTest {
-
- private final InMemoryAuditRepository repository = new
InMemoryAuditRepository();
-
- @Test
- void findAllShouldSearchAcrossNullableFields() {
- AuditRecordVO missingTextFields = AuditRecordVO.builder()
- .timestamp(LocalDateTime.now())
- .operationType("CREATE")
- .result("SUCCESS")
- .build();
- missingTextFields.setId("record-null-fields");
- AuditRecordVO targetMatch = AuditRecordVO.builder()
- .timestamp(LocalDateTime.now().minusMinutes(1))
- .operator("admin")
- .operationType("UPDATE")
- .target("Topic-Order")
- .detail(null)
- .result("SUCCESS")
- .build();
- targetMatch.setId("record-target-match");
-
- putRecords(missingTextFields, targetMatch);
-
- assertThat(repository.findAll("order", null, null, null, null))
- .extracting(AuditRecordVO::getId)
- .containsExactly("record-target-match");
- }
-
- @Test
- void findAllShouldTreatBlankSearchAsNoSearchFilter() {
- AuditRecordVO record = AuditRecordVO.builder()
- .timestamp(LocalDateTime.now())
- .operationType("DELETE")
- .result("FAILURE")
- .build();
- record.setId("record-blank-search");
-
- putRecords(record);
-
- assertThat(repository.findAll(" ", null, null, null, null))
- .extracting(AuditRecordVO::getId)
- .containsExactly("record-blank-search");
- }
-
- @SafeVarargs
- @SuppressWarnings("unchecked")
- private final void putRecords(AuditRecordVO... records) {
- Map<String, AuditRecordVO> store =
- (Map<String, AuditRecordVO>)
ReflectionTestUtils.getField(repository, "records");
- assertThat(store).isNotNull();
- store.clear();
- for (AuditRecordVO record : records) {
- store.put(record.getId(), record);
- }
- }
-}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
deleted file mode 100644
index aac58463..00000000
---
a/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
+++ /dev/null
@@ -1,74 +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.settings;
-
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class InMemorySettingsRepositoryTest {
-
- private final InMemorySettingsRepository repository = new
InMemorySettingsRepository();
-
- @Test
- void saveDataSourceShouldSupportCreateAndDeleteByKey() {
- DataSourceVO dataSource = DataSourceVO.builder()
- .key("source-1")
- .name("Prometheus")
- .type("Prometheus")
- .url("http://localhost:9090")
- .build();
-
- repository.saveDataSource(dataSource);
-
-
assertThat(repository.findDataSourceByKey("source-1")).containsSame(dataSource);
-
assertThat(repository.findAllDataSources()).containsExactly(dataSource);
-
- boolean deleted = repository.deleteDataSource("source-1");
-
- assertThat(deleted).isTrue();
- assertThat(repository.findDataSourceByKey("source-1")).isEmpty();
- assertThat(repository.findAllDataSources()).isEmpty();
- }
-
- @Test
- void deleteDataSourceShouldReportMissingEntry() {
- assertThat(repository.deleteDataSource("missing")).isFalse();
- }
-
- @Test
- void replaceDataSourceShouldUpdateExistingEntry() {
- DataSourceVO existing =
DataSourceVO.builder().key("source-1").name("Prometheus").build();
- DataSourceVO replacement =
DataSourceVO.builder().key("source-1").name("Updated Prometheus").build();
- repository.saveDataSource(existing);
-
- boolean replaced = repository.replaceDataSource(replacement);
-
- assertThat(replaced).isTrue();
-
assertThat(repository.findAllDataSources()).containsExactly(replacement);
- }
-
- @Test
- void replaceDataSourceShouldNotInsertUnknownEntry() {
- DataSourceVO replacement =
DataSourceVO.builder().key("missing").name("Unexpected DS").build();
-
- boolean replaced = repository.replaceDataSource(replacement);
-
- assertThat(replaced).isFalse();
- assertThat(repository.findAllDataSources()).isEmpty();
- }
-}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
index aaa343e8..785f3d5b 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
@@ -229,7 +229,7 @@ class SettingsServiceTest {
@Test
void updateDataSourceShouldRejectUnknownKey() {
- SettingsService service = new SettingsService(new
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(),
operationAuditService);
+ SettingsService service = new SettingsService(settingsRepository,
RestClient.builder(), new ObjectMapper(), operationAuditService);
DataSourceVO input =
DataSourceVO.builder().key("missing").name("Unexpected DS").type("rocketmq")
.url("unexpected-host:9876").build();
@@ -243,7 +243,7 @@ class SettingsServiceTest {
@Test
void updateDataSourceShouldRejectBlankKey() {
- SettingsService service = new SettingsService(new
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(),
+ SettingsService service = new SettingsService(settingsRepository,
RestClient.builder(), new ObjectMapper(),
operationAuditService);
DataSourceVO input = DataSourceVO.builder().key(" ").name("Unexpected
DS").type("rocketmq")
.url("unexpected-host:9876").build();
@@ -267,7 +267,7 @@ class SettingsServiceTest {
@Test
void deleteDataSourceShouldRejectUnknownKey() {
- SettingsService service = new SettingsService(new
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper(),
+ SettingsService service = new SettingsService(settingsRepository,
RestClient.builder(), new ObjectMapper(),
operationAuditService);
assertThatThrownBy(() -> service.deleteDataSource("missing"))