基于 autoPicCdn 改造为 Cloudflare Pages Functions(TypeScript),无需 PHP / MySQL 即可运行。
- 支持 GitHub 和 Gitee 双平台上传
- 多 CDN 线路切换(仅 GitHub):jsDelivr / Gcore / Statically / GitHub Raw / GHProxy 镜像 / GitHub 加速
- 时间戳自动重命名:上传文件按
年月日时分秒毫秒+随机字符命名,同名文件不会冲突 - 可选 R2 双写:图片同时存到 Cloudflare R2,自带 CDN
- 访问口令可开关:
TOKEN_REQUIRED=true启用,false关闭 - 安全防护:恒定时间比较防时序攻击、空口令绕过防护、失败限速防暴力破解
- 前端支持点击上传 + Ctrl+V 粘贴上传
- 无需数据库
| 准备项 | 说明 |
|---|---|
| GitHub 账号 | 用于存放图片的公开仓库 |
| Cloudflare 账号 | 用于部署 Pages 和可选的 R2 存储 |
| GitHub Token | 在 GitHub 生成,仅需 repo 权限 |
- 登录 GitHub,新建一个公开仓库(如
tc) - 初始化时选择
main分支并添加一个 README 文件(让仓库不为空)
- 访问 https://github.com/settings/tokens → Generate new token (classic)
- 权限勾选:
repo(Full control of private repositories) - 生成后复制 Token(仅显示一次,务必保存)
- 登录 Cloudflare Dashboard → Workers & Pages → Create application → Pages → Connect to Git
- 选择图床代码所在的 GitHub 仓库
- 构建设置:
- Framework preset: None
- Build command: 留空
- Build output directory:
.(点号,表示根目录)
- 点击 Save and Deploy
在 Pages 项目 → Settings → Environment variables 中添加以下变量:
| 变量名 | 说明 | 示例值 |
|---|---|---|
GH_TOKEN |
GitHub Personal Access Token | ghp_xxxxxxxxxxxx |
GH_USER |
GitHub 用户名 | pxhzaii |
GH_REPO |
图片存储仓库名 | tc |
ACCESS_TOKEN |
上传口令(用户上传时需填写) | your-secret-password |
| 变量名 | 说明 | 默认值 | 示例值 |
|---|---|---|---|
GH_BRANCH |
仓库分支 | main |
main |
GH_EMAIL |
提交邮箱 | picbed@example.com |
you@example.com |
TOKEN_REQUIRED |
是否强制校验上传口令 | true |
true / false |
UPLOAD_R2 |
R2 全局总开关 | false |
true / false |
R2_PUBLIC_URL |
R2 公共访问基础 URL | 空 | https://cdn.yourdomain.com |
| 变量名 | 说明 | 示例值 |
|---|---|---|
GITEE_TOKEN |
Gitee Personal Access Token | 在 https://gitee.com/personal_access_tokens 生成 |
GITEE_USER |
Gitee 用户名 | your-gitee-username |
GITEE_REPO |
Gitee 仓库名(需公开) | pic-repo |
GITEE_BRANCH |
分支名 | master(默认) |
GITEE_EMAIL |
提交邮箱 | you@example.com |
注意:选择 Gitee 上传时,如果
GITEE_TOKEN、GITEE_USER、GITEE_REPO任一未配置,会返回友好提示。
仅 GitHub 有效,Gitee 使用官方 download_url 直链。
| 线路 | 地址格式 | 特点 |
|---|---|---|
| jsDelivr | cdn.jsdelivr.net/gh/... |
全球 CDN,国内网宿加速,默认推荐 |
| Gcore | gcore.jsdelivr.net/gh/... |
Gcore 节点加速 |
| Statically | cdn.staticaly.com/gh/... |
备用线路 |
| GitHub Raw | raw.githubusercontent.com/... |
原始链接,无 CDN |
| GHProxy 镜像 | mirror.ghproxy.com/... |
GitHub 反代加速,国内友好 |
| GitHub 加速 | gh.api.99988866.xyz/... |
Cloudflare Workers 反代 |
TOKEN_REQUIRED=true(默认):用户必须填写正确的口令才能上传TOKEN_REQUIRED=false:任何人无需口令即可上传(公开图床模式,请谨慎使用)
如果需要将图片同时存到 Cloudflare R2,需额外配置:
- Cloudflare Dashboard → R2 Object Storage → Create bucket
- 填写桶名(如
picture-cdn),选择区域(建议选离用户最近的) - 创建完成后,进入桶 → Settings → R2.dev subdomain → Enable(获取公共访问 URL)
- Pages 项目 → Settings → Functions → R2 bucket bindings
- 点击 Add binding:
- Variable name:
MY_BUCKET(必须填这个名字) - R2 bucket: 选择刚创建的桶
- Variable name:
- 保存
将以下环境变量设为:
UPLOAD_R2=trueR2_PUBLIC_URL= R2 的公共访问 URL(如https://pub-xxxx.r2.dev)
双重开关机制:R2 写入需要同时满足两个条件:
- 环境变量
UPLOAD_R2=true(管理员全局开关,决定 R2 功能是否可用) - 前端用户勾选「同时存到 R2」开关(用户单次选择,决定本次上传是否存 R2)
- 两者都为 true 才会执行 R2 写入
- Pages 项目 → Custom domains → Add
- 输入你的域名并按提示添加 DNS 记录
- 等待 SSL 证书签发完成
- 打开部署后的页面
- 如果开启了口令(
TOKEN_REQUIRED=true),在「访问口令」输入框填写正确的口令 - 选择存储平台:GitHub(默认)或 Gitee
- 如选 GitHub,可选择 CDN 线路(jsDelivr / Gcore / Statically / GitHub Raw / GHProxy 镜像 / GitHub 加速)
- 如果需要同时存到 R2,勾选「同时存到 R2」开关
- 点击上传区域选择图片,或直接 Ctrl+V 粘贴剪贴板中的图片
- 上传成功后会显示:
- URL: CDN 加速链接(根据所选线路不同)
- UBB: 论坛 BBCode 格式
- Markdown: Markdown 图片格式
- R2 URL: R2 直链(仅当存到 R2 时显示)
- 点击缩略图可跳转查看原图
- 前端 layui 组件限制:20MB
- 后端校验限制:20MB
- GitHub Contents API 限制:约 19MB 原始文件(base64 编码后约 25MB)
上传文件自动重命名为时间戳,格式为 YYYYMMDDHHmmssSSS+4位随机字符.扩展名,例如:
2026/08/05/20260805143052783a2f.png
- 日期目录:
年/月/日/ - 文件名:
年月日时分秒毫秒+4位随机字符+.扩展名 - 同名文件不会冲突,无需担心覆盖
- 所有密钥(GH_TOKEN、ACCESS_TOKEN 等)务必配置在 Cloudflare Pages 环境变量中,禁止硬编码在代码里
- 代码仓库中不要提交
.env文件或包含真实 Token 的配置文件 - GitHub Token 仅需
repo权限,不要授予更多权限 - Gitee Token 建议仅授予
projects权限
| 防护项 | 实现方式 |
|---|---|
| 时序攻击 | 使用恒定时间比较(timingSafeEqual),填充到相同长度后逐字符异或,不泄露长度信息 |
| 空值绕过 | ACCESS_TOKEN 未配置时拒绝所有上传请求,前端传空字符串也无法通过 |
| 暴力破解 | 每 IP 每分钟最多 10 次失败请求,超限后锁定 1 分钟;成功请求不计入 |
| 传输安全 | Cloudflare Pages 强制 HTTPS,口令在传输层已加密 |
| 内存泄漏 | 速率限制 Map 每 5 分钟清理过期条目,防止长时间运行内存无限增长 |
TOKEN_REQUIRED=false 时任何人无需口令即可上传,请仅在可信网络或测试时关闭。
所有 API 响应均为 HTTP 200 + JSON,通过 code 字段区分结果:
| code | 含义 | 触发条件 |
|---|---|---|
"success" |
上传成功 | — |
403 |
口令错误或未填写 | 口令校验失败 |
429 |
请求过于频繁 | 同一 IP 1 分钟内失败超过 10 次 |
404 |
文件无法识别 | 文件为空或小于 100 字节 |
413 |
文件过大 | 超过 20MB 限制 |
400 |
请求格式错误 | 非 multipart/form-data 请求 |
500 |
服务器错误 | GitHub/Gitee API 调用失败、环境变量未配置等 |
成功响应示例:
{
"code": "success",
"data": {
"url": "https://cdn.jsdelivr.net/gh/user/repo@main/2026/08/14/20260814143052783a2f.png",
"r2url": "https://pub-xxx.r2.dev/2026/08/14/20260814143052783a2f.png",
"filemd5": ""
}
}失败响应示例:
{
"code": 403,
"msg": "访问口令错误或未填写",
"url": null
}├── functions/
│ └── up.ts # Pages Function 上传接口(核心后端)
├── static/
│ ├── css/
│ │ └── mystyle.css
│ ├── js/
│ │ ├── embed.js # 前端上传逻辑
│ │ ├── clipBoard.min.js
│ │ ├── jquery.min.js
│ │ └── Message.js
│ └── layui/ # layui UI 框架
├── index.html # 主页面
└── wrangler.toml # 本地开发配置参考
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 上传返回 403 | 口令未填写或错误 | 检查前端口令与 ACCESS_TOKEN 环境变量是否一致 |
| 上传返回 403 | ACCESS_TOKEN 未配置 |
在环境变量中设置 ACCESS_TOKEN,未设置时所有请求被拒绝 |
| 上传返回 429 | 请求过于频繁 | 同一 IP 每分钟最多 10 次失败请求,请等待 1 分钟后重试 |
| 上传返回 400 | 请求格式错误 | 确保使用 multipart/form-data 上传 |
| 上传返回 500 + GitHub 未配置 | 环境变量缺失 | 确认 GH_TOKEN、GH_USER、GH_REPO 已正确配置 |
| 上传返回 500 + Gitee 未配置 | 环境变量缺失 | 确认 GITEE_TOKEN、GITEE_USER、GITEE_REPO 已正确配置 |
| 上传返回 500 + GitHub API 错误 | Token 无权限或仓库不存在 | 确认 GH_TOKEN 有 repo 权限,GH_USER/GH_REPO 正确 |
| 上传返回 422 | 文件冲突或 base64 异常 | 自动时间戳重命名极少冲突;若出现请重试 |
| 上传成功但 jsDelivr 链接 404 | 缓存未刷新或分支名不对 | 等待几分钟;确认 GH_BRANCH 与仓库实际默认分支一致 |
| R2 链接不显示 | 环境变量或绑定缺失 | 确认 UPLOAD_R2=true、MY_BUCKET 已绑定、R2_PUBLIC_URL 已填写 |
| R2 链接显示但无法访问 | R2 未开启公共访问 | 在 R2 桶设置中启用 R2.dev subdomain 或自定义域名 |
| 上传大文件失败(413) | 超过大小限制 | 压缩图片后重试(上限 20MB) |
| 选择 Gitee 后 CDN 线路隐藏 | 正常行为 | Gitee 使用官方直链,无需选择 CDN 线路 |