Azure Blob Storage 分片上传方式(预签名 url)
azure 云存储方式有三种传输方式:block blobs, append blobs, and page blobs。这里我们只涉及到上传,所以 Block blob
方式足够。
用户上传的 file
首先拿到用户上传的文件,需要符合 file:File 类型,一般是 <input type="file">
上传拿到的数据,这里的 File
支持 slice
方法可以切片
预签名接口 preSignedUploadUrl
前后端分离的项目,一般后端会存储上传的认证信息密钥之类,每次前端上传,请求后端接口,后端生成一个临时的预签名(pre-signed)url,一般几分钟之后会失效。前端使用这个预签名 url (后面提到的 preSignedUploadUrl) 进行上传操作。
整个文件一次性上传 uploadComplete
如果文件的size比较小,比如 file.size < 4 * 1024 * 1024
文件小于 4MB,那么可以选择一次上传全部。
1async function uploadComplete(preSignedUploadUrl:string,file:File) {
2 return axios.put(preSignedUploadUrl, file, {
3 headers: {
4 "x-ms-blob-type": "BlockBlob", // block blob 方式上传
5 },
6 });
7}
8
分片上传 uploadChunks
分片操作在 azure 这里需要分两步:
- 一段一段上传,可以顺序,或乱序,但是浏览器一般会限制并发数(比如 chrome 限制为 6 个),按需考虑。
- 上传需要合并的分片信息 xml 结构的表,里面的 blockId 信息需要按照上传顺序依次排列,告诉 azure 需要合并这个请求
注意:分片上传和提交的合并分片表单两个接口,是同一个预签名 url。
这里的 demo 中使用了 await-to-js ,将整个上传流程更直观,同步化
1import to from "await-to-js";
2import { v4 as uuidv4 } from "uuid";
3
4async function uploadChunks(preSignedUploadUrl:string, file:File) {
5 const fileSize = file.size;
6 const CHUNK_SIZE = 4 * 1024 * 1024; // 4 M
7 const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
8 const blockIdList = [];
9
10 // 1. 传递每一个分片
11 for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
12 const start = chunkIndex * CHUNK_SIZE;
13 const end = Math.min(start + CHUNK_SIZE, fileSize);
14 const chunk = file.slice(start, end);
15
16 // 生成唯一的 blockId, 所有的 blockId 长度必须一致,否则,后传的就 error
17 const blockId = btoa(uuidv4());
18 blockIdList.push(blockId);
19
20 // 注意这里 url 需要拼参数 &comp=block&blockid=${blockId}
21 const url = `${preSignedUploadUrl}&comp=block&blockid=${blockId}`;
22
23 const [err, values] = await to(
24 axios.put(url, file, {
25 headers: {
26 "x-ms-blob-type": "BlockBlob",
27 "Content-Type": file.type,
28 "Content-Range": `bytes=${start}-${end}/${fileSize}`,
29 },
30 }),
31 );
32 if (err) {
33 // err todo
34 }
35 }
36
37 // 2. 提交合并分片的请求
38 const xmlBodyString = `<?xml version="1.0" encoding="UTF-8"?><BlockList>${blockIds
39 .map(blockId => `<Latest>${blockId}</Latest>`)
40 .join("")}</BlockList>`;
41
42// 注意这里 url 需要拼参数 &comp=blocklist
43 const [err, res] = await to(
44 axios.put(`${preSignedUploadUrl}&comp=blocklist`, xmlBodyString, {
45 headers: {
46 "Content-Type": "text/plain",
47 },
48 }),
49 );
50 if (err) {
51 // err todo
52 }
53
54 // end
55}
56
上传进度 onUploadProgress
对于整个一次性的上传,使用 axios 的可以使用 onUploadProgress 进行进度同步更新。
1axios.put(upload_url, file, {
2 headers: { "x-ms-blob-type": "BlockBlob", ...headers },
3 onUploadProgress(progressEvent) {
4 const { loaded, total } = progressEvent;
5 if (!total || !loaded) return;
6
7 const percent = Math.floor((loaded * 100) / total);
8
9 onUpdate(percent); // 0-100
10 },
11});
12
对于分片的文件,可以用分片的进度和各个分片上传的进度,两者结合来做判断,大家可以尝试下,这里不过多着墨。
参考
相关笔记