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

203 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cloud-artifacts
> **生产出口**`https://cloud-artifacts.claude-code-best.win`
>
> 服务端CLI / RCS 后台)通过单一 bearer token 上传 HTML得到一个公开可访问的 URL。
> 文件到期由 R2 lifecycle rule 自动删除(默认 7 天,最长 30 天)。
## 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----▶│ 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 /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
国内直连 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
```json
{
"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` 只能是 `7d``30d`(其他路径返回 404/not_found。返回 `text/html; charset=utf-8` + `Cache-Control: public, max-age=86400`。任何人拿到 URL 都可访问hash 即秘密。
## 示例
```bash
# 默认随机 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>.html``30d/<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`)。
```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
```
## 本地开发
```bash
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>.html`hash21 字符 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-Length``arrayBuffer().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.json``workspaces: ["packages/*", ...]` 自动识别本包,但主 CLI 不会 import 它。