fix(web): tolerant polling, objectURL cleanup, throttled pointermove

- home/detail video pollers no longer clearInterval on a single transient error
  (give up only after 10 consecutive failures), matching the agent page
- agent page creates preview objectURLs inside useEffect so each has a matching
  revoke under strict-mode double-invocation
- login pointermove throttled via rAF and skipped on coarse pointers
- source-analysis.html: changelog entry for this hardening pass

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 02:04:59 +08:00
parent b56d5177e5
commit 6201ee9a7d
5 changed files with 54 additions and 11 deletions

View File

@@ -1310,6 +1310,22 @@ ProductRefStateItem {
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-30 · 稳定性 / 安全加固子进程超时、SSRF、并发锁、上传持久化、轮询容错</h3>
<span class="tag blue">API</span>
<span class="tag violet">Canvas</span>
<span class="tag orange">Bugfix</span>
<span class="tag green">Security</span>
</header>
<div class="body">
<p><strong>问题:</strong>只读审查发现一批可复现隐患:<code>run()</code> 子进程yt-dlp/ffmpeg/ffprobe无超时会让 job 永挂 <code>downloading</code> 并泄漏进程;<code>create_job</code> 的源链接未校验,可被 <code>file://</code>/内网地址做 SSRF 与本地文件读取;视频队列多线程并发改同一 job 状态无锁互相覆盖;画布上传视频用 <code>blob:</code> URL重载后视频丢失首页/详情页轮询一次网络抖动就永久停。</p>
<p><strong>改动(后端 <code>api/main.py</code> / <code>api/db.py</code></strong><code>run()</code> 增加 timeout下载 <code>DOWNLOAD_TIMEOUT_SECONDS</code> 默认 600s其余 300s超时 kill 并标 <code>failed</code>;新增 <code>validate_source_url()</code>——只允许 http(s)、拒绝私有/环回/链路本地 IP、域名走 <code>SOURCE_URL_ALLOWED_HOSTS</code> 白名单(默认主流短视频平台);新增 per-job <code>RLock</code><code>save_state</code>/<code>update</code>/<code>update_generated_video</code> 及 retry 的 check-and-set 全部在锁内;<code>db.py</code> 改用 <code>psycopg_pool</code> 连接池、写失败由 <code>logging.error</code> 暴露;只读 GET 媒体路由改用不创建目录的 <code>job_path()</code>;多处 <code>Image.open()</code><code>with</code> 防 fd 泄漏。</p>
<p><strong>改动(前端画布 <code>web/canvas-app/src/</code></strong><code>VideoNode.vue</code> 上传改走后端 <code>/jobs/upload</code> 拿稳定 URL新增 <code>uploadCanvasVideo</code><code>cleanNodeForStorage</code> 同时剥离 <code>blob:</code><code>useCachedMediaUrl.js</code> 用真实 <code>blob.size</code> 统计缓存(修复 chunked 视频 size=0 让 LRU 失效、catch 路径补 token 竞态校验;<code>useApi.js</code> 读参考图补 <code>credentials</code>、移除与 Canvas 层重复的节点级视频轮询;<code>request.js</code> timeout 改 60s + <code>withCredentials</code>;删除 <code>api/video.js</code> 中忽略 taskId 的死代码。</p>
<p><strong>改动Next 首页 <code>web/app/</code></strong>首页/详情页视频轮询改为容错(连续失败 10 次才停);<code>agent</code> 页预览 ObjectURL 创建移入 <code>useEffect</code> 确保配对 revoke登录页 <code>pointermove</code> 用 rAF 节流并跳过 coarse 指针。飞书自动跳转行为按确认保留不动。</p>
<p><strong>影响 / 验证:</strong>新增后端依赖 <code>psycopg-pool</code>(已写入 <code>api/requirements.txt</code>,未装时自动回退按调用建连);新增可选 env<code>DOWNLOAD_TIMEOUT_SECONDS</code><code>SOURCE_URL_ALLOWED_HOSTS</code><code>DB_POOL_MAX_SIZE</code>。本地 <code>py_compile</code><code>pnpm build</code>canvas + next通过。描述需求时源链接受白名单约束新平台需加 <code>SOURCE_URL_ALLOWED_HOSTS</code>);画布上传视频现在持久化为后端 <code>/api/jobs/...</code> 地址。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-27 · 图片 API 改为运行时可配置并接入 Ark Seedream</h3>