# cf-r2-manager **Repository Path**: hais/cf-r2-manager ## Basic Information - **Project Name**: cf-r2-manager - **Description**: 查看 cloudflare 里 r2 中的文件。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-19 - **Last Updated**: 2026-09-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # r2-lens 一个 Cloudflare R2 图片时间线。填入 Cloudflare 账户邮箱和 Global API Key,它会列出你账号下的所有 R2 存储桶,选一个进入,里面的图片就会按日期从新到旧铺成一整面缩略图墙。 TypeScript + React + Vite。两种用法:**本地运行**,或**部署到 Cloudflare Workers**。 --- ## 目录 - [方式一:本地运行](#方式一本地运行) - [方式二:部署到 Cloudflare Workers](#方式二部署到-cloudflare-workers) - [获取凭据](#获取凭据) - [它是怎么工作的](#它是怎么工作的) - [安全说明](#安全说明) - [目录结构](#目录结构) --- ## 方式一:本地运行 适合自己用。不需要 Cloudflare 账号授权,不需要部署。 ```bash pnpm install pnpm dev # http://localhost:5173 ``` 浏览器打开 `http://localhost:5173`,然后: 1. 填 **账户邮箱** 和 **Global API Key** 2. 点「连接并列出存储桶」 3. 在卡片列表里点任意一个存储桶,进入它的图片时间线 本地运行不需要设置任何口令,也不需要给存储桶配 CORS 策略。 **其它命令** ```bash pnpm build # 类型检查 + 构建到 dist/ pnpm preview # 预览构建产物(不含 /api,仅看界面) pnpm typecheck # 只做类型检查 ``` > `pnpm preview` 只有静态文件,**代理不在里面**,所以连不上 R2。要完整功能请用 `pnpm dev`。 --- ## 方式二:部署到 Cloudflare Workers 适合想在任意设备上访问。Worker 同时提供静态页面和 `/api`,所以**页面与接口同源**,不需要另外配 Pages。 ### 1. 首次准备 ```bash pnpm install cd worker && pnpm install --ignore-workspace && cd .. npx wrangler login # 浏览器里授权你的 Cloudflare 账号 ``` > `pnpm install` 若提示 `ERR_PNPM_IGNORED_BUILDS`,可以忽略 —— 那只是 pnpm 的构建脚本审批提示,wrangler 需要的二进制已经装好了。想消除提示可在 `worker/` 下跑 `pnpm approve-builds`。 ### 2. 先干跑确认 ```bash pnpm deploy:dry ``` 它会做类型检查 → 构建前端 → 让 Wrangler 校验打包结果(不上传)。看到类似输出就说明没问题: ``` Total Upload: 27.55 KiB / gzip: 8.10 KiB ✨ Read 16 files from the assets directory .../dist env.ASSETS Assets ``` ### 3. 正式部署 ```bash pnpm deploy ``` 完成后会输出你的访问地址,形如 `https://r2-lens.<你的子域>.workers.dev`,直接打开即可使用。 `pnpm deploy` 会**自动先构建前端再部署**,不需要你手动跑 `pnpm build`。 ### 4. 设置访问口令(强烈建议) Worker 在公网上,谁知道域名谁就能用。因为应用是让每个访客填**自己的**凭据,陌生人用你的部署填的是他们自己的密钥 —— 所以这**不构成你的密钥泄露**;但你的域名会变成一个通往 `api.cloudflare.com` 的开放中继。 ```bash cd worker npx wrangler secret put ACCESS_TOKEN ``` 设好之后,连接页面会出现「部署在 Worker 上?填写访问口令」这一栏,把同一个口令填进去即可。 **不设就完全开放**;本地运行从来不需要。 ### 5. 改 Worker 名字(可选) `r2-lens` 这个名字在全网唯一。如果部署时报「名字已被占用」,改 `worker/wrangler.jsonc` 里的 `"name"` 字段再重新部署。 ### 更新已部署的版本 改了代码之后重新跑一次即可: ```bash pnpm deploy ``` --- ## 获取凭据 | 需要什么 | 在哪里拿 | | --- | --- | | **账户邮箱** | 你登录 Cloudflare 用的邮箱 | | **Global API Key** | 控制台 → **我的个人资料** → **API 令牌** → **API Keys** → Global API Key → **查看** | Global API Key 需要邮箱已验证,且长度通常为 37 位字符。 --- ## 它是怎么工作的 ### 为什么需要一个服务端 浏览器自己做不到这两件事: 1. **Cloudflare 的 REST API 完全不返回 CORS 响应头。** 实测:`api.cloudflare.com` 对带 `Origin` 的请求不返回任何 `Access-Control-*`。浏览器无法读取它的响应,所以列账户、列存储桶必须由服务端发起。 2. **R2 的对象读取需要签名**,而邮箱 + Global Key 无法直接派生出 S3 密钥。 因此本地跑一个代理、部署时跑一个 Worker。两者同源,也意味着**不需要给存储桶配任何 CORS 策略**。 ### 凭据流转 ``` 邮箱 + Global API Key │ ├─▶ GET /accounts 列出可访问的账户 ├─▶ GET /accounts/{id}/r2/buckets 列出每个账户下的存储桶 │ └─▶ POST /accounts/{id}/tokens 为「选中的那个桶」签发一个 · 只读(Object Read) · 限定单桶 · 一小时有效 的临时 R2 令牌 │ ▼ 用临时令牌签名所有对象请求 ``` 关键点:**Global API Key 从不用于读取图片**。它只用来列账户、列存储桶,以及换一个受限的只读令牌。之后每一次列对象、取图片,用的都是那个只能读一个桶、一小时后就失效的令牌。 Cloudflare 文档给出的派生关系是:S3 的 Access Key ID 就是令牌的 `id`,Secret Access Key 是令牌 `value` 的 SHA-256(十六进制)。 ### 读取图片 ``` 浏览器 ──▶ /api/r2?accountId=…&bucket=…&key=… │ └─▶ 服务端用临时令牌签 SigV4,流式转发 R2 的响应 ``` 不缓冲响应体,图片边收边转发,大图不会占满内存。 ### 两个刻意的设计决定 **按天分组使用 UTC,不是浏览器本地时区。** R2 的 `LastModified` 就是 UTC。一个在 `21:04Z` 写入的图片,如果按本地时间分组,对格林威治以东的用户会被归到「第二天」—— 时间线会随访问者所处时区而变。按 UTC 分组则到处一致。 **列出对象时不传 `delimiter`。** 带分隔符的列表会把子目录里的图片折叠进 `CommonPrefixes`,它们就永远不会出现在时间线上。扁平列表才能得到真实的时间顺序;排序在客户端完成,同时在后台继续翻页。 ### 本地与 Worker 的关系 两者共用同一份实现,只有适配层不同: | | 本地 | Worker | | --- | --- | --- | | 适配层 | `server/proxy.ts`(Node req/res ↔ Request/Response) | `worker/src/index.ts` | | 业务逻辑 | `shared/api.ts` + `shared/core.ts`(**共用**) | 同左 | | 静态资源 | Vite 开发服务器 | `dist/` 资源绑定 | | 访问限制 | 只接受 `127.0.0.1` | 可选的 `ACCESS_TOKEN` | 共用实现意味着部署版的行为不会和本地版本不一致。 --- ## 功能 - 自动列出账号下所有存储桶,点选进入 - 按天分组、从新到旧的缩略图墙,左侧日期栏滚动时吸顶 - 按键名搜索、按目录筛选、按名称/大小排序 - 「仅图片」开关 —— 非图片对象显示为带标签的瓦片,而不是破图 - 可调缩略图尺寸(小 / 中 / 大 / 超大) - 大图预览:方向键切换、底部胶片条、复制键名、下载、新标签页打开 - 滚动时分页加载;骨架屏与真实布局同尺寸,不会跳动 - 明确的空状态、错误状态与各类失败提示 --- ## 安全说明 - **Global API Key 权限等同你本人**,能改 DNS、删域名、动账单。这是本工具固有的一点,值得你权衡。 - **主密钥不用于读图片。** 图片请求只用一个限单桶、只读、一小时有效的临时令牌。 - **默认不落盘。** 密钥除非你勾选「在此设备上记住密钥」,否则只存在于内存中,刷新页面就要重填。 - **可随时吊销。** 点「断开连接」会让服务端调用 Cloudflare 接口删除它创建的那些令牌。 - **本地版只接受本机请求**(`127.0.0.1` / `::1`),其他来源一律 403,避免变成局域网内的开放中继。 - **部署版无状态。** Worker 不写 KV、不写 D1、不绑 R2,**不存任何数据**。凭据随请求而来、用完即弃;派生令牌只在 isolate 内存里,一小时到期,冷启动即消失。 - **部署版建议设 `ACCESS_TOKEN`**,否则是一个公网可达的中继。 --- ## 目录结构 ``` shared/ core.ts Cloudflare API 访问、临时令牌派生、S3 签名工具(双端共用) api.ts /api 路由(双端共用) server/ proxy.ts 本地适配层:Node req/res ↔ Request/Response,回环限制 worker/ src/index.ts Worker 入口:静态资源 + /api,可选 ACCESS_TOKEN wrangler.jsonc 资源绑定与 run_worker_first 配置 src/ lib/ cloudflare.ts 访问 /api 的客户端 r2.ts R2 S3 客户端(经代理) timeline.ts 筛选、排序、按天分组与显示格式化 thumbnails.ts 有上限的 LRU blob URL 缓存 config.ts 凭据与会话的选填持久化 types.ts 共享领域类型 hooks/ useConnection.ts 三步骤连接流程(凭据 → 选桶 → 已连接) useListing.ts 分页列表,带过期响应保护 components/ 凭据表单、存储桶选择、工具栏、时间线、瓦片、大图预览 scripts/ deploy.mjs 构建前端 + 部署 Worker(一条命令) ``` ## 常见问题 **部署后打开页面,连接时报「无法连接本地代理」** 说明 `/api` 没有正确命中 Worker。确认 `pnpm deploy` 成功、且 `worker/wrangler.jsonc` 里的 `run_worker_first` 包含 `/api/*`。 **提示「访问口令不正确」** 在 Worker 上设了 `ACCESS_TOKEN`,但页面里没填或填错了。展开连接页面的「部署在 Worker 上?」填写同一个口令。 **提示「邮箱或 Global API Key 不正确」** 密钥或邮箱有误。注意 Global API Key 与 API Token 是两种不同的东西,这里要的是前者。 **图片加载失败** 多半是临时令牌权限不足。检查该桶是否为 jurisdiction 桶(EU/US/FedRAMP),这类桶需要走不同端点 —— 代码已处理,但如果报权限错误,可以看 Worker 日志:`cd worker && npx wrangler tail`。