Spring AI企业级应用实战(4):Chat Memory、会话隔离、持久化与上下文压缩

发布时间:2026/7/28 20:38:04

Spring AI企业级应用实战(4):Chat Memory、会话隔离、持久化与上下文压缩
文章摘要前几篇已经完成Spring AI统一调用层和流式输出。本篇继续实现企业级多轮对话使用MessageChatMemoryAdvisor管理近期消息要求每次请求显式提供conversationId通过PostgreSQL保存完整Chat History与持久化Memory校验租户和用户所有权并在上下文接近预算时生成结构化摘要。最终形成一个支持多实例部署、流式调用、审计和安全删除的会话系统。一、本篇目标完成以下能力创建会话 稳定conversationId 多租户所有权校验 Chat Memory持久化 完整Chat History 同步与流式调用 窗口控制 上下文摘要 清空与删除 可观测性最终链路HTTP请求 → 身份认证 → 会话所有权校验 → 写入Chat History → MessageChatMemoryAdvisor → ChatClient → 模型 → 保存结果 → 更新指标二、项目结构src/main/java/com/zyentor/ai ├── config │ └── ChatMemoryConfig.java ├── conversation │ ├── Conversation.java │ ├── ConversationRepository.java │ ├── ConversationService.java │ └── ConversationController.java ├── history │ ├── ChatMessageRecord.java │ ├── ChatHistoryRepository.java │ └── JdbcChatHistoryRepository.java ├── memory │ ├── MemoryCompactionService.java │ └── ConversationSummary.java ├── service │ └── EnterpriseConversationAiService.java └── security └── UserContext.java三、依赖dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion2.0.0/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-deepseek/artifactId/dependencydependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-webflux/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdorg.postgresql/groupIdartifactIdpostgresql/artifactIdscoperuntime/scope/dependency/dependencies具体Starter名称应以当前Spring AI 2.0.x文档和依赖清单为准。四、配置数据源和模型spring:datasource:url:jdbc:postgresql://localhost:5432/spring_ai_demousername:ai_userpassword:${DB_PASSWORD}ai:model:chat:deepseekdeepseek:api-key:${DEEPSEEK_API_KEY}chat:model:deepseek-v4-flashtemperature:0.2max-tokens:2048生产环境不要把密钥写进仓库。五、配置Chat MemoryConfigurationpublicclassChatMemoryConfig{BeanChatMemorychatMemory(ChatMemoryRepositoryrepository){returnMessageWindowChatMemory.builder().chatMemoryRepository(repository).maxMessages(30).build();}Bean(conversationChatClient)ChatClientconversationChatClient(ChatClient.Builderbuilder,ChatMemorychatMemory){returnbuilder.defaultSystem( 你是企业AI助手。 只能使用当前用户有权访问的上下文。 不得泄露其他用户或租户的信息。 不确定时明确说明。 ).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();}}maxMessages30只是示例生产应通过真实Token分布评测。六、为什么每次请求必须传conversationIdSpring AI 2.0内置Memory Advisor要求显式提供ChatMemory.CONVERSATION_ID调用chatClient.prompt().advisors(spec-spec.param(ChatMemory.CONVERSATION_ID,conversationId)).user(message).call().content();不要使用默认会话也不要把conversationId写死在Advisor Bean中。七、会话表CREATETABLEai_conversation(idVARCHAR(64)PRIMARYKEY,tenant_idVARCHAR(64)NOTNULL,user_idVARCHAR(64)NOTNULL,titleVARCHAR(200),statusVARCHAR(20)NOTNULL,memory_versionBIGINTNOTNULLDEFAULT1,created_atTIMESTAMPNOTNULL,updated_atTIMESTAMPNOTNULL);memory_version可以用于缓存失效摘要版本调试会话重置。八、完整消息表CREATETABLEai_chat_message(idVARCHAR(64)PRIMARYKEY,conversation_idVARCHAR(64)NOTNULL,tenant_idVARCHAR(64)NOTNULL,user_idVARCHAR(64)NOTNULL,roleVARCHAR(20)NOTNULL,contentTEXTNOTNULL,statusVARCHAR(20)NOTNULL,request_idVARCHAR(64)NOTNULL,modelVARCHAR(100),prompt_versionVARCHAR(50),input_tokensBIGINT,output_tokensBIGINT,created_atTIMESTAMPNOTNULL);Chat Memory表只服务模型上下文该表保存完整历史。九、会话创建ServicepublicclassConversationService{privatefinalConversationRepositoryrepository;publicConversationcreate(UserContextuser){InstantnowInstant.now();ConversationconversationnewConversation(conv_UUID.randomUUID(),user.tenantId(),user.userId(),null,ConversationStatus.ACTIVE,1L,now,now);repository.save(conversation);returnconversation;}}tenantId和userId来自认证上下文。十、所有权校验publicConversationrequireOwned(UserContextuser,StringconversationId){returnrepository.findOwned(conversationId,user.tenantId(),user.userId()).orElseThrow(()-newAccessDeniedException(无权访问该会话));}不能只验证会话存在。十一、同步对话服务ServicepublicclassEnterpriseConversationAiService{privatefinalChatClientchatClient;privatefinalConversationServiceconversationService;privatefinalChatHistoryRepositoryhistoryRepository;publicStringchat(UserContextuser,StringconversationId,Stringmessage){conversationService.requireOwned(user,conversationId);StringrequestIdUUID.randomUUID().toString();historyRepository.saveUserMessage(user,conversationId,requestId,message);try{StringanswerchatClient.prompt().advisors(spec-spec.param(ChatMemory.CONVERSATION_ID,conversationId).param(tenantId,user.tenantId())).user(message).call().content();historyRepository.saveAssistantMessage(user,conversationId,requestId,answer,MessageStatus.COMPLETED);returnanswer;}catch(RuntimeExceptionexception){historyRepository.markFailed(conversationId,requestId,exception.getClass().getSimpleName());throwexception;}}}十二、避免长数据库事务模型调用可能持续数秒。不要开启数据库事务 → 写用户消息 → 等模型十几秒 → 写助手消息 → 提交推荐事务1写用户消息 → 模型调用 → 事务2写助手消息或失败状态通过request_id关联两段记录。十三、流式调用publicFluxServerSentEventChatStreamEventstream(UserContextuser,StringconversationId,Stringmessage){conversationService.requireOwned(user,conversationId);StringrequestIdUUID.randomUUID().toString();historyRepository.saveUserMessage(user,conversationId,requestId,message);StringBuilderbuffernewStringBuilder();returnchatClient.prompt().advisors(spec-spec.param(ChatMemory.CONVERSATION_ID,conversationId)).user(message).stream().content().doOnNext(buffer::append).map(chunk-ServerSentEvent.builder(newChatStreamEvent(requestId,delta,chunk)).build()).doOnComplete(()-historyRepository.saveAssistantMessage(user,conversationId,requestId,buffer.toString(),MessageStatus.COMPLETED)).doOnCancel(()-historyRepository.markCancelled(conversationId,requestId,buffer.toString()));}需要继续验证Provider在取消订阅后是否真正终止上游生成。十四、半段回答是否进入Memory用户取消后可能只生成根据你的问题主要有三个方面第一……不建议把这段内容当作完整Assistant消息进入稳定Memory。可选策略取消时不写Memory 完整Chat History保存CANCELLED消息 下一轮提示用户上一轮已中断十五、为什么需要上下文压缩长期会话会不断消耗Token。只扩大Message Window会导致成本上升延迟增加重要信息被噪声淹没超出上下文窗口。推荐上下文结构化摘要 最近若干轮消息 当前RAG证据十六、摘要表CREATETABLEai_conversation_summary(idVARCHAR(64)PRIMARYKEY,conversation_idVARCHAR(64)NOTNULL,tenant_idVARCHAR(64)NOTNULL,summary_versionBIGINTNOTNULL,covered_until_message_idVARCHAR(64)NOTNULL,content JSONBNOTNULL,created_atTIMESTAMPNOTNULL);结构化内容{goal:构建企业RAG方案,facts:[客户使用PostgreSQL,数据不能离开私有云],decisions:[向量存储选择pgvector],openQuestions:[峰值并发尚未确认]}十七、什么时候触发压缩可以使用消息数量阈值 Token预算阈值 会话持续时间 工具事件数量推荐以Token预算为主预计上下文Token 可用历史预算的80% → 触发压缩十八、压缩流程1. 读取上次摘要 2. 读取摘要之后的新消息 3. 生成新结构化摘要 4. 校验Schema 5. 保存summary_version 6. 更新covered_until_message_id 7. 后续Prompt加载摘要和近期消息原始消息不删除用于审计和重新生成摘要。十九、摘要不能替代业务事实模型摘要可能错误。以下信息必须从业务系统实时获取订单状态当前权限余额-审批结果产品库存合同版本。摘要只能作为对话背景不能作为强一致事实来源。二十、会话清空与删除清空模型上下文chatMemory.clear(conversationId);同时增加memory_version使缓存失效。删除会话流程标记DELETING → 删除Memory → 删除摘要和向量记忆 → 处理附件 → 按策略删除或匿名化History → 标记DELETED二十一、监控指标chat_conversation_created_count chat_memory_message_count chat_memory_load_duration chat_memory_clear_count conversation_cross_owner_denied_count summary_generation_count summary_validation_failed_count stream_cancelled_count history_memory_write_mismatch_count二十二、测试场景同一会话连续多轮 同一用户两个窗口 同租户两个用户 两个不同租户 应用重启 请求进入不同实例 流式取消 模型超时 Memory清空 摘要触发 非法conversationId访问最重要的测试是多租户隔离。二十三、本篇完整架构认证用户 → Conversation所有权 → 完整History写入 → 显式conversationId → MessageChatMemoryAdvisor → 持久化Memory Repository → ChatClient → 模型 → 保存状态 → Token预算触发摘要总结生产级Spring AI多轮对话不能只注册一个Memory Advisor。完整方案必须同时解决稳定会话ID 租户与用户隔离 持久化Memory 独立Chat History 流式状态 上下文压缩 审计与删除下一篇将继续实现Spring AI企业级应用实战5Tool Calling、权限校验、幂等与人工确认。

相关新闻

Python异常嵌套日志处理与结构化日志实践

Python异常嵌套日志处理与结构化日志实践

2026/7/28 20:38:04

1. 异常嵌套日志的痛点解析在Python项目开发中,异常嵌套场景几乎无处不在。当外层异常捕获内层异常时,传统的日志记录方式往往存在三个典型问题:信息割裂:内层异常被外层捕获后,原始堆栈信息可能被覆盖日志冗余&#x…

ExifToolGUI图片元数据管理工具:免费开源的批量编辑终极指南

ExifToolGUI图片元数据管理工具:免费开源的批量编辑终极指南

2026/7/28 20:28:04

ExifToolGUI图片元数据管理工具:免费开源的批量编辑终极指南 【免费下载链接】ExifToolGui A GUI for ExifTool 项目地址: https://gitcode.com/gh_mirrors/ex/ExifToolGui ExifToolGUI是一款功能强大的免费开源图片元数据管理工具,它基于强大的E…

MySQL INSERT 导致的死锁分析

MySQL INSERT 导致的死锁分析

2026/7/28 20:28:03

MySQL INSERT 导致的死锁分析 在MySQL的并发事务处理中,死锁是一个常见且棘手的问题。许多开发者认为死锁只会在 UPDATE 或 DELETE 操作中发生,但实际上 INSERT 语句也能导致死锁。本文将从原理出发,深入剖析INSERT导致死锁的机制&#xff0c…

Chrome插件安全最佳实践:防止XSS、CSRF攻击

Chrome插件安全最佳实践:防止XSS、CSRF攻击

2026/7/28 22:38:18

Chrome插件安全最佳实践:防止XSS、CSRF攻击 前言 Chrome 插件运行在浏览器的高权限环境里,content script 又能直接接触网页 DOM,稍不注意就会引入 XSS(跨站脚本)和 CSRF(跨站请求伪造)风险。一…

从 XSS 到社工库,深扒十大黑客网站的核心资源分布

从 XSS 到社工库,深扒十大黑客网站的核心资源分布

2026/7/28 22:38:18

按威胁情报类型重组黑客社区资源对于安全分析师而言,盲目浏览各类地下论坛不仅效率低下,更伴随着极高的安全风险。真正有价值的做法是将这些分散的站点视为结构化的网络威胁情报(CTI)来源,根据其核心资源属性进行分类归…

网络安全入门避坑指南,盘点那些容易误入的非法交易区

网络安全入门避坑指南,盘点那些容易误入的非法交易区

2026/7/28 22:38:18

为什么这些“黑客圣地”可能是你职业生涯的终点很多刚接触网络安全的朋友,在寻找学习资料时,往往会被一些标题党文章吸引,误以为只要混迹于某些所谓的“全球十大黑客论坛”,就能快速掌握核心技术,甚至实现“技术变现”…

STARK:ICCV‘21革命性视觉跟踪模型,端到端无后处理的终极解决方案

STARK:ICCV‘21革命性视觉跟踪模型,端到端无后处理的终极解决方案

2026/7/28 22:38:18

STARK:ICCV21革命性视觉跟踪模型,端到端无后处理的终极解决方案 【免费下载链接】Stark [ICCV21] Learning Spatio-Temporal Transformer for Visual Tracking 项目地址: https://gitcode.com/gh_mirrors/st/Stark STARK(Spatio-Tempo…

商业策略:为何平庸产品能创造高利润

商业策略:为何平庸产品能创造高利润

2026/7/28 22:38:18

1. 商业策略的本质思考"通过平庸赚钱"这个看似矛盾的命题,实际上揭示了商业世界中一个被忽视的真相。在追求差异化、创新和独特性的商业环境中,我们常常忽略了那些看似普通却稳定盈利的商业模式的巨大价值。我从业十余年来观察到一个有趣的现象…

剪映AI配音音色衰减真相:实测287组样本后发现的3个致命采样缺陷

剪映AI配音音色衰减真相:实测287组样本后发现的3个致命采样缺陷

2026/7/28 22:28:18

更多请点击: https://intelliparadigm.com 第一章:剪映AI配音音色衰减真相:实测287组样本后发现的3个致命采样缺陷 在对剪映v12.4.0(Windows/macOS双平台)AI配音模块进行系统性压力测试过程中,我们采集并分…

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

2026/7/28 13:30:18

目标:电脑作为RTSP 服务端,循环推送 H264/H265 视频流; RDK X5 通过 rtsp2display 拉流预览,完全不需要在开发板编译 live555。 提供两套成熟方案: ✅ 方案 A:FFmpeg(最简单,优先推…

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

2026/7/28 16:04:36

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

2026/7/28 16:04:35

说实话,提到PDF拆分再压缩,我真是被折腾得够呛。 上个月公司年度合同归档,一份300多页的PDF总合同,需要按年份拆分成三个独立文件,再分别压缩到10MB以内方便邮件发送各部门确认。我心想这还不简单?先找个海…

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

2026/7/28 0:06:55

📌 一、工具核心优势盘点 数据本地存储,安全系数高所有操作日志、文档资料均保存在本机,不会上传至云端,能够有效保护企业文件与个人隐私,规避数据泄露风险。 上手简单,零编程门槛采用全图形化可视化界面&…

计算机毕业设计之基于springboot的购物平台设计与实现

计算机毕业设计之基于springboot的购物平台设计与实现

2026/7/28 0:06:55

由于移动应用技术的持续性的快速发展,现实生活中人们大多数都是通过移动手机、电脑等智能设备来完成生活中的事务。因此,许多的人工传统行业也开始与互联网结合,不再一味的依靠人工手动,努力打造半自动数字化甚至是全自动数字化模…

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

2026/7/28 0:06:55

更多请点击: https://codechina.net 第一章:豆包AI绘图提示词失效现象全景扫描 近期大量用户反馈,豆包(Doubao)AI绘图功能对常规提示词(Prompt)响应异常:语义明确的指令被忽略、中英…