feat(export): 函数图像静态渲染 + 导出服务审阅修复(复审) #28

Closed
yxx wants to merge 0 commits from feat/export-service into main
Collaborator

概述

导出服务(Markdown → HTML)与函数图像静态渲染,含两轮审阅修复。本 PR 是 #20 审阅结论(1 P1 + 2 P2)修复后的复审提交。

主要内容

导出服务(Markdown → HTML)

  • backend/app/export/:异步导出任务(内存注册表 + 取消)、mistune AST 渲染、HTML 导出器。
  • 安全加固:URL 协议白名单、原始 HTML 转义 + warning、导出文件生命周期与孤儿清理。

函数图像静态渲染(Function Plot)

  • backend/app/plot/:白名单表达式解析(ast 白名单,不 eval/exec)、结构化模型、纯几何 SVG 静态渲染(无 <script>)。
  • 接入 HTML 导出链路:function-plot fenced block → 内嵌 SVG;非法表达式回退占位 + 可定位诊断。

审阅修复(本轮,对应 #20 结论)

  • [P1] 浮点刻度生成死循环 → 有上限的整数索引迭代 + 步长推进校验。
  • [P2] 表达式求值异常导致整篇导出失败 → 非有限/复数采样点作为断点处理。
  • [P2] 函数图像渲染异常不阻断整篇导出 → try/except 回退占位并记 warning。

合并 main

  • 同步 main054f704),解决 contracts.py / main.py / README.md / 技术栈说明 合并冲突。

测试

backend 全量 pytest616 passed(含新增 test_plot.py 与审阅回归用例)。

说明

  • 仅改动 backend/ 代码(合并 main 带来的 frontend 变更除外)。
  • 函数图像交互预览由前端完成,后端只负责解析/表达/静态渲染。
## 概述 导出服务(Markdown → HTML)与函数图像静态渲染,含两轮审阅修复。本 PR 是 `#20` 审阅结论(1 P1 + 2 P2)修复后的复审提交。 ## 主要内容 ### 导出服务(Markdown → HTML) - `backend/app/export/`:异步导出任务(内存注册表 + 取消)、mistune AST 渲染、HTML 导出器。 - 安全加固:URL 协议白名单、原始 HTML 转义 + warning、导出文件生命周期与孤儿清理。 ### 函数图像静态渲染(Function Plot) - `backend/app/plot/`:白名单表达式解析(`ast` 白名单,不 eval/exec)、结构化模型、纯几何 SVG 静态渲染(无 `<script>`)。 - 接入 HTML 导出链路:`function-plot` fenced block → 内嵌 SVG;非法表达式回退占位 + 可定位诊断。 ### 审阅修复(本轮,对应 `#20` 结论) - **[P1]** 浮点刻度生成死循环 → 有上限的整数索引迭代 + 步长推进校验。 - **[P2]** 表达式求值异常导致整篇导出失败 → 非有限/复数采样点作为断点处理。 - **[P2]** 函数图像渲染异常不阻断整篇导出 → try/except 回退占位并记 warning。 ### 合并 main - 同步 `main`(`054f704`),解决 `contracts.py` / `main.py` / `README.md` / 技术栈说明 合并冲突。 ## 测试 `backend` 全量 `pytest`:**616 passed**(含新增 `test_plot.py` 与审阅回归用例)。 ## 说明 - 仅改动 `backend/` 代码(合并 main 带来的 frontend 变更除外)。 - 函数图像交互预览由前端完成,后端只负责解析/表达/静态渲染。
yxx added 6 commits 2026-09-05 21:48:37 +08:00
实现 Export Service 完整生命周期:mistune AST → Document AST → HtmlExporter 渲染完整 HTML5,异步任务注册表 + 取消 + 24h 产物过期。新增 5 个 /api/exports 端点与 15 项测试;pdf/docx 与函数图像静态渲染留待后续 PR。
- P1 链接/图片 URL 协议白名单校验,危险协议降级为纯文本 + warning
- P2 图片 AST 字段映射(src=attrs.url,alt 取 children 文本)
- P2 原始 HTML 块转义保留,正文不丢失 + warning
- P2 过期/淘汰/重启清理导出产物文件
- P2 解析与渲染移入 asyncio.to_thread,运行中取消生效
- P2 function-plot 围栏别名补全
- 回归测试覆盖全部修复

Co-Authored-By: Claude Code <noreply@anthropic.com>
- 新增 app/plot 包:白名单表达式解析(ast 无 eval)+ FunctionPlot 模型 + 静态 SVG 渲染
- HtmlExporter 的 function_plot 节点解析并内嵌 SVG,解析失败回退占位并转诊断
- 新增 test_plot.py(13 个测试)覆盖表达式安全、指令解析、SVG 输出与导出链路集成

Co-Authored-By: Claude Code <noreply@anthropic.com>
- P1 浮点刻度死循环:_ticks 改为有上限的整数索引推进并校验步长推进
- P2 求值异常:白名单函数校验参数数量;负数底非整数指数按断点处理;采样容错复数
- P2 无效纵轴范围:退化/非有限 range 丢弃并自动采样重算;渲染异常回退占位不阻断导出
- 补 6 个回归测试

Co-Authored-By: Claude Code <noreply@anthropic.com>
同步 main(054f704),解决 contracts.py / main.py / README.md / 技术栈说明 的合并冲突。
- contracts.py:保留 pydantic 多行导入并新增 RequestOverride
- main.py:合并 lifespan(导出孤儿清理 + 转写/本地模型生命周期)
- README.md / 技术栈说明:文档取 main 最新版本

Co-Authored-By: Claude Code <noreply@anthropic.com>
Owner

审阅结论:请求修改,暂不建议合并

审阅提交:50d7fb4

上一轮提出的 1 个 P1、2 个 P2 已完成修复,对应回归测试也已补充。本轮复审另发现 2 个 P2、1 个 P3

[P2] 复杂表达式会绕过函数图像的异常回退

位置:backend/app/export/exporters/html.py:205–213

parse_source(node.text) 位于 try 块之外。由约 1000 个连续加法组成的合法表达式会在 AST 递归检查过程中触发 RecursionError,异常直接离开 HtmlExporter,最终导致整篇笔记的导出任务变为 EXPORT_RENDER_FAILED

建议:

  • 限制表达式的 AST 深度和节点数量;
  • 将函数图像的解析与渲染共同纳入局部异常回退;
  • 单个图像失败时保留源码并添加 warning,不应阻断整篇笔记导出;
  • 补充复杂表达式的回归测试。

[P2] 极端但有限的坐标范围会生成包含 nan 的 SVG

位置:backend/app/plot/render.py:188–218

当前只检查坐标端点是否有限,没有检查端点相减后的跨度是否仍为有限数值。例如:

domain: -1e308, 1e308
range: -1, 1
y=0

或者:

domain: -1, 1
range: -1e308, 1e308
y=x

端点均能通过现有校验,但跨度计算会溢出为 inf,后续坐标换算生成包含 nan 的 SVG。导出任务仍报告成功,且不会产生 warning。

建议:

  • 校验 xmax - xminymax - ymin 必须有限且大于零;
  • 对无法表示的范围回退到安全范围或自动范围;
  • SVG 输出前拒绝非有限坐标;
  • 增加极端数值范围的回归测试。

[P3] 接口文档仍描述函数图像静态渲染尚未实现

位置:docs/contracts/第二阶段接口契约-开发版.md:1083

本 PR 已经实现 function-plot 到静态 SVG 的导出,但文档仍称函数图像将保留为占位代码块、静态渲染等待后续 PR,与实际实现和 PR 描述不一致。

请将状态更新为:

  • function-plot 已支持静态 SVG;
  • 解析或渲染失败时回退为源码占位并记录 warning;
  • Mermaid 目前仍使用占位代码块。

验证结果

  • 后端全量测试:616 passed
  • git diff --check:通过
  • main:无合并冲突
  • 远程状态:可合并

请修复以上问题并补充边界测试后再提交复审。

## 审阅结论:请求修改,暂不建议合并 审阅提交:`50d7fb4` 上一轮提出的 **1 个 P1、2 个 P2 已完成修复**,对应回归测试也已补充。本轮复审另发现 **2 个 P2、1 个 P3**。 ### [P2] 复杂表达式会绕过函数图像的异常回退 位置:`backend/app/export/exporters/html.py:205–213` `parse_source(node.text)` 位于 `try` 块之外。由约 1000 个连续加法组成的合法表达式会在 AST 递归检查过程中触发 `RecursionError`,异常直接离开 `HtmlExporter`,最终导致整篇笔记的导出任务变为 `EXPORT_RENDER_FAILED`。 建议: - 限制表达式的 AST 深度和节点数量; - 将函数图像的解析与渲染共同纳入局部异常回退; - 单个图像失败时保留源码并添加 warning,不应阻断整篇笔记导出; - 补充复杂表达式的回归测试。 ### [P2] 极端但有限的坐标范围会生成包含 `nan` 的 SVG 位置:`backend/app/plot/render.py:188–218` 当前只检查坐标端点是否有限,没有检查端点相减后的跨度是否仍为有限数值。例如: ```text domain: -1e308, 1e308 range: -1, 1 y=0 ``` 或者: ```text domain: -1, 1 range: -1e308, 1e308 y=x ``` 端点均能通过现有校验,但跨度计算会溢出为 `inf`,后续坐标换算生成包含 `nan` 的 SVG。导出任务仍报告成功,且不会产生 warning。 建议: - 校验 `xmax - xmin`、`ymax - ymin` 必须有限且大于零; - 对无法表示的范围回退到安全范围或自动范围; - SVG 输出前拒绝非有限坐标; - 增加极端数值范围的回归测试。 ### [P3] 接口文档仍描述函数图像静态渲染尚未实现 位置:`docs/contracts/第二阶段接口契约-开发版.md:1083` 本 PR 已经实现 `function-plot` 到静态 SVG 的导出,但文档仍称函数图像将保留为占位代码块、静态渲染等待后续 PR,与实际实现和 PR 描述不一致。 请将状态更新为: - `function-plot` 已支持静态 SVG; - 解析或渲染失败时回退为源码占位并记录 warning; - Mermaid 目前仍使用占位代码块。 ### 验证结果 - 后端全量测试:**616 passed** - `git diff --check`:通过 - 与 `main`:无合并冲突 - 远程状态:可合并 请修复以上问题并补充边界测试后再提交复审。
Kronecker closed this pull request 2026-09-05 22:20:06 +08:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Kronecker/NotesAgentic#28