山东大学软件学院项目实训-创新实训-计科智伴(七)——知识库构建与系统稳定性打磨
写在前面
这是"计科智伴"项目实训系列的第六篇博客。在前五篇中,我们完整记录了从项目立项、技术选型、基础架构搭建,到前后端联调与核心功能攻坚的全过程。
如果说之前的努力是为了让项目"跑起来",那么本篇博客所记录的,则是让项目"跑得稳、跑得好"的关键一步。我们将聚焦于博客五之后新增的几个核心模块与系统级优化:RAG知识库的完整实现、SSE流式通信的稳定性保障、语音输入功能的集成、数据库版本化管理实践以及题库的系统化建设。这些工作标志着项目从"功能可用"向"生产就绪"的重要跨越。
一、RAG知识库模块完整实现
1.1 需求背景
AI对话虽能回答通用问题,却难以触及特定课程教材、PPT、笔记等私有学习资料。用户期望AI在回答时能"言之有物",引用具体的课程资料,提供更精准、更有依据的解答。这正是构建检索增强生成(RAG,Retrieval-Augmented Generation)系统的初衷。
1.2 架构设计
知识库模块采用经典的三层架构,职责分明:
前端展示层(科目导航 + 资料浏览)
↓
后端服务层(CRUD + 搜索 + 推荐)
↓
AI集成层(自动分类 + 检索增强 + 资料来源推送)
1.3 数据库设计
新增 knowledge_base表,核心设计是利用PostgreSQL的 JSONB类型存储灵活标签,并对科目、知识点、类型等高频查询字段建立1.4 后端实现
Controller层提供五个核心接口:科目统计、分页查询、详情获取、全文搜索、个性化推
1.5 前端实现
知识库首页采用科目网格 + 推荐列表的布局。科目卡片以4列网格展示图标、名称和资料数量;推荐卡片通过不同颜色的类型标签(教材/PPT/习题等)和难度标签,让信息一目了然。

1.6 AI集成
我们做了两件事来让知识库"活"起来。
自动分类与摘要:新增 AiClassificationService,资料入库时自动调用大模型提取科目、章节、类型、标签和摘要,实现资料的自动化治理。
检索增强对话:改造 AiChatServiceImpl,用户开启知识库模式时,先检索相关资料注入提示词,再将资料来源以卡片形式推送给用户。
二、SSE流式通信稳定性保障
2.1 问题背景
在实际使用中,SSE通信暴露出三个稳定性问题:AI生成长文时的超时无响应、网络波动导致的连接断连、以及各类错误的提示模糊。
2.2 超时控制机制
核心思路是设置60秒超时阈值,但每次收到数据块时重置计时器,避免生成长文本时被误判超时。
2.3 断连重连机制
实现自动重试,最多重试2次,每次间隔3秒,仅对超时和网络错误重试。
2.4 错误分类提示
SSE解析器区分 delta(增量内容)、done(正常结束)、error(服务端错误)、cancelled(取消)等事件类型,分别处理。
三、语音输入功能集成
3.1 需求背景
移动端用户更习惯语音输入,尤其是长问题或复杂术语的场景。语音输入可以大幅提升移动端使用体验。
3.2 录音管理
录音管理器同时支持小程序端和Web端。Web端使用Web Audio API录制标准WAV格式(Sophnet语音识别API不支持浏览器默认的webm格式),小程序端使用 uni.getRecorderManager()。
3.3 后端语音识别服务
后端调用Sophnet异步语音转写API,先上传文件创建任务获取 taskId,再轮询查询结果,最多等待30秒。
public String transcribe(MultipartFile file, String fileName) {
String taskId = createTranscriptionTask(file, fileName);
return pollTranscriptionResult(taskId, 30);
}
private String pollTranscriptionResult(String taskId, int maxWaitSeconds) {
int elapsed = 0;
while (elapsed < maxWaitSeconds) {
Thread.sleep(1000);
elapsed++;
SophnetResultResponse response = restTemplate.exchange(url, GET, entity, SophnetResultResponse.class).getBody();
if ("success".equalsIgnoreCase(response.status)) {
return response.results.get(0).transcripts.get(0).text;
} else if ("failed".equalsIgnoreCase(response.status)) {
throw new RuntimeException("转写失败: " + response.errorMsg);
}
}
throw new RuntimeException("转写超时");
}
3.4 页面交互
语音模式下长按按钮录音,松开结束。录音时按钮变为紫色渐变背景,麦克风图标添加脉冲动画。
四、数据库版本化管理实践
4.1 问题背景
项目初期数据库脚本管理混乱:重复备份文件、脚本无执行顺序、不具备幂等性、缺乏文档说明。
4.2 版本化迁移方案
参考Flyway命名规范,将迁移脚本版本化:
V1__init_courses_and_kp.sql
V2__alter_user_profile.sql
V3__user_sign.sql
命名规范:V{版本号}__{描述}.sql,版本号递增确保执行顺序。
4.3 幂等性设计
所有迁移脚本可重复执行,不会产生副作用。
4.4 脚本结构与执行文档
最终脚本目录结构如下:
db/
├── README.md
├── V1__init_courses_and_kp.sql
├── V2__alter_user_profile.sql
├── V3__user_sign.sql
├── insert_*.sql (20个)
├── supplement_*.sql (4个)
├── add_notification_settings.sql
└── myapp_db_clean_backup_20260616.sql
README.md记录执行顺序和验证SQL:
SELECT COUNT(*) FROM course; -- 应返回 21
SELECT COUNT(*) FROM knowledge_point; -- 应返回 102
SELECT COUNT(*) FROM question; -- 应返回 510+
五、题库系统化建设
5.1 建设目标
项目涵盖21门计算机核心课程,目标是每门课程都有完整题库,支持知识前测和课后练习。具体要求:21门课程全部关联知识点,每个知识点至少5道题,覆盖不同难度和题型。
5.2 知识点补充
查询发现16门课程没有关联知识点,编写SQL脚本为每门课程补充5个核心知识点,知识点总数从22个增加到102个。
5.3 题目批量插入
为每个知识点生成5道题目,共400道新题目。题目格式包含选项(JSONB)、类型、难度、答案和解析。
5.4 题目数据分布
|
科目 |
知识点数 |
题目数 |
|---|---|---|
|
高等数学、线性代数、概率论等21门课程 |
每科5个 |
每科25道 |
|
总计 |
102 |
510+ |
5.5 题目质量把控
题目来源包括教材课后习题、历年期末考试题、考研真题和在线题库。审核标准为:选项明确无歧义、答案唯一正确、解析详细、难度分级合理。
六、其他关键修复
6.1 答题计时器修复
问题:答题完成后计时器未停止,导致时间重复计算。
修复:提交答案后清除计时器。
6.2 任务完成状态同步修复
问题:前端标记任务完成后,刷新页面状态恢复为未完成。根因是前端调用了 PUT /tasks/{taskId}/status接口(只更新状态字段),而非 POST /tasks/{taskId}/complete接口(同时设置 completedAt和知识掌握度)。
修复:区分完成任务和普通状态更新,完成任务时调用专用接口,并用后端返回的最新数据同步前端。
6.3 任务详情智能化优化
问题:任务详情弹窗仅展示基础信息,缺乏学习引导和操作入口。
优化:将 uni.showModal替换为自定义底部弹出式弹窗,新增"问AI伴学"功能。点击后跳转到AI对话页,自动发送包含任务类型、标题、科目、练习题数量的预填消息。

6.4 流式消息渲染优化
问题:AI流式响应过程中,消息气泡出现白框(有背景色但无内容)。
根因:渲染节流机制导致 renderedHtml更新滞后,rich-text渲染空节点时气泡背景仍存在。
修复:流式过程中只累积原始文本不渲染HTML,流式结束后统一用完整内容渲染Markdown。
6.5 历史消息渲染修复
问题:刷新页面后,历史消息中的AI回复显示空白。
根因:自定义 codespan渲染器生成 <code>标签,uni-app的 <rich-text>解析失败导致整个HTML渲染异常。
修复:移除 codespan自定义渲染器,让marked使用默认渲染方式。
七、知识库体验全面升级
7.1 全局搜索能力打通
问题:首页搜索栏仅为占位展示,科目标签、热力图知识点不可点击搜索。
修复:搜索栏改为可输入框,支持关键词搜索。科目卡片点击后传递中文名到列表页筛选。首页元素添加 @click.stop搜索联动。
后端科目筛选从精确匹配改为模糊匹配:
if (query.getSubject() != null && !query.getSubject().isEmpty()) {
wrapper.like(KnowledgeBase::getSubject, query.getSubject());
}
7.2 列表页搜索体验优化
搜索进入列表页时,顶部显示黄色提示栏展示当前关键词,提供一键清除功能。筛选下拉框在有选中值时显示蓝色激活态。资料卡片升级为带图标、标签、来源信息的完整卡片。
7.3 骨架屏加载动效
数据加载期间展示骨架屏占位动画,使用呼吸闪烁和流光扫过两种动效。
7.4 科目卡片自适应优化
使用 aspect-ratio: 1 / 1确保正方形比例,文字字号缩小至20rpx,支持最多2行显示,超出部分省略。网格间距从20rpx缩小至12rpx,提升空间利用率。
7.5 统一色彩体系
为科目卡片引入8种循环渐变配色,资料类型图标使用独立渐变。页面背景改为三段渐变,搜索栏添加主题色阴影。
7.6 向AI提问跳转与资料上下文传递
问题:chat页面是tabBar页面,navigateTo跳转报错。
修复:改为 switchTab跳转,通过 uni.setStorageSync传递资源上下文。
goToChat() {
const content = this.detail?.content?.replace(/<[^>]+>/g, '').substring(0, 500) || ''
uni.setStorageSync('chatAutoResource', {
subject: this.detail.subject,
title: this.detail.title,
contentPreview: content
})
uni.switchTab({ url: '/pages/chat/chat' })
}
Chat页面在 onShow中检测资源上下文,自动构造提问消息并发送。

八、个人主页样式优化
8.1 优化思路
在不改变任何功能的前提下,通过添加底纹装饰、优化色彩搭配、增强视觉层次来提升页面美感。核心原则是"轻装饰"——底纹透明度控制在0.04-0.15之间,不干扰内容阅读。
8.2 页面底纹装饰
页面背景使用三段渐变(浅蓝→浅灰→浅蓝),叠加8个装饰元素:大圆装饰(径向渐变半透明圆形)、小圆点、菱形(旋转45度边框)。
8.3 头部区域与统计卡片
头部用户信息区域使用紫色渐变背景,添加浮动装饰元素(圆形边框、旋转方形、小圆点)。统计卡片使用 ::before和 ::after伪元素添加径向渐变光晕。
8.4 菜单图标渐变优化
为每个功能入口的图标添加独立渐变背景和阴影,使用与功能语义相关的颜色:蓝色(个人信息)、靛蓝(学习计划)、橙色(错题本)、翠绿(学情报告)、灰色(设置)。

九、会话生命周期管理优化
9.1 问题背景
此前用户退出AI对话页面后再次进入,会自动恢复上一次会话,导致无法快速开始新对话,不符合大多数AI产品的交互习惯。
9.2 优化方案
页面卸载时清空会话状态,页面进入时保持空态(欢迎页)。用户可通过"历史"按钮手动选择历史会话,发送第一条消息时自动创建新sessionId。
// Store层
resetSession() {
this.messages = []
this.conversationId = ''
this.isStreaming = false
this.streamingContent = ''
this.streamingMsgId = ''
this.historyPage = 1
this.hasMoreHistory = true
this.persistLastSession('')
}
// 页面生命周期
onUnload() {
this.chatStore.resetSession()
uni.$off('voiceRecognized')
}
十、练习系统多题型支持
10.1 问题背景
此前练习系统仅支持选择题。题库建设中导入了大量填空题和简答题,前端缺乏对应的交互组件。
10.2 题型体系
系统支持四种题型:单选题(选项列表单选)、多选题(选项列表多选)、填空题(单行输入框)、简答题(多行文本域)。


10.3 前端实现
根据题型动态切换交互组件:
<!-- 选择题 -->
<view v-if="questionType === 'single' || questionType === 'multiple'" class="options-section">
<view v-for="option in currentQuestion.options" :key="option.label"
class="option-item" @click="selectAnswer(option.label)">
{{ option.content }}
</view>
</view>
<!-- 填空题 -->
<view v-else-if="questionType === 'fill'">
<input v-model="fillAnswer" placeholder="请输入你的答案..." @confirm="submitFillAnswer" />
</view>
<!-- 简答题 -->
<view v-else-if="questionType === 'judge' || questionType === 'short'">
<textarea v-model="fillAnswer" placeholder="请输入你的详细答案..." maxlength="500" />
</view>
10.4 后端答案比对优化
支持填空题的模糊匹配:精确匹配、包含匹配、多答案匹配(用 /或 |分隔)。
private boolean checkAnswer(String userAnswer, String correctAnswer) {
String u = normalizeAnswer(userAnswer);
String c = normalizeAnswer(correctAnswer);
if (u.equalsIgnoreCase(c)) return true;
if (c.length() > 2 && u.contains(c)) return true;
for (String possible : c.split("[/|]")) {
if (u.equalsIgnoreCase(possible.trim()) || u.contains(possible.trim())) return true;
}
return false;
}
十一、App端跨端适配
11.1 问题背景
项目基于uni-app开发,最初主要在H5和小程序端运行。打包为Android App时遇到大量跨端兼容性问题,导致首页白屏、智能问答无法使用。
11.2 Markdown渲染引擎替换
App端的JS引擎不支持 \p{L}\p{N}等Unicode属性转义语法,markedv18启动时抛出 SyntaxError。尝试降级到v14、v10均无效,最终替换为 markdown-it。
import MarkdownIt from 'markdown-it'
const md = new MarkdownIt({ html: true, breaks: true, linkify: true })
export function renderMarkdown(text) {
if (!text) return ''
try { return md.render(text) }
catch (e) { return text.replace(/</g, '<').replace(/\n/g, '<br/>') }
}
11.3 SSE流式通信桥接
App端不支持 uni.request的 enableChunked模式,使用 renderjs+ XMLHttpRequest桥接。通信链路为:逻辑层 → bridgeData → AppSseBridge组件 → renderjs层 → XHR发起SSE请求 → onprogress接收数据 → dispatchEvent回传逻辑层。
关键修复:Content-Type重复设置导致后端500错误,修复为统一由headers对象控制。
// 修复前:重复设置
xhr.setRequestHeader("Content-Type", "application/json")
Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]))
// 修复后:统一由headers控制
Object.keys(headers).forEach(key => {
if (headers[key]) xhr.setRequestHeader(key, headers[key])
})
11.4 Polyfill补充
App环境缺少部分现代API,补充 String.prototype.replaceAll和 Object.fromEntries的polyfill。
十二、多模态图片识别问答
12.1 需求背景
用户经常遇到需要拍照提问的场景:不会做的数学题、看不懂的代码截图、复杂的图表等。纯文本输入无法满足这些需求。
12.2 架构设计
前端选择图片后通过 uni.uploadFile上传,后端进行图像预处理(缩放/压缩),调用多模态AI模型识别,返回结构化结果(识别文本、科目、知识点、答案、置信度)。
12.3 前端实现
图片选择支持相册和相机,使用压缩模式减少上传体积。发送时检测是否有图片,有则走多模态接口,无则走SSE流式接口。
async sendMessage() {
if (this.selectedImage) {
this.chatStore.addUserMessage(this.inputText || '图片提问', this.selectedImage)
await this.chatStore.sendMultimodalMessage(this.selectedImage, this.inputText)
} else {
this.chatStore.addUserMessage(this.inputText)
await this.chatStore.sendChatMessage(this.inputText)
}
}
12.4 置信度提示
AI模型返回 confidence字段(0-1),低于0.7时前端自动追加提示。
if (data.confidence < 0.7) {
const tip = `\n\n⚠️ 提示:图片识别置信度较低(${Math.round(data.confidence * 100)}%),结果可能不够准确。`
msg.content += tip
}
总结
本篇博客记录了项目从"功能可用"向"生产就绪"跨越的关键工作:
-
RAG知识库:实现了从资料爬取、AI自动分类到检索增强对话的完整链路,让AI回答有据可依
-
SSE稳定性:通过超时控制、断连重连、错误分类,将流式通信的可靠性提升到生产级别
-
语音输入:集成移动端语音识别,提供多模态输入方式
-
数据库版本化:参考Flyway规范,实现可重复、可追溯的数据库迁移管理
-
题库建设:系统化构建102个知识点、510+道题目的完整题库,覆盖21门核心课程
-
任务详情智能化:将任务详情从"信息展示"升级为"学习引导入口",新增"问AI伴学"功能
-
流式消息渲染优化:解决流式过程中白框问题和历史消息渲染空白问题
-
知识库体验升级:全局搜索能力打通、骨架屏加载动效、科目卡片自适应、统一色彩体系
-
App端跨端适配:替换markdown-it解决Unicode正则不兼容、renderjs+XHR桥接SSE流式通信
-
多模态图片识别:实现图片上传+AI识别问答,支持置信度提示
这些工作虽然不像核心功能那样引人注目,但它们是系统稳定性和用户体验的基石。下一步将重点关注性能优化、测试覆盖和部署自动化,让项目真正具备生产环境的能力。
更多推荐

所有评论(0)