Files
claude-code/packages/cloud-artifacts/README.md
claude-code-best 1ac7d57904 docs: 完善 cloud-artifacts 文档并统一出口域名
- CLAUDE.md 加 cloud-artifacts 到 Workspace Packages 表和新增 HTML Artifact Hosting 段落
- docs.json 注册 cloud-artifacts 到运行模式 group
- README 加 Quickstart、架构图(含 Deno Deploy 代理层)、Security Considerations、Troubleshooting
- 统一出口域名为 https://cloud-artifacts.claude-code-best.win(wrangler.toml PUBLIC_URL、test.sh 默认 WORKER_URL、所有文档示例)
- test.sh expect() 加 [via body] fallback:经 Deno Deploy 代理(status 抹平为 200)时按 body 的 error 字段断言

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
2026-06-20 13:58:17 +08:00

9.8 KiB
Raw Blame History

cloud-artifacts

生产出口https://cloud-artifacts.claude-code-best.win

服务端CLI / RCS 后台)通过单一 bearer token 上传 HTML得到一个公开可访问的 URL。 文件到期由 R2 lifecycle rule 自动删除(默认 7 天,最长 30 天)。

Quickstart

# 上传一份 html默认随机 ID + 7 天 TTL
echo '<h1>hello</h1>' > /tmp/t.html
curl -X POST "https://cloud-artifacts.claude-code-best.win/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/html" \
  --data-binary @/tmp/t.html
# {"id":"V1StGXR8_Z5jdHi6B-myT",
#  "url":"https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html",
#  "expiresAt":"2026-06-27T10:00:00.000Z"}

# 任何人拿到 url 都能访问
curl "https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html"

架构

                           ┌──────────────────────────┐
客户端  --POST /upload----▶│  Deno Deploy 边缘代理     │
                          │  cloud-artifacts.ccb.win  │
                          └────────────┬─────────────┘
                                       │ 透传
                                       ▼
                          ┌──────────────────────────┐
                          │  Cloudflare Worker        │
                          │  - 鉴权 + MIME + 大小校验  │
                          │  - ttl∈{7,30} + hash 校验 │
                          │  - R2 put / R2 get        │
                          └────────────┬─────────────┘
                                       │
                                       ▼
                          ┌──────────────────────────┐
                          │  R2 bucket                │
                          │  key: <7d|30d>/<id>.html  │
                          │  lifecycle:               │
                          │    7d/  -> expire 7 days  │
                          │    30d/ -> expire 30 days │
                          └──────────────────────────┘
  • POST /uploadBearer 鉴权 → text/html 校验 → 10MB 上限 → ttl ∈ {7,30} → R2 put
  • GET /<7d|30d>/.htmlWorker 从 R2 读 → 返回 text/html; charset=utf-8 + Cache-Control: public, max-age=86400
  • TTLR2 prefix + lifecycle rule 实现Worker 不参与过期处理(零额外代码)
  • 覆盖:指定 ?hash= 时,先删 7d/<hash>.html30d/<hash>.html 旧 key再写新 key
  • ID:默认 nanoid(21)126 bit 熵),可指定 ?hash=<custom-id>

为什么套一层 Deno Deploy

国内直连 Cloudflare Workers 边缘节点延迟高、丢包严重DNS 污染 + 路由问题)。在 cloud-artifacts.claude-code-best.win 上套 Deno Deploy 边缘代理后:

  • 国内访问延迟显著降低Deno Deploy 在国内可达性好)
  • POST/GET body 完整透传
  • 副作用Deno Deploy 代理会把上游 HTTP status code 抹平为 200但 body 内的 {error: ...} 字段完整保留)。客户端若依赖 status code 判断错误类型,应改为解析 body 中的 error 字段。直连 Worker 自身(如 *.workers.dev)时 status code 正常透传。

API

POST /upload

Header / Query 必填 说明
Authorization: Bearer <TOKEN> 与 Worker secret TOKEN 完全相等
Content-Type: text/html 不接受其他类型
?ttl=7|30 默认 7只允许 7 或 30(与 R2 lifecycle prefix 对应)
?hash=<custom-id> 自定义 ID校验 ^[A-Za-z0-9_-]{1,128}$;指定时覆盖同 ID 旧版本
body 原始 HTML--data-binary @file.html≤10MB

成功 200

{
  "id": "V1StGXR8_Z5jdHi6B-myT",
  "url": "https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html",
  "expiresAt": "2026-06-27T10:00:00.000Z"
}

错误(统一 { "error": "<code>" },状态码见下):

状态码(直连) error code 触发条件
400 invalid_ttl ttl 非 7 或 30
400 invalid_hash hash 不匹配 ^[A-Za-z0-9_-]{1,128}$
401 unauthorized 缺 Authorization / token 不匹配
404 not_found /upload 路径或 GET 路径不匹配 /<7d|30d>/<id>.html
413 payload_too_large body > 10MB
415 unsupported_media_type Content-Type 非 text/html

经 Deno Deploy 代理时:以上所有错误状态码统一返回 200,但 body 仍是上表中的 {error: ...} JSON。客户端解析逻辑应以 body 的 error 字段为准。

GET /<ttl-prefix>/<id>.html

ttl-prefix 只能是 7d30d(其他路径返回 404/not_found。返回 text/html; charset=utf-8 + Cache-Control: public, max-age=86400。任何人拿到 URL 都可访问hash 即秘密。

示例

# 默认随机 ID + 7 天
curl -X POST "https://cloud-artifacts.claude-code-best.win/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/html" \
  --data-binary @/tmp/t.html

# 自定义 hash + 30 天(再次上传同 hash 覆盖)
curl -X POST "https://cloud-artifacts.claude-code-best.win/upload?ttl=30&hash=my-report" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/html" \
  --data-binary @/tmp/report.html

# 访问
curl "https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html"

覆盖语义

指定 ?hash= 时:

  1. 校验 hash 字符集(^[A-Za-z0-9_-]{1,128}$
  2. 删除 7d/<hash>.html30d/<hash>.html 两个 keyR2 delete 不存在的 key 不报错,零成本)
  3. ?ttl= 写入新 key
  4. 返回新的 expiresAt

不指定 ?hash= 时:用 nanoid(21) 随机 ID几乎不可能碰撞不做碰撞检查。

部署

前置:本机已 npx wrangler login 登录目标 Cloudflare 账号。Deno Deploy 代理层由部署者另配CNAME cloud-artifacts.<your-domain>alias.deno.net,并在 Deno Deploy 项目里把上游设为 https://<worker>.<account>.workers.dev)。

cd packages/cloud-artifacts
bun install                          # 在 monorepo 根执行也行workspace 自动识别)

cp .dev.vars.example .dev.vars       # 填本地 dev 用的 TOKEN仅 wrangler dev 读)
bun run setup                        # 创建 bucket + 加 lifecycle rule + 设生产 TOKEN secret

# 绑 Worker custom domain如要在 Cloudflare 直连域名上访问):
#   Dashboard: Workers & Pages > cloud-artifacts > Settings > Domains & Routes > Add > Custom Domain

# 改 wrangler.toml 中 [vars] PUBLIC_URL 为对外出口域名(生产用 https://cloud-artifacts.claude-code-best.win

bun run deploy

测试

scripts/test.sh 覆盖 7 个错误用例 + 3 个成功用例 + R2 写入验证。支持双模式:直连 Worker 时按 HTTP status code 断言;经 Deno Deploy 代理status 抹平为 200时自动按 body 的 error 字段断言(标记 [via body])。

WORKER_URL=https://cloud-artifacts.claude-code-best.win \
TOKEN=<your-token> \
bash scripts/test.sh

本地开发

cp .dev.vars.example .dev.vars
# 编辑 .dev.vars 填 TOKEN

bun run dev                          # wrangler dev启动本地 Miniflare + 本地 R2 模拟
curl -X POST "http://localhost:8787/upload" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: text/html" \
  --data-binary @/tmp/t.html

安全注意事项

  • TOKEN 是上传侧唯一鉴权:值泄露后任何人可上传/覆盖。生产应使用 ≥32 字符的随机串,定期轮换(wrangler secret put TOKEN 即时生效,无需 redeploy
  • GET 完全公开URL 形如 /<ttl>/<id>.htmlhash21 字符 nanoId即唯一秘密。不要把 URL 贴到公开频道再期望它"私密"。
  • 覆盖即写:知道 hash 的任何持 token 者都能覆盖该 ID 的内容。若需要"创建后不可改"语义,应在客户端自行约束(不传 ?hash=)。
  • 不校验 HTML 内容:上传的 html 会被原样返回,浏览器渲染时会执行其中的 <script>。本服务定位是"托管自己产出的 html",不要作为任意用户上传入口。
  • TTL 上限 30 天lifecycle rule 是 prefix 级全局规则,所有对象最多保留 30 天,无法延长。

Troubleshooting

现象 原因 / 处理
所有请求返 HTTP 200 但业务出错 经 Deno Deploy 代理时正常现象,看 body 的 error 字段判断真实状态
curl*.workers.dev 超时 国内 DNS 污染 + 路由问题,走 cloud-artifacts.claude-code-best.win 出口或挂代理
响应 html 多一段 <a href="/cdn-cgi/content..."><script> Cloudflare 默认注入的 Browser InsightsRUM不影响内容渲染。要纯净响应dashboard → Workers & Pages → cloud-artifacts → 关 Web Analytics
上传 413 但文件不到 10MB 检查 Content-Length header 是否被中间层改写Worker 同时按 Content-LengtharrayBuffer().byteLength 双重校验
?ttl=14 返 400 设计如此,只允许 7 或 30对应 R2 lifecycle prefix
wrangler secret list 看到 TOKEN 但上传 401 token 值不一致。重新 wrangler secret put TOKEN 设正确值

依赖

  • wrangler ^4 — Cloudflare Workers CLI
  • nanoid ^5 — ID 生成(纯 ESMWorker 兼容)

不被主 CLI 引用

这是独立 Cloudflare Worker 服务,类似 packages/remote-control-server/ 的定位。Monorepo 根 package.jsonworkspaces: ["packages/*", ...] 自动识别本包,但主 CLI 不会 import 它。