简介
本文档旨在详细介绍如何使用 AWS S3 的多段式上传功能管理大文件上传。我们将深入探讨一个 JavaScript 类 S3,它封装了 AWS SDK 的相关功能,提供了便捷的文件上传、下载和断点续传等操作。
环境配置
在开始使用 AWS S3 进行文件操作前,首先需要配置 AWS SDK 和相关的凭证信息。以下是配置代码片段:
import { S3Client } from "@aws-sdk/client-s3";
const config = {
credentials: {
accessKeyId: "xxx",
secretAccessKey: "xxx",
},
region: "xxx",
}
const s3 = new S3Client(config);
S3 类实现
我们将通过一个名为 S3 的 JavaScript 类来实现对 AWS S3 的各种操作,包括文件上传、获取文件路径、断点续传、终止任务以及通过 URL 下载文件。
类的定义和构造函数
class S3 {
constructor(config, bucket = "xxx") {
this.bucket = bucket
this.s3 = new S3Client(config);
this.parts = []
this.createResponse = {}
this.tasks = []
}
}
在构造函数中,我们初始化了 S3Client 实例,并设置了一些基本的属性,包括 bucket(桶名称)、parts(存储文件分片信息的数组)、createResponse(存储创建多段上传的响应信息)以及 tasks(任务列表)。
文件上传功能
文件上传功能主要通过 UploadFile 方法实现。该方法使用了 AWS SDK 提供的 PutObjectCommand 命令,将文件上传到指定的 S3 存储桶中。
// 上传文件
UploadFile(file) {
return this.s3.send(new PutObjectCommand({ Key: file.name, Body: file, Bucket: this.bucket, ContentType: file.type }))
}
获取文件路径
GetFileUrl 方法用于获取文件的 URL 地址,以便前端可以通过 URL 直接访问文件内容。该方法首先通过 GetObjectCommand 获取文件流,然后将其转换为 Blob 对象,最后生成一个 URL 地址。
// 获取文件路径
async GetFileUrl(file) {
const stream = await this.s3.send(new GetObjectCommand({ Key: file.name, Bucket: this.bucket }));
const streamBlob = await binconv.readableStreamToBlob(stream.Body);
const blob = new Blob([streamBlob], { type: stream.ContentType });
return Promise.resolve(URL.createObjectURL(blob))
}
断点续传功能
断点续传功能是该类的核心功能之一,主要通过 uploadPointerFile 方法实现。该方法将大文件分割成多个小片段,并逐一上传到 S3。
// 断点续传
async uploadPointerFile(file, callback, chunkSize = 5 * 1024 * 1024) {
callback(0, '解析中...')
const {md5} = await md5ByFile(file)
// 创建文件数据
const createMultipartUploadCommand = new CreateMultipartUploadCommand({ Key: md5, Bucket: this.bucket, ContentType: file.type });
// 创建文件记录
this.createResponse = await this.s3.send(createMultipartUploadCommand);
// 切片
let start = 0;
let index = 0
const number = Math.ceil(file.size / chunkSize)
while (start < file.size) {
if (this.status === status.STOP) {
callback(-1, '已取消...')
break
}
const chunk = file.slice(start, start + chunkSize);
const reader = new FileReader();
reader.readAsArrayBuffer(chunk);
const res = await new Promise((resolve, reject) => {
reader.onload = async () => {
// 上传切片
const uploadPartCommand = new UploadPartCommand({
Bucket: this.bucket,
Key: md5,
UploadId: this.createResponse.UploadId,
PartNumber: index + 1,
Body: chunk
});
const partResponse = await this.s3.send(uploadPartCommand);
resolve(partResponse)
};
});
this.parts.push({ PartNumber: index + 1, ETag: res.ETag });
start = start + chunkSize;
index += 1
callback(0, null, Math.round(index * 100 / number))
// 合并分片
if (number == index) {
const sortedParts = this.parts.sort((a, b) => a.PartNumber - b.PartNumber);
const params = {
Bucket: this.bucket,
Key: md5,
UploadId: this.createResponse.UploadId,
MultipartUpload: { Parts: sortedParts }
};
const completeMultipartUploadCommand = new CompleteMultipartUploadCommand(params);
const data = await this.s3.send(completeMultipartUploadCommand).catch((e) => console.log(e));
callback(1, {...data,md5,file}, 100)
}
}
}
终止任务功能
当需要终止一个正在进行的上传任务时,可以使用 abortUpload 方法。该方法会发送一个 AbortMultipartUploadCommand 命令给 AWS S3,从而取消当前的多段上传任务。
// 终止任务
async abortUpload(file) {
const CMD = new AbortMultipartUploadCommand({ Bucket: this.bucket, Key: file.name, UploadId: this.createResponse.UploadId })
const data = await this.s3.send(CMD).catch((e) => console.log(e));
if (data) {
this.status = "STOP"
}
return data
}
URL 下载文件
通过 URL 下载文件的功能是使用一个隐藏的 a 标签来实现的。该标签设置了 download 属性,并将 href 指向文件的 URL。当标签被点击时,浏览器会自动下载文件。
// url下载文件
downloadFile(name, url) {
const elink = document.createElement('a');
elink.download = name;
elink.style.display = 'none';
elink.href = url;
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
}
完整代码
以下是完整的 S3 类代码:
import { CompleteMultipartUploadCommand, CreateMultipartUploadCommand, GetObjectCommand, PutObjectCommand, S3Client, UploadPartCommand, AbortMultipartUploadCommand } from "@aws-sdk/client-s3";
import * as binconv from 'binconv';
import { md5ByFile } from "./md5";
const config = {
credentials: {
accessKeyId: "xxx",
secretAccessKey: "xxx",
},
region: "xxx",
}
const status = { STOP: "STOP", OK: "OK", ING: "ING" }
class S3 {
constructor(config, bucket = "xxx") {
this.bucket = bucket
this.s3 = new S3Client(config);
this.parts = []
this.createResponse = {}
this.tasks = []
}
// 上传文件
UploadFile(file) {
return this.s3.send(new PutObjectCommand({ Key: file.name, Body: file, Bucket: this.bucket, ContentType: file.type }))
}
// 获取文件路径
async GetFileUrl(file) {
const stream = await this.s3.send(new GetObjectCommand({ Key: file.name, Bucket: this.bucket }));
const streamBlob = await binconv.readableStreamToBlob(stream.Body);
const blob = new Blob([streamBlob], { type: stream.ContentType });
return Promise.resolve(URL.createObjectURL(blob))
}
// 断点续传
async uploadPointerFile(file, callback, chunkSize = 5 * 1024 * 1024,) {
callback(0, '解析中...')
const {md5} = await md5ByFile(file)
// 创建文件数据
const createMultipartUploadCommand = new CreateMultipartUploadCommand({ Key: md5, Bucket: this.bucket, ContentType: file.type });
// 创建文件记录
this.createResponse = await this.s3.send(createMultipartUploadCommand);
// 切片
let start = 0;
let index = 0
const number = Math.ceil(file.size / chunkSize)
while (start < file.size) {
if (this.status === status.STOP) {
callback(-1, '已取消...')
break
}
const chunk = file.slice(start, start + chunkSize);
const reader = new FileReader();
reader.readAsArrayBuffer(chunk);
const res = await new Promise((resolve, reject) => {
reader.onload = async () => {
// 上传切片
const uploadPartCommand = new UploadPartCommand({
Bucket: this.bucket,
Key: md5,
UploadId: this.createResponse.UploadId,
PartNumber: index + 1,
Body: chunk
});
const partResponse = await this.s3.send(uploadPartCommand);
resolve(partResponse)
};
});
this.parts.push({ PartNumber: index + 1, ETag: res.ETag });
start = start + chunkSize;
index += 1
callback(0, null, Math.round(index * 100 / number))
// 合并分片
if (number == index) {
const sortedParts = this.parts.sort((a, b) => a.PartNumber - b.PartNumber);
const params = {
Bucket: this.bucket,
Key: md5,
UploadId: this.createResponse.UploadId,
MultipartUpload: { Parts: sortedParts }
};
const completeMultipartUploadCommand = new CompleteMultipartUploadCommand(params);
const data = await this.s3.send(completeMultipartUploadCommand).catch((e) => console.log(e));
callback(1, {...data,md5,file}, 100)
}
}
}
// 终止任务
async abortUpload(file) {
const CMD = new AbortMultipartUploadCommand({ Bucket: this.bucket, Key: file.name, UploadId: this.createResponse.UploadId })
const data = await this.s3.send(CMD).catch((e) => console.log(e));
if (data) {
this.status = "STOP"
}
return data
}
// url下载文件
downloadFile(name, url) {
const elink = document.createElement('a');
elink.download = name;
elink.style.display = 'none';
elink.href = url;
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
}
}
export default new S3(config)
总结
通过上述实现,我们能够方便地使用 AWS S3 进行大文件的上传和管理。无论是普通文件上传、断点续传,还是获取文件 URL 和终止上传任务,这个封装类都提供了简洁的接口。同时,通过断点续传功能,可以有效提高上传大文件的效率和可靠性。
希望本文档能为您提供有效的指导,帮助您更好地理解和使用 AWS S3 进行文件管理。如果有任何问题或建议,欢迎与我们联系。