Files
lpt-meta/OPTIMIZATION_BACKLOG.md
T

335 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LPT 实现优化清单
> 面向接手实现的代理。每条都是**功能性问题**(数据正确性、能力缺失、性能、安全),不含外观类改进。
> §3 列出已确认的**设计决策**:它们是有意为之的行为,不要当作缺陷修改。
## 0. 基线信息
| 项目 | 核对基线 |
|------|---------|
| 生成时间 | 2026-09-14 |
| 代码基线 | `lpt-be` c961157 / `lpt-fe` e66bdb7 / `lpt-ai` 200f088 / `lpt-infra` 0a23075 / `lpt-docs` 9cb9c75 |
| 核对方式 | 通读上述仓库源码 + 对线上 prod/dev 做只读探测(见附录 A |
若某仓库 HEAD 已明显晚于上表,先确认问题是否已被修复,再动手。
## 1. 协作约定
- 完成一项后:勾选对应条目并追加一行「完成于 <仓库> <commit>」;若条目语义已被改动,直接重写条目,不要追加开发日志。
- 涉及概念/设计语义的改动(如会话生命周期、有效时间定义):必须同步更新 `lpt-docs` 的概念或设计文档;`lpt-docs` 只写概念与设计,不写实现过程。
- 本文档不记载开发过程,只保留「待办 + 依据 + 验收标准」。禁止写入任何密钥、密码、Token 明文。
- 条目粒度按「一个代理能独立完成并自测」切分,跨仓库条目已标注涉及仓库。
- §3「设计决策」不是待办:改动前必须先与维护者确认,禁止当成缺陷直接「修复」。若某项决策需要调整,先在 §3 改写该条,再开对应实现条目。
## 2. 状态总览
| 优先级 | 条目 |
|--------|------|
| P0 数据正确性 | LPT-01、LPT-03 ~ LPT-05(原 LPT-02 已改为设计决策 D-01,编号不再复用)|
| P1 会挡路的功能缺口 | LPT-06 ~ LPT-10 |
| P2 AI 链路 | LPT-11 ~ LPT-14 |
| P3 检索与数据增长 | LPT-15 ~ LPT-20 |
| P4 安全与运维 | LPT-21 ~ LPT-26 |
---
## 3. 已确认的设计决策(不是待办,不要当缺陷改)
### D-01 少于 10 分钟的有效学习时间不计入总学习时间
- **依据**`StudySessionsEntity.endedStudySession()``effectiveTime < 10 * 60 → setEffectiveTime(0); setEffectivenessRatio(0)`;常量 `MIN_EFFECTIVE_TIME_SECONDS`
- **决策**:25 分钟的番茄钟已经足够碎片化,更短的碎片视为噪声,刻意不记录。
- **注意**:这与 `lpt-docs/design/principles.md` 中「量化,但不评判」存在张力,但已由维护者确认为有意取舍。除非维护者主动改变口径,否则不要改成「保留真实值 + 打标记」。
- **来源**:原 LPT-02 条目,2026-09-14 由维护者澄清后重新归类。
### D-02 遗忘判定阈值为 50 分钟(2 × `WORK_DURATION`
- **依据**`StudySessionsEntity.isOverTime()``minutes >= WORK_DURATION * 2`;触发时按 `lastStartTime + 25 分钟` 截断有效时间。
- **决策**:25~50 分钟区间默认用户仍在屏幕前(番茄钟已提醒但可能正在收尾),不做截断;超过 50 分钟才判定为「忘了点暂停/结束」。
- **注意**:不要把这个阈值改成等于番茄钟长度(25 分钟);阈值存在的意义是区分「在学」与「忘了关」。真正要修的是执行路径不统一,见 LPT-01。
---
## 4. P0 数据正确性
### LPT-01 遗忘检测只在「重新进入学习页面」这一条路径上执行
- [ ] 未完成
- **涉及仓库**`lpt-be`(可选 `lpt-fe` 心跳)
- **前提**:系统已具备遗忘检测(见 D-02),问题不在于缺少机制,而在于它只挂在一条路径上。
- **依据**`pausedStudySession(lastStartTime.plusMinutes(WORK_DURATION))` 只在 `StudySessionsServiceImpl.startOrContinueStudySession()` 中出现(全仓唯一)。而 `pauseStudySession()``StudySessionsEntity.endedStudySession()`(内部 `pausedStudySession(now)`)都是直接 `effective += now - lastStartTime`,没有遗忘检测。
- **影响**:页面不刷新、停留在前台时,pause / ended 两条路径依然可达。典型场景:番茄钟响起后用户离开,3 小时后回到同一个页面点「休息」(`StartTask.vue``restTimer()``stopTimer()` → pause 接口),这 3 小时被全额计入有效学习时间。是否被截断取决于用户有没有刷新过页面。
- **理由**:这恰好落在 D-02 想覆盖的「忘了点暂停/结束」场景里,属于检测点遗漏,会让同一行为产生两种统计结果,直接损害时间数据的可信度。
- **建议**:抽出统一的结算时间函数(如 `resolveSettlementTime(session)``now - lastStartTime > 50min` 时返回 `lastStartTime + 25min`,否则返回 `now`),在 pause / ended / 定时收敛三条路径共用;番茄钟长度与两类阈值统一配置化。
- **验收**:构造「ONGOING 且 `last_start_time` 为 3 小时前」的会话,分别调用 pause 与 ended 接口,断言 `effective_time` 被截断到 25 分钟;`StudySessionsEntityTest` 同步补用例。
### LPT-03 `isOverTime()` 误报「超时 25 分钟」
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`StudySessionsEntity.isOverTime()` 只比较 `last_start_time` 与当前时间,不看 `end_time``session_state``startOrContinueStudySession()` 用同一判定既驱动截断又驱动 `systemMessage`
- **影响**:已暂停很久的会话(时间早已在暂停时正确入账),每次回到页面都会弹「上段学习任务已经超时 25 分钟,将仅计算为 25 分钟的有效学习时间」。
- **建议**:拆分「是否需要截断」与「是否需要提示」两个判定,并纳入 `end_time` / `state`
- **验收**:对 `state=PAUSED``end_time` 已存在的会话,接口不返回 `systemMessage`
### LPT-04 结束会话不幂等,会产生重复学习报告
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`StudySessionsEntity.endedStudySession()``state = ENDED` 时只 `log.warn` 并原样返回;`StudySessionsServiceImpl.endedStudySession()` 随后无条件 `studyReportsMapper.insert(...)`
- **影响**:重复点击、网络重试即可写入多条报告 → 复习 Feed 重复项、标准导图重复节点、统计重复计数。
- **建议**`state != ENDED` 才允许写报告,否则幂等返回;接口层可加幂等键。
- **验收**:对同一 `sessionNum` 连续调用两次结束接口,第二次不新增报告(断言报告条数)。
### LPT-05 缺少服务端兜底:悬挂会话与超长会话无人收敛
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**:全仓无 `@Scheduled` / `EnableScheduling``getActiveSession()` 直接返回最新的 ONGOING/PAUSED 会话;遗忘检测只在用户主动访问该任务页面时执行(见 LPT-01)。
- **影响**
- 会话可长期停留「进行中/暂停」,只能等用户回到该任务页才被被动修正;用户不访问时「今日有效学习时间」等统计是滞后或错误的。
- 用户始终不回到该任务时,ONGOING 会话永久悬挂,并被 LPT-07 的活跃会话守卫用来阻塞新任务。
- **建议**
1. 增加超长会话硬兜底:ONGOING 且 `now - last_start_time` 超过第二阈值(暂定 2 小时)时强制暂停、按遗忘规则(D-02)截断,并打异常标记,供前端提示「本次会话疑似忘记结束」。
2. 收敛方式:惰性收敛(读取时收敛)+ 低频定时扫描兜底,二选一或并用。
3. 三类阈值(25 分钟番茄钟、50 分钟遗忘判定、2 小时强制暂停)统一配置化,不要散落在实体常量里。
- **验收**:制造一个 3 小时前的 ONGOING 会话,在不访问该任务页面的前提下,惰性/定时收敛后状态为 PAUSED 且有效时间已按 D-02 截断;正常进行中的会话不受影响。
---
## 5. P1 会挡路的功能缺口
### LPT-06 「误操作结束(零数据关闭会话)」文档已描述但代码未实现
- [ ] 未完成
- **涉及仓库**`lpt-docs` + `lpt-be`+ `lpt-fe`
- **依据**`lpt-docs/docs/concepts/study-session.md``learning-loop.md``learning-expectation.md``concepts/README.md` 均把该能力描述为已有设计;但 `lpt-be/src``lpt-fe/src` 中检索「误操作」零命中,也不存在任何删除会话的端点。
- **影响**:误点「开始学习」后必须填写学习预期、必须走完整流程,最后留下一条 0 时间垃圾会话(再被 LPT-02 清零)。文档与实现漂移,代理读文档会得到错误结论。
- **建议**:二选一并明确记录——(a) 实现:仅当会话无知识碎片时可用,需输入确认语「我误操作导致开启了本次学习」,删除会话与学习预期且不产生报告;(b) 从 `lpt-docs` 撤回该能力描述。
- **验收**:文档与实现一致;若实现,需覆盖「有碎片时拒绝」「确认语错误时拒绝」两条负面用例。
### LPT-07 活跃会话拦截没有出路,会永久堵住新任务
- [ ] 未完成
- **涉及仓库**`lpt-fe`+ `lpt-be` 需提供结束/放弃能力)
- **依据**`Study.vue``startTask()` 在发现其他活跃会话后只提供「前往继续 / 取消」,取消分支为空函数,不跳转。
- **影响**:一个搁置数月的暂停会话会阻止开始任何新任务(所有任务入口都撞同一个守卫),用户必须先找回那个任务手动结束。
- **建议**:弹窗补充「结束它并开始新任务」或「放弃该会话」出口;与 LPT-06 的收敛/关闭能力复用。
- **验收**:存在陈旧活跃会话时,用户可在一次交互内开始新任务。
### LPT-08 学习报告与知识碎片不可修订
- [ ] 未完成
- **涉及仓库**`lpt-be`+ `lpt-fe`
- **依据**:端点清单——`StudyReportFragments` 只有 `POST` / `PUT /{id}` / `GET``StudyReports` 只有创建与查询,**没有 PUT、没有 DELETE**;碎片也没有 DELETE。
- **影响**:写错不能改,也不能删除,只能改数据库。
- **理由**:知识碎片是学习报告、复习 Feed、标准导图的共同原料,记录成本上升会直接降低记录率,连锁削弱整个复习闭环。
- **建议**:报告支持编辑与删除;碎片支持删除;删除需考虑对标准导图、回忆记录、统计的影响(软删除 + 标记重建导图)。
- **验收**:报告/碎片可编辑与删除;删除后 Feed 与统计不再包含该条。
### LPT-09 复习 Feed 只读,没有反馈动作
- [ ] 未完成
- **涉及仓库**`lpt-be`+ `lpt-fe`
- **依据**:Feed 仅提供列表/详情/回忆对比;`ReviewServiceImpl.reviewNeedWeight()` 的智能权重只有两个信号——时间衰减(`createdTime`)与任务最近一次回忆覆盖率。
- **影响**:无法表达「这条其实很熟」与「这条完全没印象」;权重长期只有两个偏置,也没有「已复习」状态,同一批内容会反复出现。
- **建议**:加轻量动作(稍后再看 / 已掌握),把「上次复习时间」变成第三个权重信号;保持既有「无压力、偶遇式」设计,不引入强制清单。
- **验收**:用户反馈动作可落库并在下一次 Feed 排序中生效。
### LPT-10 无数据导出
- [ ] 未完成
- **涉及仓库**`lpt-be`+ `lpt-fe`
- **依据**:端点清单中没有任何导出能力。
- **理由**`lpt-docs` 明确系统定位是「辅助学习的工具」,工具型系统应保证数据可携带;同时可缓解备份粒度过粗的问题(见 LPT-25)。
- **建议**:导出学习报告、知识碎片、任务统计为 Markdown / JSON,按任务或时间范围筛选。
- **验收**:导出内容可独立阅读,包含报告/碎片原文与时间信息。
---
## 6. P2 AI 链路(收益成本比最高)
### LPT-11 `recallCompare` 在事务中同步等待 AI,长事务占用数据库连接
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`StandardMindMapServiceImpl.recallCompare()` 标注 `@Transactional`,内部同步调用 `AiServiceClient.compareRecall()`(轮询上限 `lpt.ai-service.timeout-seconds: 600`)。Hikari 连接池未显式配置,为默认 10。
- **影响**:并发几个回忆对比即可耗尽连接池,导致全站请求阻塞。**这是建议最先修的一条:把 AI 调用移出事务即可,改动小、风险低。**
- **建议**:先做纯读的对比计算(事务外),再开短事务写入回忆记录;或引入任务表 + 异步回写(与 LPT-12 一起做)。
- **验收**:AI 调用期间不持有数据库连接(可通过连接池监控或长事务日志证明)。
### LPT-12 AI 全链路同步阻塞,用户需干等最长 10 分钟
- [ ] 未完成
- **涉及仓库**`lpt-be` + `lpt-fe` + `lpt-ai`
- **依据**`lpt-fe/src/utils/request.ts``timeout: 600000``lpt-be``AiServiceClient.submitAndWait()` 内部轮询最长 600s`GET /study-sessions/{sessionNum}/report-draft` 这类 GET 也会触发 10 分钟阻塞并计费;`ReviewRecall.vue` 存在「AI 对比分析中,已等待 N 秒…」的分档文案。
- **影响**:页面只能干等;刷新或断开后请求丢失,AI 已消耗的 token 无法回收,结果也不落库。
- **建议**`lpt-be` 建 AI 任务表(提交后立即返回 taskId),前端轮询 `lpt-be`;结果回写后前端再取。生成期间允许离开页面再回来。
- **验收**:提交 AI 任务后接口在秒级返回任务标识;任务结果可重复获取,无需重试即可拿到。
### LPT-13 `lpt-ai` 单 worker 串行且无重试
- [ ] 未完成
- **涉及仓库**`lpt-ai`+ `lpt-be` 超时策略)
- **依据**`task-queue.ts` 只有一个 `processing` 标志顺序处理;失败直接置 `failed`,无退避重试;任务记录 `TTL_MS = 1 小时`
- **影响**:N 个并发请求时第 N 个需等待 N × LLM 耗时,超过 `lpt-be` 的 600s 超时后表现为「AI 实际成功、客户端已超时」,结果既不落库也不告知,用户重复点击导致重复计费。LLM 的 429/5xx 抖动被当成永久失败。
- **建议**worker pool(并发度对齐服务商限流)+ 429/5xx 退避重试 + 幂等键/结果缓存(同一导图、同一报告草稿的相同输入直接复用)。
- **验收**:并发 N 个任务时耗时不再线性增长;可重试错误不产生失败结论。
### LPT-14 防并发锁是 JVM 内存锁
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`StandardMindMapServiceImpl``ConcurrentHashMap<String, AtomicBoolean> generatingTasks`
- **影响**:多副本部署时锁失效;「生成中」的重复请求会返回 `queryByTaskNum()` 的结果(可能为 `null`)而被前端当成成功。
- **建议**:改为数据库/分布式锁,并明确「生成中」的对外语义(返回任务状态而不是实体)。
- **验收**:多副本场景下同一任务不会并发生成;生成中重复请求返回明确的进行中状态。
---
## 7. P3 检索与数据增长
### LPT-15 业务主表缺索引,而每条查询都被追加租户条件
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`V20250629_1__LPT_dataBase_init.sql``study_sessions` / `study_reports` / `study_report_fragments` / `tasks` 均只有主键;`MybatisPlusConfig` 的租户拦截器给每条 SELECT/UPDATE/DELETE 追加 `created_by = ?`;全仓仅少数 review 表有索引。
- **影响**:所有高频查询(Feed、统计、按会话取碎片/报告)都是全表扫描 + 过滤,随使用量线性劣化。
- **建议**:按实际查询补复合索引,例如 `(created_by, task_num, session_state)``(created_by, session_num, created_time)``(created_by, created_time)``(created_by, task_num, calculated_priority)`
- **验收**`EXPLAIN` 显示关键查询走索引而非全表扫描。
### LPT-16 Feed 使用 `ORDER BY RAND()`
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`ReviewServiceImpl.getReviewFeed()``getSmartFeed()` 对报告表和碎片表分别使用 `ORDER BY RAND() LIMIT ...`smart 模式抽样量为 `limit × 5`(上限 500)。
- **影响**:MySQL 上属全表随机排序,单次请求两次全表随机。
- **建议**:改为 id 区间随机、预生成候选池或「按时间窗口随机 + 按需补充」。
- **验收**:Feed 接口不产生全表随机排序(`EXPLAIN` 可验证)。
### LPT-17 取「每任务最近一次回忆覆盖率」时拉取全部回忆记录
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`ReviewServiceImpl.latestRecallRatioByTask()``taskNum in (...)` 查询全部记录(含体积较大的 `compare_result` JSON)后只在内存里取每条任务的第一条。
- **影响**:数据传输量与内存占用随复习次数线性增长。
- **建议**:改为分组取最新(子查询/`MAX(created_time)`),或把最近覆盖率冗余到任务表/导图表。
- **验收**:该查询返回行数等于任务数而非回忆记录数。
### LPT-18 统计聚合在内存中完成且无时间范围
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`ReviewServiceImpl.fillReviewTaskStats()` 加载**所有任务的全部会话**到内存再聚合,无时间窗、无分页。
- **影响**:使用越久越慢,且会随会话数增长而内存膨胀。
- **建议**:下推为 SQL 聚合(含今日/本周条件),必要时物化统计表。
- **验收**:统计接口加载的会话行数与任务数同阶,不随历史会话数线性增长。
### LPT-19 Feed 返回全文
- [ ] 未完成
- **涉及仓库**`lpt-be` + `lpt-fe`
- **依据**`ReviewFeedItem.content` 直接返回报告/碎片全文,`getReviewFeed(100, "smart")` 一次最多 100 条;前端只展示前几十个字(详情页才需要全文)。
- **建议**:Feed 返回摘要字段(含长度标记),详情接口按需拉全文。
- **验收**:Feed 响应体积显著下降且展示行为不变。
### LPT-20 编号生成有撞号风险且无唯一约束
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`GenerateNumTool.getNextSequence()` 使用 `System.currentTimeMillis() % 1_000_000`,格式为 `PREFIX-yyyyMMdd-<6位>``session_num` / `task_num` 上没有唯一索引(仅两张导图表有 `unique key`)。
- **影响**:同毫秒创建可能撞号;撞号后 `getOneOpt` 会因多行结果抛异常。
- **建议**:改用 UUID/ULID 或加随机位,并对业务编号加唯一约束。
- **验收**:并发创建 N 个任务/会话无重复编号,数据库层有唯一约束保护。
---
## 8. P4 安全与运维
### LPT-21 数据库凭证明文入库
- [ ] 未完成
- **涉及仓库**`lpt-be` + `lpt-infra`
- **依据**`application-prod.yml``application-dev.yml``application-local.yml` 均包含明文数据库账号密码(三个环境同一份)。`lpt-infra` 已通过 `lpt-secrets` 注入 `SPRING_DATASOURCE_PASSWORD` 覆盖,但配置文件仍在 Git 历史中。
- **建议**:轮换凭证 → 配置文件只保留占位符/环境变量引用 → 清理历史(或至少视为已泄露处理)。
- **验收**:仓库中不再出现明文凭证;本地开发通过环境变量或本地未跟踪配置提供。
- **注意**:**不要把任何密码值复制进本文档。**
### LPT-22 Actuator 端点鉴权边界未固定
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**:线上只读探测确认 `/api/actuator/health``/api/actuator/info` 未登录返回 200,而 `/api/actuator/env` 与业务接口返回 401(已注册的 actuator 端点不受 `SaInterceptor` 约束)。
- **影响**:当前探针正常,但一旦放开 metrics/prometheus 等端点,会连带公开。
- **建议**:显式固定暴露白名单,或把管理端点放到独立端口并加网络策略。
- **验收**:未鉴权可访问的端点清单与预期完全一致。
### LPT-23 `lpt-ai` 缺少入站防护(SSRF 与滥用)
- [ ] 未完成
- **涉及仓库**`lpt-ai`
- **依据**`POST /fetch-title` 由服务端抓取用户可控 URL(可探测内网/云元数据地址);`/ai/tasks``/admin` 无鉴权、无速率限制、无请求体上限;`admin/store.ts` 把完整 prompt 与学习内容明文写入 `data/logs.json`
- **建议**:抓取改为白名单/内网地址黑名单 + 协议与重定向校验;入站加鉴权与限流;`/admin` 加访问控制;日志中学习内容按需脱敏或缩短保留期。
- **验收**`/fetch-title` 无法访问内网与元数据地址;未授权请求无法提交 AI 任务。
### LPT-24 生产环境打印全部 SQL
- [ ] 未完成
- **涉及仓库**`lpt-be`
- **依据**`application.yml``mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl`(注释里保留了 `Slf4jImpl` 备选)。
- **影响**:生产日志包含全部 SQL 与参数(含学习内容),噪音大且不利于排查。
- **建议**:改为 `Slf4jImpl` 并按环境分级,生产默认不打印参数。
- **验收**:生产环境不再输出逐条 SQL。
### LPT-25 备份只在集群内同一 PVC
- [ ] 未完成
- **涉及仓库**`lpt-infra`
- **依据**`mysql-backup-cronjob.yaml` 每日 `mysqldump --all-databases` 写入同一集群的 `mysql-backup` PVC,保留 14 天,且 dev 与 prod 混在同一 dump。
- **影响**:集群级故障即全丢;无法按环境恢复。
- **建议**:增加异地/对象存储副本,按库分文件,并定期做恢复演练。
- **验收**:可在集群外取得可恢复的备份,并完成一次演练记录。
### LPT-26 死代码与死表清理
- [ ] 未完成
- **涉及仓库**`lpt-be` + `lpt-fe`
- **依据**`TestController``/test/{id}`)与 `test_table` 仍在生产代码中;`review_mind_maps` 表自 `V20260620_1` 创建后无任何代码引用也未 drop`lpt-fe/src/api/review.ts` 仍导出 `/review/mind-map/*`(含 xmind 上传解析)相关调用,而后端已无对应端点。
- **建议**:确认无外部调用后删除端点/表/前端死代码;若保留,需在 `lpt-docs` 说明定位。
- **验收**:前后端不再存在无实现的接口调用;数据库无孤儿表。
---
## 9. 建议落地顺序
1. **LPT-11**(AI 移出事务)—— 最小改动,避免全站卡死。
2. **LPT-01 / LPT-04**(结算时间统一、结束幂等)—— 一批改完即修正时间数据口径,建议同时补用例。
3. **LPT-05 / LPT-06 / LPT-07**(服务端收敛与 2 小时硬兜底、误操作结束的决定与实现、活跃会话出口)。
4. **LPT-12 / LPT-13**AI 异步化与队列可靠性)。
5. **LPT-15 ~ LPT-18**(索引与聚合下推)—— 数据量还小时成本最低。
---
## 10. 待确认的设计变更(确认前不要实施)
### DC-01 内容归属与待归类
- **文档**`lpt-docs/docs/concepts/knowledge-ownership.md`(草案,含三个待决问题)、`lpt-docs/docs/design/system-structure.md`(设计总纲)
- **背景**:学习 A 内容时顺手记下的旁支知识(例:学 AI 时记下“1B = one billion”)目前会被算作 A 的知识,进 A 的导图与回忆对比基准。根因是把「记录地点」当成了「内容归属」。
- **变更内容**:拆分记录上下文(不可变,时间统计用)与内容归属(可变、单一、可为空);时间按会话统计、内容按归属聚合;新增待归类视图;归类动作挂在复习 Feed 里;AI 只给归属建议;待归类内容可升格为学习任务。
- **影响范围**:碎片表结构、标准导图取材、按任务的复习与检索(`StandardMindMapServiceImpl.doGenerate()``ReviewServiceImpl.getTaskReview()``getTaskFragments()` / `getTaskReports()`)。时间与状态机不受影响。
- **前置条件**:需维护者确认草案文末三个待决问题(碎片是否可脱离会话、报告是否结构化、归属是否保持单一)。
- **实施要求**:一次替换干净,禁止出现“导图按归属、检索仍按会话”的双口径并存;归属变更后需标记导图待更新。
---
## 附录 A 核对方式(可复现)
- 代码核对:阅读 `lpt-be``service/impl/*``entity/*``controller/*``config/*``resources/application*.yml``resources/db/migration/*``lpt-fe/src``utils/request.ts``components/*``api/*``lpt-ai/src` 全部;`lpt-infra``dev/``prod/``traefik/`
- 线上只读探测(仅 GET,未做任何写操作):
- `GET https://lpt.cat-shark.xyz/api/actuator/health` → 200
- `GET https://lpt.cat-shark.xyz/api/actuator/info` → 200
- `GET https://lpt.cat-shark.xyz/api/actuator/env` → 401
- `GET https://lpt.cat-shark.xyz/api/review/feed`(未登录)→ 401
- 结构性核对命令示例:`rg -n "@Scheduled" lpt-be/src``rg -n "ORDER BY RAND" lpt-be/src``rg -n "unique" lpt-be/src/main/resources/db/migration`