@sv-kit/uploader-pro
v1.0.0
Published
Chunked/resumable uploads with hash-based instant upload and adapters (HTTP multipart & S3/R2 presigned).
Readme
@sv-kit/uploader-pro
分片 / 断点续传 / 秒传(Merkle Hash)/ 并发与重试;可直传任意云,通过“适配器(adapter)”切换后端协议。 自带两套适配器:HTTP 分片(自有后端)、S3 兼容多段(S3/R2/Spaces/MinIO/Ceph …)。 想上其他云(OSS/COS/GCS/七牛/又拍/TUS)?写 50~100 行适配器就能接。🧱🚀
✨ 特性
- 分片上传(默认 5 MiB,可配置)· 并发上传 · 指数退避重试 · 可暂停/恢复/取消
- “秒传” + 断点续传:Merkle 式文件指纹(分片 hash → 汇总 hash)+ 本地 manifest + 服务端校验
- 适配器可插拔:协议/云供应商解耦,业务不动、一键切换
- 事件回调:
onProgress/onState/onChunk,好做 UI - 纯 TS,无重依赖;Svelte/SvelteKit 里用更顺,但不是 Svelte 专用
- 全局取消:基于 AbortSignal,
task.cancel()会立刻中断所有进行中的分片请求(不再重试),并调用适配器的abort()(若实现)
📦 安装
pnpm add @sv-kit/uploader-pro
# or: npm i / yarn add🚀 快速开始(任选适配器)
import {
UploadTask,
HttpMultipartAdapter,
S3MultipartAdapter
} from "@sv-kit/uploader-pro";
// 方案 A:自有后端分片(最通用、最可控)
const http = new HttpMultipartAdapter({ baseURL: "/api" });
// 方案 B:S3 兼容直传(S3、R2、Spaces、MinIO、Ceph…)
const s3 = new S3MultipartAdapter({ baseURL: "/api", acl: "public-read" });
const task = new UploadTask(file, {
chunkSize: 5 * 1024 * 1024,
concurrency: 4,
retry: { attempts: 3, baseDelayMs: 600, jitter: true },
adapter: http // 或 s3 / 其他云适配器
}, {
onProgress: p => console.log(p.percent + "%"),
onState: s => console.log("state:", s),
onChunk: c => console.log("chunk", c.index, "ok?", c.ok)
});
await task.start({ bizId: "example-42" });
// task.pause(); task.resume(); task.cancel();⛔️ 取消上传(AbortSignal)
本库内置全局取消:task.cancel() 会触发 AbortController.abort(),让所有正在进行的分片请求立刻停止,并将状态切到 "canceled"。如果适配器实现了 abort(sessionId),也会被调用以释放后端会话资源。
const task = new UploadTask(file, {
chunkSize: 5 * 1024 * 1024,
concurrency: 4,
retry: { attempts: 3, baseDelayMs: 600, jitter: true },
adapter: http // 或 s3 / 自定义适配器
}, { onState: s => console.log(s) });
// …用户点“取消”:
task.cancel(); // 中断所有 in-flight 分片请求,停止后续调度自定义适配器如何接入取消?
只需要把 ctx.signal 透传给你的 fetch 即可:
// 适配器中的分片上传实现
async uploadChunk(ctx) {
const res = await fetch(ctx.uploadUrl, {
method: 'PUT',
body: ctx.body,
signal: ctx.signal, // ← 关键:支持 AbortSignal
});
return { ok: res.ok, etag: res.headers.get('ETag') ?? undefined };
}说明
- 取消后不会进入
finalize;如需清理后端临时分片,请在适配器实现abort(sessionId)并在服务端删除会话。- 如果你用
XMLHttpRequest实现直传,需自行处理中止;推荐使用fetch+signal以获得一致体验。
🔌 适配器总览(多云友好)
| 适配器 | 适用场景 | 后端职责 | 前端行为 |
|---|---|---|---|
| HttpMultipartAdapter | 自有后端存临时分片再合并;任意云最终落盘 | prepare 返回 sessionId + 已有分片;接收 PUT /upload/:sid/:index;complete 合并 | 前端并发 PUT 分片到你后端 |
| S3MultipartAdapter | S3 及兼容 S3 协议的对象存储(Cloudflare R2、DigitalOcean Spaces、MinIO、Ceph…) | create 返回 uploadId/key 与每片 预签名 URL;complete 合并 | 前端直接 PUT 到对象存储(收集 ETag) |
| 自定义适配器 | 阿里 OSS / 腾讯 COS / GCS / 七牛 / 又拍 / TUS 等 | 由你后端按该云的 API 签名/创建会话/合并 | 实现 prepare/uploadChunk/finalize 三步即可 |
为什么 README 里示例 S3? 因为 S3 Multipart 是事实标准,大量云兼容 S3 协议。但本包并不绑定 S3——换适配器就能换云。
🧩 接口一眼懂(写你自己的适配器)
interface UploadAdapter {
name: string;
prepare(ctx): Promise<{ sessionId: string; alreadyUploaded?: number[]; uploadUrls?: Record<number,string> }>;
uploadChunk(ctx): Promise<{ ok: boolean; etag?: string }>;
finalize(ctx): Promise<{ ok: boolean; url?: string }>;
abort?(sessionId: string): Promise<void>;
}约定俗成:
prepare:返回上传会话sessionId,可选返回已上传分片(续传/秒传)与每片直传 URLuploadChunk:上传分片(成功可回etag供合并用)finalize:服务端合并或调用云端 Complete APIabort:可选,取消会话释放资源
☁️ 各云怎么用(除了 S3)
1) 阿里云 OSS / 腾讯云 COS
- 两者都提供各自的 Multipart API,也在不少场景提供 S3 兼容端点。
- 优先选 S3 兼容端点 → 直接用
S3MultipartAdapter。 - 若需原生 API:写
OssAdapter/CosAdapter:prepare:后端调用云端“初始化分片上传”,为每个partNumber生成 预签名 URL 返回uploadChunk:前端PUT分片到对应 URL(注意某些云要求Content-MD5或x-oss-*/x-cos-*头)finalize:后端用UploadId + Parts(ETag)触发合并,返回最终 URL
迁移成本:更换适配器即可;业务代码与 UI 零修改。
2) Google Cloud Storage(GCS)
- 推荐走 Resumable Upload(单会话、多次
PUT,用Content-Range标定偏移)。 - 写
GcsResumableAdapter:prepare:后端向 GCS 发起 Resumable 会话,返回sessionUrl(你可直接当sessionId)uploadChunk:前端对sessionUrl发送PUT,带Content-Range: bytes start-end/total,分片体在 bodyfinalize:最后一片PUT成功即完成;或让后端确认并回 URL
3) 七牛 / 又拍 / 其它对象存储
- 它们均有分片/断点续传 API(有的也提供 S3 兼容层)。
- 套路与 OSS/COS 相同:后端生成短时可用的上传 URL(或会话),前端按片
PUT,收集标识(ETag/ctx),合并完成。
4) 通用协议:TUS
- 若你想和跨语言/跨云的现成服务对接(
tusd、S3-tus-bridge等),写TusAdapter:prepare:POST创建上传;服务端回Location为会话 URLuploadChunk:使用PATCH,带Upload-Offset与分片 bodyfinalize:Tus 完成由服务端把关,前端可空实现
🧪 参考后端端点(示例)
A) HTTP 分片(最通用,自有后端落盘再合并)
POST /upload/prepare→{ sessionId, alreadyUploaded?: number[] }PUT /upload/:sessionId/:index→ 写入第index片(返回可带ETag)POST /upload/:sessionId/complete→ 合并,返回{ ok, url? }POST /upload/:sessionId/abort(可选)
B) S3 兼容直传
POST /s3/create→{ sessionId, uploadUrls: Record<index,string> }- (前端)逐片
PUT uploadUrls[index],收集ETag POST /s3/complete→ 传{ sessionId, parts: [{partNumber, etag}] },返回{ ok, url? }
这些端点只是示意;你也可以把
prepare/complete合并到一个 GraphQL mutation、或走 Cloudflare Worker、或任何后端框架。
🧯 CORS / 安全 / 配置清单
- CORS:直传对象存储必须在存储端配置允许:
Origin、PUT/POST/OPTIONS方法、Content-Type+ 需要的自定义头(如x-oss-*/x-cos-*)。 - 签名只在后端做:前端只拿时效性强的 URL/会话。
- 分片大小:S3 兼容通常要求除最后一片外 ≥ 5 MiB;其他云按文档设置。建议默认 5–16 MiB。
- 断点续传:前端 manifest + 后端
alreadyUploaded双保险;更换设备也能靠prepare返回的已上传列表续传。 - 哈希计算:超大文件建议放 WebWorker,避免阻塞 UI 线程。
🧭 我该选哪条路?
- 你全控后端、想要最大自由度 →
HttpMultipartAdapter - 你就想直传对象存储,而且是 S3 兼容家族 →
S3MultipartAdapter - 你正在用 GCS/七牛/又拍 → 写一个 小适配器(
prepare/uploadChunk/finalize三步照抄模板),十来分钟能跑 - 跨云/跨端生态 → 考虑 TUS 协议,接上
TusAdapter
🗺️ Roadmap(欢迎 PR)
- WebWorker 内计算哈希(避免主线程卡顿)
- 多文件批量队列管理器(限速/优先级/并行度动态调整)
- 自适应并发(根据 RTT/吞吐自动调度)
- 现成适配器:
OssAdapter/CosAdapter/GcsResumableAdapter/TusAdapter
📄 许可
MIT
