Files
lpt-fe/DESIGN_REVIEW.md
cat-shark e66bdb79dc docs: 复习流程设计文档(DESIGN_REVIEW.md)
完整描述复习功能的核心概念、流程设计、三个入口、
API 清单、组件结构、数据流图和文件索引。
2026-07-07 23:10:44 +08:00

14 KiB
Raw Permalink Blame History

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[]
    }
    
  • 节点标记约定(用于对比着色):
    • notesMATCHED| 开头 → 绿色(回忆命中)
    • notesMISSED| 开头 → 红色(回忆遗漏)
    • 无标记 → 默认色(未参与对比)

1.2 回忆对比记录(Recall Record

  • 每次用户提交回忆 → 生成一条 review_recall_records 记录
  • 核心字段:
    • task_num:关联任务
    • standard_map_id:对比时使用的标准导图版本
    • focus_path:复习起点节点路径("根 / 分支A / 子节点B"),null 为全量
    • recall_content:用户回忆大纲文本
    • compare_result:对比结果 JSON(含 matchedTreeextraNodes、覆盖率等)
    • recall_ratio / matched_count / missed_count / extra_count:统计摘要

2. 复习流程

2.1 核心逻辑

1. 确定复习起点(节点路径)
2. 获取标准导图
3. 按起点提取子树作为对比基准
4. 用户在大纲编辑器中回忆填充
5. 提交对比(AI 语义匹配 或 BUILTIN 字符串匹配)
6. 展示对比结果(着色 + 统计)

2.2 节点选择

  • 用户在标准导图上点击节点 → MindMapViewer 发射 node-select 事件(含 titlepathchildCount
  • 弹窗确认:「以「节点名」为起点,复习其下 N 个子知识点?」
  • 确认后:
    • focusPath = 选中节点的路径(如 "Java基础 / 集合 / HashMap"
    • recallTree 根节点标题 = 选中节点标题
    • 回忆导图以该标题为根,用户补充子节点

2.3 对比范围

  • focusPathMindMapTreeTool.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}`)

流程:

  1. 跳转到 ReviewRecall.vueURL 为 /review/recall/:taskNum
  2. 页面加载 → loadData() 获取标准导图
  3. 标准导图 selectable=true,显示提示「点击导图节点选择复习起点」
  4. 用户点击节点 → 弹窗确认 → 进入回忆模式
  5. 回忆填充 → 提交对比 → 展示结果

相关文件:

  • lpt-fe/src/components/Review.vue
  • lpt-fe/src/components/ReviewRecall.vue

入口 B:从某个残片/报告详情 → "回忆复习"

路径: Welcome.vue 滚动回顾 → 点击卡片 → ReviewDetail.vue → "回忆复习"按钮

前端代码: ReviewDetail.vuegoToRecall()

const res = await findNode(taskNum, content.substring(0, 500));
if (res?.data?.path) {
  router.push(`/review/recall/${taskNum}?focusPath=${encodeURIComponent(res.data.path)}`);
}

流程:

  1. 用户在详情页查看某个残片/报告的内容
  2. 点击"回忆复习" → 前端调 POST /review/standard-mind-map/{taskNum}/find-node
  3. 后端 findClosestNode() 用 bigram Jaccard 匹配最接近的节点
  4. 返回 { path, nodeTitle, score }
  5. 跳转到 /review/recall/:taskNum?focusPath=...
  6. 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):

  1. 展平标准导图所有节点
  2. 对每个节点,计算其 title 与输入文本的 bigram Jaccard 相似度
  3. notes 字段也参与匹配(权重 0.5
  4. 返回得分最高(>0.1)的节点

相关文件:

  • lpt-fe/src/components/ReviewDetail.vue
  • learning-progress-tracker/.../utils/MindMapTreeTool.javafindClosestNodesimilarityScore

入口 C:首页欢迎页 → 回忆卡片 → "前往回忆复习"

路径: Welcome.vue → 滚动回顾标签 → 点击弹出回忆对话框 → "前往回忆复习"按钮

前端代码: Welcome.vuegoToRecallFromCard()

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)}`);
}

流程:

  1. Welcome 页加载 GET /review/feed 获取复习 feed
  2. 按 session 分组为标签卡片
  3. 用户点击某个标签 → 弹出回忆对话框(先回忆→展开对照)
  4. 点击"前往回忆复习" → 同入口 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/sourceTypenode-click 溯源不可用
  • selectable 模式下,点击无子节点的叶子节点,childCount = 0 的确认弹窗体验略奇怪
  • findClosestNode 的 bigram 相似度对短文本效果有限,可能需要 notes 加权优化
  • 欢迎页(Welcome.vue)的回忆卡片入口「前往回忆复习」按钮文字和位置可以优化