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,那么可以选择一次上传全部。
ts
async function uploadComplete(preSignedUploadUrl:string,file:File) {
return axios.put(preSignedUploadUrl, file, {
headers: {
"x-ms-blob-type": "BlockBlob", // block blob 方式上传
},
});
}
分片上传 uploadChunks
分片操作在 azure 这里需要分两步:
- 一段一段上传,可以顺序,或乱序,但是浏览器一般会限制并发数(比如 chrome 限制为 6 个),按需考虑。
- 上传需要合并的分片信息 xml 结构的表,里面的 blockId 信息需要按照上传顺序依次排列,告诉 azure 需要合并这个请求
注意:分片上传和提交的合并分片表单两个接口,是同一个预签名 url。
这里的 demo 中使用了 await-to-js ,将整个上传流程更直观,同步化
ts
import to from "await-to-js";
import { v4 as uuidv4 } from "uuid";
async function uploadChunks(preSignedUploadUrl:string, file:File) {
const fileSize = file.size;
const CHUNK_SIZE = 4 * 1024 * 1024; // 4 M
const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
const blockIdList = [];
// 1. 传递每一个分片
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, fileSize);
const chunk = file.slice(start, end);
// 生成唯一的 blockId, 所有的 blockId 长度必须一致,否则,后传的就 error
const blockId = btoa(uuidv4());
blockIdList.push(blockId);
// 注意这里 url 需要拼参数 &comp=block&blockid=${blockId}
const url = `${preSignedUploadUrl}&comp=block&blockid=${blockId}`;
const [err, values] = await to(
axios.put(url, file, {
headers: {
"x-ms-blob-type": "BlockBlob",
"Content-Type": file.type,
"Content-Range": `bytes=${start}-${end}/${fileSize}`,
},
}),
);
if (err) {
// err todo
}
}
// 2. 提交合并分片的请求
const xmlBodyString = `<?xml version="1.0" encoding="UTF-8"?><BlockList>${blockIds
.map(blockId => `<Latest>${blockId}</Latest>`)
.join("")}</BlockList>`;
// 注意这里 url 需要拼参数 &comp=blocklist
const [err, res] = await to(
axios.put(`${preSignedUploadUrl}&comp=blocklist`, xmlBodyString, {
headers: {
"Content-Type": "text/plain",
},
}),
);
if (err) {
// err todo
}
// end
}
上传进度 onUploadProgress
对于整个一次性的上传,使用 axios 的可以使用 onUploadProgress 进行进度同步更新。
js
axios.put(upload_url, file, {
headers: { "x-ms-blob-type": "BlockBlob", ...headers },
onUploadProgress(progressEvent) {
const { loaded, total } = progressEvent;
if (!total || !loaded) return;
const percent = Math.floor((loaded * 100) / total);
onUpdate(percent); // 0-100
},
});
对于分片的文件,可以用分片的进度和各个分片上传的进度,两者结合来做判断,大家可以尝试下,这里不过多着墨。