私有图床 + 小型网盘:配合「阿里云 OSS → Cloudflare 免流」教程使用,界面优雅、功能完整:
📖 配套教程:奶昔论坛 · 阿里云 OSS + Cloudflare 免流图床教程 | NodeSeek · 同教程帖子
- 存储:阿里云 OSS 私有桶(海外地域,如香港)
- 分发:Cloudflare 免流域名(你的
2.com),文件访问不经过任何函数,无限流量 - 上传:浏览器直传 OSS,函数只负责签名、不接触文件内容
- 小文件(≤10MB):PostObject 表单直传,每传 1 个文件仅调用 1 次函数
- 大文件(>10MB):分片上传(Multipart)——每片 10MB 独立签名、即签即传,慢网络不限总时长;中断后重选同一文件自动断点续传,失败分片自动重签重试
- 一套代码,三个平台可跑:腾讯 EdgeOne Makers / 阿里云 ESA Pages + 边缘函数 / Cloudflare Workers & Pages
上传时按扩展名自动落到对应目录,前端可切换目录浏览:
| 目录 | 内容 |
|---|---|
upweb/img/ |
图片:jpg jpeg png gif webp svg avif bmp ico tiff |
upweb/video/ |
视频:mp4 webm mov mkv m4v avi flv ts |
upweb/other/ |
其他一切:压缩包、文档、音频、安装包… |
- 🔒 密码登录门禁(UPLOAD_PASSWORD,≥10 位强制;验证通过换 7 天登录令牌,硬到期不续期,本机不存明文密码;改密码立即作废全部令牌)
- 🗂️ 任意文件上传:图片 / 视频 / 压缩包 / 文档,拖拽 / 粘贴 / 点选,支持多选批量上传,右下角浮动上传面板实时汇总进度与实时速度(可折叠 / 清空 / 暂停 / 继续 / 取消),大文件长传不挡浏览
- 🧩 大文件分片上传:>10MB 自动走 OSS Multipart,逐片进度百分比与实时速度;断点续传(进度存本机,中断/刷新/关页面后重选同一文件自动接着传);暂停 / 继续 / 取消(暂停保留断点,取消自动 Abort 清理云端残留分片);分片失败自动重试 3 次并重新签名;ETag 读不到时本地计算分片 MD5 兜底
- 🗜️ 图片可选智能压缩为 WebP(画质可调,压缩后更大则自动保留原图)
- ☁️ 云端文件:目录标签页切换(全部 / 图片 / 视频 / 其他)、文件名搜索筛选、分页加载、骨架屏
- 🎬 灯箱预览:图片看大图、视频在线播放、其他文件显示图标并可打开/下载;复制链接 / Markdown、双次点击确认删除(直接从 OSS 删除);下载走文件直链,IDM 等下载器可自动接管
- 🔗 上传后一键复制 URL / Markdown
- 🕘 本机历史记录(localStorage,不上传)
- 📱 响应式布局,手机可用
├── index.html # 前端页面(登录门禁/分片上传/断点续传/云端文件/灯箱/历史记录)
├── favicon.png # 站点图标
├── functions/api/
│ ├── sign.js # 小文件上传签名 + 密码预检(EdgeOne Pages / CF Pages 直接用)
│ ├── list.js # 列出桶内文件(支持目录参数)
│ ├── delete.js # 删除桶内文件
│ ├── rename.js # 重命名文件(同目录 CopyObject + 删除旧 key)
│ └── multipart.js # 大文件分片上传(init/part签名/complete/abort)
└── adapters/
├── cloudflare-workers.js # Cloudflare Workers 独立版(含全部五个接口)
└── esa-edge-function.js # 阿里云 ESA 边缘函数版(含全部五个接口)
- 按教程建好 OSS 海外私有桶、CF IP 白名单、自定义主机名,确认
https://2.com/某张图能正常访问 - 额外做一步:配置 Bucket 的 CORS(教程里没有,但浏览器直传需要;分片上传必须有 PUT):
- OSS 控制台 → 你的 Bucket → 数据安全 → 跨域设置 → 创建规则
- 来源(AllowedOrigin):填你的图床站点域名,如
https://img-admin.edgeone.app(调试期可先用*) - 方法(AllowedMethods):
POST、PUT(⚠️ 老配置只开了 POST,分片上传会失败,务必补上 PUT) - 允许的 Header(AllowedHeaders):
* - 暴露 Header(ExposeHeaders):
ETag(建议配;即使不配,前端也会本地计算分片 MD5 兜底,不影响上传) ⚠️ 多条规则时注意:OSS 按「第一条匹配的规则」生效。若存在*通配规则排在前面,会抢先匹配你的站点——请给它也加上 ETag 暴露头,或直接删除多余的通配规则⚠️ 跨域规则生效有最长 15 分钟延迟;改完后若浏览器仍报旧错,用无痕窗口重试(避开预检缓存)
- (强烈建议)配置生命周期规则自动清理残留分片:分片上传中断后,已传的分片会暂存 OSS 并计费。OSS 控制台 → 你的 Bucket → 数据管理 → 生命周期 → 创建规则 → 前缀
upweb/→ 「碎片管理」勾选过期碎片 3 天后删除。这样任何烂尾上传都会自动清理,不会悄悄产生存储费 - 建议创建 RAM 子账号 AccessKey,直接授权系统策略
AliyunOSSFullAccess(管理对象存储服务 OSS 权限)即可,别用主账号 AK。⚠️ 不推荐手动勾选最小权限:分片上传的合并请求需要oss:ListParts,很容易漏配导致合并报AccessDenied(分片已传成功但 Complete 失败)。已有国内版 OSS 用户踩坑(国际版策略规则可能略有差异),直接给 FullAccess 最省事- 如坚持最小化授权,该桶至少需要:
oss:PutObject、oss:GetObject(重命名复制源需要)、oss:ListObjects、oss:ListParts(分片合并需要)、oss:DeleteObject、oss:AbortMultipartUpload
| 变量 | 示例 | 说明 |
|---|---|---|
OSS_ACCESS_KEY_ID |
LTAI5t... |
RAM 子账号 AK |
OSS_ACCESS_KEY_SECRET |
xxxx |
对应 SK |
OSS_BUCKET |
my-img |
Bucket 名 |
OSS_ENDPOINT |
oss-cn-hongkong.aliyuncs.com |
Bucket 概览页底部可查 |
PUBLIC_URL_BASE |
https://img.example.com |
教程里的访问域名 2.com |
UPLOAD_PASSWORD |
(必填,≥10 位) | 上传密码。未配置或少于 10 位时所有接口拒绝服务(500) |
ALLOW_ANONYMOUS_UPLOAD |
(可选) | 显式设为 true 才允许免密码公开上传(不推荐,等于把桶开放给全世界) |
MAX_SIZE_MB |
(可选,默认 100) | 单文件大小上限;分片上传已突破单请求限制,传大视频可设 500~2000 |
PART_SIZE_MB |
(可选,默认 10) | 分片大小(MB),范围 5~100。网络差调小(重试代价小),网络好调大(签名次数少) |
- 把本目录推送到 CNB / GitHub 仓库(环境变量不要写进代码,在控制台配)
- EdgeOne Pages → 新建项目 → 导入仓库 → 根目录直接部署(
functions/api/*.js会自动注册为/api/*) - 项目设置 → 环境变量,填上表中的变量
- 打开分配的
.edgeone.app域名即可使用;可绑自定义域名(国内节点需备案)
函数消耗:小文件每传 1 个 = 1 次请求;大文件分片模式 = 每片 1 次签名请求(几 KB),免费额度 300 万次/月,等于用不完。
- Workers 控制台 → Create Worker → 粘贴
adapters/cloudflare-workers.js→ Deploy - Settings → Variables and Secrets 配置环境变量
- 前端
index.html部署在 CF Pages(同一仓库时 Pages Functions 可直接用functions/api/*.js,无需 Worker),或任何静态托管 - 若前端和 Worker 不同域,已内置 CORS,无需额外配置
- ESA 控制台 → 边缘函数 → 新建 → 粘贴
adapters/esa-edge-function.js→ 发布 - 若 ESA 支持环境变量则配置变量;不支持就把文件顶部
CONFIG填好(填完别再提交到公开仓库) - 把云盘管理域名关联到该函数;
index.html部署在 ESA Pages 或任意静态托管 - 免费版边缘函数 10 万次/天,小文件每个仅 1 次,大文件每片 1 次签名,够用
打开页面 → 先过登录门禁(输入 UPLOAD_PASSWORD 对应的密码)→ 拖文件 / 粘贴图片 / 点选 → 自动归类目录 → 一键复制链接。
💡 想先逛逛界面? 直接把
index.html部署到任意静态托管(或不配后端环境变量),输入演示密码demo1234即可进入纯前端预览模式——界面可完整体验,上传与云端文件需配置后端后生效。
密码验证通过后,服务端会签发一个 7 天有效的登录令牌(HMAC-SHA256 签名),前端此后只保存令牌、不再保存明文密码;7 天内打开自动解锁,到期不续期,回到锁屏重新输入密码即可。
令牌签名密钥是从 OSS SK + Bucket 名 + 密码哈希 派生的(不新增环境变量),带来两个特性:
- 改密码 = 全部令牌立刻失效:密钥随密码哈希变化,旧令牌验签不过,可直接用于"踢人"
- 令牌绑定 Bucket:其他部署实例(即使复用同一个 SK)签发的令牌无法登录本站
云端文件:解锁后自动加载 OSS 桶内 upweb/ 下的文件,顶部标签页切换目录(全部/图片/视频/其他),支持平铺/瀑布流/列表三种视图、按时间或大小排序、分页「加载更多」、「刷新」和文件名筛选;点击缩略图打开灯箱——图片看大图、视频直接播放,支持重命名、下载、复制链接/Markdown,其他文件可打开/下载;缩略图底部常驻文件名与大小/日期,灯箱内删除操作独立沉底、需双次确认。
历史记录只存在浏览器 localStorage,不上传、不同步。页脚「退出登录」可清除本机保存的登录令牌。
安全说明:登录门禁只是前端体验层,真正的拦截在函数里——
/api/sign、/api/multipart、/api/list、/api/delete、/api/rename都会校验身份(密码或 7 天登录令牌),不对直接 401;分片接口的 key 强制upweb/前缀白名单,且 init 会签发绑定 key/uploadId/声明大小/分片上限/分片尺寸的会话令牌,合并前逐片向 OSS 核验分片号、ETag 与字节数,无法越权操作桶内其他对象、也无法「声明小传大」或「检查后偷换分片」。若未设置UPLOAD_PASSWORD或密码少于 10 位,所有接口将拒绝服务(除非显式设置ALLOW_ANONYMOUS_UPLOAD=true)。
| 接口 | 方法 | 说明 |
|---|---|---|
/api/sign |
POST | 小文件上传签名(PostObject Policy,bucket + key 精确绑定本次生成的对象键 + 防覆盖条件写入签名);{check:true} 时为身份预检,仅密码验证通过才签发 7 天登录令牌,持令牌预检不续签 |
/api/multipart |
POST | 大文件分片上传:action=init 初始化(返回 uploadId+key+partSize+会话令牌);action=part 为单个分片签发 1 小时有效的预签名 URL(校验会话令牌,分片号不得超声明上限);action=complete 合并分片(先 ListParts 逐片核验分片号/ETag/字节数与 init 声明完全一致,不符自动 Abort 清理);action=abort 清理残留分片。全程密钥不出服务端 |
/api/list |
POST | 列出文件,{auth, token?, dir?}(token 为分页游标),dir ∈ all/img/video/other,每页 60 条 |
/api/delete |
POST | 删除文件,{auth, key},仅允许 upweb/ 前缀 |
/api/rename |
POST | 重命名文件,{auth, key, name}(新文件名,含扩展名):同目录 CopyObject(x-oss-forbid-overwrite 防覆盖,重名返回 409)+ 删除旧 key,新名字限定 A-Za-z0-9._- 字符集 |
身份字段说明:所有接口接受
password(明文密码)或auth(登录令牌)任一;分片接口的session为 init 签发的会话令牌。
- 为什么分片能解决「上传超过 10 分钟」:PostObject 的 Policy 只有 10 分钟有效期,慢网络传大文件可能拖到过期;分片模式下每个分片单独申请预签名 URL(1 小时有效)、即签即传,总上传时长不受任何单一签名限制——哪怕 1GB 文件传一晚上也没问题。
- 断点续传:已成功分片保存在 OSS 服务端,进度与会话令牌记录在浏览器 localStorage。网络中断、刷新页面、甚至隔天再传——重新选择同一个文件即自动跳过已传分片继续上传。若 OSS 侧会话或本地会话令牌已过期(7 天),前端会自动清掉本地进度重新整个上传。
- ETag 双保险:合并分片需要每片的 ETag。前端优先读取 OSS 响应头里的 ETag(需 CORS 暴露),读不到时用本地计算的分片 MD5(二者本来相等),因此 CORS 暴露头配不配都能传。
- 小文件的兜底:≤10MB 文件仍走 PostObject 一次直传;若慢网络拖到 Policy 过期,前端检测到
Policy expired会自动重签重传一次。 - 瞬时故障自愈:边缘函数到 OSS 的子请求偶发
net_exception_timeout类网络抖动。函数侧 init/complete/abort 的 OSS 调用统一带 15s 超时 + 3 次重试;前端对 init/complete 再做一轮重试(part 分片本就有 3 次重签重传)。合并接口额外做了幂等处理——「合并成功但响应丢失」时重试会收到 OSS 的NoSuchUpload,此时检测到对象已存在即按成功返回,不会触发整包重传;abort 同理,会话已不存在视为清理成功。 ⚠️ 同名存在性检查用GET + Range: bytes=0-0(init 预检与 complete 幂等检查均是)。这个环节踩过两个坑:①GET ?objectMeta的 200 响应头Content-Length是对象大小却没有响应体,EO/ESA 边缘运行时的 fetch 会干等 body 直至平台超时(表现为同名文件上传卡死后报net_exception_timeout);②HEAD(HeadObject)在本链路返回 403 且错误按 HTTP 语义无响应体,拿不到 OSS 错误码。Range 请求所有状态都有响应体(存在→206、不存在→404、0 字节对象→416 同样证明存在),且 Range 是普通请求头不参与 V1 签名,三个平台运行时都验证可用。- 阈值:前端固定以 10MB 为界选择两条通道,与服务端默认
PART_SIZE_MB=10对齐。
- 函数:三平台免费额度内,小文件 1 次调用/个、大文件每片 1 次签名调用,可忽略
- 文件访问流量:走 CF 带宽联盟回源免流 + CF 免费 CDN,不产生费用
- 唯一固定成本:OSS 存储费(海外标准存储,每月每 GB 约 0.1 元人民币量级)
⚠️ 带宽联盟政策要求源站为非中国大陆地域的标准型存储桶;建议用阿里云国际版账号(含每月 1 亿次免费请求等权益),上车后先小额实测一张账单确认⚠️ OSS 是后付费:保持账户有少量余额 + 开通余额告警,避免欠费停服导致数据进入释放倒计时
- AccessKey 只存于服务端环境变量,前端不可见;令牌签名密钥由
OSS SK + Bucket 名 + 密码哈希派生,不新增环境变量 - fail-closed 鉴权:未设置
UPLOAD_PASSWORD或密码少于 10 位时,所有接口全部返回 500 拒绝服务——不存在"忘配密码就裸奔"的情况;确需公开模式必须显式设置ALLOW_ANONYMOUS_UPLOAD=true - 前端不保存明文密码:登录成功换取 7 天 HMAC 登录令牌(硬到期不续期,过期自动回锁屏),本机只存令牌;修改密码立即作废所有已签发令牌,可用于紧急踢人
- 密码校验为 SHA-256 哈希比对 + 失败统一延迟 0.4s,拖慢在线暴力破解;仍请务必使用强密码(≥10 位,建议更长、真正随机)
- 上传签名 Policy 同时绑定 Bucket 和精确对象键(
eq条件),x-oss-forbid-overwrite也写入 Policy 条件(客户端无法剥离该字段来覆盖已有对象) - 分片上传有会话令牌绑定:init 时签发、绑定 key/uploadId/声明大小/分片数上限/分片尺寸;part 分片号超限直接拒绝;complete 前向 OSS ListParts 逐片核验(分片号 + ETag + 每片字节数必须与声明完全吻合),任何不符自动 Abort 清理会话——封死"声明小传大"和"检查后偷换分片"两条绕过路径
- 请求硬化:所有接口限制请求体大小(普通接口 8KB / 分片合并 256KB)与密码、令牌、文件名等字段长度,防畸形请求消耗函数额度
- 文件路径为
upweb/<类型>/日期/32位随机串.扩展名,不可枚举;key 额外限定安全字符集(字母数字._-/),杜绝注入型恶意文件名;桶本身私有 + 仅 CF IP 段只读白名单
代码内的 0.4s 失败延迟只能拖慢串行猜测,无法阻止并行爆破——边缘函数是无状态的,真正的限流必须在平台 WAF 层做。三个平台免费版都自带,对 /api/* 路径按 IP 限频即可(如每分钟 20 次、突发 10 次):
- 腾讯 EdgeOne:EO 控制台 → 站点 → 安全防护 → Web 防护 → 速率限制(CC 防护)→ 新建规则:匹配路径
/api/*,按客户端 IP 计数,超限拦截 - Cloudflare:Security → WAF → Rate limiting rules(免费版含 1 条规则)→ 路径匹配
/api/*,period 1 minute,阈值 20,action Block - 阿里云 ESA:控制台 → 安全防护 → WAF → 自定义规则/频次控制 → 路径
/api/*,按 IP 限速

