将 DeepSeek 集成到 Spring Boot 项目实现通过 AI 对话方式操作后台数据

项目简介

在本项目中,我们基于 Spring Boot 构建并通过 Docker 部署了 MCP 服务端与 MCP 客户端,通过 HTTP 协议 实现与现有系统的远程控制与集成。整个系统作为独立模块运行,服务端直接连接数据库,核心业务逻辑抽象于 tool 层中,具备良好的扩展性与解耦性。

为提升系统的实时交互能力,项目支持通过 Cherry Studio 使用 SSE(Server-Sent Events) 与 MCP 服务端保持长连接,实现消息的实时推送与指令响应。

此外,还新增了对 WebSocket 通信 的支持。相比于 HTTP 和 SSE,WebSocket 是一种 全双工、低延迟、连接持久 的通信协议,特别适用于高频次、强交互的控制场景。通过 WebSocket,客户端不仅可以实时获取状态更新,还可主动发送控制指令并立即获取响应,进一步增强了系统的交互性和用户体验。

本项目分两大模块

  • MCP 服务端:

    • 连接数据库
    • Tool 业务实现
  • MCP 客户端:

    • 连接 DeepSeek AI 模型
    • 连接 MCP 服务端加载 Tool 列表
    • 接收用户输入的提示词(问题)
    • DeepSeek 识别提示词(问题)意图
    • DeepSeek 在 Tool 列表中找到符合要求的 Tool 并调用 Tool
    • 接收 MCP 服务端 Tool 的业务处理结果
    • DeepSeek 分析结果并转换为 md 文档格式输出

Gitee

MCP 简介

MCP 是一种开放协议,用于标准化应用程序如何向 LLM 提供上下文。可以将 MCP 视为 AI 应用程序的 USB-C 端口。就像 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。
在这里插入图片描述

  • MCP 主机 (MCP Hosts):例如 Claude Desktop、IDE 或希望通过 MCP 访问数据的 AI 工具等程序
  • MCP 客户端 (MCP Clients):与服务器保持 1:1 连接的协议客户端
  • MCP 服务器 (MCP Servers):轻量级程序,每个程序通过标准化的模型上下文协议 (MCP) 公开特定的功能
  • 本地数据源 (Local Data Sources):您的计算机的文件、数据库和 MCP 服务器可以安全访问的服务
  • 远程服务 (Remote Services):可通过互联网访问的外部系统(例如,通过 API),MCP 服务器可以连接到这些系统

MCP 官网

MCP Apifox 文档

MCP 官方示例

环境要求

  • Java 环境 >= JDK17
  • Spring Boot >= 3.x

在这里插入图片描述

项目代码

git clone https://gitee.com/wufengsheng/spring-mcp-server.git

注: Java 开发环境必须 >= JDK17,否则项目编译不过。

在这里插入图片描述

核心实现代码

  • MCP 依赖 jar 包
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-M7</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
	<!-- mcp 服务端引入 -->
	<dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    </dependency>

	<!-- mcp 客户端引入 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    ...
</dependencies>

MCP 服务端

  • ToolCallbackProviderConfig.java
import lombok.AllArgsConstructor;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration
@AllArgsConstructor
public class ToolCallbackProviderConfig {

    private final ApplicationContext applicationContext;

    @Bean
    public ToolCallbackProvider methodToolCallbackProvider() {
        Map<String, McpTool> mcpBeanMap = applicationContext.getBeansOfType(McpTool.class);
        return MethodToolCallbackProvider.builder()
                .toolObjects(mcpBeanMap.values().toArray())
                .build();
    }

}
  • McpDemoService.java
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.server.core.McpTool;
import com.server.module.demo.domain.Demo;
import com.server.module.demo.service.IDemoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Service
@AllArgsConstructor
public class McpDemoService implements McpTool {

    private final IDemoService demoService;

    @Tool(description = "根据年龄查询 Demo 列表")
    @Transactional(rollbackFor = {RuntimeException.class, Exception.class})
    public String queryDemoInfoByAge(@ToolParam(description = "年龄") Integer age) {
        Page<Demo> page = new Page<>(1, 10);
        List<Demo> demoList = demoService.list(page, Wrappers.<Demo>lambdaQuery().eq(Demo::getAge, age).orderByAsc(Demo::getName));
        return JSON.toJSONString(demoList);
    }

}

MCP 客户端

  • ChatService.java
import com.alibaba.fastjson2.JSON;
import io.modelcontextprotocol.client.McpSyncClient;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class ChatService {

    private final ChatClient chatClient;

    @Getter
    private final ToolCallback[] toolCallbacks;

    public ChatService(OpenAiChatModel openAiChatModel, List<McpSyncClient> mcpSyncClientList) {
        log.info("mcpSyncClientList={}", JSON.toJSONString(mcpSyncClientList));
        log.info("===============================================\n");
        for (McpSyncClient mcpSyncClient : mcpSyncClientList) {
            log.info("clientInfo={}", mcpSyncClient.getClientInfo());
            log.info("serverInfo={}", mcpSyncClient.getServerInfo());
        }
        log.info("===============================================\n");
        var mcpToolCallbackProvider = new SyncMcpToolCallbackProvider(mcpSyncClientList);
        toolCallbacks = mcpToolCallbackProvider.getToolCallbacks();
        log.info("toolCallbacks={}", JSON.toJSONString(toolCallbacks));
        log.info("===============================================\n");
        for (ToolCallback toolCallback : toolCallbacks) {
            log.info("toolCallback={}", JSON.toJSON(toolCallback.getToolDefinition()));
        }
        log.info("===============================================\n");
        this.chatClient = ChatClient.builder(openAiChatModel)
                .defaultTools(mcpToolCallbackProvider)
                .build();
    }

    public String askQuestion(String prompt) {
        return chatClient.prompt(prompt).call().content();
    }

}
  • application.yml
spring:
  ai:
    openai:
      api-key: sk-xxxxx
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat
    mcp:
      client:
        type: sync
        name: spring-mcp-client-ws
        sse:
          connections:
            server1:
              url: http://localhost:9800

DeepSeek API

在这里插入图片描述

Docker

  • 打包 jar 包并复制到 docker/app/ 对应目录中
  • start.sh 脚本按顺序先启动 mcp-server 再启动 mcp-client-ws

在这里插入图片描述

  • 配置 API_KEY 与 DeepSeek 模型
cd docker/app/mcp-client-ws
vim application-prod.yml
spring:
  ai:
    openai:
      api-key: sk-xxxxx
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat
cd docker
docker-compose up -d mcp-server
docker logs -f mcp-server

sse 连接

http://127.0.0.1:9800/sse

ws 连接(推荐)

ws://127.0.0.1:9802/mcp/ws
  • js 测试脚本
cd node-ws-client
npm install
node client.js

在这里插入图片描述

  • Nginx 配置 ws:// 或 wss://
location /mcp/ {
    proxy_pass http://192.168.0.160:9802/mcp/;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

http 连接

http://127.0.0.1:9801/mcp/mcpClient/listTools

在这里插入图片描述

http://127.0.0.1:9801/mcp/mcpClient/chat?prompt=查询章若楠用户信息

在这里插入图片描述

vue2-chat-window

  • 聊天窗口通过 ws:// 连接 mcp-client-ws 服务

在这里插入图片描述

cd vue2-chat-window
npm install
npm run serve
  • vue2 聊天界面

在这里插入图片描述

Cherry Studio

配置模型

在这里插入图片描述

在这里插入图片描述

配置 MCP

在这里插入图片描述
在这里插入图片描述

调用 MCP

在这里插入图片描述
在这里插入图片描述

### 集成 DeepSeek 实现流式 AI 对话 为了在 Spring Boot 项目集成 DeepSeek实现流式的 AI 对话功能,可以按照如下方式操作: #### 添加依赖项 首先,在 `pom.xml` 文件中添加必要的 Maven 依赖来支持与 DeepSeek 和 WebSocket 的交互。 ```xml <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Starter WebSocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- spring-ai-openai-spring-boot-starter for DeepSeek integration --> <dependency> <groupId>com.spring.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>${latest.version}</version> </dependency> </dependencies> ``` 此部分配置确保应用程序能够通过 HTTP 请求访问外部 API,并且可以通过 WebSocket 进行实时通信[^1]。 #### 创建配置类 接着创建一个 Java 类用于加载并管理连接到 DeepSeek 所需的各项参数。这通常涉及到设置 API 密钥和其他可能影响请求行为的选项。 ```java @Configuration @ConfigurationProperties(prefix = "deepseek") @Data public class DeepSeekConfig { private String apiKey; @Bean public ClientV4 getDeepSeekClient() { return new ClientV4.Builder(apiKey).build(); } } ``` 这段代码定义了一个名为 `DeepSeekConfig` 的 Bean 来存储和提供给其他组件使用的客户端实例[^4]。 #### 构建控制器处理请求 最后一步是在应用内构建 RESTful 或者基于 WebSocket 的接口以便前端或其他服务发起对话请求。这里展示的是简单的 REST 控制器例子,它接收消息并通过 DeepSeek 发送出去获取回复。 ```java @RestController @RequestMapping("/api/chat") public class ChatController { private final ClientV4 deepSeekClient; public ChatController(ClientV4 deepSeekClient) { this.deepSeekClient = deepSeekClient; } @PostMapping("/stream") public ResponseEntity<StreamingHttpResponse> streamChat(@RequestBody Map<String, Object> requestMap) throws Exception { // Prepare the chat completion request with streaming enabled. var responseStream = deepSeekClient.createChatCompletion( CreateChatCompletionRequest.builder() .model("text-davinci-003") .messages(List.of(new Message("user", (String)requestMap.get("message")), new Message("assistant",""))) .stream(true) .build()); StreamingHttpResponse response = new StreamingHttpResponse(responseStream); return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(response); } } ``` 上述代码片段展示了如何利用 `@RestController` 注解建立一个新的端点 `/api/chat/stream` 接收 POST 方法传入的消息体作为输入数据发送至 DeepSeek,并开启流模式返回结果[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逢生博客

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值