基于Spring Boot与Vue的智能信息管理系统架构设计与AI集成实践
1. 项目概述:一个AI驱动的智能信息管理平台
最近在做一个挺有意思的项目,叫IIMS,全称是Intelligent Information Management System。简单来说,它想干的事儿,是把传统的文档管理、教务管理这些系统,跟现在火热的AI能力,特别是大语言模型(LLM)给揉到一块儿去。你想想看,一个学校里,既有学生档案、成绩单这些结构化数据要管,又有大量的教学文档、规章制度这些非结构化文件,以前可能得用好几个不同的系统,现在如果能用一个平台全搞定,还能让AI帮你从这些海量信息里快速找到答案,是不是效率能提升一大截?这个项目就是奔着这个目标去的。
我自己在软件行业干了十几年,做过不少企业级应用,深知信息“孤岛”和“沉睡”数据的痛点。很多单位花大价钱上了各种管理系统,数据是录进去了,但用的时候找起来费劲,更别提深度分析和利用了。IIMS这个项目的核心思路,就是用Spring Boot、Vue这些成熟的技术栈搭一个稳固的后台和友好的前端,然后把Ollama、OpenAI这类AI模型接进来,让系统不仅能“存”信息,更能“懂”信息、“用”信息。它内置了电子教务系统(EAS)和文档管理系统(DMS)作为基础业务模块,再往上构建了基于知识库的智能问答、对话管理等AI高级功能。接下来,我就结合自己的开发经验,把这个项目的设计思路、技术实现细节以及踩过的那些“坑”好好跟大家唠一唠,无论你是想了解AI如何落地业务系统,还是正在规划类似的全栈项目,相信都能找到一些参考。
2. 整体架构设计与技术选型考量
做一个融合了传统业务与AI能力的系统,架构设计是第一步,也是最关键的一步。这决定了系统未来的扩展性、维护成本和核心体验。IIMS采用的是典型的前后端分离架构,但在技术栈和模块划分上,有一些针对性的思考。
2.1 前后端技术栈的抉择
后端(Spring Boot生态):选择Java和Spring Boot作为后端基石,几乎是中型以上企业级应用的首选。理由很实在:第一,生态成熟。从Web框架(Spring MVC)、安全框架(Spring Security或其轻量级替代品Sa-Token)、数据访问(MyBatis-Plus或JPA)到各种中间件集成,都有现成的、经过大量生产验证的解决方案。第二,团队人才储备足。Java开发者基数大,项目后续维护和扩展的人力成本相对可控。第三,性能与稳定性。经过优化的Spring Boot应用,应对高并发和复杂业务逻辑的能力是经过考验的。在这个项目里,我们特别引入了Spring AI这个新兴但潜力巨大的项目。它相当于为Spring生态接入各种AI模型(如OpenAI、Ollama、Azure OpenAI等)提供了一套统一的抽象接口,大大简化了集成AI能力的代码复杂度。你不用为每个模型供应商写一套不同的调用逻辑,Spring AI帮你做了封装。
前端(Vue 3 + TypeScript + Vite):前端方面,Vue 3的响应式系统和组合式API在构建复杂交互的管理后台时非常得心应手。搭配TypeScript,能在开发阶段就捕获很多潜在的类型错误,对于大型项目维护至关重要。而Vite作为新一代构建工具,其极快的冷启动和热更新速度,能显著提升开发体验。选择TS而不是纯JS,尤其是在需要与后端定义复杂的DTO(数据传输对象)和状态类型时,能保证前后端接口契约的一致性,减少联调时的低级错误。
权限控制(Sa-Token):权限是管理系统的灵魂。我们没有选用重量级的Spring Security,而是选择了更轻量、设计更直观的Sa-Token。它的API设计非常简洁,对于RBAC(基于角色的访问控制)模型支持友好,几行代码就能完成登录认证和权限校验。比如,在IIMS中,教务模块的“成绩管理”需要多级权限(教师录入、教务主任审核、校长查看),用Sa-Token的权限码和角色标识可以很清晰地实现。它的会话管理也支持分布式,为未来系统扩展预留了空间。
注意:技术选型没有绝对的好坏,只有是否适合。比如,如果团队更熟悉React,那用Next.js也是不错的选择。关键在于统一技术栈、降低学习成本,并确保所选技术能满足核心业务需求(如Spring AI对AI集成的友好性)。
2.2 核心模块划分与数据流设计
系统在逻辑上分为三层,数据流自上而下,权限和AI能力横向贯穿。
- 表现层(前端Vue应用):负责所有用户交互。包括EAS和DMS的业务操作界面,以及AI对话、知识库管理的专属界面。通过Axios与后端API通信,接收流式或非流式响应。
- 应用层(Spring Boot后端服务):这是业务逻辑的核心。它又细分为几个子域:
- 业务模块:EAS和DMS。它们处理各自的纯业务逻辑,如学生信息CRUD、档案树形结构维护、文件上传与预览。
- AI能力中台:这是系统的“智能大脑”。它不直接处理具体业务,而是提供通用的AI服务。例如,一个“文档解析与向量化服务”,可以被DMS调用来处理上传的文档,也可以被“知识库Q&A”功能调用来构建检索源。
- 权限与安全网关:所有请求到达业务或AI逻辑之前,都必须经过Sa-Token的认证拦截器,验证用户身份和权限。
- 数据层与AI基础设施:
- 业务数据库(MySQL/PostgreSQL):存储所有结构化数据,如用户、角色、学生信息、档案元数据、权限关系等。
- 向量数据库(如Chroma、Milvus、Qdrant):这是AI功能的关键。当用户上传一篇论文或规章制度文档后,系统会通过Embedding模型(一种将文本转换为数值向量的AI模型)将文档切片并转换为向量,存入向量数据库。当用户提问时,问题也会被转换为向量,并在向量数据库中进行相似度搜索,找到最相关的文档片段,再交给LLM生成最终答案。
- AI模型服务:对接Ollama(本地部署的LLM)或OpenAI API(云端LLM)。Spring AI的
ChatClient接口在这里发挥了巨大作用,让后端代码可以用几乎相同的方式调用不同的模型。
数据流示例(一次知识库问答):
- 用户在前端界面提问:“我们学校的研究生奖学金评定标准是什么?”
- 前端将问题发送到后端
/api/ai/knowledge/ask接口。 - 权限网关校验用户是否有“知识库问答”权限。
- AI服务接收到问题,首先使用Embedding模型将问题转换为向量。
- 用这个问题向量,去向量数据库中搜索最相关的几个文档片段(比如《研究生手册.pdf》中的奖学金章节)。
- 将问题和检索到的文档片段组合成一个详细的提示词(Prompt),发送给LLM(例如本地的DeepSeek模型或GPT-4)。
- LLM基于提供的上下文文档,生成一个准确、有据可循的答案。
- 后端通过HTTP Streaming(流式输出)或一次性响应,将答案返回给前端。
- 前端实时显示或渲染最终答案。
这个架构的优势在于“高内聚、低耦合”。业务模块和AI模块可以独立开发和演进。比如,未来想增加一个“合同智能审查”功能,只需要在AI中台增加相应的提示词工程和流程编排,业务模块的代码几乎不用动。
3. 核心功能模块的深度实现解析
有了宏观架构,我们深入到各个核心功能模块,看看具体是怎么实现的,以及过程中有哪些值得分享的细节。
3.1 电子教务系统(EAS)的实现要点
EAS是一个典型的CRUD密集型管理系统,但其中几个功能点需要特别设计。
自动排课算法:这是EAS的难点之一。排课问题是一个多约束条件(教室容量、教师时间、课程连贯性、班级偏好等)的优化问题,属于NP-Hard。在实际项目中,我们并没有追求全球最优解,而是采用了一种“贪心算法+冲突回溯”的实用策略。
- 优先级排序:先将所有需要排课的课程任务按优先级排序(例如,合班课、外聘教师的课优先级更高)。
- 资源时间格化:将一周的时间划分为以课时为单位的时间格,所有教室、教师都对应一套时间格状态(空闲/占用)。
- 贪心尝试:从高优先级课程开始,为其寻找第一个能满足所有约束(教师有空、教室有空且容量够、班级无其他课)的时间格,并占用。
- 冲突处理:当为某个课程找不到合适时间格时,触发回溯。尝试调整之前已安排的、优先级较低的课程的时间,为当前课程腾出空间。这里需要设置回溯深度,避免无限循环。
- 结果评估:算法结束后,输出排课结果,并给出一些量化指标,如“课程分布均匀度”、“教师单日课时数”等,供教务人员人工微调。
实操心得:完全自动化的完美排课是不现实的。我们的系统设计为“算法辅助决策”,提供多个备选方案,并允许教务员在可视化界面上通过拖拽进行手动调整。系统记录手动调整的规则,可以作为反馈数据优化下一次的算法权重。
多级权限的成绩管理:成绩的敏感度极高。我们设计了“录入-审核-发布-查询”四级权限流。
- 任课教师:只能录入和修改自己所授课程的成绩,提交后状态为“待审核”。
- 教研室主任/教务员:拥有“审核”权限,可以查看并审核本教研室或指定年级的所有待审核成绩。审核通过后,状态变为“已审核”。
- 教务处长:拥有“发布”权限。只有已审核的成绩才能被发布。发布后,成绩对学生和家长可见,且原则上不可再修改(如需修改需走特殊流程申请)。
- 学生/家长:只能查询已发布的、与自己相关的成绩。 这个流程通过Sa-Token的权限码(如
score:enter,score:audit,score:publish)和后台数据行级权限(通过SQL拦截器自动添加WHERE条件,如teacher_id = #{currentUserId})共同实现。
3.2 文档管理系统(DMS)与知识库构建
DMS不仅是文件存储柜,更是AI知识库的“原料加工厂”。
档案树与表单的灵活配置:很多单位的档案分类是动态变化的。我们设计了基于数据库的树形结构表来存储分类,支持无限级嵌套。前端使用el-tree等组件动态渲染。每个档案分类可以关联一个“元数据表单配置”(JSON格式存储),定义该类别档案需要录入哪些字段(如“文号”、“密级”、“成文日期”等)。这样,当用户选择“红头文件”类别时,出现的就是一套字段;选择“合同”时,出现的是另一套字段。这种设计避免了为每种档案类型硬编码前端页面和后端接口。
文件解析与向量化流水线:这是将普通文档变成AI可“理解”知识的关键步骤。我们构建了一个异步处理流水线:
- 文件上传与预处理:用户上传PDF、Word、Excel、PPT、TXT等文件。系统使用
Apache Tika或专业库(如pdfboxfor PDF,poifor Office)进行文本提取。对于扫描版PDF,会集成OCR引擎(如Tesseract)进行文字识别。 - 文本清洗与分割:提取的原始文本可能包含页眉页脚、无意义字符。需要进行清洗。更关键的是分割(Chunking)。不能把一整本书扔给AI,需要按语义切成大小合适的片段(如每段500-1000字符)。分割策略直接影响检索质量。我们采用了递归字符分割为主,并尝试在段落、标题等自然边界处进行切分,尽量保证每个片段的语义完整性。
- 向量化(Embedding)与存储:使用Embedding模型(如
text-embedding-3-small、BGE-M3或本地部署的nomic-embed模型)将每个文本片段转换为一个高维向量(例如1536维)。然后将(向量, 文本片段, 元数据(如来源文件、页码))这个三元组存入向量数据库。
踩坑记录:初期我们使用了简单的固定长度分割,导致一个问题:检索到的片段经常是“半句话”,缺乏上下文,LLM生成的答案就不准确。后来改进了分割算法,并尝试了“重叠分割”(相邻片段有部分内容重叠),有效提升了检索片段的质量。另一个坑是Embedding模型的选择,不同模型对中文的语义理解能力差异很大,需要根据实际语料进行测试评估。
3.3 AI智能功能的核心实现
这是项目的“智能”所在,重点在对话管理、工具调用和知识问答。
对话管理:不仅仅是保存聊天记录。我们设计了“对话-消息”两级结构。一个对话(Conversation)包含一个主题(可重命名)、关联的知识库来源、以及使用的AI模型等元数据。消息(Message)则存储用户提问和AI回复。实现“对话复制”时,需要注意深度复制整个消息链,而不仅仅是复制对话ID。“对话收藏”功能则允许用户标记有价值的对话,便于后续查找复盘。
流式输出(Streaming)的实现与优化:LLM生成较长的回答需要数秒甚至更长时间,让用户干等体验很差。流式输出是必备功能。在后端,当调用Spring AI的ChatClient时,我们调用其返回Flux(响应式流)的方法。前端使用EventSource或fetchAPI来读取这个流,并实时将收到的token追加到页面上。
// 后端示例 (Spring AI) @GetMapping("/chat/stream") public Flux<String> streamChat(@RequestParam String message) { Prompt prompt = new Prompt(new UserMessage(message)); return chatClient.stream(prompt) .map(chatResponse -> chatResponse.getResult().getOutput().getContent()); }// 前端示例 (Vue + EventSource) const eventSource = new EventSource(`/api/chat/stream?message=${encodeURIComponent(userInput)}`); eventSource.onmessage = (event) => { this.answer += event.data; // 实时追加到响应文本 };关键优化点:1.错误处理:网络中断或AI服务异常时,前端需要能优雅地关闭连接并提示用户。2.上下文管理:在流式交互中,如何将多轮对话的历史上下文有效地传递给模型?我们通常会在每次请求时,将最近N轮对话的历史(包括用户问题和AI回答)作为上下文一起发送,但这会增加token消耗。需要权衡上下文长度和成本/性能。
工具调用(Function Calling)与内部系统集成:这是让AI从“聊天员”升级为“业务助理”的关键。例如,用户问:“帮我查一下张三同学本学期的成绩。” AI不应该仅仅回复“我无法查询”,而应该能自动调用后端的“查询学生成绩”接口。我们通过以下步骤实现:
- 定义工具:在后端,我们将“查询学生成绩”这个功能,描述成一个符合OpenAI Function Calling格式的JSON Schema,包括函数名、描述、参数列表及其类型。
- 模型请求:将用户问题、工具定义一起发送给支持Function Calling的模型(如GPT-4, DeepSeek最新版本也支持)。
- 模型决策:模型分析问题后,如果判断需要调用工具,它会返回一个结构化消息,指明要调用哪个函数,以及具体的参数值(如
studentName: "张三")。 - 执行与回复:后端接收到这个结构化消息后,动态调用对应的Java方法(查询数据库),得到结果,再将结果作为上下文重新发送给模型,让模型组织成自然语言回复给用户。 这样,AI就具备了操作系统的能力。在IIMS规划中,“内部系统Q&A”和“权限集成”正是基于此。AI在回答关于系统数据的问题前,会先检查当前用户的权限,确保不会越权访问信息。
4. 开发环境搭建与关键配置实战
理论说再多,不如动手搭一遍。这里我分享一下从零开始搭建一个IIMS核心开发环境的详细步骤和配置要点,你可以把它看作一个可操作的“脚手架”指南。
4.1 后端Spring Boot项目初始化
首先,我们用Spring Initializr(start.spring.io)创建一个新项目。
- Project: Maven Project (Gradle也可,看团队习惯)
- Language: Java 17 (LTS版本,兼顾新特性和稳定性)
- Spring Boot: 选择当前稳定版,如3.2.x
- Dependencies:
Spring Web(构建Web API)Spring Data JPA(或MyBatis-Plus, 这里以JPA为例)MySQL Driver(连接MySQL数据库)Lombok(减少样板代码)Sa-Token(需手动添加依赖,后面详述)Spring AI(核心AI依赖)
创建完成后,在pom.xml中手动添加Sa-Token和Spring AI的依赖。注意Spring AI的版本和Boot版本的兼容性。
<!-- Sa-Token 权限认证 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot3-starter</artifactId> <version>1.37.0</version> <!-- 请使用最新稳定版 --> </dependency> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-jwt</artifactId> <!-- 如果需要JWT --> <version>1.37.0</version> </dependency> <!-- Spring AI - 核心依赖,注意选择与Boot兼容的版本 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>0.8.1</version> <!-- 示例版本,需查最新 --> <type>pom</type> <scope>import</scope> </dependency> <!-- 使用OpenAI --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <!-- 或使用Ollama --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> </dependency>关键配置 (application.yml):
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/iims_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用update,生产环境务必用validate或none show-sql: true properties: hibernate: format_sql: true # Sa-Token配置 sa-token: token-name: satoken # token名称 timeout: 2592000 # 30天有效期 active-timeout: -1 # 永不过期(每次访问刷新) is-concurrent: true # 允许并发登录 is-share: true # 在多个服务间共享token token-style: uuid # token风格 jwt-secret-key: your_jwt_secret_here # 如果用了sa-token-jwt # Spring AI - OpenAI配置 spring: ai: openai: api-key: ${OPENAI_API_KEY:sk-your-key-here} # 建议从环境变量读取 chat: options: model: gpt-3.5-turbo # 默认模型 temperature: 0.7 # Spring AI - Ollama配置 (二选一) # spring: # ai: # ollama: # base-url: http://localhost:11434 # chat: # options: # model: llama2 # 或 deepseek-coder, qwen等4.2 前端Vue项目初始化与基础框架
使用Vite快速创建Vue + TypeScript项目。
npm create vue@latest iims-frontend # 按照提示选择:TypeScript, Vue Router, Pinia, ESLint等。 cd iims-frontend npm install安装核心UI库和工具库。我们选用Element Plus,因为它组件丰富,适合管理后台。
npm install element-plus @element-plus/icons-vue npm install axios # HTTP客户端 npm install pinia-plugin-persistedstate # Pinia状态持久化在main.ts中引入Element Plus和样式。
import { createApp } from 'vue' import App from './App.vue' import router from './router' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // Element Plus import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' const app = createApp(App) const pinia = createPinia() pinia.use(piniaPluginPersistedstate) // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(pinia) app.use(router) app.use(ElementPlus) app.mount('#app')配置Axios实例,统一处理请求拦截(添加Token)、响应拦截(处理错误)和基础URL。
// src/utils/request.ts import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API || '/api', timeout: 10000, }) service.interceptors.request.use( (config) => { const token = localStorage.getItem('satoken') // 根据Sa-Token实际存储位置获取 if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } ) service.interceptors.response.use( (response) => { const res = response.data // 假设后端统一返回格式为 { code: 200, data: {}, msg: 'success' } if (res.code !== 200) { ElMessage.error(res.msg || 'Error') // 处理特定code,如401未登录,403无权限 if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.msg || 'Error')) } return res }, (error) => { ElMessage.error(error.message || 'Request Failed') return Promise.reject(error) } ) export default service4.3 权限系统的初步集成
在后端,我们需要配置Sa-Token。创建一个配置类SaTokenConfigure。
@Configuration public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 注册Sa-Token的拦截器,校验登录状态 registry.addInterceptor(new SaInterceptor(handler -> { // 这里可以配置更细粒度的路由拦截规则 SaRouter.match("/**") // 拦截所有路径 .notMatch("/auth/login", "/auth/register") // 排除登录注册 .check(r -> StpUtil.checkLogin()); // 校验登录 })).addPathPatterns("/**"); } }创建一个简单的登录控制器。
@RestController @RequestMapping("/auth") public class AuthController { @PostMapping("/login") public R login(@RequestBody LoginDto dto) { // 1. 这里应该是真实的数据库校验 if ("admin".equals(dto.getUsername()) && "123456".equals(dto.getPassword())) { // 2. 登录成功,为这个用户创建会话 StpUtil.login(10001); // 参数是用户id,应来自数据库 // 3. 返回Token信息给前端 return R.ok().data("token", StpUtil.getTokenInfo().getTokenValue()); } return R.error("用户名或密码错误"); } @GetMapping("/userInfo") public R getUserInfo() { // 通过StpUtil.getLoginId()获取当前登录用户ID,然后查询数据库获取完整信息 long userId = StpUtil.getLoginIdAsLong(); // ... 查询用户、角色、权限列表 UserInfoVo userInfo = new UserInfoVo(); userInfo.setUserId(userId); userInfo.setPermissions(Arrays.asList("sys:user:view", "eas:score:enter")); // 模拟权限码 return R.ok().data(userInfo); } }在前端,登录成功后,将返回的Token存储起来(如localStorage),并在后续所有请求的Header中携带。同时,可以根据/auth/userInfo接口返回的权限列表,动态生成路由和菜单,控制页面按钮的显示与隐藏(v-permission指令)。
4.4 AI模型服务的本地与云端配置
本地模型(Ollama):
- 前往Ollama官网下载并安装。
- 在命令行拉取一个模型,例如中文能力不错的
qwen2.5:7b或专精代码的deepseek-coder:6.7b。
ollama pull qwen2.5:7b- 启动Ollama服务(通常安装后自动运行),默认端口11434。
- 在后端
application.yml中配置Ollama。
spring: ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b- 在代码中注入
ChatClient即可使用。
@RestController @RequestMapping("/ai/chat") public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { // Spring AI会自动注入配置好的ChatClient this.chatClient = chatClient; } @PostMapping("/simple") public String simpleChat(@RequestParam String message) { Prompt prompt = new Prompt(new UserMessage(message)); ChatResponse response = chatClient.call(prompt); return response.getResult().getOutput().getContent(); } }云端模型(OpenAI):
- 注册OpenAI账号,获取API Key。
- 在
application.yml中配置API Key和模型。
spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-3.5-turbo- 代码使用方式与Ollama完全一样,这就是Spring AI抽象层带来的好处——无需修改业务代码,只需更换配置,即可切换AI模型提供商。
重要提示:API Key是敏感信息,绝对不要提交到代码仓库。务必使用环境变量(
${OPENAI_API_KEY})或在生产环境使用配置中心来管理。
5. 典型问题排查与性能优化经验
在开发这样一个融合系统时,会遇到各种各样的问题。我总结了几类最常见的问题及其解决方法,希望能帮你少走弯路。
5.1 AI相关问题的诊断思路
问题一:调用AI模型超时或无响应。
- 检查网络与连通性:对于Ollama,确认服务是否运行(
curl http://localhost:11434/api/tags)。对于OpenAI,确认网络能访问其API(考虑网络策略)。 - 检查模型状态:Ollama确认模型已下载(
ollama list)。OpenAI确认API Key有效且有余额。 - 调整超时设置:在Axios或Spring的RestTemplate配置中增加超时时间。LLM生成长文本可能很慢。
- 查看日志:开启Spring AI的Debug日志,查看具体的请求和响应。
logging: level: org.springframework.ai: DEBUG
问题二:AI回答质量差、胡言乱语或答非所问。
- 优化提示词(Prompt):这是影响效果最直接的因素。确保你的提示词清晰、具体,包含足够的上下文和约束。例如,在知识库问答中,使用“根据以下上下文回答问题,如果上下文不包含答案,请直接说‘根据已知信息无法回答’”这样的指令。
- 检查上下文(Context):对于需要背景知识的对话,确保将相关的历史对话或检索到的文档片段正确拼接后传给模型。注意上下文长度限制(Token数)。
- 调整模型参数:尝试降低
temperature(如从0.8调到0.3)可以减少随机性,让回答更确定。调整top_p等参数。 - 评估Embedding模型:如果是检索增强生成(RAG)效果不好,问题可能出在检索阶段。测试不同的Embedding模型,或优化文本分割策略,确保检索到的片段确实与问题相关。
问题三:流式输出中断或前端显示异常。
- 后端确保返回
Flux<String>或SseEmitter,而不是一次性返回完整字符串。 - 前端使用正确的API接收流:使用
EventSource或fetch的response.body.getReader()来读取流。注意EventSource只支持GET,复杂请求可用fetch。 - 处理流结束和错误:监听
onerror和onclose事件,给用户明确的反馈。 - 网络代理问题:如果前端通过Nginx等代理,确保代理配置支持流式传输(不缓冲响应)。在Nginx中可能需要设置
proxy_buffering off;。
5.2 系统集成与业务逻辑问题
问题四:文件上传慢或失败。
- 前端分片上传:对于大文件,实现分片上传(如用
el-upload的http-request自定义)和断点续传。 - 后端优化:调整Spring Boot的
spring.servlet.multipart.max-file-size和max-request-size。使用异步处理,上传后立即返回,文件解析等耗时操作放入消息队列(如RabbitMQ)异步执行。 - 存储考虑:文件直接存数据库(BLOB)性能很差。应存储到对象存储(如MinIO、阿里云OSS)或文件服务器,数据库中只存访问路径。
问题五:权限校验不生效或逻辑混乱。
- 确认注解使用正确:Sa-Token的
@SaCheckLogin,@SaCheckRole("admin"),@SaCheckPermission("sys:user:add")等注解要打在Controller方法或类上。 - 检查权限码一致性:前端按钮的
v-permission指令值、后端注解里的值、数据库里存储的值,三者必须完全一致。 - 理解权限拦截顺序:Sa-Token拦截器在Spring Security之后(如果同时用了)。确保过滤链顺序正确。通常建议只使用一套权限框架。
问题六:复杂查询性能低下。
- 数据库索引:为经常用于
WHERE,ORDER BY,JOIN的字段添加索引。使用EXPLAIN分析SQL执行计划。 - 分页查询:列表接口务必实现分页,避免一次性拉取大量数据。前端使用
el-pagination,后端使用JPA的Pageable或MyBatis-Plus的Page。 - 关联查询优化:避免N+1查询问题。JPA中使用
@EntityGraph或JOIN FETCH;MyBatis-Plus中使用自定义SQL优化联表。 - 缓存策略:对于不常变的热点数据(如字典表、权限菜单),使用Redis等缓存。使用
@Cacheable注解要小心缓存一致性问题。
5.3 部署与运维注意事项
环境配置分离:使用application-dev.yml,application-prod.yml配合spring.profiles.active来管理不同环境的配置(数据库地址、AI API Key、日志级别等)。
健康检查与监控:为关键组件(数据库、Redis、Ollama服务)添加健康检查端点。集成Spring Boot Actuator和Prometheus+Grafana进行JVM和业务指标监控。
日志聚合:使用ELK(Elasticsearch, Logstash, Kibana)或Loki+Grafana集中管理日志,便于问题追踪。
向量数据库的选择与调优:生产环境慎用内存型向量数据库。Chroma适合轻量级和原型,Milvus、Qdrant、Weaviate更适合生产,支持持久化、分布式和更丰富的检索算法。需要根据数据规模、并发量和运维能力进行选型。
最后,我想说的是,构建IIMS这样的系统是一个持续迭代的过程。从最核心的“增删改查”和“文件上传预览”做起,逐步接入AI能力,先实现简单的对话,再完善知识库,最后尝试工具调用和复杂Agent。不要试图一次性做完所有规划的功能。每完成一个模块,就进行充分的测试和内部试用,收集反馈,持续优化。技术是为业务服务的,清晰的业务边界和持续的价值交付,比追求技术的“炫酷”更重要。在这个项目中,我们最大的收获不是用了多少新技术,而是通过Spring AI这样的抽象,让业务代码和复杂的AI基础设施解耦,使得团队能够更专注地解决实际的业务问题,让AI能力真正平稳地“流”入到传统的管理系统中。
