Commit 3fd25a2b8b99bd6735353a445510f413b290eb6f

Authored by yibo
1 parent 3d4f4c99

h3-console: 给 /assets 静态文件加 immutable 缓存头

排查用户反馈的"每次刷新46个请求9237KB":参考图缩略图直接用原图(1.4~1.7MB/张),
StaticFiles 挂载又没设 Cache-Control,每次刷新都要重新下载同一批图。文件名带
uuid、内容写完不再变,可以放心标 immutable,一行子类化搞定重复下载;缩略图本身
还是原图尺寸没缩小,留了 ponytail 注释记这个还没做的部分。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJEL5jqdZR1QPToULRaTAz
CHANGELOG.md
... ... @@ -31,6 +31,17 @@ raylight 4卡跑 FSDP 集群时长期 100% GPU 利用率,这个 HTTP 请求要
31 31 `/data/models/minimax-h3/console/`,`systemctl restart h3-console-lan h3-console-public`
32 32 生效(两个 systemd 单元都是 `active`)。
33 33  
  34 +**同一天顺带发现的第二个问题**:用户在 Chrome Network 面板发现每次刷新页面要拉 46 个
  35 +请求、9237KB。排查发现列表页缩略图直接用参考图原图(`/assets/*`,上传时的原始分辨率,
  36 +实测单张 1.4~1.7MB),而且 `StaticFiles` 挂载没给任何 `Cache-Control`——每次刷新页面,
  37 +即使是同一批任务、同一张参考图,浏览器也要重新下载一遍。文件名是
  38 +`{uuid}__{原名}.ext`(见 `upload_asset`),内容写完不会再变,同一个 URL 永远对应同一份
  39 +字节,可以放心标 immutable。改法:`StaticFiles` 子类化,给 `/assets/*` 的 200 响应加
  40 +`Cache-Control: public, max-age=31536000, immutable`,一行改动,重复刷新直接不再发请求。
  41 +**没解决的部分**(标了 ponytail 留着):缩略图本身还是原图原尺寸,immutable 缓存只省得掉
  42 +"重复"下载,省不掉页面第一次打开时的下载量——真影响到人再加 PIL 服务端生成小图(`server.py`
  43 +已经在用 PIL 校验上传,加缩略图生成不用新依赖)。
  44 +
34 45 ## 2026-09-03 — raylight 修复中文参考文件名 UnicodeEncodeError + 排队优先级/插队 + 重启不再清空队列
35 46  
36 47 **触发**:任务 `f2de718e1e30`(ref2va,带一个中文文件名的参考视频)秒失败,报
... ...
h3-console/server.py
... ... @@ -181,7 +181,20 @@ def _public_base() -&gt; str:
181 181 "这个地址会被服务器上的 api.py worker 反向拉取,见 README。")
182 182  
183 183  
184   -app.mount(f"{BASE_PATH}/assets", StaticFiles(directory=str(ASSETS)), name="assets")
  184 +# 2026-09-09:文件名是 {uuid}__{原名}.ext(见 upload_asset),内容写完就不会再变,
  185 +# 同一个 URL 永远对应同一份字节——可以放心标 immutable。之前没有 Cache-Control,
  186 +# 列表页每条任务都直接用原图当缩略图,刷新一次页面这些图就要重新传一遍。
  187 +# ponytail: 缩略图本身还是原图原尺寸(没有服务端生成小图),immutable 缓存能省掉
  188 +# "重复"下载,省不掉"第一次"下载太大——参考图动辄1-2MB,真影响到人再加 PIL 缩略图。
  189 +class CachedStaticFiles(StaticFiles):
  190 + async def get_response(self, path, scope):
  191 + response = await super().get_response(path, scope)
  192 + if response.status_code == 200:
  193 + response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
  194 + return response
  195 +
  196 +
  197 +app.mount(f"{BASE_PATH}/assets", CachedStaticFiles(directory=str(ASSETS)), name="assets")
185 198  
186 199  
187 200 # ---------------- 生成任务:console 自己是排队的主人,gateway.py 只是"当前有空的执行者" ----------------
... ...