fix(export): 为函数图像与导出产物增加资源上限 #30

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

修复内容(PR #29 审阅 P1)

针对「函数数量没有限制,可能生成数百 MB 的 SVG」,为导出链路增加资源上限:

代码

  • app/plot/parser.py:单块 function-plot 表达式数量上限 _MAX_EXPRESSIONS = 16,超限整块回退并记 FUNCTION_PLOT_TOO_MANY_EXPRESSIONS 诊断。
  • app/export/exporters/html.py:单篇文档函数图像数量上限 _MAX_FUNCTION_PLOTS = 16,超出部分回退为源码占位并记 warning。
  • app/export/service.py:输入源 MAX_MARKDOWN_CHARS(200k)与产物 MAX_EXPORT_BYTES(20MB)上限,超限分别拒绝创建(EXPORT_OPTIONS_INVALID)或标记 failed(EXPORT_OUTPUT_TOO_LARGE)。

测试

  • 新增 4 条回归测试(表达式数量上限、文档级图像数量上限、note 源大小上限、产物大小上限),全量 625 通过。

文档

  • 同步更新 Export开发说明.md 资源上限章节与错误码表、契约 §10.5。

🤖 Generated with Claude Code

## 修复内容(PR #29 审阅 P1) 针对「函数数量没有限制,可能生成数百 MB 的 SVG」,为导出链路增加资源上限: ### 代码 - `app/plot/parser.py`:单块 function-plot 表达式数量上限 `_MAX_EXPRESSIONS = 16`,超限整块回退并记 `FUNCTION_PLOT_TOO_MANY_EXPRESSIONS` 诊断。 - `app/export/exporters/html.py`:单篇文档函数图像数量上限 `_MAX_FUNCTION_PLOTS = 16`,超出部分回退为源码占位并记 warning。 - `app/export/service.py`:输入源 `MAX_MARKDOWN_CHARS`(200k)与产物 `MAX_EXPORT_BYTES`(20MB)上限,超限分别拒绝创建(`EXPORT_OPTIONS_INVALID`)或标记 failed(`EXPORT_OUTPUT_TOO_LARGE`)。 ### 测试 - 新增 4 条回归测试(表达式数量上限、文档级图像数量上限、note 源大小上限、产物大小上限),全量 625 通过。 ### 文档 - 同步更新 `Export开发说明.md` 资源上限章节与错误码表、契约 §10.5。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
yxx added 8 commits 2026-09-05 23:32:38 +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>
针对 PR 审阅「函数数量没有限制,可能生成数百 MB 的 SVG」:

- parser: 单块 function-plot 表达式上限 _MAX_EXPRESSIONS=16,超限整块回退
- html: 单篇文档函数图像上限 _MAX_FUNCTION_PLOTS=16,超出回退源码占位
- service: 输入源 MAX_MARKDOWN_CHARS、产物 MAX_EXPORT_BYTES,超限分别
  拒绝创建或标记 failed(EXPORT_OUTPUT_TOO_LARGE)
- 补充 4 条回归测试与文档说明

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

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

审阅提交:124024a

上一轮提出的函数数量、输入大小和产物大小问题已经分别增加限制。本轮复审发现 1 个 P1、1 个 P3

[P1] 组合复杂度仍可长时间占满导出线程

位置:backend/app/plot/parser.py:365–374

当前分别限制了:

  • 单条表达式的 AST 深度和节点数;
  • 单个图块最多 16 条表达式;
  • 单篇文档最多 16 个函数图像;
  • 输入最多 200,000 个字符;
  • 最终产物最多 20 MB。

但是没有限制整篇文档的累计 AST 节点数或采样工作量,因此这些上限组合后仍然允许高开销输入。

实测一个包含 16 条表达式、每条由 150 项加法组成的图块:

  • 源码约 4.8 KB;
  • 渲染约 0.86 秒。

包含 16 个此类图块的文档源码约 77 KB,仍低于输入上限,预计渲染约 13.7 秒,最终 SVG 也不会触发 20 MB 产物限制。

渲染运行在 asyncio.to_thread 中,期间无法响应取消信号。最多 100 个活动任务可同时进入队列,可能持续占满工作线程和 CPU。

建议:

  • 增加文档级累计表达式数量或 AST 节点预算;
  • 或直接降低文档级函数图像与表达式组合上限;
  • 为实际执行中的导出任务增加并发限制;
  • 在采样过程中支持取消或超检查;
  • 补充组合上限和并发资源保护测试。

[P3] `EXPORT_OUTPUT_TOO_LARGE`` 不会对应 HTTP 413

位置:docs/development/Export开发说明.md:68–79

产物超限时:

  1. POST /api/exports 已经返回 202
  2. 后台任务将状态改为 failed
  3. EXPORT_OUTPUT_TOO_LARGE`` 写入 ExportJob.error_code`;
  4. 前端通过后续 GET 请求取得任务状态,该请求仍返回 200

因此该错误不会通过 HTTP 413 返回。文档将它标记为 413,可能导致前端错误地按 HTTP 状态码处理。

建议将同步 HTTP 错误和异步任务错误分开记录,并注明该错误通过:

{
  "status": "failed",
  "error_code": "EXPORT_OUTPUT_TOO_LARGE"
}

返回,而不是 HTTP 413。

验证结果

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

请修复组合资源预算问题并更新文档后再提交复审。

## 审阅结论:请求修改,暂不建议合并 审阅提交:`124024a` 上一轮提出的函数数量、输入大小和产物大小问题已经分别增加限制。本轮复审发现 **1 个 P1、1 个 P3**。 ### [P1] 组合复杂度仍可长时间占满导出线程 位置:`backend/app/plot/parser.py:365–374` 当前分别限制了: - 单条表达式的 AST 深度和节点数; - 单个图块最多 16 条表达式; - 单篇文档最多 16 个函数图像; - 输入最多 200,000 个字符; - 最终产物最多 20 MB。 但是没有限制整篇文档的累计 AST 节点数或采样工作量,因此这些上限组合后仍然允许高开销输入。 实测一个包含 16 条表达式、每条由 150 项加法组成的图块: - 源码约 4.8 KB; - 渲染约 0.86 秒。 包含 16 个此类图块的文档源码约 77 KB,仍低于输入上限,预计渲染约 13.7 秒,最终 SVG 也不会触发 20 MB 产物限制。 渲染运行在 `asyncio.to_thread` 中,期间无法响应取消信号。最多 100 个活动任务可同时进入队列,可能持续占满工作线程和 CPU。 建议: - 增加文档级累计表达式数量或 AST 节点预算; - 或直接降低文档级函数图像与表达式组合上限; - 为实际执行中的导出任务增加并发限制; - 在采样过程中支持取消或超检查; - 补充组合上限和并发资源保护测试。 ### [P3] `EXPORT_OUTPUT_TOO_LARGE`` 不会对应 HTTP 413 位置:`docs/development/Export开发说明.md:68–79` 产物超限时: 1. `POST /api/exports` 已经返回 `202`; 2. 后台任务将状态改为 `failed`; 3. `EXPORT_OUTPUT_TOO_LARGE`` 写入 `ExportJob.error_code`; 4. 前端通过后续 `GET` 请求取得任务状态,该请求仍返回 `200`。 因此该错误不会通过 HTTP `413` 返回。文档将它标记为 `413`,可能导致前端错误地按 HTTP 状态码处理。 建议将同步 HTTP 错误和异步任务错误分开记录,并注明该错误通过: ```json { "status": "failed", "error_code": "EXPORT_OUTPUT_TOO_LARGE" } ``` 返回,而不是 HTTP 413。 ### 验证结果 - 后端全量测试:**625 passed** - `git diff --check`:通过 - 与 `main`:无冲突 - 远程状态:可合并 请修复组合资源预算问题并更新文档后再提交复审。
Kronecker closed this pull request 2026-09-06 00:26:51 +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#30