Prechádzať zdrojové kódy

WebMvc模式转换位WebFlux模式

andyliu 1 mesiac pred
rodič
commit
59c32a7810

+ 1 - 1
pom.xml

@@ -56,7 +56,7 @@
     <dependencies>
         <dependency>
             <groupId>org.springframework.ai</groupId>
-            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
+            <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
             <exclusions>
                 <exclusion>
                     <groupId>org.springframework.boot</groupId>

+ 1 - 1
src/main/java/com/shkpr/service/mcpcenterservice/KprMcpCenterServiceApplication.java

@@ -24,7 +24,7 @@ public class KprMcpCenterServiceApplication {
                 .build();
     }
     /**
-     * 1) 现在cmd终点调用:curl -N -i -X GET "http://127.0.0.1:9100/kpr-mcp-center/sse"
+     * 1) 现在cmd终点调用:curl -N -i -X GET -H "Authorization: xxx" "http://127.0.0.1:9100/kpr-mcp-center/sse"
      *    以获取sessionId并保持连接
      * 2) 以post方法调用:http://127.0.0.1:9100/kpr-mcp-center/sse/message?sessionId=xxx
      *    ```

+ 38 - 0
src/main/java/com/shkpr/service/mcpcenterservice/commtools/HttpUtils.java

@@ -0,0 +1,38 @@
+package com.shkpr.service.mcpcenterservice.commtools;
+
+import com.shkpr.service.mcpcenterservice.constants.ApiURI;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.StringUtils;
+
+import java.net.InetSocketAddress;
+
+public class HttpUtils {
+    public static String getIpAddress(ServerHttpRequest request) {
+        String fromSource = ApiURI.HEADER_X_SOURCE_IP;
+        String ip = request.getHeaders().getFirst(fromSource);
+        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)){
+            fromSource = "X-Real-IP";
+            ip = request.getHeaders().getFirst(fromSource);
+        }
+        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+            fromSource = "X-Forwarded-For";
+            ip = request.getHeaders().getFirst(fromSource);
+        }
+        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+            fromSource = "Proxy-Client-IP";
+            ip = request.getHeaders().getFirst(fromSource);
+        }
+        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+            fromSource = "WL-Proxy-Client-IP";
+            ip = request.getHeaders().getFirst(fromSource);
+        }
+        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+            fromSource = "request.getRemoteAddr";
+            InetSocketAddress addr = request.getRemoteAddress();
+            ip = addr != null ? addr.getAddress().getHostAddress() : "unknown";
+        }
+        if (!StringUtils.isEmpty(ip) && ip.contains(","))
+            return ip.split(",")[0];
+        return ip;
+    }
+}

+ 1 - 1
src/main/java/com/shkpr/service/mcpcenterservice/commtools/JwtTokenUtil.java

@@ -5,7 +5,7 @@ import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.ExpiredJwtException;
 import io.jsonwebtoken.Jwts;
 
-public class JwtTokenUtil {
+public class JwtTokenUtils {
     public static final String SECRET = "TRICP_ALAM_DMA";                              // JWT密码
     public static final String CLAIM_FLAGKEY = "flagkey";
     public static final String CLAIM_ACCOUNT = "account";

+ 23 - 21
src/main/java/com/shkpr/service/mcpcenterservice/configuration/McpSecurityConfig.java

@@ -5,17 +5,17 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpStatus;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.HttpStatusEntryPoint;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
+import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
 
 @Configuration
-@EnableWebSecurity
-@EnableMethodSecurity(prePostEnabled = true)
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
 public class McpSecurityConfig {
     @Value("${spring.ai.mcp.server.sseEndpoint:/kpr-mcp-center/sse}")
     private String mcpSSEPath;
@@ -24,18 +24,20 @@ public class McpSecurityConfig {
     private String mcpSSEMsgPath;
 
     @Bean
-    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-        http
-                .csrf(csrf -> csrf.disable())
-                .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
-                .authorizeHttpRequests(auth -> auth
-                        .requestMatchers("/").permitAll()
-                        .requestMatchers("/actuator/health").permitAll()
-                        .requestMatchers(mcpSSEPath, mcpSSEMsgPath).authenticated()
-                        .anyRequest().authenticated()
-                )
-                .exceptionHandling(eh -> eh.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
-                .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
+    public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
+        http.csrf(csrf -> csrf.disable())
+            .httpBasic(basic -> basic.disable())
+            .formLogin(form -> form.disable())
+            .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
+            .authorizeExchange(auth -> auth
+                    .pathMatchers("/").permitAll()
+                    .pathMatchers("/actuator/health").permitAll()
+                    .pathMatchers(mcpSSEPath, mcpSSEMsgPath).authenticated()
+                    .anyExchange().authenticated()
+            )
+            .exceptionHandling(eh -> eh.authenticationEntryPoint(
+                    new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)))
+            .addFilterAt(new JwtAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION);
 
         return http.build();
     }

+ 1 - 6
src/main/java/com/shkpr/service/mcpcenterservice/constants/ApiURI.java

@@ -1,11 +1,5 @@
 package com.shkpr.service.mcpcenterservice.constants;
 
-/**
- * 认证相关全局常量。
- * <p>
- * 包括 HTTP Header 名、JWT claim 字段名、认证错误码等。
- * 错误码与 {@code ResponseRes} 统一格式中的 {@code rescode} 对齐。
- */
 public final class ApiURI {
 
     private ApiURI() {
@@ -15,6 +9,7 @@ public final class ApiURI {
     public static final String HEADER_AUTHORIZATION = "Authorization";
     public static final String BEARER_PREFIX = "Bearer ";
     public static final String QUERY_TOKEN_PARAM = "access_token";
+    public static final String HEADER_X_SOURCE_IP = "X-Source-IP";
 
 
     public static final String ROLE_AUTHORITY_PREFIX = "ROLE_";

+ 60 - 82
src/main/java/com/shkpr/service/mcpcenterservice/filters/JwtAuthenticationFilter.java

@@ -3,33 +3,35 @@ package com.shkpr.service.mcpcenterservice.filters;
 import com.global.base.log.LogLevelFlag;
 import com.global.base.log.LogPrintMgr;
 import com.global.base.tools.FastJsonUtil;
-import com.shkpr.service.mcpcenterservice.commtools.JwtTokenUtil;
+import com.shkpr.service.mcpcenterservice.commtools.HttpUtils;
+import com.shkpr.service.mcpcenterservice.commtools.JwtTokenUtils;
 import com.shkpr.service.mcpcenterservice.constants.ApiURI;
 import com.shkpr.service.mcpcenterservice.dto.LogFlagBizType;
 import com.shkpr.service.mcpcenterservice.dto.McpAuthUser;
 import com.shkpr.service.mcpcenterservice.dto.ResponseCode;
 import com.shkpr.service.mcpcenterservice.dto.ResponseRes;
-import com.shkpr.service.mcpcenterservice.globalmgr.McpAuthContextMgr;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.core.io.buffer.DataBuffer;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
-import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
 
-import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
-public class JwtAuthenticationFilter extends OncePerRequestFilter {
-    private String mStrClassName = "";
-    private String mBusinessType = "";
-    private String logTag = "";
+public class JwtAuthenticationFilter implements WebFilter {
+    private final String mStrClassName;
+    private final String mBusinessType;
+    private final String logTag;
+
     public JwtAuthenticationFilter() {
         mStrClassName = this.getClass().getSimpleName();
         mBusinessType = LogFlagBizType.BIZ_AUTH.toStrValue();
@@ -37,99 +39,75 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
     }
 
     @Override
-    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
         String token = extractToken(request);
+
         if (StringUtils.isEmpty(token)) {
-            writeAuthError(response, ResponseCode.STATUS_EMPTY_TOKEN);
-            LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_WARN, mBusinessType, mStrClassName
-                    , String.format("Empty Token, Uri{%s} Remote{%s:%d}"
-                            ,request.getRequestURI()
-                            ,request.getRemoteAddr()
-                            ,request.getRemotePort()));
-            return;
+            logWarn(request, "Empty Token");
+            return writeAuthError(response, ResponseCode.STATUS_EMPTY_TOKEN);
         }
 
-        McpAuthUser user = null;
+        McpAuthUser user;
         try {
-            user = JwtTokenUtil.toAuthUser(token);
-            List<SimpleGrantedAuthority> authorities = StringUtils.isEmpty(user.getRoleId())
-                    ? List.of() : List.of(new SimpleGrantedAuthority(ApiURI.ROLE_AUTHORITY_PREFIX + user.getRoleId()));
-
-            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
-            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
-            SecurityContextHolder.getContext().setAuthentication(authentication);
-            McpAuthContextMgr.set(token, user);
+            user = JwtTokenUtils.toAuthUser(token);
         } catch (Exception ex) {
-            SecurityContextHolder.clearContext();
-            McpAuthContextMgr.clear();
-            writeAuthError(response, ResponseCode.STATUS_INVALID_TOKEN);
-            LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_WARN, mBusinessType, mStrClassName
-                    , String.format("Token Parse Failed, Uri{%s} Remote{%s:%d}"
-                            ,request.getRequestURI()
-                            ,request.getRemoteAddr()
-                            ,request.getRemotePort()));
-            return;
+            logWarn(request, "Token Parse Failed");
+            return writeAuthError(response, ResponseCode.STATUS_INVALID_TOKEN);
         }
 
-        if (user == null){
-            SecurityContextHolder.clearContext();
-            McpAuthContextMgr.clear();
-            writeAuthError(response, ResponseCode.STATUS_INVALID_TOKEN);
-            LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_WARN, mBusinessType, mStrClassName
-                    , String.format("Invalid Token, Uri{%s} Remote{%s:%d}"
-                            ,request.getRequestURI()
-                            ,request.getRemoteAddr()
-                            ,request.getRemotePort()));
-            return;
-        }else {
-            if (user.getExpiredTm() < System.currentTimeMillis()){
-                SecurityContextHolder.clearContext();
-                McpAuthContextMgr.clear();
-                writeAuthError(response, ResponseCode.STATUS_EXPIRED_TOKEN);
-                LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_WARN, mBusinessType, mStrClassName
-                        , String.format("Token Expired, Uri{%s} Remote{%s:%d}"
-                                ,request.getRequestURI()
-                                ,request.getRemoteAddr()
-                                ,request.getRemotePort()));
-                return;
-            }
+        if (user == null) {
+            logWarn(request, "Invalid Token");
+            return writeAuthError(response, ResponseCode.STATUS_INVALID_TOKEN);
         }
 
-        try {
-            filterChain.doFilter(request, response);
-        } finally {
-            McpAuthContextMgr.clear();
-            SecurityContextHolder.clearContext();
+        if (user.getExpiredTm() < System.currentTimeMillis()) {
+            logWarn(request, "Token Expired");
+            return writeAuthError(response, ResponseCode.STATUS_EXPIRED_TOKEN);
         }
+
+        List<SimpleGrantedAuthority> authorities = StringUtils.isEmpty(user.getRoleId())
+                ? List.of()
+                : List.of(new SimpleGrantedAuthority(ApiURI.ROLE_AUTHORITY_PREFIX + user.getRoleId()));
+
+        UsernamePasswordAuthenticationToken authentication =
+                new UsernamePasswordAuthenticationToken(user, token, authorities);
+
+        return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
     }
 
-    private String extractToken(HttpServletRequest request) {
-        String header = request.getHeader(ApiURI.HEADER_AUTHORIZATION);
+    private String extractToken(ServerHttpRequest request) {
+        String header = request.getHeaders().getFirst(ApiURI.HEADER_AUTHORIZATION);
         if (!StringUtils.isEmpty(header))
             return header.trim();
 
-        String queryToken = request.getParameter(ApiURI.QUERY_TOKEN_PARAM);
+        String queryToken = request.getQueryParams().getFirst(ApiURI.QUERY_TOKEN_PARAM);
         if (!StringUtils.isEmpty(queryToken))
             return queryToken.trim();
         return "";
     }
 
-    private void writeAuthError(HttpServletResponse response, ResponseCode code) throws IOException {
-        response.setHeader("Access-Control-Allow-Origin", "*");
-        response.setHeader("Access-Control-Allow-Methods", "*");
-        response.setHeader("Access-Control-Allow-Headers", ApiURI.ALLOW_HEADERS);
-        response.setStatus(HttpStatus.UNAUTHORIZED.value());
-        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
+    private Mono<Void> writeAuthError(ServerHttpResponse response, ResponseCode code) {
+        response.getHeaders().set("Access-Control-Allow-Origin", "*");
+        response.getHeaders().set("Access-Control-Allow-Methods", "*");
+        response.getHeaders().set("Access-Control-Allow-Headers", ApiURI.ALLOW_HEADERS);
+        response.setStatusCode(HttpStatus.UNAUTHORIZED);
+        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
 
-        ResponseRes<String> resResult = new ResponseRes<String>();
+        ResponseRes<String> resResult = new ResponseRes<>();
         resResult.setRescode(code.toStrCode());
         resResult.setResmsg(code.toStrMsg());
         resResult.setResdata(code.toString());
         resResult.setTimestamp(System.currentTimeMillis());
-        try {
-            response.getWriter().write(FastJsonUtil.toJSON(resResult));
-            response.getWriter().flush();
-        }catch (Exception e){
-        }
+
+        byte[] bytes = FastJsonUtil.toJSON(resResult).getBytes(StandardCharsets.UTF_8);
+        DataBuffer buffer = response.bufferFactory().wrap(bytes);
+        return response.writeWith(Mono.just(buffer));
+    }
+
+    private void logWarn(ServerHttpRequest request, String msg) {
+        LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_WARN, mBusinessType, mStrClassName,
+                String.format("%s, Uri{%s} Remote{%s}", msg, request.getURI().getPath(), HttpUtils.getIpAddress(request)));
     }
 }

+ 64 - 12
src/main/java/com/shkpr/service/mcpcenterservice/globalmgr/McpAuthContextMgr.java

@@ -1,28 +1,80 @@
 package com.shkpr.service.mcpcenterservice.globalmgr;
 
 import com.shkpr.service.mcpcenterservice.dto.McpAuthUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolder;
+import reactor.core.publisher.Mono;
 
 public final class McpAuthContextMgr {
-    private static final ThreadLocal<String> TOKEN_HOLDER = new ThreadLocal<>();
-    private static final ThreadLocal<McpAuthUser> USER_HOLDER = new ThreadLocal<>();
-
     private McpAuthContextMgr() {}
 
-    public static void set(String rawToken, McpAuthUser user) {
-        TOKEN_HOLDER.set(rawToken);
-        USER_HOLDER.set(user);
+    public static McpAuthUser getCurrentUser() {
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth != null && auth.getPrincipal() instanceof McpAuthUser user) {
+            return user;
+        }
+        return null;
     }
 
     public static String getCurrentToken() {
-        return TOKEN_HOLDER.get();
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth != null && auth.getCredentials() instanceof String token) {
+            return token;
+        }
+        return null;
     }
 
-    public static McpAuthUser getCurrentUser() {
-        return USER_HOLDER.get();
+    /*public static McpAuthUser requireCurrentUser() {
+        McpAuthUser user = getCurrentUser();
+        if (user != null) return user;
+
+        return ReactiveSecurityContextHolder.getContext()
+                .mapNotNull(ctx -> {
+                    Authentication auth = ctx.getAuthentication();
+                    if (auth != null && auth.getPrincipal() instanceof McpAuthUser u) return u;
+                    return null;
+                })
+                .block();
+    }
+
+    public static String requireCurrentToken() {
+        String token = getCurrentToken();
+        if (token != null) return token;
+
+        return ReactiveSecurityContextHolder.getContext()
+                .mapNotNull(ctx -> {
+                    Authentication auth = ctx.getAuthentication();
+                    if (auth != null && auth.getCredentials() instanceof String t) return t;
+                    return null;
+                })
+                .block();
+    }*/
+
+    public static Mono<McpAuthUser> rxCurrentUser() {
+        McpAuthUser fast = getCurrentUser();
+        if (fast != null)
+            return Mono.just(fast);
+
+        return ReactiveSecurityContextHolder.getContext()
+                .mapNotNull(ctx -> {
+                    Authentication auth = ctx.getAuthentication();
+                    if (auth != null && auth.getPrincipal() instanceof McpAuthUser u)
+                        return u;
+                    return null;
+                });
     }
 
-    public static void clear() {
-        TOKEN_HOLDER.remove();
-        USER_HOLDER.remove();
+    public static Mono<String> rxCurrentToken() {
+        String fast = getCurrentToken();
+        if (fast != null)
+            return Mono.just(fast);
+
+        return ReactiveSecurityContextHolder.getContext()
+                .mapNotNull(ctx -> {
+                    Authentication auth = ctx.getAuthentication();
+                    if (auth != null && auth.getCredentials() instanceof String t) return t;
+                    return null;
+                });
     }
 }

+ 35 - 41
src/main/java/com/shkpr/service/mcpcenterservice/mcptool/DateMcpTool.java

@@ -1,87 +1,81 @@
 package com.shkpr.service.mcpcenterservice.mcptool;
 
 import com.global.base.tools.FastJsonUtil;
-import com.shkpr.service.mcpcenterservice.dto.McpAuthUser;
 import com.shkpr.service.mcpcenterservice.globalmgr.McpAuthContextMgr;
 import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
 import io.modelcontextprotocol.spec.McpSchema.TextContent;
 import org.springaicommunity.mcp.annotation.McpTool;
 import org.springaicommunity.mcp.annotation.McpToolParam;
 import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
 
 import java.time.LocalDate;
 import java.util.List;
 
 /**
  * 注:1) 使用@McpTool的工具方法,会被自动扫描注册,前提是所在类型添加了@Service或@Component注解
- *    2) 当@McpTool的工具方法返回的是CallToolResult结构时,服务不会在进行封装,而是直接返回
- *    3) 当@McpTool的工具方法返回的是非CallToolResult结构时,服务会自动封装成CallToolResult结构后再返回
+ *    2) 使用@McpTool的工具方法,WebFlux/WebMVC的SYNC模式下,方法返回的是CallToolResult类型时,服务不会在进行封装,而是直接返回
+ *    3) 使用@McpTool的工具方法,WebFlux/WebMVC的SYNC模式下,方法返回的是非CallToolResult类型时,服务会自动封装成CallToolResult结构后再返回
+ *    4) 使用@McpTool的工具方法,WebFlux的ASYNC模式下,方法返回任何类型,都需要自己显示地封装成Mono<>
  */
 @Service
 public class DateMcpTool {
-    /**
-     *
-     * @param days
-     * @return
-     */
+
     @McpTool(name = "addDays", description = "Adds days to the current date")
-    public CallToolResult addDays(@McpToolParam(description = "The number of days to add", required = true) Integer days) {
-        if (days == null){
-            return new CallToolResult(
+    public Mono<CallToolResult> addDays(@McpToolParam(description = "The number of days to add", required = true) Integer days) {
+        if (days == null) {
+            return Mono.just(new CallToolResult(
                     List.of(new TextContent("缺少必要参数days,无法完成存储操作。")),
                     true
-            );
+            ));
         }
-        if (days < 0){
-            return new CallToolResult(
+        if (days < 0) {
+            return Mono.just(new CallToolResult(
                     List.of(new TextContent("参数days不能小于0,请重新输入。")),
                     true
-            );
+            ));
         }
-        return new CallToolResult(
+        return Mono.just(new CallToolResult(
                 List.of(new TextContent(LocalDate.now().plusDays(days).toString())),
                 false
-        );
+        ));
     }
 
     @McpTool(name = "subtractDays", description = "Subtracts days from the current date")
-    public CallToolResult subtractDays(@McpToolParam(description = "The number of days to subtract", required = true) Integer days) {
-        if (days == null){
-            return new CallToolResult(
+    public Mono<CallToolResult> subtractDays(@McpToolParam(description = "The number of days to subtract", required = true) Integer days) {
+        if (days == null) {
+            return Mono.just(new CallToolResult(
                     List.of(new TextContent("缺少必要参数days,无法完成存储操作。")),
                     true
-            );
+            ));
         }
-        if (days < 0){
-            return new CallToolResult(
+        if (days < 0) {
+            return Mono.just(new CallToolResult(
                     List.of(new TextContent("参数days不能小于0,请重新输入。")),
                     true
-            );
+            ));
         }
-        return new CallToolResult(
+        return Mono.just(new CallToolResult(
                 List.of(new TextContent(LocalDate.now().minusDays(days).toString())),
                 false
-        );
+        ));
     }
 
-    //服务会自动将string封装成CallToolResult后再返回给前端
     @McpTool(name = "today", description = "Return current date")
-    public String today() {
-        return LocalDate.now().toString();
+    public Mono<String> today() {
+        return Mono.just(LocalDate.now().toString());
     }
 
     @McpTool(name = "myUser", description = "Returns current user info.")
-    public CallToolResult myUser() {
-        McpAuthUser user = McpAuthContextMgr.getCurrentUser();
-        if (user == null) {
-            return new CallToolResult(
-                    List.of(new TextContent("未检查到Token令牌")),
-                    true
-            );
-        }
-        return new CallToolResult(
-                List.of(new TextContent(FastJsonUtil.toJSON(user))),
-                false
-        );
+    public Mono<CallToolResult> myUser() {
+        return McpAuthContextMgr.rxCurrentUser()
+                .map(user -> new CallToolResult(
+                        List.of(new TextContent(FastJsonUtil.toJSON(user))),
+                        false
+                ))
+                .defaultIfEmpty(new CallToolResult(
+                        List.of(new TextContent("无法识别当前用户")),
+                        true
+                ));
     }
 }

+ 2 - 1
src/main/java/com/shkpr/service/mcpcenterservice/mcptool/MathMcpTool.java

@@ -7,7 +7,8 @@ import org.springframework.stereotype.Service;
 /**
  * 注:1) 使用@Tool的工具方法,不会被自动扫描注册,需要显示调用MethodToolCallbackProvider.builder()进行注册
  *       [!!!]哪怕是所在类型添加了@Service或@Component注解[!!!]
- *    2) 使用@Tool的工具方法,其返回参数不管是什么类型,都会被自动封装成CallToolResult结构后再返回
+ *    2) 使用@Tool的工具方法,WebFlux/WebMVC的SYNC模式下,方法返回任何类型,都会被自动封装成CallToolResult结构后再返回
+ *    3) 使用@Tool的工具方法,WebFlux的ASYNC模式下,方法返回任何类型,都会被自动封装成Mono<CallToolResult>结构后再返回
  */
 @Service
 public class MathMcpTool {

+ 4 - 16
src/main/resources/application.properties

@@ -1,22 +1,10 @@
-#\u9879\u76EE\u7684\u5168\u5C40\u914D\u7F6E==============>
+#项目的全局配置=============>
 spring.application.name=kpr-mcp-center-service
-server.servlet.context-path=/
 server.error.path= /
-# \u670D\u52A1\u7AEF\u53E3
+# 服务端口
 server.port=9100
-server.tomcat.max-http-post-size=-1
-spring.servlet.multipart.max-file-size=200MB
-spring.servlet.multipart.max-request-size=200MB
-
-server.servlet.session.timeout=60s
 server.connection-timeout=60s
 
-#Tomcat\u914D\u7F6E================>
-server.tomcat.uri-encoding=UTF-8
-server.tomcat.basedir=./trilog/tomcattmp
-server.tomcat.access-log-enabled=false
-server.tomcat.access-log-pattern=common
-
 global.sql.config.path=./sql.properties
 
 spring.ai.mcp.server.request-timeout=60s
@@ -31,7 +19,7 @@ spring.ai.mcp.server.tool-change-notification=true
 spring.ai.mcp.server.prompt-change-notification=true
 spring.ai.mcp.server.sse-endpoint=/kpr-mcp-center/sse
 spring.ai.mcp.server.sse-message-endpoint=/kpr-mcp-center/sse/message
-spring.ai.mcp.server.type=SYNC
+spring.ai.mcp.server.type=ASYNC
 spring.ai.mcp.server.capabilities.completion=true
 spring.ai.mcp.server.capabilities.prompt=true
 spring.ai.mcp.server.capabilities.tool=true
@@ -39,4 +27,4 @@ spring.ai.mcp.server.capabilities.resource=true
 spring.ai.mcp.server.capabilities.ping=true
 
 #logging.level.io.modelcontextprotocol=DEBUG
-#logging.level.org.springframework.ai.mcp=DEBUG
+#logging.level.org.springframework.ai.mcp=DEBUG