feat(export): 函数图像静态渲染复审修复(2 P2 + 1 P3) #29

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

概述

函数图像静态渲染(Function Plot)与导出服务(Markdown → HTML)的复审修复。本 PR 是 #28 审阅结论(2 P2 + 1 P3)修复后的复审提交。

本轮修复(对应 #28 结论)

  • [P2] 复杂表达式绕过函数图像的异常回退:ast 深度(200)/节点数(1000)上限拦截 RecursionError;解析与渲染共同纳入局部异常回退,单个图像失败只回退占位 + warning,不阻断整篇导出。
  • [P2] 极端有限范围生成含 nan 的 SVG:新增跨度校验(端点有限、跨度有限且 >0),domain/range 无效时回退安全范围;_polyline 拒绝非有限像素坐标。
  • [P3] 接口文档仍描述静态渲染未实现:更新 docs/contracts/第二阶段接口契约-开发版.md,标注 function-plot 已支持静态 SVG、失败回退占位。

测试

backend 全量 pytest621 passed(新增 5 项边界回归:超深表达式、超量节点、HTML 深表达式回退、极端 domain/range 无 nan/inf)。

说明

  • 仅改动 backend/ 代码 + 接口契约文档。
  • 函数图像交互预览由前端完成,后端只负责解析/表达/静态渲染。
## 概述 函数图像静态渲染(Function Plot)与导出服务(Markdown → HTML)的复审修复。本 PR 是 `#28` 审阅结论(2 P2 + 1 P3)修复后的复审提交。 ## 本轮修复(对应 `#28` 结论) - **[P2]** 复杂表达式绕过函数图像的异常回退:`ast` 深度(200)/节点数(1000)上限拦截 `RecursionError`;解析与渲染共同纳入局部异常回退,单个图像失败只回退占位 + warning,不阻断整篇导出。 - **[P2]** 极端有限范围生成含 `nan` 的 SVG:新增跨度校验(端点有限、跨度有限且 >0),domain/range 无效时回退安全范围;`_polyline` 拒绝非有限像素坐标。 - **[P3]** 接口文档仍描述静态渲染未实现:更新 `docs/contracts/第二阶段接口契约-开发版.md`,标注 `function-plot` 已支持静态 SVG、失败回退占位。 ## 测试 `backend` 全量 `pytest`:**621 passed**(新增 5 项边界回归:超深表达式、超量节点、HTML 深表达式回退、极端 domain/range 无 nan/inf)。 ## 说明 - 仅改动 `backend/` 代码 + 接口契约文档。 - 函数图像交互预览由前端完成,后端只负责解析/表达/静态渲染。
yxx added 7 commits 2026-09-05 22:50:39 +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>
- P2 复杂表达式绕过异常回退:解析与渲染共同纳入局部异常回退;
  AST 深度/节点数上限拦截 RecursionError
- P2 极端有限范围生成 nan SVG:校验坐标跨度有限且 >0,回退安全范围;
  _polyline 拒绝非有限像素坐标
- P3 更新接口契约文档:function-plot 静态 SVG 已实现

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

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

审阅提交:b87f945

上一轮提出的 2 个 P2、1 个 P3 均已正确修复

  • 复杂表达式可以安全回退,不再导致整篇导出失败;
  • 极端坐标跨度不会再生成包含 nan 的 SVG;
  • 接口文档已经同步函数图像静态渲染状态。

本轮复审新发现 1 个 P1

[P1] 函数数量没有限制,可能生成数百 MB 的 SVG

位置:backend/app/plot/parser.py:363–370

当前复杂度限制只作用于单条表达式,parse_source 可以无限追加合法表达式。

实测结果:

  • 100 行 y=x:约 0.56 MB SVG;
  • 1000 行 y=x:约 5.59 MB SVG;
  • 输入源码仅约 4 KB。

未保存 Markdown 当前允许最多 200,000 个字符,可容纳约 50,000 行简单表达式,理论上会生成约 280 MB SVG,并执行约 4,000 万次采样求值。生成过程中还会存在中间字符串和编码副本,实际内存占用会更高。

同时:

  • 渲染运行在 asyncio.to_thread 中,取消请求在渲染结束前无法停止线程;
  • 最多可存在 100 个活动导出任务;
  • note 来源没有执行 MAX_MARKDOWN_CHARS 检查。

多个并发任务可能耗尽内存和工作线程。

建议:

  • 限制每个 function-plot 图块的表达式数量;
  • 限制单篇文档包含的函数图像数量;
  • notemarkdown 来源统一执行输入大小限制;
  • 对最终导出产物设置大小上限;
  • 超限时回退为源码占位并记录结构化 warning;
  • 补充表达式数量上限、笔记来源大小限制及导出产物上限的回归测试。

验证结果

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

请修复上述资源上限问题并补充测试后再提交复审。

## 审阅结论:请求修改,暂不建议合并 审阅提交:`b87f945` 上一轮提出的 **2 个 P2、1 个 P3 均已正确修复**: - 复杂表达式可以安全回退,不再导致整篇导出失败; - 极端坐标跨度不会再生成包含 `nan` 的 SVG; - 接口文档已经同步函数图像静态渲染状态。 本轮复审新发现 **1 个 P1**。 ### [P1] 函数数量没有限制,可能生成数百 MB 的 SVG 位置:`backend/app/plot/parser.py:363–370` 当前复杂度限制只作用于单条表达式,`parse_source` 可以无限追加合法表达式。 实测结果: - 100 行 `y=x`:约 0.56 MB SVG; - 1000 行 `y=x`:约 5.59 MB SVG; - 输入源码仅约 4 KB。 未保存 Markdown 当前允许最多 200,000 个字符,可容纳约 50,000 行简单表达式,理论上会生成约 280 MB SVG,并执行约 4,000 万次采样求值。生成过程中还会存在中间字符串和编码副本,实际内存占用会更高。 同时: - 渲染运行在 `asyncio.to_thread` 中,取消请求在渲染结束前无法停止线程; - 最多可存在 100 个活动导出任务; - `note` 来源没有执行 `MAX_MARKDOWN_CHARS` 检查。 多个并发任务可能耗尽内存和工作线程。 建议: - 限制每个 `function-plot` 图块的表达式数量; - 限制单篇文档包含的函数图像数量; - 对 `note` 和 `markdown` 来源统一执行输入大小限制; - 对最终导出产物设置大小上限; - 超限时回退为源码占位并记录结构化 warning; - 补充表达式数量上限、笔记来源大小限制及导出产物上限的回归测试。 ### 验证结果 - 后端全量测试:**621 passed** - `git diff --check`:通过 - 与 `main`:无冲突 - 远程状态:可合并 请修复上述资源上限问题并补充测试后再提交复审。
Kronecker closed this pull request 2026-09-05 23:13:47 +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#29