基于RAG与SpringBoot3的智慧养殖AI诊断系统实战
1. 项目概述与核心价值最近在做一个挺有意思的项目一个基于RAG检索增强生成的生猪健康管理智慧医药系统。简单来说就是给养猪场或者兽医做个“AI助手”让生猪疾病的诊断和管理更智能、更高效。这个项目整合了SpringBoot3、Vue3、Ollama、Spring AI这些主流技术目标是打造一个集AI兽医诊断、疾病药品管理、知识库于一体的数字化平台。我自己在农业信息化和AI应用领域摸爬滚打了好些年深知传统养殖业在疾病防控上的痛点经验依赖性强、信息获取慢、用药不规范。这个项目就是想用技术手段把这些痛点一个个给解决掉。这个系统主要面向几类用户一线的养殖户他们可能缺乏专业的兽医知识遇到猪生病了容易慌张驻场或远程的兽医需要一个高效的工具来辅助诊断和病例管理以及养殖场的管理人员需要对药品库存、疾病历史进行数字化管理。系统的核心价值在于它不是一个简单的信息查询工具而是一个能“思考”、能“学习”的智能系统。通过RAG技术我们把大量的生猪疾病文献、诊疗手册、药品说明书都“喂”给了AI让它能基于这些专业知识来回答用户的问题提供诊断思路和治疗建议。这比单纯的关键词搜索要精准得多也更贴近实际诊疗场景。2. 技术架构选型与深度解析2.1 为什么是SpringBoot3 Vue3的全栈组合选择SpringBoot3作为后端框架几乎是Java生态下微服务开发的“标准答案”了。SpringBoot3全面拥抱了Java 17的特性尤其是对虚拟线程Virtual Threads的初步支持这对于我们这种需要处理大量并发AI请求和文件上传的I/O密集型应用来说是未来性能提升的一个关键方向。虽然项目初期可能用不到但技术栈的前瞻性很重要。MyBatis-Plus作为ORM框架它的Wrapper条件构造器和强大的CRUD接口能极大减少样板代码比如在复杂的疾病-药品关联查询时用QueryWrapper可以非常优雅地构建动态SQL避免了在XML里写一大堆if标签。前端选择Vue3 Vite Pinia的组合则是看中了其极致的开发体验和性能。Vite的闪电般冷启动和热更新对于需要频繁调整管理后台界面的我们来说效率提升是实实在在的。Pinia作为状态管理库其API比Vuex更简洁且完美支持TypeScript在管理全局用户状态、权限信息时非常顺手。Element-Plus组件库提供了丰富的后台管理组件像复杂的表格筛选、表单验证、文件上传等都能快速搭建让我们能把精力更集中在业务逻辑而非UI细节上。注意SpringBoot 3.x 与 2.x 在一些依赖和配置上有不兼容的改动比如spring-boot-starter-data-jpa的自动配置方式、部分注解的包路径。在引入第三方库时特别是那些还没明确声明支持SpringBoot 3的需要仔细检查其兼容性避免启动报错。我们在这个项目里就遇到了一个老版本的邮件starter不兼容的问题最后是通过升级到最新的spring-boot-starter-mail解决的。2.2 AI核心Ollama Spring AI DeepSeek的落地实践AI部分是整个系统的“大脑”。我们选择了Ollama作为本地大模型运行环境而不是直接调用OpenAI或国内云厂商的API主要基于三点考虑成本可控、数据隐私和网络稳定性。养殖场的网络环境可能并不理想且诊疗数据涉及商业隐私本地化部署是更稳妥的选择。Ollama完美解决了大模型在本地部署和管理的复杂度一条命令就能拉取和运行模型。模型方面我们选择了DeepSeek。经过多轮测试DeepSeek在中文理解、逻辑推理和指令遵循方面表现非常出色尤其对于专业领域的知识问答其效果不输于一些更大的通用模型但资源消耗却友好得多。在4核8G的普通服务器上就能流畅运行这大大降低了硬件门槛。Spring AI是整个AI能力的“粘合剂”。它提供了一套统一的抽象接口如ChatClientVectorStore让我们可以几乎无感地切换底层AI模型或向量数据库。例如我们的RAG检索增强流程是这样实现的文档处理与向量化通过KnowledgeInitializer组件在应用启动时或通过管理端手动触发读取resources/knowledge目录下的文档支持txt, md, pdf, docx。使用Spring AI的DocumentReader和TextSplitter进行文档解析和分块。向量存储与检索将分块后的文本通过嵌入模型Embedding Model转化为向量存入配置好的VectorStore项目默认使用内存向量库生产环境可换为Redis、PgVector等。当用户提问时先将问题向量化然后在向量库中进行相似度检索找出最相关的几个知识片段。提示词工程与生成将检索到的相关片段作为上下文与用户原始问题一起构造一个详细的提示词Prompt发送给通过Spring AI封装的DeepSeek模型。模型基于这些“证据”生成最终的回答从而避免了“幻觉”胡编乱造保证了回答的专业性和准确性。# application.yml 中关于AI的部分配置示例 spring: ai: ollama: base-url: http://localhost:11434 # Ollama服务地址 chat: model: deepseek-r1:7b # 使用的聊天模型 embedding: model: nomic-embed-text # 使用的嵌入模型用于向量化 vectorstore: simple: initialize-schema: true # 初始化向量存储schema这个架构的优势在于解耦和可扩展。如果未来需要更换为Qwen、ChatGLM等其他模型或者将向量存储迁移到专业的数据库中只需要修改配置和少量适配代码核心的业务逻辑几乎不用动。2.3 存储层MySQL、Redis与MinIO的分工协作数据库选型上MySQL 8.0作为关系型数据库的主力存储所有结构化数据用户、疾病、药品、文章、会话记录等。这里特别利用了MySQL 8.0的窗口函数和JSON字段增强功能例如在统计用户活跃度、处理药品信息中的复杂说明如用法用量时比老版本更方便。Redis承担了两个关键角色缓存和会话存储。对于热点数据如疾病分类列表、常用药品信息我们使用Redis缓存查询性能提升了一个数量级。同时我们整合了Sa-Token权限框架并将Sa-Token的会话存储后端设置为Redis。这样做的好处是实现了分布式会话在多实例部署时用户登录状态可以共享无缝切换。// Sa-Token 整合 Redis 的配置示例 Configuration public class SaTokenConfigure { Bean public SaTokenConfig getSaTokenConfig() { SaTokenConfig config new SaTokenConfig(); config.setTokenName(pig-token); config.setTimeout(30 * 24 * 60 * 60); // 30天 config.setActivityTimeout(-1); // 永不过期通过Redis管理 config.setIsConcurrent(true); // 允许并发登录 config.setIsShare(true); config.setTokenStyle(uuid); // 重点配置Token持久化到Redis config.setTokenSessionCheckLogin(true); return config; } // Sa-Token Redis 数据源配置 (通过starter自动配置此处仅为示意) // 在application.yml中配置sa-token.jwt-secret-key 和 spring.redis.* }对象存储选择了MinIO用来管理用户上传的头像、文章配图、以及RAG知识库的原始文件。MinIO兼容S3协议部署简单性能也不错。我们将文件元信息路径、大小、类型存在MySQL的files表而实际文件流存储在MinIO的桶中。这种“元数据对象存储”的模式既方便了文件的管理和查询又利用了对象存储的海量扩展能力。实操心得MinIO的桶策略Bucket Policy一定要仔细配置。初期我们图省事设置了公开读结果被爬虫扫到了不少测试图片。后来严格遵循最小权限原则所有桶默认私有前端通过预签名URLPresigned URL的方式临时授权访问链接过期时间设为几分钟安全性大大提升。生成预签名URL的代码很简单但很关键。3. 核心功能模块设计与实现细节3.1 智能兽医诊断从问诊到推荐的完整闭环这是系统的王牌功能。其核心流程并非简单的“用户提问 - AI回答”而是一个精心设计的交互式诊断流程。3.1.1 会话管理与上下文保持我们设计了conversation_session和conversation两张表。每次用户开始一次新的问诊就会创建一个会话Session并生成一个标题通常自动提取用户的第一句话。同一个会话下的所有对话轮次都关联到这个会话ID。这样做的好处是历史回顾用户和兽医可以完整查看某次病例的整个交流过程。上下文关联AI模型在回答时可以获取本次会话的历史记录作为上下文使得对话更连贯。例如用户先说了“猪发烧”接着问“该用什么药”AI能知道这个“药”是针对“发烧”这个症状的。状态管理会话有“进行中”和“已结束”状态方便用户和管理员分类管理。3.1.2 基于RAG的精准诊断这是区别于普通聊天机器人的关键。当用户描述症状如“我家猪不吃食还拉稀体温有点高”后后端服务会执行以下步骤症状关键词提取与扩展首先对用户输入进行简单的自然语言处理NLP提取核心症状关键词“不吃食”、“拉稀”、“体温高”。同时利用预定义的疾病症状同义词库进行扩展例如“拉稀”扩展为“腹泻”、“拉肚子”。向量检索将这些扩展后的关键词向量化在RAG知识库的向量存储中进行相似度搜索。这里有一个调优技巧单纯用症状描述去检索可能会召回大量不相关的文档。我们采用了“症状疾病类型”的组合查询方式。例如同时检索“腹泻”和“消化道疾病”相关的向量准确率更高。证据合成与提示词构建检索出Top K比如3-5条最相关的知识片段可能是某本兽医手册中关于“猪传染性胃肠炎”的段落或者是“猪瘟”的临床症状描述。将这些片段作为“证据”与用户问题、历史对话一起填充到预设的诊断提示词模板中。你是一名专业的AI兽医助手。请根据以下提供的专业知识片段和用户的问题进行诊断分析。 【相关专业知识】 1. 片段1内容... 2. 片段2内容... 3. 片段3内容... 【当前会话历史】 用户之前说过猪发烧。 AI发烧可能由多种原因引起需要更多症状判断。 【用户本次问题】 我家猪不吃食还拉稀体温有点高。 请按以下结构回答 1. **可能性分析**列出最可能的2-3种疾病并给出置信度。 2. **关键鉴别点**说明这几种疾病在症状上的主要区别。 3. **初步建议**给出下一步操作建议如隔离、测量具体体温、观察粪便性状等。 4. **关联药品**系统将根据你分析的疾病自动从数据库关联药品并展示。AI生成与后处理将构建好的提示词发送给DeepSeek模型。收到AI回复后后端会进行后处理解析回复的结构化内容如疾病名称然后自动去illness和medicine表中关联查询将具体的药品信息名称、功效、用法用量附在AI回答的后面一并返回给前端。这样就形成了一个“症状输入 - AI分析 - 疾病推测 - 药品推荐”的完整闭环。3.2 疾病与药品管理系统的设计哲学疾病和药品是系统的核心数据它们的结构设计直接影响了AI诊断的准确性和用户体验。3.2.1 疾病数据的结构化与关联illness表的设计没有停留在“病名-症状-疗法”的简单三元组上。我们将其拆解得更细include_reason诱发因素记录环境、管理、病原等致病原因。illness_symptom典型症状详细描述。special_symptom特殊/示病症状这是鉴别诊断的关键。比如“猪丹毒”的“打火印”皮肤病变“口蹄疫”的口鼻蹄部水疱。在AI诊断时我们会优先匹配特殊症状能极大提高诊断精度。通过illness_kind疾病种类表和illness_medicine疾病-药品关联表建立了疾病分类和多对多的药品关联关系。3.2.2 药品信息的完备性与安全性medicine表的设计充分考虑了实际用药场景interaction相互作用明确标注不能混用的其他药品这是避免用药事故的核心。taboo禁忌标明孕畜、幼畜、特定疾病状态下的禁用情况。us_age用法用量不是简单的一句话而是结构化或半结构化的描述如“肌内注射一次量每1kg体重XXmg一日2次连用3-5日”。前端展示时会做特别格式化处理。medicine_type药品类型区分西药、中药、中成药方便分类管理和筛选。重要提醒所有疾病和药品数据在录入管理后台时必须经过严格的审核最好能由专业兽医最终把关。AI的诊断和建议完全依赖于这些基础数据的质量。我们初期用爬虫抓取了一些网络数据里面存在大量矛盾和陈旧信息直接导致AI“胡说八道”。后来我们建立了数据审核流程并优先录入权威教材和官方手册的内容效果立竿见影。3.3 前后端工程化与性能优化实践3.3.1 后端API设计与全局处理我们采用RESTful风格设计API并使用Knife4j生成交互式文档。全局异常处理ControllerAdvice是必须的它能优雅地捕获并转换各种异常如业务异常BusinessException、参数校验异常MethodArgumentNotValidException返回统一的JSON格式方便前端处理。RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public ResultVoid handleBusinessException(BusinessException e) { log.error(业务异常: {}, e.getMessage(), e); return Result.error(e.getCode(), e.getMessage()); } ExceptionHandler(MethodArgumentNotValidException.class) public ResultVoid handleValidException(MethodArgumentNotValidException e) { log.error(参数校验异常, e); String message e.getBindingResult().getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(; )); return Result.error(ErrorCode.PARAMS_ERROR.getCode(), message); } // ... 其他异常处理 }3.3.2 前端状态管理与组件封装前端使用Pinia管理全局状态例如用户信息、权限列表、诊断会话历史等。对于复杂的表格页面如疾病管理、药品列表我们封装了CommonTable组件通过Props传入配置项列定义、查询表单项、API地址大大减少了重复代码。template CommonTable :columnstableColumns :query-form-itemsqueryFormItems :fetch-apifetchIllnessList :row-actionsrowActions / /template script setup import { ref } from vue; import CommonTable from /components/CommonTable/index.vue; import { deleteIllness } from /api/illness; const tableColumns ref([ { prop: illnessName, label: 疾病名称, width: 150 }, { prop: kindName, label: 疾病分类, width: 120 }, // ... 更多列 ]); const queryFormItems ref([ { type: input, prop: illnessName, label: 疾病名称, placeholder: 请输入 }, { type: select, prop: kindId, label: 疾病分类, options: [] /* 动态加载 */ }, ]); const rowActions ref([ { label: 编辑, type: primary, handler: (row) handleEdit(row) }, { label: 删除, type: danger, handler: async (row) { await deleteIllness(row.id); // 刷新表格 } }, ]); /script3.3.3 性能优化点图片与文件懒加载文章详情、药品详情中的图片以及聊天记录中的历史图片都采用懒加载仅当滚动到视口内时才加载。API响应缓存对于变化不频繁的字典数据如疾病分类、药品类型前端在首次加载后存入LocalStorage并设置有效期减少不必要的请求。数据库查询优化对illness、medicine等核心表的高频查询字段如illness_name,medicine_name,keyword建立了索引。对于复杂的关联查询如查询某种疾病的所有关联药品使用MyBatis-Plus的TableField注解进行一对一、一对多映射避免N1查询问题。Ollama服务异步调用AI诊断是一个耗时操作可能几秒到十几秒。我们将AI调用封装为异步任务使用Spring的Async注解并立即返回一个“正在诊断”的提示和任务ID给前端。前端通过WebSocket或轮询的方式去获取最终诊断结果。这样避免了HTTP请求超时用户体验更流畅。4. 部署、运维与踩坑实录4.1 多环境部署配置项目通过Spring Boot的Profile机制区分开发dev、测试test、生产prod环境。核心配置如数据库连接、Redis地址、MinIO端点、Ollama地址都放在application-{profile}.yml中。生产环境部署要点Ollama服务建议部署在单独的GPU服务器上并与应用服务器通过内网通信。可以使用systemd或Docker Compose来管理Ollama服务确保其高可用。向量数据库开发阶段用的内存向量库SimpleVectorStore不适合生产。生产环境建议改用RedisVectorStore或PgVectorStore。以Redis为例需要引入spring-ai-redis依赖并配置连接。前端构建使用npm run build:prod命令构建前端生成静态文件。然后将dist目录下的文件部署到Nginx或Apache等Web服务器上。注意配置反向代理将/api等请求转发到后端Spring Boot应用。4.2 常见问题与解决方案速查表在实际开发和部署中我们遇到了不少坑这里总结一下问题现象可能原因解决方案前端访问API报404或CORS错误1. 后端服务未启动或端口不对。2. Nginx反向代理配置错误。3. 后端未配置CORS。1. 检查后端日志确认启动端口默认9999。2. 检查Nginx配置确保proxy_pass指向正确。3. 在后端添加CORS配置类允许前端域名。上传文件到MinIO失败报Connection refused1. MinIO服务未启动。2. 应用配置中的MinIO endpoint地址或端口错误。3. 防火墙阻止了连接。1. 检查MinIO服务状态 (docker ps或systemctl status minio)。2. 核对application.yml中的endpoint、access-key、secret-key。3. 检查服务器防火墙/安全组规则开放MinIO端口默认9000。AI诊断响应慢或超时1. Ollama服务所在服务器资源CPU/内存/GPU不足。2. 网络延迟高如果Ollama部署在远端。3. 检索的知识库文档过大或分块不合理。1. 监控服务器资源使用情况升级配置或优化模型使用更小的量化版本。2. 确保Ollama与应用服务器在同一内网减少延迟。3. 优化文本分块策略调整chunk-size和chunk-overlap避免单个片段过长。RAG知识库上传PDF后检索效果差1. PDF解析器未能正确提取文本特别是扫描版PDF。2. 文本分块策略不适合该类型文档破坏了语义完整性。1. 尝试使用OCR工具处理扫描版PDF或寻找文本版源文件。2. 尝试不同的分块方式如按章节、按段落分块并调整重叠区域大小。管理端登录后菜单或按钮权限不显示1. 用户角色role_status字段值不正确。2. 前端路由守卫中获取用户信息的API调用失败或未完成。3. 权限码Permission Code与后端配置不匹配。1. 检查数据库user表中对应用户的role_status1管理员0普通用户。2. 检查网络请求确保登录后成功获取了用户信息和权限列表。3. 核对前端路由meta中的permission字段与后端接口返回的权限列表是否一致。应用启动报java.lang.NoSuchMethodError依赖版本冲突特别是Spring Boot、Spring AI、MyBatis-Plus等核心依赖。使用mvn dependency:tree命令查看依赖树排除冲突的传递依赖或统一升级到兼容的版本。建议使用Spring Boot官方推荐的依赖版本管理。4.3 数据安全与隐私考量密码存储所有用户密码均使用Spring Security Crypto的BCryptPasswordEncoder进行加盐哈希存储绝对禁止明文存储。API安全所有管理端API和敏感用户操作API如修改信息、删除数据均通过Sa-Token进行鉴权SaCheckLogin和SaCheckRole注解是标配。敏感数据脱敏在前端展示用户手机号、邮箱时进行部分脱敏处理如138****1234。SQL注入防护坚持使用MyBatis-Plus的Wrapper或#{}预编译方式传参杜绝字符串拼接SQL。文件上传安全除了后缀名白名单校验.txt, .md, .pdf, .doc, .docx后端还对文件内容进行了简单的魔数Magic Number校验并限制了单个文件大小。文件存储在MinIO的私有桶中通过有时效的预签名URL访问。5. 项目总结与未来展望这个项目从技术选型到功能实现再到部署上线算是一个比较完整的全栈AI应用实践。最大的感触是技术是为业务服务的。无论是RAG、大模型还是微服务架构最终都要落到“如何更准、更快地帮用户解决猪病问题”这个核心目标上。在开发过程中我们不断和一位有经验的兽医朋友沟通调整疾病和药品的数据结构优化AI诊断的提示词模板这个过程比单纯写代码更有价值。目前系统已经实现了核心的智能问诊、疾病药品管理、知识库构建等功能能够作为一个有效的辅助工具。但距离一个成熟的商业产品还有很长的路要走。我个人认为后续可以从这几个方向深化多模态输入支持用户上传病猪的图片或短视频结合CV计算机视觉模型进行初步的体态、皮肤病变识别为AI诊断提供更丰富的输入信息。诊断反馈闭环增加“诊断结果评价”和“治疗效果反馈”功能。当用户采纳了AI的建议并实施治疗后可以反馈结果有效/无效。这些反馈数据可以作为强化学习的奖励信号用于微调模型让它越用越“聪明”。疾病预警与预测结合养殖场录入的日常监测数据如体温、采食量、环境温湿度尝试构建简单的时序预测模型对潜在疾病风险进行早期预警。离线能力增强考虑将核心的AI模型和向量知识库打包提供轻量级的离线SDK或单机版应用满足网络条件不佳的偏远养殖场的需求。这个项目所有代码都已开源初衷就是提供一个结合传统行业与AI技术的实战案例。如果你对农业科技、AI应用或者全栈开发感兴趣欢迎一起交流探讨。在仓库的Issue里提问题或者直接Fork代码进行改造都是最好的支持。技术之路一起踩坑一起成长。