This is an automated email from the ASF dual-hosted git repository.
jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 4435b8fe68 feature: add global and branch session and lock management
MCP tools (#7893)
4435b8fe68 is described below
commit 4435b8fe6873e04ba8fade53a68e32436ea8c5dc
Author: 徐博 洪 <[email protected]>
AuthorDate: Sun Dec 28 13:45:11 2025 +0800
feature: add global and branch session and lock management MCP tools (#7893)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 1 +
.../seata/common/NamingServerLocalMarker.java | 19 ++
.../core/constants/ServerTableColumnsName.java | 0
.../org/apache/seata/core/model/GlobalStatus.java | 0
.../console/entity/param/GlobalLockParam.java | 0
.../console/entity/param/GlobalSessionParam.java | 0
.../server/console/entity/vo/BranchSessionVO.java | 0
.../server/console/entity/vo/GlobalSessionVO.java | 0
.../seata/console/config/WebSecurityConfig.java | 18 +-
.../filter/JwtAuthenticationTokenFilter.java | 6 +
.../apache/seata/console/utils/JwtTokenUtils.java | 2 +-
.../core/config/TimestampToStringDeserializer.java | 39 ++++
.../seata/mcp/core/constant/RPCConstant.java | 30 +++
.../apache/seata/mcp/core/props/MCPProperties.java | 12 -
.../seata/mcp/core/props/NameSpaceDetail.java | 56 +++++
.../mcp/core/props/NamingServerProperties.java | 52 +++++
.../org/apache/seata/mcp/core/utils/DateUtils.java | 80 +++++++
.../org/apache/seata/mcp/core/utils/UrlUtils.java | 88 ++++++++
.../mcp/entity/dto/McpGlobalLockParamDto.java | 97 +++++---
.../mcp/entity/dto/McpGlobalSessionParamDto.java | 142 ++++++++++++
.../param/McpGlobalAbnormalSessionParam.java | 69 ++++++
.../mcp/entity/param/McpGlobalLockDeleteParam.java | 53 +----
.../seata/mcp/entity/param/McpGlobalLockParam.java | 43 ++++
.../mcp/entity/param/McpGlobalSessionParam.java | 48 ++++
.../seata/mcp/entity/vo/McpBranchSessionVO.java | 53 +++++
.../seata/mcp/entity/vo/McpGlobalLockVO.java | 121 +++++-----
.../seata/mcp/entity/vo/McpGlobalSessionVO.java | 76 +++++++
.../seata/mcp/exception/ServiceCallException.java | 54 +++++
.../seata/mcp/service/ConsoleApiService.java | 48 ++++
.../seata/mcp/service/ModifyConfirmService.java | 26 +++
.../mcp/service/impl/ConsoleRemoteServiceImpl.java | 244 +++++++++++++++++++++
.../mcp/service/impl/ModifyConfirmServiceImpl.java | 73 ++++++
.../apache/seata/mcp/tools/BranchSessionTools.java | 105 +++++++++
.../apache/seata/mcp/tools/GlobalLockTools.java | 141 ++++++++++++
.../apache/seata/mcp/tools/GlobalSessionTools.java | 231 +++++++++++++++++++
.../apache/seata/mcp/tools/ModifyConfirmTools.java | 59 +++++
.../org/apache/seata/mcp/tools/NameSpaceTools.java | 58 +++++
.../service/ConsoleLocalServiceImpl.java | 169 ++++++++++++++
.../service/NamingServerLocalMarkerImpl.java | 23 ++
namingserver/src/main/resources/application.yml | 9 +-
41 files changed, 2187 insertions(+), 159 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 99e2dcbe56..f40ffda695 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -36,6 +36,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#7872](https://github.com/apache/incubator-seata/pull/7872)] Automatically
calculate the values for JVM parameters
- [[#7876](https://github.com/apache/incubator-seata/pull/7876)] feature: add
MCP custom configuration and authentication code
- [[#7878](https://github.com/apache/incubator-seata/pull/7878)] console
supports creation and modification of transaction groups for Raft clusters
+- [[#7893](https://github.com/apache/incubator-seata/pull/7893)] add global
and branch session and lock management MCP tools
### bugfix:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 5b97efd42b..d9986ebc21 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -36,6 +36,7 @@
- [[#7872](https://github.com/apache/incubator-seata/pull/7872)] 根据当前内存值自动计算
JVM 参数
- [[#7876](https://github.com/apache/incubator-seata/pull/7876)] feature:
添加MCP服务自定义配置属性和鉴权功能
- [[#7878](https://github.com/apache/incubator-seata/pull/7878)]
控制台支持raft集群模式的事务分组管理
+- [[#7893](https://github.com/apache/incubator-seata/pull/7893)]
添加全局和分支事务及锁管理MCP工具
### bugfix:
diff --git
a/common/src/main/java/org/apache/seata/common/NamingServerLocalMarker.java
b/common/src/main/java/org/apache/seata/common/NamingServerLocalMarker.java
new file mode 100644
index 0000000000..2724008df9
--- /dev/null
+++ b/common/src/main/java/org/apache/seata/common/NamingServerLocalMarker.java
@@ -0,0 +1,19 @@
+/*
+ * 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.seata.common;
+
+public interface NamingServerLocalMarker {}
diff --git
a/core/src/main/java/org/apache/seata/core/constants/ServerTableColumnsName.java
b/common/src/main/java/org/apache/seata/core/constants/ServerTableColumnsName.java
similarity index 100%
rename from
core/src/main/java/org/apache/seata/core/constants/ServerTableColumnsName.java
rename to
common/src/main/java/org/apache/seata/core/constants/ServerTableColumnsName.java
diff --git a/core/src/main/java/org/apache/seata/core/model/GlobalStatus.java
b/common/src/main/java/org/apache/seata/core/model/GlobalStatus.java
similarity index 100%
rename from core/src/main/java/org/apache/seata/core/model/GlobalStatus.java
rename to common/src/main/java/org/apache/seata/core/model/GlobalStatus.java
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
b/common/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
similarity index 100%
copy from
server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
copy to
common/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalSessionParam.java
b/common/src/main/java/org/apache/seata/server/console/entity/param/GlobalSessionParam.java
similarity index 100%
rename from
server/src/main/java/org/apache/seata/server/console/entity/param/GlobalSessionParam.java
rename to
common/src/main/java/org/apache/seata/server/console/entity/param/GlobalSessionParam.java
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/vo/BranchSessionVO.java
b/common/src/main/java/org/apache/seata/server/console/entity/vo/BranchSessionVO.java
similarity index 100%
rename from
server/src/main/java/org/apache/seata/server/console/entity/vo/BranchSessionVO.java
rename to
common/src/main/java/org/apache/seata/server/console/entity/vo/BranchSessionVO.java
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/vo/GlobalSessionVO.java
b/common/src/main/java/org/apache/seata/server/console/entity/vo/GlobalSessionVO.java
similarity index 100%
rename from
server/src/main/java/org/apache/seata/server/console/entity/vo/GlobalSessionVO.java
rename to
common/src/main/java/org/apache/seata/server/console/entity/vo/GlobalSessionVO.java
diff --git
a/console/src/main/java/org/apache/seata/console/config/WebSecurityConfig.java
b/console/src/main/java/org/apache/seata/console/config/WebSecurityConfig.java
index 387a846550..dc773eeabb 100644
---
a/console/src/main/java/org/apache/seata/console/config/WebSecurityConfig.java
+++
b/console/src/main/java/org/apache/seata/console/config/WebSecurityConfig.java
@@ -106,15 +106,7 @@ public class WebSecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
- StringBuilder ignoreURLsBuilder = new StringBuilder(ignoreURLs);
- if (!mcpProperties.isEnableAuth()) {
- List<String> mcpEndpoints = mcpProperties.getEndpoints();
- for (String endpoint : mcpEndpoints) {
- ignoreURLsBuilder.append(",").append(endpoint);
- }
- }
- RequestMatcher[] ignoredMatchers =
- buildAntMatchers(ignoreURLsBuilder.toString().trim());
+ RequestMatcher[] ignoredMatchers = buildAntMatchers(ignoreURLs.trim());
return web -> {
if (ignoredMatchers.length > 0) {
web.ignoring().requestMatchers(ignoredMatchers);
@@ -126,11 +118,9 @@ public class WebSecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager)
throws Exception {
StringBuilder csrfIgnoreUrlsBuilder = new
StringBuilder(csrfIgnoreUrls);
- if (mcpProperties.isEnableAuth()) {
- List<String> mcpEndpoints = mcpProperties.getEndpoints();
- for (String endpoint : mcpEndpoints) {
- csrfIgnoreUrlsBuilder.append(",").append(endpoint);
- }
+ List<String> mcpEndpoints = mcpProperties.getEndpoints();
+ for (String endpoint : mcpEndpoints) {
+ csrfIgnoreUrlsBuilder.append(",").append(endpoint);
}
RequestMatcher[] csrfIgnored =
buildAntMatchers(csrfIgnoreUrlsBuilder.toString().trim());
diff --git
a/console/src/main/java/org/apache/seata/console/filter/JwtAuthenticationTokenFilter.java
b/console/src/main/java/org/apache/seata/console/filter/JwtAuthenticationTokenFilter.java
index 2994c9a473..74c0607f5d 100644
---
a/console/src/main/java/org/apache/seata/console/filter/JwtAuthenticationTokenFilter.java
+++
b/console/src/main/java/org/apache/seata/console/filter/JwtAuthenticationTokenFilter.java
@@ -69,6 +69,12 @@ public class JwtAuthenticationTokenFilter extends
OncePerRequestFilter {
chain.doFilter(request, response);
}
+ @Override
+ protected boolean shouldNotFilterAsyncDispatch() {
+ // allow this filter to run during async dispatch so JWT is applied
for async requests
+ return false;
+ }
+
/**
* Get token from header
*/
diff --git
a/console/src/main/java/org/apache/seata/console/utils/JwtTokenUtils.java
b/console/src/main/java/org/apache/seata/console/utils/JwtTokenUtils.java
index 803da87937..e78ce33c5a 100644
--- a/console/src/main/java/org/apache/seata/console/utils/JwtTokenUtils.java
+++ b/console/src/main/java/org/apache/seata/console/utils/JwtTokenUtils.java
@@ -109,7 +109,7 @@ public class JwtTokenUtils {
AuthorityUtils.commaSeparatedStringToAuthorityList((String)
claims.get(AUTHORITIES_KEY));
User principal = new User(claims.getSubject(), "", authorities);
- return new UsernamePasswordAuthenticationToken(principal, "",
authorities);
+ return new UsernamePasswordAuthenticationToken(principal, token,
authorities);
}
/**
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/config/TimestampToStringDeserializer.java
b/console/src/main/java/org/apache/seata/mcp/core/config/TimestampToStringDeserializer.java
new file mode 100644
index 0000000000..c53baf2af0
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/core/config/TimestampToStringDeserializer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.seata.mcp.core.config;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+public class TimestampToStringDeserializer extends JsonDeserializer<String> {
+ private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext cxt) throws
IOException {
+ long timestamp = p.getLongValue();
+ LocalDateTime dateTime =
+
Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDateTime();
+ return dateTime.format(FORMATTER);
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/constant/RPCConstant.java
b/console/src/main/java/org/apache/seata/mcp/core/constant/RPCConstant.java
new file mode 100644
index 0000000000..650e59ec4e
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/core/constant/RPCConstant.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.mcp.core.constant;
+
+public class RPCConstant {
+
+ public static final String GLOBAL_SESSION_BASE_URL =
"/api/v1/console/globalSession";
+
+ public static final String BRANCH_SESSION_BASE_URL =
"/api/v1/console/branchSession";
+
+ public static final String GLOBAL_LOCK_BASE_URL =
"/api/v1/console/globalLock";
+
+ public static final String SERVER_LOG_BASE_URL =
"/api/v1/console/serverLog";
+
+ public static final String GET_NAMESPACE_PATH = "/api/v1/naming/namespace";
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/props/MCPProperties.java
b/console/src/main/java/org/apache/seata/mcp/core/props/MCPProperties.java
index 25cd1134ca..50c6cbf964 100644
--- a/console/src/main/java/org/apache/seata/mcp/core/props/MCPProperties.java
+++ b/console/src/main/java/org/apache/seata/mcp/core/props/MCPProperties.java
@@ -37,8 +37,6 @@ public class MCPProperties {
private final Environment env;
- private boolean enableAuth = true;
-
private Long queryDuration = TimeUnit.DAYS.toMillis(1);
private final McpServerProperties mcpServerProperties;
@@ -79,12 +77,6 @@ public class MCPProperties {
} catch (NumberFormatException ex) {
queryDuration = TimeUnit.DAYS.toMillis(1);
}
- enableAuth =
Boolean.parseBoolean(env.getProperty("seata.mcp.auth.enabled", "true"));
-
- if (!enableAuth) {
- logger.warn(
- "MCP server authentication is disabled. This creates a
security risk. It is strongly recommended to enable authentication by setting
seata.mcp.auth.enabled=true");
- }
if (mcpServerProperties != null) {
McpServerProperties.ServerProtocol protocol =
mcpServerProperties.getProtocol();
@@ -102,8 +94,4 @@ public class MCPProperties {
logger.warn("MCP server properties not properly configured");
}
}
-
- public boolean isEnableAuth() {
- return enableAuth;
- }
}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/props/NameSpaceDetail.java
b/console/src/main/java/org/apache/seata/mcp/core/props/NameSpaceDetail.java
new file mode 100644
index 0000000000..4b51222ecc
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/core/props/NameSpaceDetail.java
@@ -0,0 +1,56 @@
+/*
+ * 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.seata.mcp.core.props;
+
+import org.apache.seata.common.util.StringUtils;
+
+public class NameSpaceDetail {
+ private String namespace;
+ private String cluster;
+ private String vGroup;
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public String getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(String cluster) {
+ this.cluster = cluster;
+ }
+
+ public String getvGroup() {
+ return vGroup;
+ }
+
+ public void setvGroup(String vGroup) {
+ this.vGroup = vGroup;
+ }
+
+ public boolean isValid() {
+ if (StringUtils.isBlank(namespace)) {
+ return false;
+ }
+ return !StringUtils.isBlank(vGroup) || !StringUtils.isBlank(cluster);
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/props/NamingServerProperties.java
b/console/src/main/java/org/apache/seata/mcp/core/props/NamingServerProperties.java
new file mode 100644
index 0000000000..6654f6818c
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/core/props/NamingServerProperties.java
@@ -0,0 +1,52 @@
+/*
+ * 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.seata.mcp.core.props;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+@ConfigurationProperties(prefix = "console.namingserver")
+@Component
+public class NamingServerProperties {
+
+ /**
+ * http, https
+ */
+ private String protocol = "http";
+
+ private List<String> addr = Collections.singletonList("127.0.0.1:8081");
+
+ public String getNamingServerUrl() {
+ if (addr == null || addr.isEmpty()) {
+ throw new IllegalStateException("No naming servers addr
configured");
+ }
+ int index = ThreadLocalRandom.current().nextInt(addr.size());
+ return protocol + "://" + addr.get(index);
+ }
+
+ public void setAddr(List<String> addr) {
+ this.addr = addr;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java
b/console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java
new file mode 100644
index 0000000000..237ec494e8
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/core/utils/DateUtils.java
@@ -0,0 +1,80 @@
+/*
+ * 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.seata.mcp.core.utils;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+
+public class DateUtils {
+
+ public static final Long ONE_DAY_TIMESTAMP = 86400000L;
+
+ private static final Pattern DATE_PATTERN =
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$");
+
+ public static boolean isValidDate(String dateStr) {
+ return DATE_PATTERN.matcher(dateStr).matches();
+ }
+
+ public static long convertToTimestampFromDate(String dateStr) {
+ if (!isValidDate(dateStr)) {
+ throw new DateTimeException("The time format does not match
yyyy-MM-dd");
+ }
+ LocalDate date = LocalDate.parse(dateStr);
+ ZonedDateTime zonedDateTime =
date.atStartOfDay(ZoneId.systemDefault());
+ return zonedDateTime.toInstant().toEpochMilli();
+ }
+
+ public static long convertToTimeStampFromDateTime(String dateTimeStr) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd
HH:mm:ss");
+
+ try {
+ LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr,
formatter);
+ return
dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ } catch (DateTimeParseException e) {
+ throw new DateTimeException("The time format does not match
yyyy-MM-dd HH:mm:ss", e);
+ }
+ }
+
+ public static String convertToDateTimeFromTimestamp(Long timestamp) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd
HH:mm:ss");
+ LocalDateTime dateTime;
+ try {
+ dateTime = Instant.ofEpochMilli(timestamp)
+ .atZone(ZoneId.systemDefault())
+ .toLocalDateTime();
+ } catch (DateTimeException | ArithmeticException e) {
+ return "Parse Failed, please check that the timestamp is correct";
+ }
+ return dateTime.format(formatter);
+ }
+
+ public static boolean judgeExceedTimeDuration(Long startTime, Long
endTime, Long maxDuration) {
+ if (endTime < startTime) throw new IllegalArgumentException("endTime
must not be earlier than startTime");
+ return endTime - startTime > maxDuration;
+ }
+
+ public static Long convertToHourFromTimeStamp(Long timestamp) {
+ return timestamp / (60 * 60 * 1000);
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/core/utils/UrlUtils.java
b/console/src/main/java/org/apache/seata/mcp/core/utils/UrlUtils.java
new file mode 100644
index 0000000000..44345f9387
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/core/utils/UrlUtils.java
@@ -0,0 +1,88 @@
+/*
+ * 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.seata.mcp.core.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class UrlUtils {
+
+ private static final Logger logger =
LoggerFactory.getLogger(UrlUtils.class);
+
+ public static String buildUrl(
+ String baseUrl, String path, Map<String, String>
queryStringParams, Map<String, Object> queryObjectParams) {
+
+ UriComponentsBuilder builder =
+ UriComponentsBuilder.fromUriString(baseUrl).path(path);
+
+ if (queryStringParams != null && !queryStringParams.isEmpty()) {
+ for (Map.Entry<String, String> entry :
queryStringParams.entrySet()) {
+ builder.queryParam(entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (queryObjectParams != null && !queryObjectParams.isEmpty()) {
+ for (Map.Entry<String, Object> entry :
queryObjectParams.entrySet()) {
+ if (entry.getValue() instanceof Iterable) {
+ for (Object value : (Iterable<?>) entry.getValue()) {
+ builder.queryParam(entry.getKey(), value);
+ }
+ } else if (entry.getValue() != null
+ && entry.getValue().getClass().isArray()) {
+ Object[] array = (Object[]) entry.getValue();
+ for (Object value : array) {
+ builder.queryParam(entry.getKey(), value);
+ }
+ } else {
+ builder.queryParam(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ return builder.build().toUriString();
+ }
+
+ public static Map<String, Object> objectToQueryParamMap(Object obj,
ObjectMapper objectMapper) {
+ if (obj == null) {
+ return Collections.emptyMap();
+ }
+ if (obj instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) obj;
+ Map<String, Object> result = new HashMap<>(map.size());
+ map.forEach((k, v) -> {
+ if (k != null && v != null) {
+ result.put(k.toString(), v);
+ }
+ });
+ return result;
+ }
+
+ try {
+ return objectMapper.convertValue(obj, new
TypeReference<Map<String, Object>>() {});
+ } catch (IllegalArgumentException e) {
+ logger.warn("Failed to convert object to map: {}", e.getMessage());
+ return Collections.emptyMap();
+ }
+ }
+}
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalLockParamDto.java
similarity index 54%
copy from
server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
copy to
console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalLockParamDto.java
index ec13f0b30e..56982afec6 100644
---
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
+++
b/console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalLockParamDto.java
@@ -14,44 +14,82 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.server.console.entity.param;
+package org.apache.seata.mcp.entity.dto;
-import org.apache.seata.common.result.BaseParam;
+import org.springaicommunity.mcp.annotation.McpToolParam;
import java.io.Serializable;
-/**
- * Global lock param
- */
-public class GlobalLockParam extends BaseParam implements Serializable {
+public class McpGlobalLockParamDto implements Serializable {
private static final long serialVersionUID = 615412528070131284L;
- /**
- * the xid
- */
+ @McpToolParam(description = "Global transaction ID", required = false)
private String xid;
- /**
- * the table name
- */
+
+ @McpToolParam(description = "The table name", required = false)
private String tableName;
- /**
- * the transaction id
- */
+
+ @McpToolParam(description = "The transaction id", required = false)
private String transactionId;
- /**
- * the branch id
- */
+
+ @McpToolParam(description = "The branch id", required = false)
private String branchId;
- /**
- * the primary Key
- */
+
+ @McpToolParam(description = "the primary Key", required = false)
private String pk;
- /**
- * the resourceId
- */
+
+ @McpToolParam(description = "resourceId", required = false)
private String resourceId;
+ @McpToolParam(description = "Page number")
+ private int pageNum;
+
+ @McpToolParam(description = "Page size")
+ private int pageSize;
+
+ @McpToolParam(
+ description = "Start time, The global lock create time is after
this time (yyyy-MM-dd HH:mm:ss)",
+ required = false)
+ private String timeStart;
+
+ @McpToolParam(
+ description = "End time, The global lock create time is before
this time (yyyy-MM-dd HH:mm:ss)",
+ required = false)
+ private String timeEnd;
+
+ 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 getTimeStart() {
+ return timeStart;
+ }
+
+ public void setTimeStart(String timeStart) {
+ this.timeStart = timeStart;
+ }
+
+ public String getTimeEnd() {
+ return timeEnd;
+ }
+
+ public void setTimeEnd(String timeEnd) {
+ this.timeEnd = timeEnd;
+ }
+
public String getTransactionId() {
return transactionId;
}
@@ -99,15 +137,4 @@ public class GlobalLockParam extends BaseParam implements
Serializable {
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
-
- @Override
- public String toString() {
- return "GlobalLockParam{" + "xid='"
- + xid + '\'' + ", tableName='"
- + tableName + '\'' + ", transactionId='"
- + transactionId + '\'' + ", branchId='"
- + branchId + '\'' + ", pk='"
- + pk + '\'' + ", resourceId='"
- + resourceId + '\'' + '}';
- }
}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalSessionParamDto.java
b/console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalSessionParamDto.java
new file mode 100644
index 0000000000..c7f4c95499
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/dto/McpGlobalSessionParamDto.java
@@ -0,0 +1,142 @@
+/*
+ * 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.seata.mcp.entity.dto;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.entity.param.McpGlobalAbnormalSessionParam;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+
+import java.io.Serializable;
+
+public class McpGlobalSessionParamDto implements Serializable {
+
+ private static final long serialVersionUID = 115488252809011284L;
+
+ @McpToolParam(description = "Global transaction ID", required = false)
+ private String xid;
+
+ @McpToolParam(description = "applicationId", required = false)
+ private String applicationId;
+
+ @McpToolParam(description = "The valid values are defined in prompts",
required = false)
+ private Integer status;
+
+ @McpToolParam(description = "The name of the transaction", required =
false)
+ private String transactionName;
+
+ @McpToolParam(description = "Whether or not it contains branch transaction
information", required = false)
+ private boolean withBranch;
+
+ @McpToolParam(description = "Page number")
+ private int pageNum;
+
+ @McpToolParam(description = "Page size")
+ private int pageSize;
+
+ @McpToolParam(description = "The transaction start time is after this time
(yyyy-MM-dd HH:mm:ss)", required = false)
+ private String timeStart;
+
+ @McpToolParam(
+ description = "The transaction start time is before this time
(yyyy-MM-dd HH:mm:ss)",
+ required = false)
+ private String timeEnd;
+
+ 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 getTimeStart() {
+ return timeStart;
+ }
+
+ public void setTimeStart(String timeStart) {
+ this.timeStart = timeStart;
+ }
+
+ public String getTimeEnd() {
+ return timeEnd;
+ }
+
+ public void setTimeEnd(String timeEnd) {
+ this.timeEnd = timeEnd;
+ }
+
+ public String getXid() {
+ return xid;
+ }
+
+ public void setXid(String xid) {
+ this.xid = xid;
+ }
+
+ public String getTransactionName() {
+ return transactionName;
+ }
+
+ public void setTransactionName(String transactionName) {
+ this.transactionName = transactionName;
+ }
+
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public boolean isWithBranch() {
+ return withBranch;
+ }
+
+ public void setWithBranch(boolean withBranch) {
+ this.withBranch = withBranch;
+ }
+
+ public static McpGlobalSessionParamDto
convertFromAbnormalParam(McpGlobalAbnormalSessionParam abParam) {
+ McpGlobalSessionParamDto param = new McpGlobalSessionParamDto();
+ if (StringUtils.isNotBlank(abParam.getTimeStart())) {
+ param.setTimeStart(abParam.getTimeStart());
+ }
+ if (StringUtils.isNotBlank(abParam.getTimeEnd())) {
+ param.setTimeEnd(abParam.getTimeEnd());
+ }
+ param.setWithBranch(abParam.isWithBranch());
+ param.setPageNum(abParam.getPageNum());
+ return param;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalAbnormalSessionParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalAbnormalSessionParam.java
new file mode 100644
index 0000000000..dc09b21a95
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalAbnormalSessionParam.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.seata.mcp.entity.param;
+
+import org.springaicommunity.mcp.annotation.McpToolParam;
+
+public class McpGlobalAbnormalSessionParam {
+ @McpToolParam(
+ description = "Whether or not it contains branch transaction
information, default is true",
+ required = false)
+ private boolean withBranch = true;
+
+ @McpToolParam(description = "The transaction start time is after this time
(yyyy-MM-dd HH:mm:ss)", required = false)
+ private String timeStart;
+
+ @McpToolParam(
+ description = "The transaction start time is before this time
(yyyy-MM-dd HH:mm:ss)",
+ required = false)
+ private String timeEnd;
+
+ @McpToolParam(description = "Page number")
+ private int pageNum;
+
+ public boolean isWithBranch() {
+ return withBranch;
+ }
+
+ public void setWithBranch(boolean withBranch) {
+ this.withBranch = withBranch;
+ }
+
+ public String getTimeStart() {
+ return timeStart;
+ }
+
+ public void setTimeStart(String timeStart) {
+ this.timeStart = timeStart;
+ }
+
+ public String getTimeEnd() {
+ return timeEnd;
+ }
+
+ public void setTimeEnd(String timeEnd) {
+ this.timeEnd = timeEnd;
+ }
+
+ public int getPageNum() {
+ return pageNum;
+ }
+
+ public void setPageNum(int pageNum) {
+ this.pageNum = pageNum;
+ }
+}
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockDeleteParam.java
similarity index 64%
copy from
server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
copy to
console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockDeleteParam.java
index ec13f0b30e..eb53a90d82 100644
---
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
+++
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockDeleteParam.java
@@ -14,51 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.server.console.entity.param;
+package org.apache.seata.mcp.entity.param;
-import org.apache.seata.common.result.BaseParam;
+import org.springaicommunity.mcp.annotation.McpToolParam;
import java.io.Serializable;
/**
* Global lock param
*/
-public class GlobalLockParam extends BaseParam implements Serializable {
+public class McpGlobalLockDeleteParam implements Serializable {
private static final long serialVersionUID = 615412528070131284L;
- /**
- * the xid
- */
+ @McpToolParam(description = "Global transaction id")
private String xid;
- /**
- * the table name
- */
+
+ @McpToolParam(description = "the table name")
private String tableName;
- /**
- * the transaction id
- */
- private String transactionId;
- /**
- * the branch id
- */
+
+ @McpToolParam(description = "the branch id")
private String branchId;
- /**
- * the primary Key
- */
- private String pk;
- /**
- * the resourceId
- */
- private String resourceId;
- public String getTransactionId() {
- return transactionId;
- }
+ @McpToolParam(description = "the primary Key")
+ private String pk;
- public void setTransactionId(String transactionId) {
- this.transactionId = transactionId;
- }
+ @McpToolParam(description = "resourceId")
+ private String resourceId;
public String getBranchId() {
return branchId;
@@ -99,15 +81,4 @@ public class GlobalLockParam extends BaseParam implements
Serializable {
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
-
- @Override
- public String toString() {
- return "GlobalLockParam{" + "xid='"
- + xid + '\'' + ", tableName='"
- + tableName + '\'' + ", transactionId='"
- + transactionId + '\'' + ", branchId='"
- + branchId + '\'' + ", pk='"
- + pk + '\'' + ", resourceId='"
- + resourceId + '\'' + '}';
- }
}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockParam.java
new file mode 100644
index 0000000000..42be0cf67d
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalLockParam.java
@@ -0,0 +1,43 @@
+/*
+ * 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.seata.mcp.entity.param;
+
+import org.apache.seata.common.util.PageUtil;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.McpGlobalLockParamDto;
+import org.apache.seata.server.console.entity.param.GlobalLockParam;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * Global lock param
+ */
+public class McpGlobalLockParam extends GlobalLockParam {
+
+ public static McpGlobalLockParam convertFromParamDto(McpGlobalLockParamDto
paramDto) {
+ PageUtil.checkParam(paramDto.getPageNum(), paramDto.getPageSize());
+ McpGlobalLockParam param = new McpGlobalLockParam();
+ BeanUtils.copyProperties(paramDto, param);
+ if (StringUtils.isNotBlank(paramDto.getTimeStart())) {
+
param.setTimeStart(DateUtils.convertToTimeStampFromDateTime(paramDto.getTimeStart()));
+ }
+ if (StringUtils.isNotBlank(paramDto.getTimeEnd())) {
+
param.setTimeEnd(DateUtils.convertToTimeStampFromDateTime(paramDto.getTimeEnd()));
+ }
+ return param;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalSessionParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalSessionParam.java
new file mode 100644
index 0000000000..8adaec20cb
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/param/McpGlobalSessionParam.java
@@ -0,0 +1,48 @@
+/*
+ * 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.seata.mcp.entity.param;
+
+import org.apache.seata.common.util.PageUtil;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.McpGlobalSessionParamDto;
+import org.apache.seata.server.console.entity.param.GlobalSessionParam;
+
+/**
+ * Global session param
+ */
+public class McpGlobalSessionParam extends GlobalSessionParam {
+
+ public static McpGlobalSessionParam
convertFromDtoParam(McpGlobalSessionParamDto paramDto) {
+ PageUtil.checkParam(paramDto.getPageNum(), paramDto.getPageSize());
+ McpGlobalSessionParam param = new McpGlobalSessionParam();
+ param.setPageSize(paramDto.getPageSize());
+ param.setPageNum(paramDto.getPageNum());
+ param.setStatus(paramDto.getStatus());
+ param.setXid(paramDto.getXid());
+ param.setApplicationId(paramDto.getApplicationId());
+ param.setTransactionName(paramDto.getTransactionName());
+ param.setWithBranch(paramDto.isWithBranch());
+ if (StringUtils.isNotBlank(paramDto.getTimeStart())) {
+
param.setTimeStart(DateUtils.convertToTimeStampFromDateTime(paramDto.getTimeStart()));
+ }
+ if (StringUtils.isNotBlank(paramDto.getTimeEnd())) {
+
param.setTimeEnd(DateUtils.convertToTimeStampFromDateTime(paramDto.getTimeEnd()));
+ }
+ return param;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/vo/McpBranchSessionVO.java
b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpBranchSessionVO.java
new file mode 100644
index 0000000000..e753afa95f
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpBranchSessionVO.java
@@ -0,0 +1,53 @@
+/*
+ * 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.seata.mcp.entity.vo;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.apache.seata.mcp.core.config.TimestampToStringDeserializer;
+import org.apache.seata.server.console.entity.vo.BranchSessionVO;
+
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
+
+@JsonAutoDetect(getterVisibility = NONE, fieldVisibility = ANY)
+public class McpBranchSessionVO extends BranchSessionVO {
+
+ private String createTime;
+ private String modifiedTime;
+
+ @JsonProperty("gmtCreate")
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ public String getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(String createTime) {
+ this.createTime = createTime;
+ }
+
+ @JsonProperty("gmtModified")
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ public String getModifiedTime() {
+ return modifiedTime;
+ }
+
+ public void setModifiedTime(String modifiedTime) {
+ this.modifiedTime = modifiedTime;
+ }
+}
diff --git
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalLockVO.java
similarity index 63%
rename from
server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
rename to
console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalLockVO.java
index ec13f0b30e..eaa4562cfe 100644
---
a/server/src/main/java/org/apache/seata/server/console/entity/param/GlobalLockParam.java
+++ b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalLockVO.java
@@ -14,66 +14,60 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.server.console.entity.param;
+package org.apache.seata.mcp.entity.vo;
-import org.apache.seata.common.result.BaseParam;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.apache.seata.mcp.core.config.TimestampToStringDeserializer;
-import java.io.Serializable;
+public class McpGlobalLockVO {
-/**
- * Global lock param
- */
-public class GlobalLockParam extends BaseParam implements Serializable {
-
- private static final long serialVersionUID = 615412528070131284L;
-
- /**
- * the xid
- */
private String xid;
- /**
- * the table name
- */
- private String tableName;
- /**
- * the transaction id
- */
+
private String transactionId;
- /**
- * the branch id
- */
+
private String branchId;
- /**
- * the primary Key
- */
+
+ private String resourceId;
+
+ private String tableName;
+
private String pk;
+
+ private String rowKey;
+
/**
- * the resourceId
+ * the vgroup
*/
- private String resourceId;
+ private String vgroup;
- public String getTransactionId() {
- return transactionId;
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ private String gmtCreate;
+
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ private String gmtModified;
+
+ public String getXid() {
+ return xid;
}
- public void setTransactionId(String transactionId) {
- this.transactionId = transactionId;
+ public void setXid(String xid) {
+ this.xid = xid;
}
- public String getBranchId() {
- return branchId;
+ public String getTransactionId() {
+ return transactionId;
}
- public void setBranchId(String branchId) {
- this.branchId = branchId;
+ public String getBranchId() {
+ return branchId;
}
- public String getXid() {
- return xid;
+ public String getResourceId() {
+ return resourceId;
}
- public void setXid(String xid) {
- this.xid = xid;
+ public void setResourceId(String resourceId) {
+ this.resourceId = resourceId;
}
public String getTableName() {
@@ -92,22 +86,43 @@ public class GlobalLockParam extends BaseParam implements
Serializable {
this.pk = pk;
}
- public String getResourceId() {
- return resourceId;
+ public String getRowKey() {
+ return rowKey;
}
- public void setResourceId(String resourceId) {
- this.resourceId = resourceId;
+ public void setRowKey(String rowKey) {
+ this.rowKey = rowKey;
+ }
+
+ public String getGmtCreate() {
+ return gmtCreate;
+ }
+
+ public void setGmtCreate(String gmtCreate) {
+ this.gmtCreate = gmtCreate;
+ }
+
+ public String getGmtModified() {
+ return gmtModified;
+ }
+
+ public void setGmtModified(String gmtModified) {
+ this.gmtModified = gmtModified;
+ }
+
+ public void setTransactionId(String transactionId) {
+ this.transactionId = transactionId;
+ }
+
+ public void setBranchId(String branchId) {
+ this.branchId = branchId;
+ }
+
+ public String getVgroup() {
+ return vgroup;
}
- @Override
- public String toString() {
- return "GlobalLockParam{" + "xid='"
- + xid + '\'' + ", tableName='"
- + tableName + '\'' + ", transactionId='"
- + transactionId + '\'' + ", branchId='"
- + branchId + '\'' + ", pk='"
- + pk + '\'' + ", resourceId='"
- + resourceId + '\'' + '}';
+ public void setVgroup(String vgroup) {
+ this.vgroup = vgroup;
}
}
diff --git
a/console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalSessionVO.java
b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalSessionVO.java
new file mode 100644
index 0000000000..594b5577fb
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/entity/vo/McpGlobalSessionVO.java
@@ -0,0 +1,76 @@
+/*
+ * 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.seata.mcp.entity.vo;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.apache.seata.mcp.core.config.TimestampToStringDeserializer;
+import org.apache.seata.server.console.entity.vo.GlobalSessionVO;
+
+import java.util.Set;
+
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
+
+@JsonAutoDetect(getterVisibility = NONE, fieldVisibility = ANY)
+public class McpGlobalSessionVO extends GlobalSessionVO {
+
+ private String beginTime;
+ private String createTime;
+ private String modifiedTime;
+ private Set<McpBranchSessionVO> mcpBranchSessionVOS;
+
+ @JsonProperty("beginTime")
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ public String getBegin() {
+ return beginTime;
+ }
+
+ public void setBeginTime(String beginTime) {
+ this.beginTime = beginTime;
+ }
+
+ @JsonProperty("gmtCreate")
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ public String getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(String createTime) {
+ this.createTime = createTime;
+ }
+
+ @JsonProperty("gmtModified")
+ @JsonDeserialize(using = TimestampToStringDeserializer.class)
+ public String getModifiedTime() {
+ return modifiedTime;
+ }
+
+ public void setModifiedTime(String modifiedTime) {
+ this.modifiedTime = modifiedTime;
+ }
+
+ @JsonProperty("branchSessionVOs")
+ public Set<McpBranchSessionVO> getMcpBranchSessionVOS() {
+ return mcpBranchSessionVOS;
+ }
+
+ public void setMcpBranchSessionVOS(Set<McpBranchSessionVO>
mcpBranchSessionVOS) {
+ this.mcpBranchSessionVOS = mcpBranchSessionVOS;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/exception/ServiceCallException.java
b/console/src/main/java/org/apache/seata/mcp/exception/ServiceCallException.java
new file mode 100644
index 0000000000..3e4e4f78f2
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/exception/ServiceCallException.java
@@ -0,0 +1,54 @@
+/*
+ * 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.seata.mcp.exception;
+
+import org.springframework.http.HttpStatusCode;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ServiceCallException extends RuntimeException {
+ private final HttpStatusCode httpStatus;
+ private final Instant timestamp;
+
+ public ServiceCallException(String message, Throwable cause) {
+ super(message, cause);
+ this.httpStatus = null;
+ this.timestamp = Instant.now();
+ }
+
+ public ServiceCallException(String message) {
+ super(message);
+ this.httpStatus = null;
+ this.timestamp = Instant.now();
+ }
+
+ public ServiceCallException(String message, HttpStatusCode httpStatus) {
+ super(message);
+ this.httpStatus = httpStatus;
+ this.timestamp = Instant.now();
+ }
+
+ public Map<String, Object> toErrorResponse() {
+ Map<String, Object> error = new HashMap<>();
+ error.put("message", this.getMessage());
+ error.put("timestamp", timestamp.toString());
+ error.put("httpStatus", httpStatus != null ? httpStatus.value() :
null);
+ return error;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/service/ConsoleApiService.java
b/console/src/main/java/org/apache/seata/mcp/service/ConsoleApiService.java
new file mode 100644
index 0000000000..fda62cebe3
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/service/ConsoleApiService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.seata.mcp.service;
+
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.springframework.http.HttpHeaders;
+
+import java.util.Map;
+
+public interface ConsoleApiService {
+
+ String getCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers);
+
+ String deleteCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers);
+
+ String putCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers);
+
+ String getCallNameSpace(String path);
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/service/ModifyConfirmService.java
b/console/src/main/java/org/apache/seata/mcp/service/ModifyConfirmService.java
new file mode 100644
index 0000000000..2993d81c3f
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/service/ModifyConfirmService.java
@@ -0,0 +1,26 @@
+/*
+ * 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.seata.mcp.service;
+
+import java.util.Map;
+
+public interface ModifyConfirmService {
+
+ Map<String, String> confirmAndGetKey();
+
+ Boolean isValidKey(String key);
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java
b/console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java
new file mode 100644
index 0000000000..0047f9b81d
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/service/impl/ConsoleRemoteServiceImpl.java
@@ -0,0 +1,244 @@
+/*
+ * 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.seata.mcp.service.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.NamingServerLocalMarker;
+import org.apache.seata.common.exception.AuthenticationFailedException;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.console.config.WebSecurityConfig;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.props.NamingServerProperties;
+import org.apache.seata.mcp.exception.ServiceCallException;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnMissingBean(NamingServerLocalMarker.class)
+@Service
+public class ConsoleRemoteServiceImpl implements ConsoleApiService {
+
+ private final JwtTokenUtils jwtTokenUtils;
+
+ private final RestTemplate restTemplate;
+
+ private final ObjectMapper objectMapper;
+
+ private final NamingServerProperties namingServerProperties;
+
+ public ConsoleRemoteServiceImpl(
+ JwtTokenUtils jwtTokenUtils,
+ RestTemplate restTemplate,
+ ObjectMapper objectMapper,
+ NamingServerProperties namingServerProperties) {
+ this.jwtTokenUtils = jwtTokenUtils;
+ this.restTemplate = restTemplate;
+ this.objectMapper = objectMapper;
+ this.namingServerProperties = namingServerProperties;
+ }
+
+ private final Logger logger =
LoggerFactory.getLogger(ConsoleRemoteServiceImpl.class);
+
+ public String getToken() {
+ Authentication auth =
SecurityContextHolder.getContext().getAuthentication();
+ if (auth == null || !auth.isAuthenticated()) {
+ throw new AuthenticationFailedException("No right to be
identified");
+ }
+ String originJwt = (String) auth.getCredentials();
+ if (!jwtTokenUtils.validateToken(originJwt)) {
+ throw new AuthenticationFailedException("Invalid token, please log
in to get a new token");
+ }
+ return WebSecurityConfig.TOKEN_PREFIX + originJwt;
+ }
+
+ public void setNamespaceHeaderAndQueryParam(
+ NameSpaceDetail nameSpaceDetail, HttpHeaders headers, Map<String,
String> queryParams) {
+ headers.add("x-seata-namespace", nameSpaceDetail.getNamespace());
+ if (StringUtils.isNotBlank(nameSpaceDetail.getvGroup())) {
+ if (queryParams != null) {
+ queryParams.put("vGroup", nameSpaceDetail.getvGroup());
+ }
+ return;
+ }
+ if (nameSpaceDetail.getCluster() != null) {
+ headers.add("x-seata-cluster", nameSpaceDetail.getCluster());
+ }
+ }
+
+ @Override
+ public String getCallNameSpace(String path) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+ String url = buildUrl(namingServerProperties.getNamingServerUrl(),
path, null, null);
+ HttpEntity<String> entity = new HttpEntity<>(headers);
+ String responseBody;
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.GET, entity, String.class);
+
+ responseBody = response.getBody();
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ String errorMsg = String.format(
+ "MCP GET request failed with status: %s, response: %s",
+ response.getStatusCode(), response.getBody());
+ logger.warn(errorMsg);
+ throw new ServiceCallException(errorMsg,
response.getStatusCode());
+ }
+ return responseBody;
+ } catch (RestClientException e) {
+ String errorMsg = "MCP GET Call NameSpace Failed.";
+ logger.error(errorMsg, e);
+ throw new ServiceCallException(errorMsg);
+ }
+ }
+
+ @Override
+ public String getCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ if (headers == null) {
+ headers = new HttpHeaders();
+ }
+ if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+ return "If you have not specified the namespace of the TC/Server,
specify the namespace first";
+ } else {
+ setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers,
queryParams);
+ }
+ headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+ Map<String, Object> queryParamsMap =
objectToQueryParamMap(objectQueryParams, objectMapper);
+ String url = buildUrl(namingServerProperties.getNamingServerUrl(),
path, queryParams, queryParamsMap);
+ HttpEntity<String> entity = new HttpEntity<>(headers);
+ String responseBody;
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.GET, entity, String.class);
+
+ responseBody = response.getBody();
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ String errorMsg = String.format(
+ "MCP GET request failed with status: %s, response: %s",
+ response.getStatusCode(), response.getBody());
+ logger.warn(errorMsg);
+ throw new ServiceCallException(errorMsg,
response.getStatusCode());
+ }
+ return responseBody;
+ } catch (RestClientException e) {
+ String errorMsg = "MCP GET Call TC Failed.";
+ logger.error(errorMsg, e);
+ throw new ServiceCallException(errorMsg);
+ }
+ }
+
+ @Override
+ public String deleteCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ if (headers == null) {
+ headers = new HttpHeaders();
+ }
+ if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+ return "If you have not specified the namespace of the TC/Server,
specify the namespace first";
+ } else {
+ setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers,
queryParams);
+ }
+ headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+ Map<String, Object> queryParamsMap =
objectToQueryParamMap(objectQueryParams, objectMapper);
+ String url = buildUrl(namingServerProperties.getNamingServerUrl(),
path, queryParams, queryParamsMap);
+ HttpEntity<String> entity = new HttpEntity<>(headers);
+ String responseBody;
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.DELETE, entity, String.class);
+
+ responseBody = response.getBody();
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ String errorMsg = String.format(
+ "MCP DELETE request returned non-success status: %s,
response: %s",
+ response.getStatusCode(), response.getBody());
+ logger.warn(errorMsg);
+ throw new ServiceCallException(errorMsg,
response.getStatusCode());
+ }
+ return responseBody;
+ } catch (RestClientException e) {
+ String errorMsg = "MCP DELETE Call TC Failed.";
+ logger.error(errorMsg, e);
+ throw new ServiceCallException(errorMsg);
+ }
+ }
+
+ @Override
+ public String putCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ if (headers == null) {
+ headers = new HttpHeaders();
+ }
+ if (nameSpaceDetail == null || !nameSpaceDetail.isValid()) {
+ return "If you have not specified the namespace of the TC/Server,
specify the namespace first";
+ } else {
+ setNamespaceHeaderAndQueryParam(nameSpaceDetail, headers,
queryParams);
+ }
+ headers.add(WebSecurityConfig.AUTHORIZATION_HEADER, getToken());
+ Map<String, Object> queryParamsMap =
objectToQueryParamMap(objectQueryParams, objectMapper);
+ String url = buildUrl(namingServerProperties.getNamingServerUrl(),
path, queryParams, queryParamsMap);
+ HttpEntity<String> entity = new HttpEntity<>(headers);
+ String responseBody;
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.PUT, entity, String.class);
+
+ responseBody = response.getBody();
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ String errorMsg = String.format(
+ "MCP PUT request returned non-success status: %s,
response: %s",
+ response.getStatusCode(), response.getBody());
+ logger.warn(errorMsg);
+ throw new ServiceCallException(errorMsg,
response.getStatusCode());
+ }
+ return responseBody;
+ } catch (RestClientException e) {
+ String errorMsg = "MCP PUT Call TC Failed.";
+ logger.error(errorMsg, e);
+ throw new ServiceCallException(errorMsg);
+ }
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java
b/console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java
new file mode 100644
index 0000000000..41e74bc4e6
--- /dev/null
+++
b/console/src/main/java/org/apache/seata/mcp/service/impl/ModifyConfirmServiceImpl.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.mcp.service.impl;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.io.Decoders;
+import org.apache.seata.console.utils.JwtTokenUtils;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class ModifyConfirmServiceImpl implements ModifyConfirmService {
+
+ private final JwtTokenUtils jwtTokenUtils;
+
+ private static final long MODIFY_TOKEN_VALIDITY_IN_MILLISECONDS = 180_000;
+
+ @Value("${seata.security.secretKey}")
+ private String secretKey;
+
+ public ModifyConfirmServiceImpl(JwtTokenUtils jwtTokenUtils) {
+ this.jwtTokenUtils = jwtTokenUtils;
+ }
+
+ @Override
+ public Map<String, String> confirmAndGetKey() {
+ Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
+ long now = (new Date()).getTime();
+ Date expirationDate = new Date(now +
MODIFY_TOKEN_VALIDITY_IN_MILLISECONDS);
+ SecretKeySpec secretKeySpec =
+ new SecretKeySpec(Decoders.BASE64.decode(secretKey),
SignatureAlgorithm.HS256.getJcaName());
+ String key = Jwts.builder()
+ .setSubject(authentication.getName())
+ .claim("modify", "")
+ .setExpiration(expirationDate)
+ .signWith(secretKeySpec, SignatureAlgorithm.HS256)
+ .compact();
+ Map<String, String> map = new HashMap<>();
+ map.put("modify_key", key);
+ map.put(
+ "Important!!!",
+ "You need to repeat the content to be modified by the user and
get confirmation from the user before you can continue to call the modification
tool");
+ return map;
+ }
+
+ @Override
+ public Boolean isValidKey(String key) {
+ return jwtTokenUtils.validateToken(key);
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/tools/BranchSessionTools.java
b/console/src/main/java/org/apache/seata/mcp/tools/BranchSessionTools.java
new file mode 100644
index 0000000000..23dd37bf16
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/tools/BranchSessionTools.java
@@ -0,0 +1,105 @@
+/*
+ * 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.seata.mcp.tools;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class BranchSessionTools {
+
+ private final ConsoleApiService mcpRPCService;
+
+ private final ModifyConfirmService modifyConfirmService;
+
+ public BranchSessionTools(ConsoleApiService mcpRPCService,
ModifyConfirmService modifyConfirmService) {
+ this.mcpRPCService = mcpRPCService;
+ this.modifyConfirmService = modifyConfirmService;
+ }
+
+ @McpTool(description = "Delete branch transactions, Get the modify key
before you delete")
+ public String deleteBranchSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Branch transaction id") String
branchId,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ pathParams.put("branchId", branchId);
+ String result = mcpRPCService.deleteCallTC(
+ nameSpaceDetail, RPCConstant.BRANCH_SESSION_BASE_URL +
"/deleteBranchSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("delete branch session failed, xid: %s,
branchId: %s", xid, branchId);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Stop the branch transaction retry, Get the modify
key before you stop")
+ public String stopBranchSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Branch transaction id") String
branchId,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ pathParams.put("branchId", branchId);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.BRANCH_SESSION_BASE_URL +
"/stopBranchSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("stop branch session failed, xid: %s,
branchId: %s", xid, branchId);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Initiate a branch transaction retries, Get the
modify key before you start")
+ public String startBranchRetry(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Branch transaction id") String
branchId,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ pathParams.put("branchId", branchId);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.BRANCH_SESSION_BASE_URL +
"/startBranchSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("start branch session failed, xid: %s,
branchId: %s", xid, branchId);
+ } else {
+ return result;
+ }
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java
b/console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java
new file mode 100644
index 0000000000..ca6e034ea6
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/tools/GlobalLockTools.java
@@ -0,0 +1,141 @@
+/*
+ * 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.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.MCPProperties;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.McpGlobalLockParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalLockDeleteParam;
+import org.apache.seata.mcp.entity.param.McpGlobalLockParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalLockVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class GlobalLockTools {
+
+ private final Logger logger =
LoggerFactory.getLogger(GlobalLockTools.class);
+
+ private final ConsoleApiService mcpRPCService;
+
+ private final MCPProperties mcpProperties;
+
+ private final ModifyConfirmService modifyConfirmService;
+
+ private final ObjectMapper objectMapper;
+
+ public GlobalLockTools(
+ ConsoleApiService mcpRPCService,
+ MCPProperties mcpProperties,
+ ModifyConfirmService modifyConfirmService,
+ ObjectMapper objectMapper) {
+ this.mcpRPCService = mcpRPCService;
+ this.mcpProperties = mcpProperties;
+ this.modifyConfirmService = modifyConfirmService;
+ this.objectMapper = objectMapper;
+ }
+
+ @McpTool(description = "Query the global lock information")
+ public PageResult<McpGlobalLockVO> queryGlobalLock(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global lock parameters")
McpGlobalLockParamDto paramDto) {
+ McpGlobalLockParam param =
McpGlobalLockParam.convertFromParamDto(paramDto);
+ Long timeStart = param.getTimeStart();
+ Long timeEnd = param.getTimeEnd();
+ Long maxQueryDuration = mcpProperties.getQueryDuration();
+ if (timeStart != null || timeEnd != null) {
+ if (timeStart == null) {
+ timeStart = timeEnd - maxQueryDuration;
+ param.setTimeStart(timeStart);
+ }
+ if (timeEnd == null) {
+ timeEnd = timeStart + maxQueryDuration;
+ param.setTimeEnd(timeEnd);
+ }
+ if (DateUtils.judgeExceedTimeDuration(timeStart, timeEnd,
maxQueryDuration)) {
+ return PageResult.failure(
+ "",
+ String.format(
+ "The query time span is not allowed to exceed
the max query duration: %s hours",
+
DateUtils.convertToHourFromTimeStamp(maxQueryDuration)));
+ }
+ }
+ PageResult<McpGlobalLockVO> result = null;
+ String response = mcpRPCService.getCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_LOCK_BASE_URL + "/query",
param, null, null);
+ try {
+ result = objectMapper.readValue(response, new
TypeReference<PageResult<McpGlobalLockVO>>() {});
+ } catch (JsonProcessingException e) {
+ logger.error(e.getMessage());
+ }
+ if (result == null) {
+ return PageResult.failure("", "query global lock failed");
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Delete the global lock, Get the modify key before
you delete")
+ public String deleteGlobalLock(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global lock delete parameters")
McpGlobalLockDeleteParam param,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ String result = mcpRPCService.deleteCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_LOCK_BASE_URL + "/delete",
param, null, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format(
+ "delete global lock failed, xid: %s, branchId: %s",
param.getXid(), param.getBranchId());
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Check if the branch session has a lock")
+ public String checkGlobalLock(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Branch transaction id") String
branchId) {
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ pathParams.put("branchId", branchId);
+ String result = mcpRPCService.getCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_LOCK_BASE_URL + "/check",
null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("check global lock failed, xid: %s, branchId:
%s", xid, branchId);
+ } else {
+ return result;
+ }
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java
b/console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java
new file mode 100644
index 0000000000..b3697e5dde
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/tools/GlobalSessionTools.java
@@ -0,0 +1,231 @@
+/*
+ * 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.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.core.props.MCPProperties;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.core.utils.DateUtils;
+import org.apache.seata.mcp.entity.dto.McpGlobalSessionParamDto;
+import org.apache.seata.mcp.entity.param.McpGlobalAbnormalSessionParam;
+import org.apache.seata.mcp.entity.param.McpGlobalSessionParam;
+import org.apache.seata.mcp.entity.vo.McpGlobalSessionVO;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class GlobalSessionTools {
+
+ private final Logger logger =
LoggerFactory.getLogger(GlobalSessionTools.class);
+
+ private final ConsoleApiService mcpRPCService;
+
+ private final MCPProperties mcpProperties;
+
+ private final ObjectMapper objectMapper;
+
+ private final ModifyConfirmService modifyConfirmService;
+
+ private final List<Integer> exceptionStatus = new ArrayList<>();
+
+ public static final int ABNORMAL_SESSION_PAGE_SIZE = 30;
+
+ public GlobalSessionTools(
+ ConsoleApiService mcpRPCService,
+ MCPProperties mcpProperties,
+ ObjectMapper objectMapper,
+ ModifyConfirmService modifyConfirmService) {
+ this.mcpRPCService = mcpRPCService;
+ this.mcpProperties = mcpProperties;
+ this.objectMapper = objectMapper;
+ this.modifyConfirmService = modifyConfirmService;
+ exceptionStatus.add(GlobalStatus.CommitFailed.getCode());
+ exceptionStatus.add(GlobalStatus.TimeoutRollbackFailed.getCode());
+ exceptionStatus.add(GlobalStatus.RollbackFailed.getCode());
+ }
+
+ @McpTool(description = "Query global transactions")
+ public PageResult<McpGlobalSessionVO> queryGlobalSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Query parameter objects")
McpGlobalSessionParamDto paramDto) {
+ McpGlobalSessionParam param =
McpGlobalSessionParam.convertFromDtoParam(paramDto);
+ Long timeStart = param.getTimeStart();
+ Long timeEnd = param.getTimeEnd();
+ Long maxQueryDuration = mcpProperties.getQueryDuration();
+ if (timeStart != null || timeEnd != null) {
+ if (timeStart == null) {
+ timeStart = timeEnd - maxQueryDuration;
+ param.setTimeStart(timeStart);
+ }
+ if (timeEnd == null) {
+ timeEnd = timeStart + maxQueryDuration;
+ param.setTimeEnd(timeEnd);
+ }
+ if (DateUtils.judgeExceedTimeDuration(timeStart, timeEnd,
maxQueryDuration)) {
+ return PageResult.failure(
+ "",
+ String.format(
+ "The query time span is not allowed to exceed
the max query duration: %s hours",
+
DateUtils.convertToHourFromTimeStamp(maxQueryDuration)));
+ }
+ }
+ PageResult<McpGlobalSessionVO> pageResult = null;
+ String result = mcpRPCService.getCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/query", param, null, null);
+ try {
+ pageResult = objectMapper.readValue(result, new
TypeReference<PageResult<McpGlobalSessionVO>>() {});
+ } catch (JsonProcessingException e) {
+ logger.error(e.getMessage());
+ }
+ if (pageResult == null) {
+ return PageResult.failure("", "query global session failed");
+ } else {
+ return pageResult;
+ }
+ }
+
+ @McpTool(description = "Delete the global session, Get the modify key
before you delete")
+ public String deleteGlobalSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ String result = mcpRPCService.deleteCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/deleteGlobalSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("delete global session failed, xid: %s", xid);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Stop the global session retry, Get the modify key
before you stop")
+ public String stopGlobalSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/stopGlobalSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("stop global session retry failed, xid: %s",
xid);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Start the global session retry, Get the modify key
before you start")
+ public String startGlobalSession(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/startGlobalSession", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("start the global session retry failed, xid:
%s", xid);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Send global session to commit or rollback to rm,
Get the modify key before you send")
+ public String sendCommitOrRollback(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/sendCommitOrRollback", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("send global session to commit or rollback to
rm failed, xid: %s", xid);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(
+ description =
+ "Change the global session status, Used to change
transactions that are in a failed commit or rollback failed state to a retry
state, Get the modify key before you change")
+ public String changeGlobalStatus(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Global transaction id") String xid,
+ @McpToolParam(description = "Modify key") String modifyKey) {
+ if (!modifyConfirmService.isValidKey(modifyKey)) {
+ return "The modify key is not available";
+ }
+ Map<String, String> pathParams = new HashMap<>();
+ pathParams.put("xid", xid);
+ String result = mcpRPCService.putCallTC(
+ nameSpaceDetail, RPCConstant.GLOBAL_SESSION_BASE_URL +
"/changeGlobalStatus", null, pathParams, null);
+ if (StringUtils.isBlank(result)) {
+ return String.format("change the global session status failed,
xid: %s", xid);
+ } else {
+ return result;
+ }
+ }
+
+ @McpTool(description = "Check out the abnormal transaction")
+ public List<McpGlobalSessionVO> getAbnormalSessions(
+ @McpToolParam(description = "Specify the namespace of the TC
node") NameSpaceDetail nameSpaceDetail,
+ @McpToolParam(description = "Query Param")
McpGlobalAbnormalSessionParam abnormalSessionParam) {
+ List<McpGlobalSessionVO> result = new ArrayList<>();
+ McpGlobalSessionParamDto param =
McpGlobalSessionParamDto.convertFromAbnormalParam(abnormalSessionParam);
+ param.setPageSize(ABNORMAL_SESSION_PAGE_SIZE);
+ for (Integer status : exceptionStatus) {
+ param.setStatus(status);
+ List<McpGlobalSessionVO> datas =
+ queryGlobalSession(nameSpaceDetail, param).getData();
+ if (datas != null && !datas.isEmpty()) {
+ result.addAll(datas);
+ }
+ }
+ return result;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/tools/ModifyConfirmTools.java
b/console/src/main/java/org/apache/seata/mcp/tools/ModifyConfirmTools.java
new file mode 100644
index 0000000000..39663c1ed2
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/tools/ModifyConfirmTools.java
@@ -0,0 +1,59 @@
+/*
+ * 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.seata.mcp.tools;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.service.ModifyConfirmService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springaicommunity.mcp.annotation.McpToolParam;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Service
+public class ModifyConfirmTools {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ModifyConfirmTools.class);
+
+ private final ModifyConfirmService modifyConfirmService;
+
+ public ModifyConfirmTools(ModifyConfirmService modifyConfirmService) {
+ this.modifyConfirmService = modifyConfirmService;
+ }
+
+ @McpTool(
+ description = "Before modifying (update or delete) a transaction
or lock, the user MUST manually confirm."
+ + "You are NOT allowed to fabricate or auto-confirm on
behalf of the user.")
+ public Map<String, String> confirmAndGetKey(
+ @McpToolParam(
+ description =
+ "The confirmation string provided by the
USER (not generated by the LLM).The content must repeat the modification action
clearly.")
+ String userInputStr) {
+ if (StringUtils.isBlank(userInputStr)) {
+ throw new IllegalArgumentException("User confirmation string is
required.");
+ }
+ if (!userInputStr.contains("确认") && !userInputStr.contains("confirm"))
{
+ throw new IllegalArgumentException(
+ "Confirmation string must explicitly contain '确认' or
'confirm' and repeat the modification content. This must come from the user.");
+ }
+ Map<String, String> keyMap = modifyConfirmService.confirmAndGetKey();
+ LOGGER.info("the user obtains a modify key:{}",
keyMap.get("modify_key"));
+ return keyMap;
+ }
+}
diff --git
a/console/src/main/java/org/apache/seata/mcp/tools/NameSpaceTools.java
b/console/src/main/java/org/apache/seata/mcp/tools/NameSpaceTools.java
new file mode 100644
index 0000000000..1decea8773
--- /dev/null
+++ b/console/src/main/java/org/apache/seata/mcp/tools/NameSpaceTools.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.mcp.tools;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.result.SingleResult;
+import org.apache.seata.mcp.core.constant.RPCConstant;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.springaicommunity.mcp.annotation.McpTool;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class NameSpaceTools {
+
+ private final ConsoleApiService mcpRPCService;
+
+ private final ObjectMapper objectMapper;
+
+ public NameSpaceTools(ConsoleApiService mcpRPCService, ObjectMapper
objectMapper) {
+ this.mcpRPCService = mcpRPCService;
+ this.objectMapper = objectMapper;
+ }
+
+ @McpTool(description = "Get the namespace and cluster or vgroup where all
TC/Servers are located")
+ public SingleResult<?> getTCNameSpaces() {
+ String result =
mcpRPCService.getCallNameSpace(RPCConstant.GET_NAMESPACE_PATH);
+ Map<String, Object> nameSpacesVo = new HashMap<>();
+ try {
+ JsonNode root = objectMapper.readTree(result);
+ JsonNode dataNode = root.get("data");
+ if (dataNode != null && !dataNode.isNull()) {
+ nameSpacesVo.put("namespaces", dataNode.toString());
+ }
+ } catch (JsonProcessingException e) {
+ return SingleResult.failure("Get namespace failed:" +
e.getMessage());
+ }
+ return SingleResult.success(nameSpacesVo);
+ }
+}
diff --git
a/namingserver/src/main/java/org/apache/seata/namingserver/service/ConsoleLocalServiceImpl.java
b/namingserver/src/main/java/org/apache/seata/namingserver/service/ConsoleLocalServiceImpl.java
new file mode 100644
index 0000000000..808f46552d
--- /dev/null
+++
b/namingserver/src/main/java/org/apache/seata/namingserver/service/ConsoleLocalServiceImpl.java
@@ -0,0 +1,169 @@
+/*
+ * 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.seata.namingserver.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.metadata.ClusterRole;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.metadata.namingserver.NamingServerNode;
+import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.mcp.core.props.NameSpaceDetail;
+import org.apache.seata.mcp.exception.ServiceCallException;
+import org.apache.seata.mcp.service.ConsoleApiService;
+import org.apache.seata.mcp.service.impl.ConsoleRemoteServiceImpl;
+import org.apache.seata.namingserver.manager.NamingManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.apache.seata.common.Constants.RAFT_GROUP_HEADER;
+import static org.apache.seata.mcp.core.utils.UrlUtils.buildUrl;
+import static org.apache.seata.mcp.core.utils.UrlUtils.objectToQueryParamMap;
+
+@ConditionalOnBean(ConsoleRemoteServiceImpl.class)
+@Primary
+@Service
+public class ConsoleLocalServiceImpl implements ConsoleApiService {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private final NamingManager namingManager;
+
+ private final RestTemplate restTemplate;
+
+ private final ObjectMapper objectMapper;
+
+ public ConsoleLocalServiceImpl(NamingManager namingManager, RestTemplate
restTemplate, ObjectMapper objectMapper) {
+ this.namingManager = namingManager;
+ this.restTemplate = restTemplate;
+ this.objectMapper = objectMapper;
+ }
+
+ public String getResult(
+ NameSpaceDetail nameSpaceDetail,
+ HttpMethod httpMethod,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ String namespace = nameSpaceDetail.getNamespace();
+ String cluster = nameSpaceDetail.getCluster();
+ String vgroup = nameSpaceDetail.getvGroup();
+ if (StringUtils.isNotBlank(namespace) &&
(StringUtils.isNotBlank(cluster) || StringUtils.isNotBlank(vgroup))) {
+ List<NamingServerNode> list = null;
+ if (StringUtils.isNotBlank(vgroup)) {
+ list = namingManager.getInstancesByVgroupAndNamespace(
+ namespace, vgroup, HttpMethod.GET.equals(httpMethod));
+ } else if (StringUtils.isNotBlank(cluster)) {
+ list = namingManager.getInstances(namespace, cluster);
+ }
+ if (CollectionUtils.isNotEmpty(list)) {
+ // Randomly select a node from the list
+ NamingServerNode node =
list.get(ThreadLocalRandom.current().nextInt(list.size()));
+ Node.Endpoint controlEndpoint = node.getControl();
+ if (controlEndpoint != null) {
+ // Construct the target URL
+ String baseUrl = "http://" + controlEndpoint.getHost() +
":" + controlEndpoint.getPort();
+ Map<String, Object> queryParamsMap =
objectToQueryParamMap(objectQueryParams, objectMapper);
+ String targetUrl = buildUrl(baseUrl, path, queryParams,
queryParamsMap);
+ if (node.getRole() == ClusterRole.LEADER) {
+ headers.add(RAFT_GROUP_HEADER, node.getUnit());
+ }
+ HttpEntity<String> entity = new HttpEntity<>(headers);
+ String responseBody;
+ try {
+ ResponseEntity<String> response =
+ restTemplate.exchange(targetUrl, httpMethod,
entity, String.class);
+
+ responseBody = response.getBody();
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ String errorMsg = String.format(
+ "MCP request failed with status: %s,
response: %s",
+ response.getStatusCode(),
response.getBody());
+ logger.warn(errorMsg);
+ throw new ServiceCallException(errorMsg,
response.getStatusCode());
+ }
+ return responseBody;
+ } catch (RestClientException e) {
+ String errorMsg = "MCP Call TC Failed.";
+ logger.error(errorMsg, e);
+ throw new ServiceCallException(errorMsg);
+ }
+ }
+ }
+ throw new IllegalArgumentException("Couldn't find target node
url");
+ }
+ throw new IllegalArgumentException("Invalid NameSpace Detail");
+ }
+
+ @Override
+ public String getCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ return getResult(nameSpaceDetail, HttpMethod.GET, path,
objectQueryParams, queryParams, headers);
+ }
+
+ @Override
+ public String deleteCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ return getResult(nameSpaceDetail, HttpMethod.DELETE, path,
objectQueryParams, queryParams, headers);
+ }
+
+ @Override
+ public String putCallTC(
+ NameSpaceDetail nameSpaceDetail,
+ String path,
+ Object objectQueryParams,
+ Map<String, String> queryParams,
+ HttpHeaders headers) {
+ return getResult(nameSpaceDetail, HttpMethod.PUT, path,
objectQueryParams, queryParams, headers);
+ }
+
+ @Override
+ public String getCallNameSpace(String path) {
+ String namespace;
+ try {
+ namespace =
objectMapper.writeValueAsString(namingManager.namespace());
+ } catch (JsonProcessingException e) {
+ logger.error("Get NameSpace failed: {}", e.getMessage());
+ return "Failed to get namespace";
+ }
+ return namespace;
+ }
+}
diff --git
a/namingserver/src/main/java/org/apache/seata/namingserver/service/NamingServerLocalMarkerImpl.java
b/namingserver/src/main/java/org/apache/seata/namingserver/service/NamingServerLocalMarkerImpl.java
new file mode 100644
index 0000000000..bc6e22e6e9
--- /dev/null
+++
b/namingserver/src/main/java/org/apache/seata/namingserver/service/NamingServerLocalMarkerImpl.java
@@ -0,0 +1,23 @@
+/*
+ * 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.seata.namingserver.service;
+
+import org.apache.seata.common.NamingServerLocalMarker;
+import org.springframework.stereotype.Service;
+
+@Service
+public class NamingServerLocalMarkerImpl implements NamingServerLocalMarker {}
diff --git a/namingserver/src/main/resources/application.yml
b/namingserver/src/main/resources/application.yml
index ba10362f93..b76b4638e5 100644
--- a/namingserver/src/main/resources/application.yml
+++ b/namingserver/src/main/resources/application.yml
@@ -40,6 +40,11 @@ logging:
heartbeat:
threshold: 90000
period: 60000
+console:
+ namingserver:
+ protocol: http
+ addr:
+ - 127.0.0.1:8081
seata:
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
@@ -51,6 +56,4 @@ seata:
mcp:
# Maximum query time interval, The unit is milliseconds, Default one day
query:
- max-query-duration: 86400000
- auth:
- enabled: true
\ No newline at end of file
+ max-query-duration: 86400000
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]