Browse Source

chore(deps): 升级 Spring Boot 3.5.8 / Spring AI 1.1.2 / Spring AI Alibaba 1.1.2.2

升级到官方兼容矩阵组合(SAA 1.1.2.2 的官方基线即 Boot 3.5.8 + SAI 1.1.2)。

编译适配
- SAA 1.1.2.2 将内部类 DashscopeChatOptionsBuilder 更名为 DashScopeChatOptionsBuilder
- Spring AI 1.1.x 起 AssistantMessage/ToolResponseMessage 带 metadata 的构造器改为
  protected,MessageConverter 改用标准 builder

依赖增删
- MCP SDK 继续排除传递依赖并锁定 0.18.3:1.1.2 传递的是 0.17.0,其 JsonMapper 包名
  为 json.jackson(无 jackson2),与本项目使用的 json.jackson2 不兼容,不锁版本会编译失败;
  已核对 spring-ai-mcp 1.1.2 仅引用 McpSyncClient/McpAsyncClient/McpClient/
  McpTransportContext/McpSchema/Assert,这些类 0.18.3 均存在,覆盖安全
- 删除 spring-security-oauth2-client:原为 Spring AI 1.0.x ToolCallingAutoConfiguration
  的 ClassNotFound workaround,1.1.2 已无该耦合(已核对字节码常量池零 oauth2 引用)
- 删除显式 victools jsonschema-generator:改由 spring-ai-model 传递引入(4.38.0),
  顺带消除原先 4.38.0/4.37.0 混用
- 删除 kryo:唯一使用者 FileBasedChatMemory 为死代码,本提交一并删除
- 新增 jtokkit 1.1.0(OverlapTokenTextSplitter 直接使用)
- 升 Knife4j 4.5.0、MyBatis-Plus 3.5.14、hutool 5.8.41、jjwt 0.12.7;
  Lombok 版本交由 Boot 父 POM 托管(1.18.42)

刻意不采纳的升级建议
- 不导入 spring-ai-alibaba-bom:该 BOM 的 dependencyManagement 并不包含
  spring-ai-alibaba-starter-dashscope,导入后仍需显式写版本号,无收益
- 保留 spring-ai-openai / spring-ai-pgvector-store 的非 starter 坐标:项目手动构建
  ChatModel/EmbeddingModel/VectorStore,换 starter 会额外引入 model-openai、chat-client、
  chat-memory 三个自动配置,可能生成与手写工厂、DatabaseChatMemory 冲突的 Bean
Spring-AI-1.1.2
wanghanlin 3 days ago
parent
commit
1731a3ca60
  1. 65
      pom.xml
  2. 5
      src/main/java/com/wok/supportbot/SupportBotApplication.java
  3. 85
      src/main/java/com/wok/supportbot/chatmemory/FileBasedChatMemory.java
  4. 4
      src/main/java/com/wok/supportbot/config/ChatModelFactory.java
  5. 13
      src/main/java/com/wok/supportbot/converter/MessageConverter.java

65
pom.xml

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<version>3.5.8</version>
<relativePath/>
</parent>
<groupId>com.cdw</groupId>
@ -14,7 +14,10 @@
<name>智能客服</name>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.0.0.4</spring-ai-alibaba.version>
<!-- Spring AI BOM 版本(统一管理 org.springframework.ai 全部依赖) -->
<spring-ai.version>1.1.2</spring-ai.version>
<!-- Spring AI Alibaba 版本(1.1.2.2 官方基线 = Boot 3.5.8 + Spring AI 1.1.2) -->
<spring-ai-alibaba.version>1.1.2.2</spring-ai-alibaba.version>
<!-- 设置为 true 可跳过前端 SDK 构建,加快后端编译:mvn compile -Dskip.frontend=true -->
<skip.frontend>false</skip.frontend>
</properties>
@ -44,10 +47,12 @@
<dependencyManagement>
<dependencies>
<!-- Spring AI BOM:统一管理所有 Spring AI 依赖版本 -->
<!-- 注意:不导入 spring-ai-alibaba-bom,因其 dependencyManagement 不包含
spring-ai-alibaba-starter-dashscope,导入后仍需显式写版本号,无收益 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.1</version>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -66,6 +71,7 @@
</dependency>
<!-- Spring AI Alibaba DashScope(通义千问)- 新版 starter,替代老版 spring-ai-alibaba-starter -->
<!-- 必须显式写版本号:spring-ai-alibaba-bom 并不管理本模块 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
@ -79,7 +85,13 @@
</dependency>
<!-- Spring AI MCP Client(Model Context Protocol 客户端) -->
<!-- 排除旧版 MCP SDK 0.10.0,显式引入新版 0.18.3 修复 SSE 400 + 超时问题 -->
<!-- 必须排除传递引入的 MCP SDK 并显式锁定 0.18.3,切勿删除:
Spring AI 1.1.2 的 spring-ai-mcp 仍锁定 MCP SDK 0.17.0,而 0.17.0 的 JsonMapper
包名是 io.modelcontextprotocol.json.jackson(无 jackson2),
本项目 McpClientManager 使用的是 0.18.0 起才有的 ...json.jackson2.JacksonMcpJsonMapper,
不锁版本会直接编译失败。0.18.3 已修复 SSE 400 + 超时问题。
安全性:spring-ai-mcp 1.1.2 仅引用 McpSyncClient/McpAsyncClient/McpClient/
McpTransportContext/McpSchema/Assert,这些类 0.18.3 均存在。 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
@ -91,7 +103,7 @@
</exclusions>
</dependency>
<!-- 显式引入新版 MCP SDK(覆盖 spring-ai-mcp 的旧版 0.10.0) -->
<!-- 显式引入新版 MCP SDK(覆盖 spring-ai-mcp 传递的 0.17.0) -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
@ -102,7 +114,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.37</version>
<version>5.8.41</version>
</dependency>
<!-- SFTP 客户端(附件上传到 SFTP 服务器) -->
<dependency>
@ -114,28 +126,24 @@
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
<version>4.5.0</version>
</dependency>
<!-- 支持结构化输出 -->
<!-- 结构化输出:victools jsonschema-generator 已由 spring-ai-model 传递引入(4.38.0),
项目不再显式声明(显式声明曾导致 4.38.0/4.37.0 版本混用) -->
<!-- Token 分词:OverlapTokenTextSplitter 直接使用(与 Spring AI TokenTextSplitter 同库同版本) -->
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.38.0</version>
</dependency>
<!-- 支持文件会话记忆持久化的序列化 -->
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.6.2</version>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>
<!-- Lombok 版本交由 Spring Boot 父 POM 托管(3.5.8 管理 1.18.42) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
<dependency>
@ -172,31 +180,31 @@
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
<version>0.12.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<version>0.12.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<version>0.12.7</version>
<scope>runtime</scope>
</dependency>
<!-- Spring AI ToolCallingAutoConfiguration 依赖 spring-security-oauth2-client,补充该依赖避免 ClassNotFoundException -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<!-- 说明:spring-security-oauth2-client 补丁依赖已移除。
Spring AI 1.0.1 的 ToolCallingAutoConfiguration 曾硬引用 oauth2-client 类导致
ClassNotFoundException;1.1.2 已无该耦合(已核对字节码常量池零 oauth2 引用),
且 ToolCallingManager Bean 仍由 spring-ai-autoconfigure-model-tool 提供。
若启动仍报相关类缺失,再按需加回。 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.12</version>
<version>3.5.14</version>
</dependency>
</dependencies>
@ -238,7 +246,8 @@
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<!-- 使用 Boot 父 POM 托管的 ${lombok.version},与运行时版本保持一致 -->
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>

5
src/main/java/com/wok/supportbot/SupportBotApplication.java

@ -8,8 +8,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 主启动类
* spring-security-oauth2-client 依赖已通过 pom.xml 补充
* 确保 ToolCallingAutoConfiguration 不会抛出 ClassNotFoundException
* <p>
* ChatModel / EmbeddingModel / VectorStore 均由项目内工厂手动构建 ChatModelFactory
* EmbeddingModelFactoryPgVectorStoreConfig不使用 Spring AI 的自动配置
*/
@SpringBootApplication
@EnableScheduling

85
src/main/java/com/wok/supportbot/chatmemory/FileBasedChatMemory.java

@ -1,85 +0,0 @@
package com.wok.supportbot.chatmemory;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 基于文件持久化的对话记忆
*/
public class FileBasedChatMemory implements ChatMemory {
private final String BASE_DIR;
private static final Kryo kryo = new Kryo();
static {
kryo.setRegistrationRequired(false);
// 设置实例化策略
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
}
// 构造对象时指定文件保存目录
public FileBasedChatMemory(String dir) {
this.BASE_DIR = dir;
File baseDir = new File(dir);
if (!baseDir.exists()) {
baseDir.mkdirs();
}
}
@Override
public void add(String conversationId, List<Message> messages) {
List<Message> conversationMessages = getOrCreateConversation(conversationId);
conversationMessages.addAll(messages);
saveConversation(conversationId, conversationMessages);
}
@Override
public List<Message> get(String conversationId) {
return getOrCreateConversation(conversationId);
}
@Override
public void clear(String conversationId) {
File file = getConversationFile(conversationId);
if (file.exists()) {
file.delete();
}
}
private List<Message> getOrCreateConversation(String conversationId) {
File file = getConversationFile(conversationId);
List<Message> messages = new ArrayList<>();
if (file.exists()) {
try (Input input = new Input(new FileInputStream(file))) {
messages = kryo.readObject(input, ArrayList.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return messages;
}
private void saveConversation(String conversationId, List<Message> messages) {
File file = getConversationFile(conversationId);
try (Output output = new Output(new FileOutputStream(file))) {
kryo.writeObject(output, messages);
} catch (IOException e) {
e.printStackTrace();
}
}
private File getConversationFile(String conversationId) {
return new File(BASE_DIR, conversationId + ".kryo");
}
}

4
src/main/java/com/wok/supportbot/config/ChatModelFactory.java

@ -157,7 +157,7 @@ public class ChatModelFactory {
}
DashScopeApi api = apiBuilder.build();
DashScopeChatOptions.DashscopeChatOptionsBuilder optionsBuilder = DashScopeChatOptions.builder()
DashScopeChatOptions.DashScopeChatOptionsBuilder optionsBuilder = DashScopeChatOptions.builder()
.withModel(config.getModelName());
if (config.getTemperature() != null) {
optionsBuilder.withTemperature(config.getTemperature());
@ -352,7 +352,7 @@ public class ChatModelFactory {
/**
* extraConfig 中读取高级参数并设置到 DashScopeChatOptions.Builder
*/
private void applyDashScopeExtraConfig(AiModelConfig config, DashScopeChatOptions.DashscopeChatOptionsBuilder optionsBuilder) {
private void applyDashScopeExtraConfig(AiModelConfig config, DashScopeChatOptions.DashScopeChatOptionsBuilder optionsBuilder) {
Map<String, Object> extra = config.getExtraConfig();
if (extra == null || extra.isEmpty()) return;

13
src/main/java/com/wok/supportbot/converter/MessageConverter.java

@ -110,12 +110,21 @@ public class MessageConverter {
}
case ASSISTANT -> {
List<AssistantMessage.ToolCall> toolCalls = extractToolCalls(cleanMetadata);
yield new AssistantMessage(text, cleanMetadata, toolCalls);
// Spring AI 1.1.x AssistantMessage metadata 的构造器为 protected改用标准 builder
yield AssistantMessage.builder()
.content(text)
.properties(cleanMetadata)
.toolCalls(toolCalls)
.build();
}
case SYSTEM -> new SystemMessage(text);
case TOOL -> {
List<ToolResponseMessage.ToolResponse> responses = extractToolResponses(cleanMetadata);
yield new ToolResponseMessage(responses, cleanMetadata);
// Spring AI 1.1.x ToolResponseMessage 构造器为 protected改用标准 builder
yield ToolResponseMessage.builder()
.responses(responses)
.metadata(cleanMetadata)
.build();
}
};
}

Loading…
Cancel
Save