乐于分享
好东西不私藏

SpringAI+MCPClient实战-MiniMax模型打造智能AIAgent

SpringAI+MCPClient实战-MiniMax模型打造智能AIAgent

文章目录

    • 一、前言
    • 二、MCP Client 核心概念
      • 三、完整实战项目
        • 四、一次工具调用的完整链路分析
          • 五、端到端测试
          • 六、部署方案
            • 七、总结

          一、前言

          各位好,上一篇我们聊了 MCP Server 端的架构设计和实战代码,今天来聊聊 MCP Client 端。在实际项目中,MCP Client 才是真正面向用户的 AI 应用核心,它负责连接各种 MCP Server,自动发现并调用工具,让大模型具备"动手"的能力。

          本文与上一篇的关系

          为什么选 MiniMax 作为示例模型?因为国内大模型里,MiniMax 的 API 设计非常规范,而且 Spring AI 1.1.5 版本已经原生支持 MiniMax,配合 MCP 协议,简直是天作之合。

          本文特色:一个 Demo 项目搞定核心功能!我们将构建一个轻量的 MCP Client 项目,包含:

          • 连接多个 MCP Server(天气、订单、文件系统等)
          • 本地工具与 MCP 工具混合使用
          • 动态工具加载(基于角色配置)
          • 同步和流式对话
          • 对话历史管理
          • 完整的端到端测试

          废话不多说,直接上干货!

          二、MCP Client 核心概念

          2.1 MCP Client 是什么?

          MCP Client 是 AI 应用与 MCP Server 之间的桥梁。它负责:

          • 协议协商
            :与 Server 协商 MCP 协议版本和能力
          • 工具发现
            :自动获取 Server 暴露的工具列表和 JSON Schema
          • 工具调用
            :将 LLM 的工具调用请求转发给 Server
          • 结果回传
            :将 Server 的执行结果返回给 LLM

          2.2 核心术语解释

          术语
          解释
          类比
          MCP (Model Context Protocol)
          模型上下文协议,AI 应用与工具服务之间的标准通信协议
          就像 USB 协议,让不同设备可以即插即用
          MCP Client
          连接 MCP Server 的客户端,负责工具发现和调用
          就像浏览器,连接各种网站服务
          MCP Server
          提供工具服务的后端,暴露工具给 Client 调用
          就像网站服务器,提供各种 API
          ToolCallback
          Spring AI 中的工具回调接口,统一本地工具和 MCP 工具
          就像统一的插头接口,不管什么电器都能插
          ChatClient
          Spring AI 的对话客户端,构建 Prompt 并调用模型
          就像智能助手的大脑,理解用户意图并执行
          ChatModel
          大模型接口(MiniMax、OpenAI 等),负责与 LLM 通信
          就像 AI 的思考引擎
          Streamable HTTP
          MCP 的传输协议,基于 HTTP 的流式通信
          就像 WebSocket,支持实时双向通信
          JSON-RPC
          MCP 的通信协议格式,基于 JSON 的远程过程调用
          就像 REST API,但使用 JSON 格式

          2.3 MCP Client 架构全景

          2.4 MCP Client 传输方式对比

          传输方式
          适用场景
          优势
          劣势
          Streamable HTTP远程服务、微服务断线重连、会话恢复、统一端点需要 HTTP 基础设施
          SSE
          远程服务(旧版)
          实现简单
          断线无法恢复,逐步弃用
          STDIO
          本地进程
          简单安全,无需网络
          仅支持本地,无法跨网络

          三、完整实战项目

          3.1 项目功能清单

          我们将构建一个轻量的 MCP Client Demo 项目,包含以下功能:

          功能
          说明
          实现方式
          多 MCP Server 连接
          同时连接天气、订单、文件等多个 Server
          spring.ai.mcp.client.connections
           配置
          本地工具
          项目内的 Java 工具方法
          Function
           接口
          混合工具调用
          LLM 自动选择本地或 MCP 工具
          统一注册到 ChatClient
          动态工具加载
          基于角色配置动态加载工具
          Mock 数据模拟角色配置
          同步对话
          一次性返回完整结果
          chatClient.prompt().call()
          流式对话
          实时流式返回结果
          chatClient.prompt().stream()
          对话历史
          支持多轮对话上下文
          ChatMemory
          工具调用日志
          记录工具调用详情
          Advisor
           拦截器

          3.2 项目结构

          mcp-client-demo/├── pom.xml                          # Maven 依赖配置├── src/│   ├── main/│   │   ├── java/│   │   │   └── com/example/mcpclient/│   │   │       ├── McpClientApplication.java          # 启动类│   │   │       ├── config/│   │   │       │   ├── McpRequestCustomizerProperties.java  # MCP 请求定制器配置属性│   │   │       │   ├── McpClientConfig.java           # MCP Client HTTP 请求定制器│   │   │       │   └── ChatConfig.java                # ChatClient 配置│   │   │       ├── tool/│   │   │       │   └── LocalWeatherTool.java          # 本地天气工具│   │   │       ├── advisor/│   │   │       │   └── ToolCallLoggingAdvisor.java    # 工具调用日志拦截器│   │   │       ├── service/│   │   │       │   ├── DynamicToolService.java        # 动态工具加载服务│   │   │       │   └── ChatService.java               # 聊天服务│   │   │       └── controller/│   │   │           └── ChatController.java            # REST API 控制器│   │   └── resources/│   │       └── application.yml                        # 配置文件│   └── test/│       └── java/│           └── com/example/mcpclient/│               └── McpClientApplicationTests.java└── target/

          说明:项目结构按依赖关系排列,底层配置和工具类在前,上层服务和控制器在后。

          3.3 依赖配置

          <?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0          http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>3.5.5</version>    </parent>    <groupId>com.example</groupId>    <artifactId>mcp-client-demo</artifactId>    <version>1.0.0</version>    <name>MCP Client Demo</name>    <description>轻量的 MCP Client Demo 项目,包含多 Server 连接、本地工具混合、动态加载等功能</description>    <properties>        <java.version>17</java.version>        <spring-ai.version>1.1.5</spring-ai.version>    </properties>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.ai</groupId>                <artifactId>spring-ai-bom</artifactId>                <version>{MINIMAX_API_KEY:your-minimax-api-key-here}      chat:        options:          model: abab6.5s-chat  # MiniMax 模型          temperature0.7    # MCP Client 配置 - Streamable HTTP 模式    mcp:      client:        enabledtrue        name: mini-mcp-client        version: 1.0.0        type: sync  # sync=同步模式,async=异步模式        request-timeout: 60s        # 多 MCP Server 连接配置(streamable-http 传输方式)        streamable-http:          connections:            server-a:              url: http://localhost:8080              endpoint: /mcp              auth:                type: api-key                header-name"X-API-Key"                header-value"demo-api-key"#            server-b:#              url: http://localhost:8080#              endpoint: /mcp#              auth:#                type: bearer#                token: "token-for-server-b"# 日志配置(调试时开启)logging:  level:    io.modelcontextprotocol: debug  # MCP 协议调试日志    com.example.mcpclient: debug    # 项目调试日志

          连接配置说明

          每个连接支持以下配置项:

          配置项
          说明
          示例
          url
          MCP Server 的基础 URL
          http://localhost:8080
          endpoint
          MCP 端点路径
          /mcp
          headers
          静态请求头(所有请求都添加)
          X-API-Key: "demo-api-key"
          auth
          认证配置(可选)
          见下方认证配置说明

          认证配置(auth)

          支持两种认证方式:

          1. API Key 认证

          auth:  type: api-key  header-name"X-API-Key"  header-value"your-api-key"

          2. Bearer Token 认证

          auth:  type: bearer  token"your-bearer-token"

          注意:没有配置 auth 的连接将不添加额外认证头,使用默认的 headers 配置即可。

          3.5 启动类

          package com.example.mcpclient;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * MCP Client 启动类 * 一个 Demo 项目搞定核心功能:多 Server 连接、本地工具混合、动态加载等 * * @author senfel * @date 2023/11/5 14:01 */@SpringBootApplicationpublic class McpClientApplication {    public static void main(String[] args) {        SpringApplication.run(McpClientApplication.class, args);    }}

          3.6 本地工具实现

          package com.example.mcpclient.tool;import com.fasterxml.jackson.annotation.JsonClassDescription;import com.fasterxml.jackson.annotation.JsonProperty;import com.fasterxml.jackson.annotation.JsonPropertyDescription;import lombok.Data;import org.springframework.ai.tool.annotation.Tool;import org.springframework.stereotype.Component;import java.util.Random;/** * 本地天气查询工具 * 与 MCP 工具互补,提供本地模拟数据 *  * 为什么需要本地工具? * - 有些工具不需要远程调用,直接在本地执行更快 * - 可以模拟数据用于测试 * - 与 MCP 工具混合使用,LLM 会自动选择最合适的工具 *  * Spring AI 1.1.5 变化: * - 使用 @Tool 注解标记工具方法,而不是实现 Function 接口 * - MethodToolCallbackProvider 会自动扫描 @Tool 注解的方法 * * @author senfel * @date 2023/11/5 14:01 */@Component("local_weather_query")public class LocalWeatherTool {    private static final String[] CONDITIONS = {"晴朗""多云""阴天""小雨""大雨""雷雨"};    private final Random random = new Random();    /**     * 工具请求参数     * JSON Schema 会自动生成,用于告诉 LLM 如何调用这个工具     */    @Data    @JsonClassDescription("查询指定城市的天气信息")    public static class Request {        @JsonProperty(required = true, value = "city")        @JsonPropertyDescription("城市名称,例如:北京、上海、广州")        private String city;    }    /**     * 工具响应结果     */    @Data    public static class Response {        private String city;        private WeatherInfo weatherInfo;        @Data        public static class WeatherInfo {            private Integer temperature;            private String condition;            private Integer humidity;            private Integer windSpeed;        }    }    /**     * 查询天气的工具方法     * 使用 @Tool 注解,Spring AI 会自动将其注册为 ToolCallback     */    @Tool(description = "查询指定城市的天气信息")    public Response queryWeather(Request request) {        Response.WeatherInfo weatherInfo = new Response.WeatherInfo();        weatherInfo.setTemperature(random.nextInt(35- 5);  // -5 ~ 30°C        weatherInfo.setCondition(CONDITIONS[random.nextInt(CONDITIONS.length)]);        weatherInfo.setHumidity(random.nextInt(80+ 20);    // 20% ~ 100%        weatherInfo.setWindSpeed(random.nextInt(30+ 1);    // 1 ~ 30 km/h        Response response = new Response();        response.setCity(request.getCity());        response.setWeatherInfo(weatherInfo);        return response;    }}

          3.7 工具调用日志拦截器

          package com.example.mcpclient.advisor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClientRequest;import org.springframework.ai.chat.client.ChatClientResponse;import org.springframework.ai.chat.client.advisor.api.CallAdvisor;import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;import org.springframework.stereotype.Component;/** * 工具调用日志记录 Advisor * 记录每次工具调用的详细信息,方便调试和监控 *  * Advisor 是什么? * - 类似于 Spring 的拦截器,可以在工具调用前后执行逻辑 * - 用于日志记录、性能监控、权限检查等 *  * Spring AI 1.1.5 变化: * - 使用 CallAdvisor 接口替代 CallAroundAdvisor * - 使用 ChatClientRequest 和 ChatClientResponse 替代 AdvisedRequest 和 AdvisedResponse * - 使用 adviseCall 方法替代 aroundCall 方法 * * @author senfel * @date 2023/11/5 14:01 */@Component@Slf4jpublic class ToolCallLoggingAdvisor implements CallAdvisor {    @Override    public String getName() {        return "ToolCallLoggingAdvisor";    }    @Override    public int getOrder() {        return 0;  // 执行顺序,数字越小越先执行    }    @Override    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {        // 工具调用前        log.info("=== 工具调用开始 ===");        log.info("请求内容: {}", request.prompt().getContents());        long startTime = System.currentTimeMillis();        // 执行工具调用        ChatClientResponse response = chain.nextCall(request);        // 工具调用后        long duration = System.currentTimeMillis() - startTime;        log.info("响应结果: {}", response);        log.info("耗时: {}ms", duration);        log.info("=== 工具调用结束 ===");        return response;    }}

          3.8 MCP Client 请求定制器配置

          package com.example.mcpclient.config;import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;import io.modelcontextprotocol.common.McpTransportContext;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.net.http.HttpRequest;/** * MCP Client 配置 * 负责定制 HTTP 请求,例如添加认证头、日志记录等 * * @author senfel * @date 2023/11/5 14:01 */@Configuration@Slf4j@RequiredArgsConstructorpublic class McpClientConfig {    private final McpRequestCustomizerProperties customizerProperties;    @Bean    public McpSyncHttpClientRequestCustomizer mcpHttpClientRequestCustomizer() {        return (builder, method, endpoint, body, context) -> {            String requestUrl = endpoint.toString();            // 根据请求 URL 匹配对应的 server 配置            customizerProperties.getConnections().values().stream()                    .filter(conn -> requestUrl.startsWith(conn.getUrl()))                    .findFirst()                    .ifPresent(conn -> {                        McpRequestCustomizerProperties.AuthConfig auth = conn.getAuth();                        if (auth != null) {                            applyAuth(builder, auth);                            log.info("MCP request to {} with auth type={}: method={}"                                    endpoint.getHost(), auth.getType(), method);                        } else {                            log.info("MCP request to {} without custom auth: method={}"                                    endpoint.getHost(), method);                        }                    });        };    }    private void applyAuth(HttpRequest.Builder builder, McpRequestCustomizerProperties.AuthConfig auth) {        switch (auth.getType()) {            case "api-key":                builder.header(auth.getHeaderName(), auth.getHeaderValue());                break;            case "bearer":                builder.header("Authorization""Bearer " + auth.getToken());                break;            default:                log.warn("Unknown auth type: {}", auth.getType());        }    }}

          配置属性类

          package com.example.mcpclient.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import java.util.Map;/** * MCP 请求定制器配置属性 * 绑定 spring.ai.mcp.client.streamable-http 配置 * * @author senfel * @date 2023/11/5 14:01 */@Data@Configuration@ConfigurationProperties(prefix = "spring.ai.mcp.client.streamable-http")public class McpRequestCustomizerProperties {    private Map<StringServerConnection> connections;    @Data    public static class ServerConnection {        private String url;        private String endpoint;        private AuthConfig auth;    }    @Data    public static class AuthConfig {        private String type;        private String headerName;        private String headerValue;        private String token;    }}

          3.9 ChatClient 配置

          package com.example.mcpclient.config;import com.example.mcpclient.advisor.ToolCallLoggingAdvisor;import com.example.mcpclient.service.DynamicToolService;import com.example.mcpclient.tool.LocalWeatherTool;import io.modelcontextprotocol.client.McpSyncClient;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;import org.springframework.ai.tool.method.MethodToolCallbackProvider;import org.springframework.ai.tool.resolution.ToolCallbackResolver;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.List;/** * ChatClient 配置 * 一个配置类搞定所有功能: * - 多 MCP Server 工具注入 * - 本地工具注入 * - 对话历史管理 * - 工具调用日志 * - 动态工具加载服务(基于角色) *  * Spring AI 1.1.5 变化: * - ChatMemory 由 Spring Boot 自动配置,无需手动创建 InMemoryChatMemory Bean * - MessageChatMemoryAdvisor 使用 builder 模式构建 * * @author senfel * @date 2023/11/5 14:01 */@Configurationpublic class ChatConfig {    private final LocalWeatherTool localWeatherTool;    private final ToolCallLoggingAdvisor toolCallLoggingAdvisor;    private final ChatModel chatModel;    private final ToolCallbackResolver toolCallbackResolver;    public ChatConfig(LocalWeatherTool localWeatherTool,                      ToolCallLoggingAdvisor toolCallLoggingAdvisor,                     ChatModel chatModel,                     ToolCallbackResolver toolCallbackResolver) {        this.localWeatherTool = localWeatherTool;        this.toolCallLoggingAdvisor = toolCallLoggingAdvisor;        this.chatModel = chatModel;        this.toolCallbackResolver = toolCallbackResolver;    }    /**     * 默认 ChatClient(包含所有工具)     * 用于 /api/chat 接口     *      * 注意:     * - McpSyncClient 由 Spring AI 自动创建     * - ChatMemory 由 Spring Boot 自动配置(基于 spring-ai-client-chat 依赖)     * - 根据 application.yml 中的 spring.ai.mcp.client.connections 配置     *   每个 connection 会自动创建一个 McpSyncClient Bean     */    @Bean    public ChatClient chatClient(ChatMemory chatMemory, List<McpSyncClient> mcpClients) {        // 1. 收集所有 MCP Server 的工具        // Spring AI 已自动创建多个 McpSyncClient,每个对应一个 Server        SyncMcpToolCallbackProvider[] mcpProviders = mcpClients.stream()                .map(SyncMcpToolCallbackProvider::new)                .toArray(SyncMcpToolCallbackProvider[]::new);        // 2. 本地工具        MethodToolCallbackProvider localProvider = MethodToolCallbackProvider.builder()                .toolObjects(localWeatherTool)                .build();        // 3. 构建 ChatClient        return ChatClient.builder(chatModel)                .defaultSystem("你是一个智能助手,可以帮助用户查询天气、订单、文件等信息。\n" +                        "当用户询问天气、订单或文件时,请使用相应的工具来获取信息。\n" +                        "回答要简洁明了,直接给出用户需要的信息。")                // 注入所有工具(MCP + 本地)                .defaultToolCallbacks(mcpProviders)                .defaultToolCallbacks(localProvider)                // 注入对话历史(使用 builder 模式)                .defaultAdvisors(                        MessageChatMemoryAdvisor.builder(chatMemory).build()                )                // 注入工具调用日志                .defaultAdvisors(toolCallLoggingAdvisor)                .build();    }    /**     * 动态工具加载服务     * 用于 /api/role/chat 接口,根据角色动态加载工具     */    @Bean    public DynamicToolService dynamicToolService(ChatModel chatModel, ChatMemory chatMemory, List<McpSyncClient> mcpClients) {        return new DynamicToolService(chatModel, chatMemory, mcpClients, toolCallbackResolver);    }}

          3.10 动态工具加载服务

          package com.example.mcpclient.service;import io.modelcontextprotocol.client.McpSyncClient;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;import org.springframework.ai.tool.ToolCallback;import org.springframework.ai.tool.resolution.ToolCallbackResolver;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;import java.util.Map;/** * 动态工具加载服务 * 根据角色配置动态加载本地工具和 MCP 工具 *  * 使用场景: * - 客服助手角色:只能查询天气和订单 * - 财务助手角色:只能查询订单和处理支付 * - 管理员角色:可以使用所有工具 * * @author senfel * @date 2023/11/5 14:01 */@Service@RequiredArgsConstructor@Slf4jpublic class DynamicToolService {    private final ChatModel chatModel;    private final ChatMemory chatMemory;    private final List<McpSyncClient> mcpClients;    private final ToolCallbackResolver toolCallbackResolver;    // Mock 角色配置(实际项目中从数据库读取)    private final Map<LongChatRole> roleConfig = Map.of(        1Lnew ChatRole(1L"客服助手""你是一个客服助手,可以帮助用户查询天气和订单。",                List.of("local_weather_query"), List.of("business-server")),        2Lnew ChatRole(2L"财务助手""你是一个财务助手,可以帮助用户查询订单和处理支付。",                List.of(), List.of("business-server""payment-server"))    );    /**     * 根据角色 ID 创建 ChatClient     * 自动加载角色关联的工具     */    public ChatClient createChatClientByRole(Long roleId) {        ChatRole role = roleConfig.get(roleId);        if (role == null) {            throw new IllegalArgumentException("角色不存在: " + roleId);        }        List<ToolCallback> toolCallbacks = new ArrayList<>();        // 1. 加载本地工具        role.toolNames().forEach(toolName -> {            ToolCallback toolCallback = toolCallbackResolver.resolve(toolName);            if (toolCallback != null) {                toolCallbacks.add(toolCallback);            }        });        // 2. 加载 MCP 工具        role.mcpClientNames().forEach(mcpClientName -> {            // 匹配对应的 McpSyncClient            mcpClients.stream()                .filter(client -> client.getClientInfo().name().contains(mcpClientName))                .findFirst()                .ifPresent(client -> {                    ToolCallback[] mcpToolCallbacks =                         new SyncMcpToolCallbackProvider(client).getToolCallbacks();                    toolCallbacks.addAll(List.of(mcpToolCallbacks));                });        });        // 3. 构建 ChatClient        return ChatClient.builder(chatModel)                .defaultSystem(role.systemMessage())                .defaultToolCallbacks(toolCallbacks.toArray(new ToolCallback[0]))                .build();    }    /**     * 角色定义     */    public record ChatRole(Long id, String name, String systemMessage,                           List<String> toolNames, List<String> mcpClientNames) {}}

          3.11 聊天服务

          package com.example.mcpclient.service;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import reactor.core.publisher.Flux;/** * 聊天服务 * 封装 ChatClient 调用逻辑 *  * 一个 Service 搞定所有功能: * - 同步对话 * - 流式对话 * - SSE 流式对话 * * @author senfel * @date 2023/11/5 14:01 */@Service@RequiredArgsConstructor@Slf4jpublic class ChatService {    private final ChatClient chatClient;    /**     * 同步对话     * @param message 用户消息     * @return AI 回复     */    public String chat(String message) {        log.info("用户消息: {}", message);        String response = chatClient.prompt()                .user(message)                .call()                .content();        log.info("AI 回复: {}", response);        return response;    }    /**     * 流式对话     * @param message 用户消息     * @return AI 回复(流式拼接)     */    public String chatStream(String message) {        log.info("用户消息(流式): {}", message);        StringBuilder result = new StringBuilder();        Flux<String> stream = chatClient.prompt()                .user(message)                .stream()                .content();        stream.doOnNext(chunk -> {            result.append(chunk);            System.out.print(chunk);        }).then().block();        return result.toString();    }    /**     * SSE 流式对话     * @param message 用户消息     * @return SseEmitter     */    public SseEmitter chatSse(String message) {        SseEmitter emitter = new SseEmitter(60000L);  // 60 秒超时        Flux<String> stream = chatClient.prompt()                .user(message)                .stream()                .content();        stream.doOnNext(chunk -> {            try {                emitter.send(SseEmitter.event().data(chunk));            } catch (Exception e) {                emitter.completeWithError(e);            }        })        .doOnComplete(emitter::complete)        .doOnError(emitter::completeWithError)        .subscribe();        return emitter;    }}

          3.12 控制器

          package com.example.mcpclient.controller;import com.example.mcpclient.service.ChatService;import com.example.mcpclient.service.DynamicToolService;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;/** * 聊天控制器 * 提供同步、流式、SSE 三种对话接口 * 支持基于角色的动态工具加载 *  * 接口说明: * - POST /api/chat          : 同步对话,一次性返回完整结果 * - POST /api/chat/stream   : 流式对话,实时返回文本片段 * - GET  /api/chat/sse      : SSE 流式对话,浏览器可直接使用 * - POST /api/role/chat     : 基于角色的对话(动态加载工具) * * @author senfel * @date 2023/11/5 14:01 */@RestController@RequestMapping("/api")@RequiredArgsConstructor@Slf4jpublic class ChatController {    private final ChatService chatService;    private final DynamicToolService dynamicToolService;    /**     * 同步对话接口     * @param message 用户消息     * @return AI 回复     */    @PostMapping("/chat")    public String chat(@RequestParam String message) {        log.info("用户消息: {}", message);        return chatService.chat(message);    }    /**     * 流式对话接口     * @param message 用户消息     * @return AI 回复(流式拼接)     */    @PostMapping("/chat/stream")    public String chatStream(@RequestParam String message) {        log.info("用户消息(流式): {}", message);        return chatService.chatStream(message);    }    /**     * SSE 流式对话接口     * SSE (Server-Sent Events) 是什么?     * - 服务器向浏览器推送实时事件的技术     * - 基于 HTTP,单向通信(服务器 → 客户端)     * - 浏览器可直接使用 EventSource API 接收     *      * @param message 用户消息     * @return SSE 流     */    @GetMapping(value = "/chat/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)    public SseEmitter chatSse(@RequestParam String message) {        log.info("用户消息(SSE): {}", message);        return chatService.chatSse(message);    }    /**     * 基于角色的同步对话     * @param roleId 角色 ID(1=客服助手,2=财务助手)     * @param message 用户消息     * @return AI 回复     */    @PostMapping("/role/chat")    public String roleChat(@RequestParam Long roleId, @RequestParam String message) {        log.info("角色 ID: {}, 用户消息: {}", roleId, message);        // 根据角色动态创建 ChatClient(自动加载对应工具)        org.springframework.ai.chat.client.ChatClient chatClient =                 dynamicToolService.createChatClientByRole(roleId);        String response = chatClient.prompt()                .user(message)                .call()                .content();        log.info("AI 回复: {}", response);        return response;    }}

          四、一次性工具调用完整链路分析

          4.1 完整调用流程图

          用户                    ChatController              ChatService              ChatClient │                          │                          │                        │ │  "北京天气怎么样?"        │                          │                        │ │─────────────────────────>│                          │                        │ │                          │  chat("北京天气怎么样?")  │                        │ │                          │─────────────────────────>│                        │ │                          │                          │  prompt().user(msg)    │ │                          │                          │───────────────────────>│ │                          │                          │                        │ │                          │                          │                   构建 Prompt │                          │                          │              + 附加工具列表 │                          │                          │                        │ │                          │                          │                        ▼ │                          │                          │                  MiniMax ChatModel │                          │                          │                        │ │                          │                          │  1. 分析用户意图         │ │                          │                          │  2. 决定调用 getWeather │ │                          │                          │  3. 生成参数 cityName   │ │                          │                          │<───────────────────────│ │                          │                          │                        │ │                          │                          ▼                        │ │                          │                    McpSyncClient                  │ │                          │                    SyncMcpToolCallbackProvider    │ │                          │                        │                        │ │                          │                        │  封装 JSON-RPC 请求      │ │                          │                        │  tools/call              │ │                          │                        │  HTTP POST /mcp          │ │                          │                        │                        │ │                          │                        ▼                        │ │                          │              ┌─────────────────┐                 │ │                          │              │  MCP Server     │                 │ │                          │              │  (localhost:8080)│                │ │                          │              │  WeatherTool    │                 │ │                          │              └─────────────────┘                 │ │                          │                        │                        │ │                          │                        │  返回天气数据            │ │                          │                        │<───────────────────────│ │                          │                          │                        │ │                          │                          ▼                        │ │                          │                    MiniMax ChatModel              │ │                          │                        │                        │ │                          │                        │  基于工具结果生成回复     │ │                          │                        │<───────────────────────│ │                          │                          │                        │ │                          │  返回 AI 回复              │                        │ │                          │<─────────────────────────│                        │ │  "北京今天天气晴朗..."    │                          │                        │ │<─────────────────────────│                          │                        │

          4.2 调用链路详细说明

          步骤
          组件
          动作
          说明
          1
          用户
          发送自然语言请求
          “北京天气怎么样?”
          2
          ChatController
          接收请求,调用 Service
          POST /api/chat?message=北京天气怎么样?
          3
          ChatService
          调用 ChatClient
          chatClient.prompt().user(message).call()
          4
          ChatClient
          构建 Prompt,注入工具列表
          将所有工具(MCP + 本地)转换为 JSON Schema
          5
          MiniMax ChatModel
          分析用户意图
          理解用户想要查询天气
          6
          MiniMax ChatModel
          决定调用工具
          选择 getWeather 工具,生成参数 {cityName: "北京"}
          7
          McpSyncClient
          封装 JSON-RPC 请求
          转换为 MCP 协议格式
          8
          Transport
          HTTP POST 到 Server
          POST http://localhost:8080/mcp
          9
          MCP Server
          执行工具方法
          WeatherTool.getWeather("北京")
          10
          MCP Server
          返回结构化结果
          {city: "北京", temperature: 22, condition: "晴朗"}
          11
          MiniMax ChatModel
          基于工具结果生成回复
          “北京今天天气晴朗,温度 22°C…”
          12
          ChatClient
          返回最终结果
          返回给用户

          五、端到端测试

          5.1 启动服务

          # 1. 先启动 MCP Server(端口 8080)# 参考:SpringAI+MCPServer实战-StreamableHTTP协议打造企业级AI工具服务.mdcd ../mcp-servermvn spring-boot:run# 2. 启动 MCP Client(端口 8081)cd mcp-client-demomvn spring-boot:run

          5.2 测试用例

          # 测试 1:本地工具调用(天气查询)curl -X POST "http://localhost:8081/api/chat" \  -d "message=北京今天天气怎么样"# 测试 2:MCP 工具调用(订单查询)curl -X POST "http://localhost:8081/api/chat" \  -d "message=帮我查一下订单 ORD-1001 的状态"# 测试 3:多工具调用(本地 + MCP)curl -X POST "http://localhost:8081/api/chat" \  -d "message=先查一下北京天气,再帮我看看 ORD-1002 订单发货没"# 测试 4:流式对话curl -X POST "http://localhost:8081/api/chat/stream" \  -d "message=今天适合外出吗?"# 测试 5:SSE 流式对话(浏览器可直接访问)curl -X GET "http://localhost:8081/api/chat/sse?message=上海天气怎么样"# 测试 6:基于角色的对话(客服助手角色)curl -X POST "http://localhost:8081/api/role/chat" \  -d "roleId=1&message=北京天气怎么样"# 测试 7:基于角色的对话(财务助手角色)curl -X POST "http://localhost:8081/api/role/chat" \  -d "roleId=2&message=订单 ORD-1001 发货没"

          5.3 预期效果

          测试 1:本地工具调用

          用户:北京今天天气怎么样AI:北京今天天气晴朗,温度 18°C,湿度 45%,风速 12 km/h。天气不错,适合外出!

          测试 2:MCP 工具调用

          用户:帮我查一下订单 ORD-1001 的状态AI:订单 ORD-1001 购买的是 iPhone 16 Pro,当前状态为"已发货",预计 2 天后送达,价格 8999.00 元。

          测试 3:多工具调用

          用户:先查一下北京天气,再帮我看看 ORD-1002 订单发货没AI:北京今天多云,温度 22°C。订单 ORD-1002(MacBook Air M3)当前状态为"处理中",预计 5 天后送达。

          5.4 调试日志示例

          2025-06-06 10:30:15.123 DEBUG [main] i.m.client.McpSyncClient : Initializing MCP client...2025-06-06 10:30:15.456 DEBUG [main] i.m.client.McpSyncClient : Connected to server: business-server v1.0.02025-06-06 10:30:15.789 DEBUG [main] i.m.client.McpSyncClient : Connected to server: filesystem-server v1.0.02025-06-06 10:30:16.123 DEBUG [main] i.m.client.McpSyncClient : Connected to server: payment-server v1.0.02025-06-06 10:30:16.456 DEBUG [main] i.m.client.McpSyncClient : Discovered tools: [getWeather, getOrderStatus, listFiles, processPayment]2025-06-06 10:30:16.789 INFO  [main] c.e.m.config.ChatConfig : Registered local tool: local_weather_query2025-06-06 10:30:20.123 INFO  [http-nio-8081-exec-1] c.e.m.service.ChatService : 用户消息: 北京今天天气怎么样2025-06-06 10:30:21.456 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : === 工具调用开始 ===2025-06-06 10:30:21.456 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具名称: local_weather_query2025-06-06 10:30:21.456 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具参数: {city=北京}2025-06-06 10:30:21.789 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具结果: {city=北京, weatherInfo={temperature=22, condition=晴朗, ...}}2025-06-06 10:30:21.789 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 耗时: 333ms2025-06-06 10:30:21.789 INFO  [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : === 工具调用结束 ===2025-06-06 10:30:22.123 INFO  [http-nio-8081-exec-1] c.e.m.service.ChatService : AI 回复: 北京今天天气晴朗,温度 22°C...

          六、部署方案

          6.1 Docker 部署

          FROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY target/mcp-client-demo-1.0.0.jar app.jarEXPOSE 8081ENV MINIMAX_API_KEY=your-api-key-hereENTRYPOINT ["java", "-jar", "app.jar"]
          # 构建镜像docker build -t mcp-client-demo:1.0.0 .# 运行容器docker run -d -p 8081:8081 \  -e MINIMAX_API_KEY=your-real-api-key \  --name mcp-client-demo mcp-client-demo:1.0.0

          6.2 Kubernetes 部署

          apiVersion: apps/v1kind: Deploymentmetadata:  name: mcp-client-demospec:  replicas2  selector:    matchLabels:      app: mcp-client-demo  template:    metadata:      labels:        app: mcp-client-demo    spec:      containers:      - name: mcp-client-demo        image: mcp-client-demo:1.0.0        ports:        - containerPort8081        env:        - name: MINIMAX_API_KEY          valueFrom:            secretKeyRef:              name: minimax-secret              key: api-key        - name: SPRING_AI_MCP_CLIENT_CONNECTIONS_BUSINESS-SERVER_URL          value"http://business-mcp-server/mcp"        - name: SPRING_AI_MCP_CLIENT_CONNECTIONS_FILESYSTEM-SERVER_URL          value"http://filesystem-mcp-server/mcp"        - name: SPRING_AI_MCP_CLIENT_CONNECTIONS_PAYMENT-SERVER_URL          value"http://payment-mcp-server/mcp"---apiVersion: v1kind: Servicemetadata:  name: mcp-client-demospec:  selector:    app: mcp-client-demo  ports:  - port80    targetPort8081  type: ClusterIP---apiVersion: v1kind: Secretmetadata:  name: minimax-secrettype: Opaquedata:  api-key: eW91ci1iYXNlNjQtZW5jb2RlZC1hcGkta2V5

          七、总结

          通过本文的实战,我们完成了一个轻量的 MCP Client Demo 项目,包含:

          1. 多 MCP Server 连接
            :同时连接天气、订单、文件、支付等多个 Server
          2. 本地工具与 MCP 工具混合使用
            :LLM 自动选择最合适的工具
          3. 动态工具加载
            :基于角色配置动态加载工具(Mock 数据)
          4. 同步和流式对话
            :支持多种对话模式
          5. 对话历史管理
            :支持多轮对话上下文
          6. 工具调用日志
            :记录工具调用详情,方便调试
          7. 完整的端到端测试
            :从用户请求到工具调用再到 AI 回复的全流程

          关键要点回顾

          要点
          说明
          模型选择
          MiniMax abab6.5s-chat,Spring AI 1.1.5 原生支持
          依赖配置
          spring-ai-starter-model-minimax + spring-ai-starter-mcp-client
          工具注入
          SyncMcpToolCallbackProvider 将 MCP 工具转换为 ToolCallback
          多 Server
          connections 配置多个 MCP Server,自动发现合并工具
          流式支持
          使用 Reactor Flux 实现流式对话
          工具选择
          LLM 根据工具描述自动选择,不需要代码逻辑判断

          适用场景建议

          • 企业内部 AI 助手
            :连接多个业务系统的 MCP Server,提供统一 AI 入口
          • 微服务架构
            :MCP Client 作为 API Gateway,后端连接多个 MCP Server 微服务
          • 角色权限管理
            :基于角色动态加载工具,不同角色使用不同工具集

          感谢各位看官的一路陪伴,大家都再接再厉!

          上一篇回顾:《Spring AI MCP Server 实战:用 Streamable HTTP 协议打造企业级 AI 工具服务》

          往下拉评论更精彩~~~

          🔍并发编程系列-基础并发包与各种锁机制源码分析、使用与常规实战案例解析

          🔍微服务-微服务架构、数据采集、指标分析、流量控制、熔断降级与隔离集成

          🔍数据结构与算法-各种排序、堆、栈、队列、链表、二叉树、Hash算法等

          🔍大数据-基础知识、环境搭建与接入项目使用、Hadoop、Spark、Flink等

          🔍数据库-中间件及其整合、Mysql、Redis、Mongodb

          🔍elasticsearch-ES基础语法使用、各种文档分析与常见问题的处理

          🔍docker-容器化相关知识,Docker部署Spring、Springboot、SpringCloud

          🔍kubernetes-相关概念、架构、流程概述,架构的搭建与微服务项目各中间件部署

          🔍Spring-项目架构、锁机制、问题解析、数据库与中间件整合

          🔍开发工具-与开发有关的工具中间件,GIT、IDEA、CANAL、JENKINS、CICD

          🔍故障排查-JVM内存泄漏、CPU超高、Canal同步定位、线上监控诊断Arthas等

          🔍性能监控-性能监控流、流量控制、熔断降级、ELK、Prometheus、Grafana等

          🔍设计模式-设计模式专题,包括创建型、结构型、行为型23种设计模式解析及案例

          点击这里关注我,记得星标哦~

          基本 文件 流程 错误 SQL 调试
          1. 请求信息 : 2026-06-12 19:47:10 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/745521.html
          2. 运行时间 : 0.223853s [ 吞吐率:4.47req/s ] 内存消耗:4,853.79kb 文件加载:145
          3. 缓存信息 : 0 reads,0 writes
          4. 会话信息 : SESSION_ID=309a97b64864bfb053cb60d4a77a2b55
          1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
          2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
          3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
          4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
          5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
          6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
          7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
          8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
          9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
          10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
          11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
          12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
          13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
          14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
          15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
          16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
          17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
          18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
          19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
          20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
          21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
          22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
          23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
          24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
          25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
          26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
          27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
          28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
          29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
          30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
          31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
          32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
          33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
          34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
          35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
          36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
          37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
          38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
          39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
          40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
          41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
          42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
          43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
          44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
          45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
          46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
          47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
          48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
          49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
          50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
          51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
          52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
          53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
          54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
          55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
          56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
          57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
          58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
          59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
          60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
          61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
          62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
          63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
          64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
          65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
          66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
          67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
          68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
          69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
          70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
          71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
          72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
          73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
          74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
          75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
          76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
          77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
          78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
          79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
          80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
          81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
          82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
          83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
          84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
          85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
          86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
          87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
          88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
          89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
          90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
          91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
          92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
          93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
          94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
          95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
          96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
          97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
          98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
          99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
          100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
          101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
          102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
          103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
          104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
          105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
          106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
          107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
          108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
          109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
          110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
          111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
          112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
          113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
          114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
          115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
          116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
          117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
          118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
          119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
          120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
          121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
          122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
          123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
          124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
          125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
          126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
          127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
          128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
          129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
          130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
          131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
          132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
          133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
          134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
          135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
          136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
          137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
          138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
          139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
          140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
          141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
          142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
          143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
          144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
          145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
          1. CONNECT:[ UseTime:0.001115s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
          2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001945s ]
          3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000766s ]
          4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000798s ]
          5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001564s ]
          6. SELECT * FROM `set` [ RunTime:0.000630s ]
          7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001634s ]
          8. SELECT * FROM `article` WHERE `id` = 745521 LIMIT 1 [ RunTime:0.001466s ]
          9. UPDATE `article` SET `lasttime` = 1781264830 WHERE `id` = 745521 [ RunTime:0.001446s ]
          10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000653s ]
          11. SELECT * FROM `article` WHERE `id` < 745521 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001181s ]
          12. SELECT * FROM `article` WHERE `id` > 745521 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001016s ]
          13. SELECT * FROM `article` WHERE `id` < 745521 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001752s ]
          14. SELECT * FROM `article` WHERE `id` < 745521 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001826s ]
          15. SELECT * FROM `article` WHERE `id` < 745521 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001986s ]
          0.227909s