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

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

背景

上一版导出服务 PR(#17)被审阅关闭,结论「请求修改」:1 个 P1 + 5 个 P2。本 PR 逐项修复、补回归测试,并继续交付第二阶段「函数图像绘制与渲染支持」(契约 §12 / §10.4)的静态渲染基础。

审阅问题修复(1 P1 + 5 P2)

  • P1 链接/图片 URL 协议白名单校验(http/https/mailto),javascript:/data: 等危险协议降级为纯文本并记 warning,杜绝注入。
  • P2 图片 AST 字段映射修正:srcattrs.urlalt 取 children 文本(原实现误读字段,导致 src 恒空、alt 丢失)。
  • P2 原始 HTML 块不再静默丢失:block_html 映射为 html_block,渲染时转义保留正文 + warning。
  • P2 导出产物文件生命周期:过期(410)/容量淘汰时删除文件;服务重启清理无对应任务的孤立文件。
  • P2 解析与渲染移入 asyncio.to_thread,避免阻塞事件循环;写文件前增加取消检查,运行中取消生效。
  • P2 function-plot 围栏别名补全(function-plot / function_plot / functionplot)。

函数图像静态渲染(function-plot → SVG)

  • 新增 app/plot 包:白名单表达式解析(ast 解析 + 逐节点白名单校验 + 递归解释器,全程无 eval/exec)、FunctionPlot 内部模型、静态 SVG 渲染(纯几何 + <text>,无 script/foreignObject/内联事件,可安全内嵌 HTML)。
  • HtmlExporterfunction_plot 节点改为解析并内嵌 SVG;解析失败回退占位 <pre> 并转诊断 warning。
  • 冻结语法:domain/range/xlabel/ylabel/grid 指令 + y = <expr> 或裸表达式,支持 + - * / ^、隐式乘法、常量 pi/e、白名单函数(sin/cos/tan/…/sqrt/abs)。

测试

  • 审阅修复回归测试 8 个(tests/test_export.py,15 → 23)。
  • 函数图像测试 13 个(tests/test_plot.py):表达式安全拒绝项、指令解析、SVG 输出、HTML 集成。
  • 全量:490 passed

范围

backend 下导出服务与函数图像相关文件;函数图像的交互式预览仍由前端 JS Renderer 完成,不在本 PR。

## 背景 上一版导出服务 PR(#17)被审阅关闭,结论「请求修改」:1 个 P1 + 5 个 P2。本 PR 逐项修复、补回归测试,并继续交付第二阶段「函数图像绘制与渲染支持」(契约 §12 / §10.4)的静态渲染基础。 ## 审阅问题修复(1 P1 + 5 P2) - **P1** 链接/图片 URL 协议白名单校验(http/https/mailto),`javascript:`/`data:` 等危险协议降级为纯文本并记 warning,杜绝注入。 - **P2** 图片 AST 字段映射修正:`src` 取 `attrs.url`、`alt` 取 children 文本(原实现误读字段,导致 src 恒空、alt 丢失)。 - **P2** 原始 HTML 块不再静默丢失:`block_html` 映射为 `html_block`,渲染时转义保留正文 + warning。 - **P2** 导出产物文件生命周期:过期(410)/容量淘汰时删除文件;服务重启清理无对应任务的孤立文件。 - **P2** 解析与渲染移入 `asyncio.to_thread`,避免阻塞事件循环;写文件前增加取消检查,运行中取消生效。 - **P2** `function-plot` 围栏别名补全(`function-plot` / `function_plot` / `functionplot`)。 ## 函数图像静态渲染(function-plot → SVG) - 新增 `app/plot` 包:白名单表达式解析(`ast` 解析 + 逐节点白名单校验 + 递归解释器,全程无 `eval`/`exec`)、`FunctionPlot` 内部模型、静态 SVG 渲染(纯几何 + `<text>`,无 script/foreignObject/内联事件,可安全内嵌 HTML)。 - `HtmlExporter` 的 `function_plot` 节点改为解析并内嵌 SVG;解析失败回退占位 `<pre>` 并转诊断 warning。 - 冻结语法:`domain/range/xlabel/ylabel/grid` 指令 + `y = <expr>` 或裸表达式,支持 `+ - * / ^`、隐式乘法、常量 `pi/e`、白名单函数(sin/cos/tan/…/sqrt/abs)。 ## 测试 - 审阅修复回归测试 8 个(`tests/test_export.py`,15 → 23)。 - 函数图像测试 13 个(`tests/test_plot.py`):表达式安全拒绝项、指令解析、SVG 输出、HTML 集成。 - 全量:`490 passed`。 ## 范围 仅 `backend` 下导出服务与函数图像相关文件;函数图像的交互式预览仍由前端 JS Renderer 完成,不在本 PR。
yxx added 3 commits 2026-09-04 22:30:13 +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>
yxx requested review from Kronecker 2026-09-04 22:30:26 +08:00
yxx added 1 commit 2026-09-04 22:47:04 +08:00
- 新增 app/plot 包:白名单表达式解析(ast 无 eval)+ FunctionPlot 模型 + 静态 SVG 渲染
- HtmlExporter 的 function_plot 节点解析并内嵌 SVG,解析失败回退占位并转诊断
- 新增 test_plot.py(13 个测试)覆盖表达式安全、指令解析、SVG 输出与导出链路集成

Co-Authored-By: Claude Code <noreply@anthropic.com>
yxx changed title from Feat/export service to feat(export): 导出服务审阅修复 + 函数图像静态渲染 2026-09-04 22:49:22 +08:00
Owner

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

审阅提交:f49d124

本次发现 1 个 P1、2 个 P2,均已复现。

[P1] 浮点刻度生成可能陷入死循环

位置:backend/app/plot/render.py:46–52

复现配置:

domain: 10000000000000000, 10000000000000002
range: -1, 1
y=0

计算出的步长为 0.5,但受浮点精度限制,v + step == v,循环会持续追加相同刻度,消耗 CPU 和内存。渲染运行在线程中,取消导出无法中止该循环。

建议改为有数量上限的整数索引迭代,并校验步长是否能推动数值变化。

[P2] 表达式求值异常导致整篇笔记导出失败

位置:backend/app/plot/render.py:70–75,以及 _polyline 中的同类逻辑。

  • y=x^0.5:默认定义域包含负数,求值返回复数,math.isfinite 抛出 TypeError
  • y=sin():解析阶段未检查参数数量,求值时抛出 TypeError

异常未在绘图层处理,最终导致整个导出任务返回 EXPORT_RENDER_FAILED

建议校验函数参数数量,将非实数采样结果作为断点处理;无法渲染的图块应保留源码并附带诊断,不应阻断整篇导出。

[P2] 无效纵轴范围的自动回退未生效

位置:backend/app/plot/render.py:195–198

复现配置:

range: 1, 1
y=x

检测到无效范围后,再次调用 _compute_range,但原来的 plot.range 未清除,因此仍返回相同范围,随后触发 ZeroDivisionError,导致导出失败。

建议清除无效范围后重新采样计算,并校验范围端点为有限数值。

验证与合并要求

现有后端测试 490 项全部通过,但未覆盖上述边界场景,请补充回归测试。

此外,当前分支与 main 在以下文件存在合并冲突:

  • backend/app/contracts.py
  • backend/app/main.py

请修复上述问题、解决冲突,并在同步后的分支上重新运行测试,再提交复审。

## 审阅结论:请求修改,暂不建议合并 审阅提交:`f49d124` 本次发现 **1 个 P1、2 个 P2**,均已复现。 ### [P1] 浮点刻度生成可能陷入死循环 位置:`backend/app/plot/render.py:46–52` 复现配置: ```text domain: 10000000000000000, 10000000000000002 range: -1, 1 y=0 ``` 计算出的步长为 `0.5`,但受浮点精度限制,`v + step == v`,循环会持续追加相同刻度,消耗 CPU 和内存。渲染运行在线程中,取消导出无法中止该循环。 建议改为有数量上限的整数索引迭代,并校验步长是否能推动数值变化。 ### [P2] 表达式求值异常导致整篇笔记导出失败 位置:`backend/app/plot/render.py:70–75`,以及 `_polyline` 中的同类逻辑。 - `y=x^0.5`:默认定义域包含负数,求值返回复数,`math.isfinite` 抛出 `TypeError`。 - `y=sin()`:解析阶段未检查参数数量,求值时抛出 `TypeError`。 异常未在绘图层处理,最终导致整个导出任务返回 `EXPORT_RENDER_FAILED`。 建议校验函数参数数量,将非实数采样结果作为断点处理;无法渲染的图块应保留源码并附带诊断,不应阻断整篇导出。 ### [P2] 无效纵轴范围的自动回退未生效 位置:`backend/app/plot/render.py:195–198` 复现配置: ```text range: 1, 1 y=x ``` 检测到无效范围后,再次调用 `_compute_range`,但原来的 `plot.range` 未清除,因此仍返回相同范围,随后触发 `ZeroDivisionError`,导致导出失败。 建议清除无效范围后重新采样计算,并校验范围端点为有限数值。 ### 验证与合并要求 现有后端测试 **490 项全部通过**,但未覆盖上述边界场景,请补充回归测试。 此外,当前分支与 `main` 在以下文件存在合并冲突: - `backend/app/contracts.py` - `backend/app/main.py` 请修复上述问题、解决冲突,并在同步后的分支上重新运行测试,再提交复审。
Kronecker closed this pull request 2026-09-05 00:37:41 +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#20