Sping-AI alibaba 记忆会话(MySQL)文生图
1、文章以阿里百炼平台大模型做示例本人已提前在Windows设置好了api-key的环境变量如果要修改api-key的环境变量记得重启IDEA避免读取不到api-key可以从阿里百炼平台生成2、也集成了ollama的模型如果要使用需要下载ollama和指定模型到本地给你们链接想玩的话可以去下载代码中记得把对应的依赖和配置Bean的地方打开呦3、下面开始示例父pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdSpringAiAlibaba_demo/artifactId version0.0.1-SNAPSHOT/version nameSpringAiAlibaba_demo/name descriptionSpringAiAlibaba_demo/description modules moduledemo2/module /modules packagingpom/packaging properties !-- spring-ai.version1.0.0/spring-ai.version-- !-- spring-ai-alibaba.version1.1.2.0/spring-ai-alibaba.version-- spring.ai.alibaba.version1.1.2.0/spring.ai.alibaba.version spring-boot.version3.5.1/spring-boot.version mysql.version8.4.0/mysql.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency !--子项目中用到了spring-ai-starter-model-ollamaollama一直是Spring Ai在维护 这两个是spring ai在维护只引入spring ai alibaba不行-- !-- dependency-- !-- groupIdorg.springframework.ai/groupId-- !-- artifactIdspring-ai-bom/artifactId-- !-- version${spring-ai.version}/version-- !-- typepom/type-- !-- scopeimport/scope-- !-- /dependency-- !-- dependency-- !-- groupIdcom.alibaba.cloud.ai/groupId-- !-- artifactIdspring-ai-alibaba-bom/artifactId-- !-- version${spring-ai-alibaba.version}/version-- !-- typepom/type-- !-- scopeimport/scope-- !-- /dependency-- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version${mysql.version}/version /dependency !-- Spring AI Alibaba DashScope Starter集成阿里云通义千问DashScope大模型能力提供 ChatModel 等核心组件 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version${spring.ai.alibaba.version}/version /dependency !-- 聊天记忆 JDBC 支持 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-memory-jdbc/artifactId version${spring.ai.alibaba.version}/version /dependency /dependencies /dependencyManagement build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version${spring-boot.version}/version /plugin /plugins /build /project子pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.example/groupId artifactIdSpringAiAlibaba_demo/artifactId version0.0.1-SNAPSHOT/version /parent artifactIddemo2/artifactId namedemo2/name descriptiondemo2/description dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency !-- 聊天记忆 JDBC 支持 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-memory-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- dependency-- !-- groupIdorg.springframework.ai/groupId-- !-- artifactIdspring-ai-starter-model-ollama/artifactId-- !-- /dependency-- /dependencies build plugins !-- 指定Java源码版本为9编译目标版本也为9。即项目使用Java9语法编写并编译生成Java9兼容的字节码-- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration source9/source target9/target /configuration /plugin /plugins /build /projectapplication.ymlserver: port: 9999 servlet: encoding: enabled: true force: true charset: utf-8 spring: application: name: demo2 ai: dashscope: #使用本地大模型这里也要指定api-key因为Spring Ai Alibaba框架底层需要读取该参数即使不用也要指定不然启动会报错 api-key: ${QIANWEN} chat: memory: repository: jdbc: initialize-schema: always #总是自动创建chatMemory存储的表结构 platform: mariadb #兼容的数据库这里mariadb对应MySQL datasource: url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_cicontrollerpackage com.example.controller; import com.example.service.Demo2Service; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; RestController public class Demo2Controller { Autowired private Demo2Service demo2Service; GetMapping(value /dochat/text) public FluxString doChatText(RequestParam(userId) Long userId, RequestParam(conversationId) Long conversationId, RequestParam(question) String question) { return demo2Service.doChatText(userId, conversationId, question); } GetMapping(value /dochat/img) public void doChatImg(HttpServletResponse response, RequestParam(question) String question) { demo2Service.doChatImg(response, question); } }servicepackage com.example.service; import jakarta.servlet.http.HttpServletResponse; import reactor.core.publisher.Flux; public interface Demo2Service { FluxString doChatText(Long userId, Long conversationId, String question); void doChatImg(HttpServletResponse response, String question); }implpackage com.example.service.impl; import com.example.service.Demo2Service; import com.example.util.AiChatUtil; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; Service public class Demo2ServiceImpl implements Demo2Service { Autowired private AiChatUtil aiChatUtil; Override public FluxString doChatText(Long userId, Long conversationId, String question) { return aiChatUtil.textStreamChatMemory(userId, conversationId, question); } Override public void doChatImg(HttpServletResponse response, String question) { aiChatUtil.imageStreamChatMemory(response, question); } }utilpackage com.example.util; import jakarta.servlet.http.HttpServletResponse; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; /** * AI 对话记忆管理工具类 * 该类封装了 Spring AI Alibaba 的chatClient、imageModel、ChatMemory组件 * 提供基于会话 ID (userId conversationId)的多轮对话支持和记忆清理功能及文生图功能。 * 适用于需要在 Service 层或 Controller 层灵活控制对话上下文的场景。 */ Component public class AiChatUtil { /** * 具备多轮对话能力的 ChatClient 实例 */ private final ChatClient chatClient; /** * 具备文生图能力的 ImageModel 实例 */ private final ImageModel imageModel; /** * 对话记忆存储组件 * 用于直接操作底层记忆数据如清空指定会话的历史记录 */ private final ChatMemory chatMemory; /** * 构造函数注入 * * param chatClient 预配置的 ChatClient负责与大模型交互并自动处理上下文拼接 * param chatMemory 预配置的 ChatMemory负责历史消息的持久化存储与检索 */ public AiChatUtil(ChatClient chatClient, Qualifier(cloudWanTwoPointFiveT2iPreview) ImageModel imageModel, ChatMemory chatMemory) { this.chatClient chatClient; this.imageModel imageModel; this.chatMemory chatMemory; } //---------------------------------------------------------------记忆会话--------------------------------------------------------------------------------------------- /** * 执行带记忆的多轮对话 * 该方法通过动态参数覆盖默认的 Advisor 配置实现针对特定会话的个性化记忆控制。 * * param userId 用户ID * param conversationId 会话唯一标识符用于隔离不同用户或不同对话窗口的上下文 * param question 用户当前输入的文本消息 * return 大模型生成的回复内容字符的形式返回 */ public String textCallChatMemory(Long userId, Long conversationId, String question) { return chatClient.prompt() // 配置 Advisors 的动态参数 .advisors(advisor - advisor // 指定当前对话所属的会话 ID // ChatMemoryAdvisor 会根据此 ID 从存储中加载对应的历史消息 .param(ChatMemory.CONVERSATION_ID, userId conversationId) ) // 设置用户当前发送的消息 .user(question) // 发起同步调用并获取纯文本回复 .call() .content(); } /** * 执行带记忆的多轮对话 * 该方法通过动态参数覆盖默认的 Advisor 配置实现针对特定会话的个性化记忆控制。 * * param userId 用户ID * param conversationId 会话唯一标识符用于隔离不同用户或不同对话窗口的上下文 * param question 用户当前输入的文本消息 * return 大模型生成的回复内容流的形式返回 */ public FluxString textStreamChatMemory(Long userId, Long conversationId, String question) { return chatClient.prompt() // 配置 Advisors 的动态参数 .advisors(advisor - advisor // 指定当前对话所属的会话 ID // ChatMemoryAdvisor 会根据此 ID 从存储中加载对应的历史消息 .param(ChatMemory.CONVERSATION_ID, userId conversationId) ) // 设置用户当前发送的消息 .user(question) // 发起同步调用并获取纯文本回复 .stream() .content(); } /** * 清空指定会话的对话记忆 * 该操作会从底层存储如内存、Redis 或数据库中删除该 userId conversationId 关联的所有历史消息。 * * 适用场景 * 1. 用户点击“新建对话”或“重置上下文”按钮 * 2. 用户退出登录或会话过期 * 3. 检测到敏感话题需要强制清除历史记录 * * param userId 用户ID * param conversationId 会话ID */ public void clearChatMemory(Long userId, Long conversationId) { // 调用 ChatMemory 接口的 clear 方法物理删除指定会话的历史数据 chatMemory.clear(userId conversationId); } //---------------------------------------------------------------文生图--------------------------------------------------------------------------------------------- /** * 根据用户消息生成图片 * * param question 用户当前输入的文本消息 * return 大模型生成的回复内容url的形式返回 */ public String imageCallChatMemory(String question) { return imageModel.call(new ImagePrompt(question)) .getResult() .getOutput() .getUrl(); } /** * 根据用户消息生成图片 * * param question 用户当前输入的文本消息 * return 大模型生成的回复内容流的形式返回 */ public void imageStreamChatMemory(HttpServletResponse response, String question) { // 使用提示词生成图片 ImageResponse imageResponse imageModel.call(new ImagePrompt(question)); // 提取生成的图片URL String imageUrl imageResponse.getResult().getOutput().getUrl(); try { // 将图片URL转换为可读流 URL url URI.create(imageUrl).toURL(); InputStream in url.openStream(); // 设置响应头为PNG格式 response.setHeader(Content-Type, MediaType.IMAGE_PNG_VALUE); // 写出图片字节到HTTP响应输出流 response.getOutputStream().write(in.readAllBytes()); response.getOutputStream().flush(); } catch (IOException e) { // IO异常处理设置500错误状态码 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } }config文中{WorkspaceId}记得换成你自己的业务空间ID呦package com.example.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; 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.memory.InMemoryChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.image.ImageModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.backoff.FixedBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; Configuration public class LLMConfig { Bean public ChatMemory chatMemory() { //1.创建内存存储库 //InMemoryChatMemoryRepository 是 ChatMemory 的底层存储实现负责实际保存和读取消息列表。 //这里使用内存存储适用于单节点或测试环境生产环境可替换为 RedisChatMemoryRepository 等持久化实现。 InMemoryChatMemoryRepository repository new InMemoryChatMemoryRepository(); //2.构建 MessageWindowChatMemory 实例 //MessageWindowChatMemory 是 ChatMemory 接口的具体实现类它封装了“窗口滑动”的逻辑。 return MessageWindowChatMemory.builder() //注入底层存储库 .chatMemoryRepository(repository) //设置窗口大小仅保留最近的 10 条消息包含用户提问和AI回答 .maxMessages(20) .build(); } Bean(name cloudQwenThreePointSevenMax) public ChatClient cloudQwenThreePointSevenMaxChatClient(ChatMemory chatMemory) { return ChatClient.builder(DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv(QIANWEN)) /** * 百炼为华北2北京、新加坡地域推出了业务空间专属域名能够为推理请求提供卓越的性能和更高的稳定性建议迁移至新域名 * 华北2北京地域从 https://dashscope.aliyuncs.com 迁移至 https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com * 新加坡地域从 https://dashscope-intl.aliyuncs.com 迁移至 https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com * 其中 {WorkspaceId} 为您的业务空间 ID可在百炼控制台的业务空间详情页面查看。现有域名仍可正常使用。 */ .baseUrl(https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com) .build()) .defaultOptions(DashScopeChatOptions.builder() .model(qwen3.7-max) //控制输出的随机性。值越低输出越确定/保守值越高输出越多样/创意。0.7 是平衡创造性与稳定性的常用值 .temperature(0.7) //控制采样的词汇范围。模型从累积概率达到 topP 的候选词中采样。1.0 表示考虑所有候选词即不做截断过滤 .topP(1.0) .build()) .build()) //增强器访问大模型前或后去做一些事这里去检查创建对应表结构 .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } Bean(name cloudWanTwoPointFiveT2iPreview) public ImageModel cloudWanTwoPointSevenT2vChatModel() { DashScopeImageApi dashScopeImageApi DashScopeImageApi.builder() /** * 百炼为华北2北京、新加坡地域推出了业务空间专属域名能够为推理请求提供卓越的性能和更高的稳定性建议迁移至新域名 * 华北2北京地域从 https://dashscope.aliyuncs.com 迁移至 https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com * 新加坡地域从 https://dashscope-intl.aliyuncs.com 迁移至 https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com * 其中 {WorkspaceId} 为您的业务空间 ID可在百炼控制台的业务空间详情页面查看。现有域名仍可正常使用。 */ .baseUrl(https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com) .apiKey(System.getenv(QIANWEN)) .build(); DashScopeImageOptions build DashScopeImageOptions.builder() //文生图可用模型地址尽量不用最新的都是问题靠。。。。。。 //https://bailian.console.aliyun.com/cn-beijing?tabapi#/api/?typemodelurl2862677 //https://bailian.console.aliyun.com/cn-beijing?tabapi#/api/?typemodelurl2712483 .model(wan2.5-t2i-preview) //设置输出分辨率 .height(1440) .width(1440) //生成1张图/视频 .n(1) .build(); //Spring Ai Alibaba老版本下这里的重试设置没用Spring Ai Alibaba在new DashScopeImageModel时给固定设置了最大10次重试和间隔15_000L无语。。。。。。 //我改用了新版本这里的配置就生效了哈哈哈 //因为图片生成较慢请求有轮询机制时比如浏览器请求超过最大重试次数Ai会返回Null,所以需要增加重试次数 RetryTemplate retryTemplate new RetryTemplate(); SimpleRetryPolicy retryPolicy new SimpleRetryPolicy(); //最大重试次数 retryPolicy.setMaxAttempts(30); retryTemplate.setRetryPolicy(retryPolicy); FixedBackOffPolicy fixedBackOffPolicy new FixedBackOffPolicy(); //重试间隔秒 fixedBackOffPolicy.setBackOffPeriod(2000L); retryTemplate.setBackOffPolicy(fixedBackOffPolicy); return new DashScopeImageModel(dashScopeImageApi, build, retryTemplate); } // /** // * 如果要使用ollama的模型需要增加Spring Ai的依赖并且在子项目引入对应starter的依赖 // */ // Bean(name localQwenThreePointFive) // public ChatClient localQwenThreePointFiveChatClient(ChatMemory chatMemory) { // return ChatClient.builder(OllamaChatModel.builder() // .ollamaApi(OllamaApi.builder() // .baseUrl(http://localhost:11434) // .build()) // .defaultOptions(OllamaOptions.builder() // .model(qwen3.5:2b) // //控制输出的随机性。值越低输出越确定/保守值越高输出越多样/创意。0.7 是平衡创造性与稳定性的常用值 // .temperature(0.7) // //控制采样的词汇范围。模型从累积概率达到 topP 的候选词中采样。1.0 表示考虑所有候选词即不做截断过滤 // .topP(1.0) // .build()) // .build()) // //增强器访问大模型前或后去做一些事这里去检查创建对应表结构 // .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // .build(); // } }我们来试一下记忆会话我们来试一下文生图