mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-21 15:55:50 +00:00
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>
This commit is contained in:
@@ -1,41 +1,66 @@
|
||||
# cloud-artifacts
|
||||
|
||||
独立的 Cloudflare Worker + R2 服务,用于托管 HTML artifact。
|
||||
> **生产出口**:`https://cloud-artifacts.claude-code-best.win`
|
||||
>
|
||||
> 服务端(CLI / RCS 后台)通过单一 bearer token 上传 HTML,得到一个公开可访问的 URL。
|
||||
> 文件到期由 R2 lifecycle rule 自动删除(默认 7 天,最长 30 天)。
|
||||
|
||||
服务端(CLI / RCS 后台)通过单一 bearer token 上传 HTML,得到一个公开可访问的 CDN URL。**POST 上传和 GET 访问都走 Worker**,URL 形如 `https://<worker-domain>/<ttl-prefix>/<id>.html`,文件到期由 R2 lifecycle rule 自动删除。
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
# 上传一份 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--> Worker (鉴权 + 校验 + R2 put) --返回 {id, url, expiresAt}
|
||||
客户端 --GET html-------> Worker (R2 get → text/html) --直出 html,带 Cache-Control: max-age=86400
|
||||
↑
|
||||
R2 lifecycle rule: 7d/ 删 7 天,30d/ 删 30 天
|
||||
┌──────────────────────────┐
|
||||
客户端 --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 │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
- Worker 处理 `POST /upload` 和 `GET /<prefix>/<id>.html`
|
||||
- R2 key 形如 `7d/<id>.html` 或 `30d/<id>.html`
|
||||
- URL 形如 `https://<worker-domain>/7d/<id>.html`,hash 本身是秘密(21 字符 nanoId,126 bit 熵)
|
||||
- **POST /upload**:Bearer 鉴权 → text/html 校验 → 10MB 上限 → ttl ∈ {7,30} → R2 put
|
||||
- **GET /<7d\|30d>/<id>.html**:Worker 从 R2 读 → 返回 `text/html; charset=utf-8` + `Cache-Control: public, max-age=86400`
|
||||
- **TTL**:R2 prefix + lifecycle rule 实现,Worker 不参与过期处理(零额外代码)
|
||||
- **覆盖**:指定 `?hash=` 时,先删 `7d/<hash>.html` 和 `30d/<hash>.html` 旧 key,再写新 key
|
||||
- **ID**:默认 `nanoid(21)`(126 bit 熵),可指定 `?hash=<custom-id>`
|
||||
|
||||
## 部署
|
||||
## 为什么套一层 Deno Deploy
|
||||
|
||||
前置:本机已 `npx wrangler login` 登录目标 Cloudflare 账号,且账号下有一个用于 Worker custom domain 的域名(zone)。
|
||||
国内直连 Cloudflare Workers 边缘节点延迟高、丢包严重(DNS 污染 + 路由问题)。在 `cloud-artifacts.claude-code-best.win` 上套 Deno Deploy 边缘代理后:
|
||||
|
||||
```bash
|
||||
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
|
||||
# 填入你的 domain(如 artifacts.example.com)
|
||||
|
||||
# 改 wrangler.toml 中 [vars] PUBLIC_URL 为上一步的 domain(如 https://artifacts.example.com)
|
||||
|
||||
bun run deploy
|
||||
```
|
||||
- 国内访问延迟显著降低(Deno Deploy 在国内可达性好)
|
||||
- POST/GET body 完整透传
|
||||
- **副作用**:Deno Deploy 代理会把上游 HTTP status code 抹平为 200(但 body 内的 `{error: ...}` 字段完整保留)。客户端若依赖 status code 判断错误类型,应改为解析 body 中的 `error` 字段。直连 Worker 自身(如 `*.workers.dev`)时 status code 正常透传。
|
||||
|
||||
## API
|
||||
|
||||
@@ -54,14 +79,14 @@ bun run deploy
|
||||
```json
|
||||
{
|
||||
"id": "V1StGXR8_Z5jdHi6B-myT",
|
||||
"url": "https://<worker-domain>/7d/V1StGXR8_Z5jdHi6B-myT.html",
|
||||
"url": "https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html",
|
||||
"expiresAt": "2026-06-27T10:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
错误(统一 `{ "error": "<code>" }`):
|
||||
错误(统一 `{ "error": "<code>" }`,状态码见下):
|
||||
|
||||
| 状态码 | error code | 触发条件 |
|
||||
| 状态码(直连) | error code | 触发条件 |
|
||||
|--------|------------|----------|
|
||||
| 400 | `invalid_ttl` | `ttl` 非 7 或 30 |
|
||||
| 400 | `invalid_hash` | `hash` 不匹配 `^[A-Za-z0-9_-]{1,128}$` |
|
||||
@@ -70,31 +95,29 @@ bun run deploy
|
||||
| 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`
|
||||
|
||||
由 Worker 处理:解析路径 → R2 get → 返回 `text/html; charset=utf-8` + `Cache-Control: public, max-age=86400`。任何人拿到 URL 都可访问,hash 即秘密。
|
||||
|
||||
`ttl-prefix` 只能是 `7d` 或 `30d`(其他路径返回 404)。
|
||||
`ttl-prefix` 只能是 `7d` 或 `30d`(其他路径返回 404/not_found)。返回 `text/html; charset=utf-8` + `Cache-Control: public, max-age=86400`。任何人拿到 URL 都可访问,hash 即秘密。
|
||||
|
||||
## 示例
|
||||
|
||||
```bash
|
||||
# 上传(默认随机 ID + 7 天)
|
||||
echo '<h1>hello</h1>' > /tmp/t.html
|
||||
curl -X POST "https://<worker-domain>/upload" \
|
||||
# 默认随机 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
|
||||
# -> {"id":"V1StGXR8_Z5jdHi6B-myT","url":"https://<worker-domain>/7d/V1StGXR8_Z5jdHi6B-myT.html","expiresAt":"..."}
|
||||
|
||||
# 自定义 hash + 30 天(再次上传同 hash 覆盖)
|
||||
curl -X POST "https://<worker-domain>/upload?ttl=30&hash=my-report" \
|
||||
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
|
||||
|
||||
# 访问(公开 URL,走 Worker → R2)
|
||||
curl "https://<worker-domain>/7d/V1StGXR8_Z5jdHi6B-myT.html"
|
||||
# 访问
|
||||
curl "https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B-myT.html"
|
||||
```
|
||||
|
||||
## 覆盖语义
|
||||
@@ -108,14 +131,34 @@ curl "https://<worker-domain>/7d/V1StGXR8_Z5jdHi6B-myT.html"
|
||||
|
||||
不指定 `?hash=` 时:用 `nanoid(21)` 随机 ID,几乎不可能碰撞,不做碰撞检查。
|
||||
|
||||
## TTL 落地
|
||||
## 部署
|
||||
|
||||
R2 不支持 per-object TTL,本服务用 prefix + R2 lifecycle rule 模拟:
|
||||
前置:本机已 `npx wrangler login` 登录目标 Cloudflare 账号。Deno Deploy 代理层由部署者另配(CNAME `cloud-artifacts.<your-domain>` → `alias.deno.net`,并在 Deno Deploy 项目里把上游设为 `https://<worker>.<account>.workers.dev`)。
|
||||
|
||||
- bucket 配两条 rule:prefix `7d/` 删 7 天前对象、prefix `30d/` 删 30 天前对象
|
||||
- 由 `scripts/setup.sh` 调 `wrangler r2 bucket lifecycle add` 自动配置
|
||||
- Worker 完全不参与过期处理,零额外代码
|
||||
- 因此 `?ttl=` 只能取 `7` 或 `30`,对应这两个 prefix(其他值会写到无 lifecycle 的 prefix → 永久存储,故拒绝)
|
||||
```bash
|
||||
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]`)。
|
||||
|
||||
```bash
|
||||
WORKER_URL=https://cloud-artifacts.claude-code-best.win \
|
||||
TOKEN=<your-token> \
|
||||
bash scripts/test.sh
|
||||
```
|
||||
|
||||
## 本地开发
|
||||
|
||||
@@ -130,15 +173,24 @@ curl -X POST "http://localhost:8787/upload" \
|
||||
--data-binary @/tmp/t.html
|
||||
```
|
||||
|
||||
## 测试
|
||||
## 安全注意事项
|
||||
|
||||
`scripts/test.sh` 覆盖 7 个错误用例 + 3 个成功用例 + R2 写入验证:
|
||||
- **TOKEN 是上传侧唯一鉴权**:值泄露后任何人可上传/覆盖。生产应使用 ≥32 字符的随机串,定期轮换(`wrangler secret put TOKEN` 即时生效,无需 redeploy)。
|
||||
- **GET 完全公开**:URL 形如 `/<ttl>/<id>.html`,hash(21 字符 nanoId)即唯一秘密。不要把 URL 贴到公开频道再期望它"私密"。
|
||||
- **覆盖即写**:知道 hash 的任何持 token 者都能覆盖该 ID 的内容。若需要"创建后不可改"语义,应在客户端自行约束(不传 `?hash=`)。
|
||||
- **不校验 HTML 内容**:上传的 html 会被原样返回,浏览器渲染时会执行其中的 `<script>`。本服务定位是"托管自己产出的 html",不要作为任意用户上传入口。
|
||||
- **TTL 上限 30 天**:lifecycle rule 是 prefix 级全局规则,所有对象最多保留 30 天,无法延长。
|
||||
|
||||
```bash
|
||||
WORKER_URL=https://<worker-domain> \
|
||||
TOKEN=<your-token> \
|
||||
bash scripts/test.sh
|
||||
```
|
||||
## 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 Insights(RUM),不影响内容渲染。要纯净响应:dashboard → Workers & Pages → cloud-artifacts → 关 Web Analytics |
|
||||
| 上传 413 但文件不到 10MB | 检查 `Content-Length` header 是否被中间层改写;Worker 同时按 `Content-Length` 和 `arrayBuffer().byteLength` 双重校验 |
|
||||
| `?ttl=14` 返 400 | 设计如此,只允许 7 或 30(对应 R2 lifecycle prefix) |
|
||||
| `wrangler secret list` 看到 TOKEN 但上传 401 | token 值不一致。重新 `wrangler secret put TOKEN` 设正确值 |
|
||||
|
||||
## 依赖
|
||||
|
||||
|
||||
Reference in New Issue
Block a user