文章目录
- 前言
- 一、正文
- 1.1 项目结构
- 1.2 项目环境
- 1.3 完整代码
- 1.3.1 spring-mcp-demo的pom文件
- 1.3.2 generate-code-server的pom文件
- 1.3.3 ChatClientConfig
- 1.3.4 FileTemplateConfig
- 1.3.5 ServiceProviderConfig
- 1.3.6 GenerateCodeController
- 1.3.7 Columns
- 1.3.8 Tables
- 1.3.9 FileTemplateEnum
- 1.3.10 InformationSchemaManager
- 1.3.11 ColumnService
- 1.3.12 ColumnServiceImpl
- 1.3.13 GenerateCodeService
- 1.3.14 GenerateCodeServiceImpl
- 1.3.15 TableService
- 1.3.16 TableServiceImpl
- 1.3.17 GenerateCodeServerApplication
- 1.3.18 CommonService
- 1.3.19 CommonServiceImpl
- 1.4 完整配置
- 1.5 页面示例
- 二、附录
前言
最近在研究学习 SpringBoot 和 MCP + 通义千问的实际使用。
本篇文章主要是连接数据库,查表信息和字段信息,然后根据这些信息做一些操作,比如生成实体类,枚举等。
项目技术栈和同系列文章【SpringBoot中使用MCP和通义千问来处理和分析数据-入门,查寻和基本的分析】类似。
本篇的主要特点是使用了 webflux 的 starter,在控制器层采取流式输出,并做了一个简单的页面来展示结果。(主要原因是同步的方式会经常超时)
另外,本文还使用 idea的【通义灵码】插件进行连接mcp服务(自己的和网上能直接使用的mcp server)–这部分内容在附录中。
这里还对本文中的代码进行了增加功能以及版本升级,因为后来spring-ai发布了1.0.0 的release版本。并且实现了动态添加或减少数据源,可以通过自然语言进行控制操作。代码放在了代码仓库中。
https://gitee.com/fengsoshuai/fengjingsong-spring-ai-demo
一、正文
1.1 项目结构
整体项目采取两部分,父模块和子模块,父级主要控制依赖版本。子级模块用于功能的实现。
1.2 项目环境
本次实践会真实连接mysql进行一些操作。使用了最简单的jdbc。
另外配置了代码示例,生成代码时会参考示例中的代码。
1.3 完整代码
1.3.1 spring-mcp-demo的pom文件
<project xmlns="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>
<groupId>org.mcp</groupId>
<artifactId>spring-mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-mcp-demo</name>
<modules>
<module>generate-code-server</module>
</modules>
<properties>
<java.version>21</java.version>
<spring-boot.version>3.4.2</spring-boot.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>maven2</id>
<name>maven2</name>
<url>https://repo1.maven.org/maven2/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai.version}.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
1.3.2 generate-code-server的pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.mcp</groupId>
<artifactId>spring-mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>generate-code-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>generate-code-server</name>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<optional>true</optional>
</dependency>
<!-- webflux的starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
<!-- 阿里ai的starter -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<inherited>true</inherited>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<parameters>true</parameters>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.mcp.GenerateCodeServerApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1.3.3 ChatClientConfig
package org.mcp.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 聊天对话配置
*/
@Configuration
public class ChatClientConfig {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
/**
* 配置ChatClient,注册系统指令和工具函数
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
InMemoryChatMemory inMemoryChatMemory = new InMemoryChatMemory();
return builder
.defaultSystem("你是一个代码生成助手,可以根据读取配置信息,以及关键词、字段名等生成同样格式的Java代码。" +
"你可以根据用户给的提示词,来指定对应的类名,文档注释等信息。注意,所有类或枚举都需要文档注释,所有public或比较关键的方法也都需要文档注释,特殊算法的代码上需要行注释。"+
"生成枚举时,根据用户给的提示信息,使用英文单词作为枚举变量名,并按指定的枚举代码示例来生成类似的枚举类。" +
"在生成代码时,你可以查数据库的表信息,包括并不限于表字段,备注,字段类型等。" +
"生成代码时使用java1.8版本。并且生成实体的日期类型数据,采用java.util.Date类,小数不使用Double,使用BigDecimal。" +
"回复时,请使用简洁友好的语言。代码中不需要转义符,需要能直接粘贴来用的格式。")
// 注册工具方法
.defaultTools(toolCallbackProvider)
// 支持多轮对话
.defaultAdvisors(new MessageChatMemoryAdvisor(inMemoryChatMemory))
.build();
}
}
1.3.4 FileTemplateConfig
package org.mcp.config;
import jakarta.annotation.PostConstruct;
import org.mcp.enums.FileTemplateEnum;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 文件模版配置
*
* @author mcp
* @version 1.0
* @since 2025-05-07 10:34
*/
@Configuration
public class FileTemplateConfig {
private static final Map<FileTemplateEnum, String> TEMPLATE_CONTENT_MAP = new HashMap<>();
@PostConstruct
private void init() throws IOException {
ClassLoader classLoader = FileTemplateConfig.class.getClassLoader();
for (FileTemplateEnum fileTemplateEnum : FileTemplateEnum.values()) {
InputStream resourceAsStream = classLoader.getResourceAsStream("code-templates/" + fileTemplateEnum.getTemplateName());
assert resourceAsStream != null;
String content = new String(resourceAsStream.readAllBytes());
TEMPLATE_CONTENT_MAP.put(fileTemplateEnum, content);
}
}
public static String getTemplateContent(FileTemplateEnum fileTemplateEnum) {
return TEMPLATE_CONTENT_MAP.get(fileTemplateEnum);
}
}
1.3.5 ServiceProviderConfig
package org.mcp.config;
import org.mcp.service.ColumnService;
import org.mcp.service.CommonService;
import org.mcp.service.GenerateCodeService;
import org.mcp.service.TableService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务提供者配置
*/
@Configuration
public class ServiceProviderConfig {
@Autowired
private GenerateCodeService generateCodeService;
@Autowired
private TableService tableService;
@Autowired
private ColumnService columnService;
@Autowired
private CommonService commonService;
@Bean
public ToolCallbackProvider serverTools() {
return MethodToolCallbackProvider.builder()
// 可以注册多个服务
.toolObjects(generateCodeService, tableService, columnService, commonService)
.build();
}
}
1.3.6 GenerateCodeController
package org.mcp.controller;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import reactor.core.publisher.Flux;
/**
* 代码生成控制器
*
* @author mcp
* @version 1.0
* @since 2025-05-07 10:21
*/
@Controller
@Slf4j
@RequestMapping("/api/chat")
public class GenerateCodeController {
@Resource
private ChatClient chatClient;
@GetMapping(value = "/generateCode", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@SneakyThrows
public Flux<String> generateCode(@RequestParam("conversationId") String conversationId, @RequestParam("message") String message) {
log.info("GenerateCodeController.generateCode(), conversationId:{},message:{}", conversationId, message);
return chatClient.prompt()
.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 50))
.user(message)
.stream()
.content();
}
}
1.3.7 Columns
package org.mcp.entity;
import lombok.Data;
/**
* 列信息
*
* @author mcp
* @version 1.0
* @since 2025-05-07 14:07
*/
@Data
public class Columns {
/**
* 列名
*/
private String columnName;
/**
* 数据类型
*/
private String dataType;
/**
* 列备注
*/
private String columnComment;
}
1.3.8 Tables
package org.mcp.entity;
import lombok.Data;
/**
* 表信息
*
* @author mcp
* @version 1.0
* @since 2025-05-07 13:54
*/
@Data
public class Tables {
/**
* 表名
*/
private String tableName;
/**
* 表注释
*/
private String tableComment;
}
1.3.9 FileTemplateEnum
package org.mcp.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文件模版名枚举
*
* @author mcp
* @version 1.0
* @since 2025-05-07 10:41
*/
@Getter
@AllArgsConstructor
public enum FileTemplateEnum {
ENTITY_TEMPLATE("entityTemplate.txt"),
ENUM_TEMPLATE("enumTemplate.txt");
private final String templateName;
}
1.3.10 InformationSchemaManager
package org.mcp.manager;
import jakarta.annotation.Resource;
import org.mcp.entity.Columns;
import org.mcp.entity.Tables;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* 数据库信息结构管理器
*
* @author mcp
* @version 1.0
* @since 2025-05-07 11:27
*/
@Component
public class InformationSchemaManager {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 查找所有可用的的表名
*/
public List<Tables> findAllTableNames(String tableSchema) {
String sql = "SELECT TABLE_NAME,TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Tables.class), tableSchema);
}
/**
* 查找所有可用的列名
*/
public List<Columns> findAllColumns(String tableSchema, String tableName) {
String sql = "SELECT COLUMN_NAME,DATA_TYPE,COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Columns.class), tableSchema, tableName);
}
/**
* 查找所有可用的schema名
*/
public List<String> findAllSchemaNames() {
String sql = "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA";
return jdbcTemplate.queryForList(sql, String.class);
}
/**
* 查找表创建语句
*/
public Map<String, Object> getCreateTableStatement(String tableSchema, String tableName) {
String sql = "SHOW CREATE TABLE `" + tableSchema + "`.`" + tableName + "`";
return jdbcTemplate.queryForMap(sql);
}
}
1.3.11 ColumnService
package org.mcp.service;
import org.mcp.entity.Columns;
import java.util.List;
/**
* 列服务
*
* @author mcp
* @version 1.0
* @since 2025-05-07 14:11
*/
public interface ColumnService {
List<Columns> findAllColumns(String tableSchema, String tableName);
}
1.3.12 ColumnServiceImpl
package org.mcp.service.impl;
import jakarta.annotation.Resource;
import org.mcp.entity.Columns;
import org.mcp.manager.InformationSchemaManager;
import org.mcp.service.ColumnService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 列服务实现
*
* @author 01434188
* @version 1.0
* @since 2025-05-07 14:11
*/
@Service
public class ColumnServiceImpl implements ColumnService {
@Resource
private InformationSchemaManager informationSchemaManager;
@Override
@Tool(name = "findAllColumns", description = "根据schema和表名,查询当前数据库下的对应数据库表字段信息,包含字段名,字段类型,字段备注。")
public List<Columns> findAllColumns(@ToolParam(description = "表的schema,即数据库名") String tableSchema, @ToolParam(description = "表名") String tableName) {
return informationSchemaManager.findAllColumns(tableSchema, tableName);
}
}
1.3.13 GenerateCodeService
package org.mcp.service;
/**
* 代码生成服务
*
* @author mcp
* @version 1.0
* @since 2025-05-07 10:18
*/
public interface GenerateCodeService {
/**
* 获取枚举代码模板
*/
String getEnumCodeTemplate();
/**
* 获取实体代码模板
*/
String getEntityCodeTemplate();
}
1.3.14 GenerateCodeServiceImpl
package org.mcp.service.impl;
import org.mcp.config.FileTemplateConfig;
import org.mcp.enums.FileTemplateEnum;
import org.mcp.service.GenerateCodeService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
/**
* 代码生成服务实现
*
* @author mcp
* @version 1.0
* @since 2025-05-07 10:19
*/
@Service
public class GenerateCodeServiceImpl implements GenerateCodeService {
@Override
@Tool(name = "getEnumCodeTemplate", description = "根据枚举名和描述、给定的java枚举类示例,生成java枚举类的代码了;例如枚举描述的格式是:单据状态:1=正常,2=已关闭,3=已作废,4=已冻结")
public String getEnumCodeTemplate() {
// 获取模版内容
return FileTemplateConfig.getTemplateContent(FileTemplateEnum.ENUM_TEMPLATE);
}
@Override
@Tool(name = "getEntityCodeTemplate", description = "根据实体名和描述、给定的java实体类示例,生成java实体类的代码,特别注意,实体如果有创建人,创建人ID,创建时间,修改人,修改人ID,修改时间,则需要继承EntityBase类。"
+ "如果存在枚举属性,则同时需要根据描述生成对应的枚举")
public String getEntityCodeTemplate() {
// 获取模版内容
return FileTemplateConfig.getTemplateContent(FileTemplateEnum.ENTITY_TEMPLATE);
}
}
1.3.15 TableService
package org.mcp.service;
import org.mcp.entity.Tables;
import java.util.List;
import java.util.Map;
/**
* 表服务
*
* @author mcp
* @version 1.0
* @since 2025-05-07 14:01
*/
public interface TableService {
List<Tables> findAllTableNames(String tableSchema);
List<String> findAllSchemaNames();
Map<String, Object> getCreateTableStatement(String tableSchema, String tableName);
}
1.3.16 TableServiceImpl
package org.mcp.service.impl;
import jakarta.annotation.Resource;
import org.mcp.entity.Tables;
import org.mcp.manager.InformationSchemaManager;
import org.mcp.service.TableService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* 表服务实现
*
* @author mcp
* @version 1.0
* @since 2025-05-07 14:01
*/
@Service
public class TableServiceImpl implements TableService {
@Resource
private InformationSchemaManager informationSchemaManager;
@Override
@Tool(name = "findAllTableNames", description = "根据schema或数据库名,查询当前数据库下的所有可用的表名和表备注")
public List<Tables> findAllTableNames(@ToolParam(description = "表的schema,即数据库名") String tableSchema) {
return informationSchemaManager.findAllTableNames(tableSchema);
}
@Override
@Tool(name = "findAllSchemaNames", description = "查询当前所有可用的schema或数据库名")
public List<String> findAllSchemaNames() {
return informationSchemaManager.findAllSchemaNames();
}
@Override
@Tool(name = "getCreateTableStatement", description = "根据schema和表名,查询当前数据库下的对应数据库表创建语句")
public Map<String, Object> getCreateTableStatement(@ToolParam(description = "表的schema,即数据库名")String tableSchema, @ToolParam(description = "表名") String tableName) {
return informationSchemaManager.getCreateTableStatement(tableSchema, tableName);
}
}
1.3.17 GenerateCodeServerApplication
package org.mcp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.CrossOrigin;
@SpringBootApplication
@CrossOrigin(
origins = "*",
allowedHeaders = "*",
exposedHeaders = {"Cache-Control", "Connection"} // 暴露必要头
)
public class GenerateCodeServerApplication {
public static void main(String[] args) {
SpringApplication.run(GenerateCodeServerApplication.class, args);
}
}
1.3.18 CommonService
追加一个公共服务,提供一些公共方法,比如获取当前时间。
package org.mcp.service;
public interface CommonService {
/**
* 获取当前时间,格式化为yyyy-MM-dd HH:mm:ss
*/
String getCurrentTime();
}
1.3.19 CommonServiceImpl
package org.mcp.service.impl;
import org.mcp.service.CommonService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
public class CommonServiceImpl implements CommonService {
/**
* 获取当前时间,格式化为yyyy-MM-dd HH:mm:ss
*/
@Override
@Tool(name = "getCurrentTime", description = "获取当前时间,格式化为yyyy-MM-dd HH:mm:ss")
public String getCurrentTime() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return now.format(formatter);
}
}
1.4 完整配置
1.4.1 entityTemplate.txt
package org.pine.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.pine.api.enums.DeliveryStatusEnum;
import org.pine.api.enums.OrderStatusEnum;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出库单
*
* @author 01434188
* @version 1.0
* @since 2025-04-29 10:46
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class OutboundOrder extends EntityBase {
/**
* 主键id
*/
private Long id;
/**
* 出库单号
*/
private String outboundNo;
/**
* 出库通知单号
*/
private String outboundNoticeNo;
/**
* 出库通知明细流水号
*/
private String outboundNoticeDetailNo;
/**
* 出库作业号
*/
private String outboundTaskNo;
/**
* 出库作业明细号
*/
private String outboundTaskDetailNo;
/**
* 出库状态:1=未出库,3=全部出库
*/
private DeliveryStatusEnum deliveryStatus;
/**
* 单据状态:1=正常,2=已关闭,3=已作废,4=已冻结
*/
private OrderStatusEnum status;
/**
* 出库日期
*/
private Date outboundDate;
/**
* 通知物料数量
*/
private BigDecimal qty;
/**
* 物料名称
*/
private String materialName;
/**
* 物料代码
*/
private String materialCode;
}
1.4.2 enumTemplate.txt
package org.pine.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 单据状态枚举
*
* @author mcp
* @version 1.0
* @since 2025-04-29 11:18
*/
@AllArgsConstructor
@Getter
public enum OrderStatusEnum {
// 单据状态:1=正常,2=已关闭,3=已作废,4=已冻结
NORMAL(1, "正常"),
CLOSED(2, "已关闭"),
INVALID(3, "已作废"),
FROZEN(4, "已冻结");
private final Integer code;
private final String message;
public static OrderStatusEnum getInstance(Integer code) {
if (Objects.isNull(code)) {
return null;
}
return Arrays.stream(OrderStatusEnum.values())
.filter(e -> Objects.equals(e.getCode(), code))
.findFirst()
.orElse(null);
}
}
1.4.3 application.yml
server:
port: 8081
tomcat:
uri-encoding: UTF-8
keep-alive-timeout: 30000
max-connections: 100
servlet:
encoding:
charset: UTF-8
force: true
enabled: true
compression:
enabled: false # 禁用压缩(否则流式数据可能被缓冲)
spring:
main:
allow-bean-definition-overriding: true
application:
name: mcp-server
datasource:
url: jdbc:mysql://localhost:3306/test1?useSSL=false&serverTimezone=UTC
username: eqq
password: 111
driver-class-name: com.mysql.cj.jdbc.Driver
ai:
mcp:
server:
stdio: false
enabled: true
name: mcp-server # MCP服务器名称
type: ASYNC
version: 1.0.0 # 服务器版本号
# 配置阿里的密钥,模型
dashscope:
api-key: sk-ba8xxx你自己的key
chat:
options:
model: qwen-plus
1.5 页面示例
1.5.1 页面展示
启动springboot项目后,访问地址:http://localhost:8081/
第一个输入框中输入你的想法,例如,查看一下数据库表xxx.xxx的信息,并生成java实体类代码,涉及到枚举的话,也生成一下
。第二个输入框是对话ID,同一个对话ID可以进行多轮对话。
得到的结果如下(页面没渲染markdown,也没做代码高亮):
1.5.2 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>流式 Markdown 展示</title>
<style>
#output {
border: 1px solid #ccc;
padding: 15px;
margin-top: 10px;
min-height: 200px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<input type="text" id="promptInput" placeholder="输入提示..." style="width: 80%; padding: 10px;">
<input type="text" id="conversationIdInput" placeholder="输入conversationId..." style="width: 80%; padding: 10px;">
<button onclick="sendPrompt()">发送</button>
<button onclick="disconnectAiServer()">断开连接</button>
<h2>流式 Markdown 输出:</h2>
<div id="output"></div>
<div style="height: 100px"> </div>
<script>
let eventSource;
function connectAiServer(url) {
eventSource = new EventSource(url);
}
function disconnectAiServer() {
if (eventSource) {
eventSource.close()
}
autoCloseAlert("已经断开sse连接!", 1500);
}
const outputDiv = document.getElementById('output');
function sendPrompt() {
outputDiv.textContent = '';
const prompt = document.getElementById('promptInput').value;
const conversationId = document.getElementById('conversationIdInput').value;
try {
connectAiServer(`http://localhost:8081/api/chat/generateCode?conversationId=${encodeURIComponent(conversationId)}&message=${encodeURIComponent(prompt)}`);
autoCloseAlert("已经建立sse连接!", 1500);
eventSource.onmessage = function(event) {
try {
const content = event.data || '';
outputDiv.textContent += (content);
} catch (e) {
console.error('解析失败:', e);
disconnectAiServer();
}
};
} catch (e) {
outputDiv.textContent = '请求失败' + e.message;
disconnectAiServer();
}
eventSource.onerror = function(err) {
console.error("SSE 错误:", err);
disconnectAiServer();
};
}
function autoCloseAlert(message, duration = 2000) {
const alertBox = document.createElement('div');
alertBox.style.position = 'fixed';
alertBox.style.top = '20px';
alertBox.style.left = '50%';
alertBox.style.transform = 'translateX(-50%)';
alertBox.style.backgroundColor = '#ff4d4d';
alertBox.style.color = '#fff';
alertBox.style.padding = '10px 20px';
alertBox.style.borderRadius = '5px';
alertBox.style.zIndex = '9999';
alertBox.innerText = message;
document.body.appendChild(alertBox);
setTimeout(() => {
alertBox.remove();
}, duration);
}
</script>
</body>
</html>
二、附录
2.1 一些可能实现的功能推荐
以下内容来自kimi。
Spring Boot + MCP 可实现的应用方向及示例:
智能对话系统
应用场景:打造一个能与用户进行智能对话的系统,如客服机器人、聊天助手等,可集成到网页、移动应用等多渠道,为用户提供更便捷的服务。
实现要点:利用 Spring Boot 搭建基础服务架构,引入 MCP 相关依赖,例如 spring-ai-core、spring-ai-anthropic-spring-boot-starter 等,通过在服务方法上添加 @Tool 注解等配置,将业务逻辑与 AI 对话功能集成,实现对用户输入的智能理解和自动回复。
智能代码助手
应用场景:为开发者提供代码编写建议、代码片段、技术文档等辅助信息,提高开发效率和代码质量。
实现要点:结合 Spring Boot 构建服务,借助 MCP 的能力,利用现有的代码库、技术文档等资源训练 AI 模型,使其理解代码逻辑和开发场景,为用户提供服务。
私密知识库问答
应用场景:为个人或团队建立专属的知识库,用户可通过自然语言提问获取知识库中的信息,满足企业内部知识管理、个人学习笔记整理等需求。
实现要点:使用 Spring Boot + MCP 构建应用,将知识库的内容进行数据化处理,并利用 AI 模型进行语义理解和知识检索,实现精准的知识问答。
智能数据分析助手
应用场景:帮助用户分析数据,如销售数据、财务数据、市场调研数据等,提供数据可视化、趋势预测等功能。
实现要点:在 Spring Boot 服务中集成数据分析库和工具,如 Apache POI 等,再通过 MCP 将其与 AI 功能整合,使用户能以对话的形式进行数据分析操作。
智能内容创作助手
应用场景:辅助用户创作文本内容,如撰写文章、生成文案、创作故事等,为内容创作者提供灵感和便利。
实现要点:利用 Spring Boot 搭建平台,结合 MCP 调用 AI 文本生成模型,根据用户输入的主题、关键词等要求生成相应的内容。
智能教育辅导
应用场景:针对教育领域,为学生提供个性化学习辅导、问题解答、作业批改等功能。
实现要点:收集整合教育资源,如教材、题库、知识点讲解等,构建教育知识图谱,通过 Spring Boot + MCP 为学生提供智能辅导服务。
智能健康助手
应用场景:为用户提供必须的健康咨询、症状分析、健康建议等服务。
实现要点:整合医疗健康知识库和用户健康数据,利用 AI 模型进行症状分析和健康风险评估,通过 Spring Boot 构建应用并用 MCP 实现智能对话功能。
智能旅行规划助手
应用场景:帮助用户规划旅行行程,包括目的地推荐、景点介绍、行程安排、酒店预订等。
实现要点:收集旅游数据,如景点信息、交通时刻表、酒店资源等,结合用户的偏好和需求,通过 Spring Boot + MCP 提供个性化的旅行规划建议。
智能音乐推荐助手
应用场景:根据用户的音乐喜好和听歌历史,为其推荐符合口味的音乐、歌单等。
实现要点:分析用户的音乐数据,建立音乐推荐模型,利用 Spring Boot + MCP 为用户提供准确的音乐推荐服务。
智能汽车助手
应用场景:为汽车用户提供一个智能助手,实现车辆信息查询、远程控制、驾驶辅助等功能,提升车主的驾驶体验。
实现要点:借助汽车的物联网技术获取车辆数据,通过 Spring Boot + MCP 构建智能助手应用,实现与车主的智能交互。
2.2 Srping-AI MCP的官方文档
https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-overview.html
2.3 阿里百炼MCP服务
mcp介绍:
https://help.aliyun.com/zh/model-studio/mcp-introduction
mcp服务:
https://bailian.console.aliyun.com/?spm=a2c4g.11186623.0.0.60907980b9Eirh&tab=mcp#/mcp-market
百炼说明文档:
https://help.aliyun.com/zh/model-studio/what-is-model-studio?spm=a2c4g.11174283.0.i0
2.4 调整为STDIO的方式,并使用idea-通义灵码插件调用
2.4.1 修改配置
在原有的配置基础上,修改以下属性(没有则新增,不要删减原有配置):
spring:
main:
web-application-type: none
banner-mode: off
ai:
mcp:
server:
stdio: true # 启用stdio
2.4.2 配置通义灵码
首先保证你的插件是最新的,这样才支持MCP扩展。
然后使用maven install 你的项目,打jar包。
在通义灵码插件中,右上角点击【个人设置】 中的【MCP服务】
然后点击【手动添加】
接着会弹出一个框,需要填入你的MCP 服务对应的东西:
- 名称:你自己的应用吗名称,随便写,也可以见名知义。
- 类型:选择 stdio
- 命令:你安装java的路径,比如我的java执行文件的完整路径是
/Users/xxx/Library/Java/JavaVirtualMachines/corretto-21.0.6/Contents/Home/bin/java
参数:-jar /Users/01434188/IdeaProjects/spring-mcp-demo/generate-code-server/target/generate-code-server-0.0.1-SNAPSHOT.jar
命令和参数组合起来,其实就是 java -jar xxx.jar
的一个执行命令。只是必须指定java 的完整路径和jar包的完整路径。
到这里之后,点击【保存】就可以看到会进行连接你的MCP 服务:
2.4.3 使用
在通义灵码这里,选择使用【智能体】。然后可以问它。会触发是否执行MCP工具的提示,点击执行即可。
其他可使用的自然语言示例(有兴趣的可以去试试):
- 当前可用的数据库有哪些
- 数据库xxx_xxx的表有哪些
- 数据库xxx_xxx的表xx有哪些字段
- 数据库xxx_xxx的表xx建表语句是啥
- 查看数据库xxx_xx的表xxx,并生成实体类和枚举
- xxx表进行代码生成,以及对应的枚举(这个会直接生成java文件)
2.4.4 参考文章
通义灵码官方文档:
-
https://modelscope.cn/docs/mcp/lingma
-
https://help.aliyun.com/zh/lingma/user-guide/guide-for-using-mcp#74173ec988kvt
2.5 可以直接使用的mcp服务
在阿里的魔塔上找到几个有趣的mcp服务:
- https://www.modelscope.cn/mcp/servers/@baranwang/mcp-trends-hub 中文趋势聚合
- https://www.modelscope.cn/mcp/servers/@21st-dev/magic-mcp 智能组件生成器
- https://www.modelscope.cn/mcp/servers/@tokenizin-agency/mcp-npx-fetch 内容抓取转换器
2.6 使用cherry studio连接mcp服务
参考文档:
https://docs.cherry-ai.com/advanced-basic/mcp