e66bdb79dc
完整描述复习功能的核心概念、流程设计、三个入口、 API 清单、组件结构、数据流图和文件索引。
14 KiB
14 KiB
LPT 复习流程设计文档
本文档描述 LPT 系统中「回忆复习」(Recall Review)功能的完整设计,包括数据模型、API、前端交互流程和三种入口的处理方式。
1. 核心概念
1.1 标准思维导图(Standard Mind Map)
- 任务级单一知识树:每个学习任务有且仅有一棵标准思维导图,存储在
review_standard_mind_maps表 - 生成方式:
BUILTIN:内置规则引擎,按 session 分组、去重、合并AI:调用 lpt-ai 服务生成缩进大纲后解析USER:用户手动编辑USER_MERGE:增量合并后(保留用户编辑 + 追加 AI 新节点)
- 数据结构:
content字段存储 JSON 树(递归MindMapNode),outline字段存储缩进大纲文本 - 节点模型:
MindMapNode { title: string // 节点标题 notes?: string // 备注 / 对比标记(MATCHED| / MISSED|) sourceType?: string // REPORT | FRAGMENT | APPLICATION sourceId?: number // 对应源数据主键 children?: MindMapNode[] } - 节点标记约定(用于对比着色):
notes以MATCHED|开头 → 绿色(回忆命中)notes以MISSED|开头 → 红色(回忆遗漏)- 无标记 → 默认色(未参与对比)
1.2 回忆对比记录(Recall Record)
- 每次用户提交回忆 → 生成一条
review_recall_records记录 - 核心字段:
task_num:关联任务standard_map_id:对比时使用的标准导图版本focus_path:复习起点节点路径("根 / 分支A / 子节点B"),null 为全量recall_content:用户回忆大纲文本compare_result:对比结果 JSON(含matchedTree、extraNodes、覆盖率等)recall_ratio/matched_count/missed_count/extra_count:统计摘要
2. 复习流程
2.1 核心逻辑
1. 确定复习起点(节点路径)
2. 获取标准导图
3. 按起点提取子树作为对比基准
4. 用户在大纲编辑器中回忆填充
5. 提交对比(AI 语义匹配 或 BUILTIN 字符串匹配)
6. 展示对比结果(着色 + 统计)
2.2 节点选择
- 用户在标准导图上点击节点 →
MindMapViewer发射node-select事件(含title、path、childCount) - 弹窗确认:「以「节点名」为起点,复习其下 N 个子知识点?」
- 确认后:
focusPath= 选中节点的路径(如"Java基础 / 集合 / HashMap")recallTree根节点标题 = 选中节点标题- 回忆导图以该标题为根,用户补充子节点
2.3 对比范围
- 有
focusPath:MindMapTreeTool.extractSubtree(root, focusPath)提取子树,对比基准 = 子树 - 无
focusPath:对比基准 = 整棵标准导图 - AI 对比:发给 lpt-ai 的 outline 只包含子树范围
- BUILTIN 对比:
compareTrees()在子树范围内做标题匹配
2.4 对比算法
AI 语义匹配(优先):
POST /ai/tasks → type: "compare-recall"
→ 异步轮询结果
→ 返回 { matches: [{standardTitle, recallTitle}], missedTitles, extraNodes, evaluation }
→ Java 端 buildCompareResultFromAI() 标注标准树
BUILTIN 字符串匹配(降级):
- 展平标准树和回忆树
- 标题标准化(去标点、去空格、转小写)
- 精确匹配 → 模糊匹配(bigram Jaccard ≥ 0.6)
- 标记 MATCHED / MISSED / 额外
2.5 结果展示
- 标准导图面板直接显示着色对比结果(不再有独立对比面板)
- 绿色(
#c8e6c9)= 命中,红色(#ffcdd2)= 遗漏 - 统计卡片:覆盖率、命中/遗漏/额外计数
- 遗漏项列表 + 额外项列表(可点击回看原文)
3. 三个复习入口
入口 A:复习总览 → "回忆复习"
路径: Welcome.vue → "复习回看" → Review.vue(任务列表)→ 每行"回忆复习"按钮
前端代码: Review.vue:110
router.push(`/review/recall/${row.taskNum}`)
流程:
- 跳转到
ReviewRecall.vue,URL 为/review/recall/:taskNum - 页面加载 →
loadData()获取标准导图 - 标准导图
selectable=true,显示提示「点击导图节点选择复习起点」 - 用户点击节点 → 弹窗确认 → 进入回忆模式
- 回忆填充 → 提交对比 → 展示结果
相关文件:
lpt-fe/src/components/Review.vuelpt-fe/src/components/ReviewRecall.vue
入口 B:从某个残片/报告详情 → "回忆复习"
路径: Welcome.vue 滚动回顾 → 点击卡片 → ReviewDetail.vue → "回忆复习"按钮
前端代码: ReviewDetail.vue 的 goToRecall()
const res = await findNode(taskNum, content.substring(0, 500));
if (res?.data?.path) {
router.push(`/review/recall/${taskNum}?focusPath=${encodeURIComponent(res.data.path)}`);
}
流程:
- 用户在详情页查看某个残片/报告的内容
- 点击"回忆复习" → 前端调
POST /review/standard-mind-map/{taskNum}/find-node - 后端
findClosestNode()用 bigram Jaccard 匹配最接近的节点 - 返回
{ path, nodeTitle, score } - 跳转到
/review/recall/:taskNum?focusPath=... ReviewRecall.vue检测到 URL 参数 → 跳过节点选择,直接进入回忆模式
后端 API:
POST /review/standard-mind-map/{taskNum}/find-node
Content-Type: application/json
{ "content": "用户查看的残片/报告文本" }
→ 200 OK
{ "code": 200, "data": { "path": "根 / 分支A / 子节点B", "nodeTitle": "子节点B", "score": 0.85 } }
匹配算法(MindMapTreeTool.findClosestNode):
- 展平标准导图所有节点
- 对每个节点,计算其
title与输入文本的 bigram Jaccard 相似度 notes字段也参与匹配(权重 0.5)- 返回得分最高(>0.1)的节点
相关文件:
lpt-fe/src/components/ReviewDetail.vuelearning-progress-tracker/.../utils/MindMapTreeTool.java(findClosestNode、similarityScore)
入口 C:首页欢迎页 → 回忆卡片 → "前往回忆复习"
路径: Welcome.vue → 滚动回顾标签 → 点击弹出回忆对话框 → "前往回忆复习"按钮
前端代码: Welcome.vue 的 goToRecallFromCard()
const tn = group.taskNum;
const res = await findNode(tn, content.substring(0, 500));
if (res?.data?.path) {
router.push(`/review/recall/${tn}?focusPath=${encodeURIComponent(res.data.path)}`);
}
流程:
- Welcome 页加载
GET /review/feed获取复习 feed - 按 session 分组为标签卡片
- 用户点击某个标签 → 弹出回忆对话框(先回忆→展开对照)
- 点击"前往回忆复习" → 同入口 B,先调 findNode 再跳转
相关文件:
lpt-fe/src/components/Welcome.vue
4. 关键 API 清单
4.1 标准思维导图
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/review/standard-mind-map/{taskNum} |
获取/自动生成标准导图 |
POST |
/review/standard-mind-map/{taskNum}/regenerate?mode=full|incremental |
重新生成 |
PUT |
/review/standard-mind-map/{taskNum} |
用户编辑(大纲文本) |
POST |
/review/standard-mind-map/{taskNum}/recall |
提交回忆对比 |
POST |
/review/standard-mind-map/{taskNum}/find-node |
匹配最近节点 |
GET |
/review/standard-mind-map/{taskNum}/recall-records |
历史回忆记录 |
4.2 回忆对比请求体
{
"recallOutline": "用户回忆的缩进大纲文本\n - 子节点1\n - 子节点2",
"focusPath": "根标题 / 分支A / 子节点B"
}
recallOutline:必填,用户回忆的缩进大纲focusPath:可选,复习起点节点路径。null = 全量,提供时对比范围为该子树
4.3 对比结果结构(CompareResult)
{
"matchedTree": {
"title": "根节点",
"notes": "",
"children": [
{
"title": "命中的知识点",
"notes": "MATCHED|完整原文内容",
"sourceType": "REPORT",
"sourceId": 1,
"children": []
},
{
"title": "遗漏的知识点",
"notes": "MISSED|完整原文内容",
"sourceType": "FRAGMENT",
"sourceId": 2,
"children": []
}
]
},
"extraNodes": [
{ "title": "用户额外回忆的知识", "path": "/额外/..." }
],
"recallRatio": 0.75,
"matchedCount": 3,
"missedCount": 1,
"extraCount": 1,
"evaluation": "AI 评价文本(仅 AI 对比时有值)"
}
5. 前端组件结构
ReviewRecall.vue(主页面)
├── Page header(返回、标题、历史记录按钮)
├── 复习起点指示(focusPath 显示 + 重选按钮)
├── 节点选择确认弹窗(el-dialog)
├── 统计卡片(覆盖率/命中/遗漏/额外)
├── 回忆历史列表
├── 回忆输入(editable MindMapViewer)
├── 标准导图 + 对比结果(color-by-compare MindMapViewer)
│ ├── 未选择起点时 → selectable 模式
│ ├── 已选择起点/已对比 → 着色展示
│ └── 编辑模式 → editable 模式
└── 遗漏项 + 额外项详情列表
5.1 MindMapViewer 组件 Props
| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
tree |
MindMapTreeNode | null |
— | 树数据 |
editable |
boolean |
false |
是否可编辑 |
colorByCompare |
boolean |
false |
按 MATCHED/MISSED 着色 |
height |
string |
'480px' |
画布高度 |
selectable |
boolean |
false |
节点单击可选择 |
selectedPath |
string |
'' |
当前选中节点路径 |
5.2 MindMapViewer 事件
| 事件 | 载荷 | 触发时机 |
|---|---|---|
change |
outline: string |
编辑模式下结构变化 |
node-click |
{ sourceType, sourceId } |
点击带溯源标签的节点 |
node-select |
{ title, path, childCount } |
selectable 模式下点击节点 |
5.3 MindMapViewer 暴露方法
defineExpose({ toOutline })
// toOutline() → 缩进大纲文本
6. 后端核心工具方法
MindMapTreeTool.java
| 方法 | 说明 |
|---|---|
extractSubtree(root, path) |
按 "/" 分隔路径提取子树,返回深拷贝 |
findClosestNode(root, content) |
bigram Jaccard 匹配最近节点 |
getPath(root, targetTitle) |
获取节点到根的路径字符串 |
similarityScore(a, b) |
两段文本的 bigram Jaccard 相似度 |
mergeTrees(oldRoot, newRoot) |
按标准化标题合并新旧树 |
toJson(root, mapper) |
序列化为 JSON |
fromJson(json, mapper) |
从 JSON 反序列化 |
toOutline(root) |
树 → 缩进大纲 |
toFullOutline(root) |
树 → 完整大纲(含根标题) |
parseOutline(text) |
缩进大纲 → 树 |
flatten(root) |
前序遍历展平 |
countNodes(root) |
节点总数 |
maxDepth(root) |
最大深度 |
StandardMindMapServiceImpl.java
| 方法 | 说明 |
|---|---|
getOrGenerate(taskNum) |
获取/自动生成标准导图 |
regenerate(taskNum) |
全量重新生成(防并发) |
incrementalGenerate(taskNum) |
增量合并(保留用户编辑) |
updateByOutline(taskNum, outline) |
用户编辑 |
recallCompare(taskNum, outline, focusPath) |
回忆对比 |
findNode(taskNum, content) |
查找最近节点 |
listRecallRecords(taskNum) |
历史记录 |
getRecallRecord(id) |
单条记录详情 |
7. 关键文件索引
前端(lpt-fe/src/)
| 文件 | 说明 |
|---|---|
components/ReviewRecall.vue |
回忆复习主页面 |
components/Review.vue |
复习总览(任务列表+入口) |
components/ReviewDetail.vue |
残片/报告详情页 |
components/Welcome.vue |
首页(内含回忆卡片入口) |
components/MindMapViewer.vue |
思维导图渲染/编辑/选择组件 |
api/standardMindMap.ts |
标准导图 API 封装 |
api/review.ts |
复习 feed API |
api/studySessions.ts |
学习会话 API |
后端(learning-progress-tracker/src/main/java/.../)
| 文件 | 说明 |
|---|---|
controller/ReviewController.java |
复习相关端点 |
service/StandardMindMapService.java |
标准导图服务接口 |
service/impl/StandardMindMapServiceImpl.java |
服务实现 |
service/impl/BuiltinMindMapGenerator.java |
内置规则生成器 |
service/impl/RemoteAiMindMapClient.java |
AI 导图客户端 |
service/impl/AiServiceClient.java |
lpt-ai 通用客户端 |
utils/MindMapNode.java |
树节点 DTO |
utils/MindMapTreeTool.java |
树操作工具类 |
utils/CompareResult.java |
对比结果 DTO |
entity/ReviewStandardMindMapEntity.java |
标准导图实体 |
entity/ReviewRecallRecordEntity.java |
回忆记录实体 |
db/migration/V20260706_1__add_focus_path_to_recall_records.sql |
focus_path 迁移 |
AI 服务(lpt-ai/src/)
| 文件 | 说明 |
|---|---|
routes/ai.ts |
AI 任务提交/轮询/抓取标题 |
task-queue.ts |
异步任务队列 |
llm/prompts.ts |
prompt 模板(generate-mind-map / compare-recall / aggregate-report) |
llm/client.ts |
LLM API 客户端 |
admin/routes.ts |
管理面板 |
8. 数据流图
学习碎片/报告
↓ (按 session 分组)
BuiltinMindMapGenerator / RemoteAiMindMapClient
↓
标准思维导图(review_standard_mind_maps)
↓ (用户选择节点)
extractSubtree(root, focusPath)
↓
子树(对比基准)
↓ AI / BUILTIN 对比 ← 用户回忆大纲
↓
CompareResult
↓ (序列化存储 + 前端渲染)
review_recall_records + 着色 MindMapViewer
9. 历史版本记录
| Commit | 说明 |
|---|---|
a636e51 |
新增 sessionNum 追踪 + 维度选择(已回退) |
e5adb40 |
前端会话选择器(已回退) |
3c3f682 |
防并发生成 + mergeTrees + 增量合 |
5a5a294 |
前端防抖/生成提示/移除冗余面板 |
84fe452 |
节点选择交互 + focusPath + findNode(当前) |
b18f8b4 |
后端 focusPath + extractSubtree + 回退 session(当前) |
ebbbeb4 |
fix: Array.map 多参数 bug |
10. 待办/已知问题
- AI 生成的导图(
RemoteAiMindMapClient)因为没有sourceId/sourceType,node-click溯源不可用 selectable模式下,点击无子节点的叶子节点,childCount= 0 的确认弹窗体验略奇怪findClosestNode的 bigram 相似度对短文本效果有限,可能需要notes加权优化- 欢迎页(Welcome.vue)的回忆卡片入口「前往回忆复习」按钮文字和位置可以优化