Ant Design Upload 通过后端预生成 URL 分片上传大文件到 AWS S3
本文概括
首先前端方面使用的是 React
以及 Ant Design
,后端使用 PHP
以及 AWS-SDK
通过后端与 AWS
进行交互创建多段上传,拿到后续所需的 Key
和 ID
然后前端将 File
文件进行 slice
分片,并在每次分片后取调用后端接口
此时后端通过之前的 Key
和 ID
来获取当前分片的上传地址(由 AWS
返回)
前端不同分片拿到各自的上传地址后,各自异步上传到相应的 URL
即可
当上传完毕后调用后端接口,后端再调用 AWS
的完成上传接口,让 AWS
对分片进行合并即可
拦截 Ant Design Upload 采用自己的上传
在 Upload
组件的 beforeUpload
方法中返回 FALSE
,然后在 return
之前插入自己的逻辑即可
const uploadProps = {
name: 'file',
multiple: true,
accept: 'video/*',
beforeUpload: function(file: RcFile, _: RcFile[]) {
// 在此处填入上传逻辑
return false;
},
onChange: function(info: UploadChangeParam<UploadFile>) {
const { status } = info.file;
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
后端创建分片上传
前端获取到上传的文件 File
对象后,获取相应的 name
、size
、type
、lastModifiedDate
并传递给后端来创建多段上传
/**
* 创建多段上传
*
* @param array $fileInfo
*
* $info = [
* 'name' => '文件名称'
* 'size' => '文件大小'
* 'type' => '文件类型'
* 'lastModifiedDate' => '最后操作时间'
* ]
*
* @return array
*
* @author hanmeimei
*/
public function create(array $fileInfo)
{
// 为了避免文件名包含中文或空格等,采用 uniqid 生成新的文件名
$fileInfo['name'] = $this->generateFileName($fileInfo['name']);
$res = $this->s3->createMultipartUpload([
// S3 桶
'Bucket' => self::BUCKET,
// 存储路径,自定义
'Key' => $this->getPrefix() . $fileInfo['name'],
'ContentType' => $fileInfo['type'],
'Metadata' => $fileInfo
]);
return [
'id' => $res->get('UploadId'),
'key' => $res->get('Key'),
];
}
前端对 File 进行分片
对 File
进行分片处理,拿到结果数组
const getBlobs = (file: RcFile) => {
let start = 0;
const blobs: Blob[] = [];
while (start < file.size) {
const filePart = file.slice(start, Math.min((start += partSize), file.size));
if (filePart.size > 0) blobs.push(filePart);
}
return blobs;
};
循环分片 并预生成相应的上传 URL
循环上方生成的分片数组,并获取对应分片的 size
已经 number
(可以直接使用数组索引来代替)
然后调用后端接口,生成当前分片对应的 AWS
上传链接
/**
* 为某个分段生成预签名url
*
* @param string $key 创建多段上传拿到的 Key
* @param string $id 创建多段上传拿到的 Id
* @param int $number 当前分片为第几片 (从 1 开始)
* @param int $length 当前分片内容大小
*
* @return string
*
* @author hanmeimei
*/
public function part(string $key, string $id, int $number, int $length)
{
$command = $this->s3->getCommand('UploadPart', [
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
'PartNumber' => $number,
'ContentLength' => $length
]);
// 预签名url有效期48小时
return (string)$this->s3->createPresignedRequest($command, '+48 hours')->getUri();
}
上传分片
拿到对应的分片上传链接后,通过异步上传分片对象 Blob
export async function sendS3(url: string, data: Blob) {
const response: Response = await fetch(url, {
method: 'PUT',
body: data,
});
await response.text();
return response.status;
}
完成上传
通过计数器来判断上传成功的次数,并与分片数组的长度进行对比
达到相等时即可调用后端 完成上传
接口,让 AWS
将分片合并,并返回结果信息
/**
* 完成上传
*
* @param string $key 创建多段上传拿到的 Key
* @param string $id 创建多段上传拿到的 Id
*
* @return array
*
* @author hanmeimei
*/
public function complete(string $key, string $id)
{
$partsModel = $this->s3->listParts([
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
]);
$this->s3->completeMultipartUpload([
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
'MultipartUpload' => [
"Parts" => $partsModel["Parts"],
],
]);
return $partsModel->toArray();
}
完整的后端代码
<?php
namespace App\Kernel\Support;
use Aws\Result;
use Aws\S3\S3Client;
use Psr\Http\Message\RequestInterface;
/**
* Class S3MultipartUpload
*
* @author hanmeimei
*
* @package App\Kernel\Support
*/
class S3MultipartUpload
{
/**
* @var S3MultipartUpload
*/
private static $instance;
/**
* @var S3Client;
*/
private $s3;
/**
* @var string
*/
const BUCKET = 'bucket';
/**
* Get Instance
*
* @return S3MultipartUpload
*
* @author viest
*/
public static function getInstance(): S3MultipartUpload
{
if (!self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
/**
* 创建多段上传
*
* @param array $fileInfo
*
* @return array
*
* @author hanmeimei
*/
public function create(array $fileInfo)
{
$fileInfo['name'] = $this->generateFileName($fileInfo['name']);
$res = $this->s3->createMultipartUpload([
'Bucket' => self::BUCKET,
'Key' => $this->getPrefix() . $fileInfo['name'],
'ContentType' => $fileInfo['type'],
'Metadata' => $fileInfo
]);
return [
'id' => $res->get('UploadId'),
'key' => $res->get('Key'),
];
}
/**
* 为某个分段生成预签名url
*
* @param string $key
* @param string $id
* @param int $number
* @param int $length
*
* @return string
*
* @author hanmeimei
*/
public function part(string $key, string $id, int $number, int $length)
{
$command = $this->s3->getCommand('UploadPart', [
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
'PartNumber' => $number,
'ContentLength' => $length
]);
// 预签名url有效期48小时
return (string)$this->s3->createPresignedRequest($command, '+48 hours')->getUri();
}
/**
* 完成上传
*
* @param string $key
* @param string $id
*
* @return array
*
* @author hanmeimei
*/
public function complete(string $key, string $id)
{
$partsModel = $this->s3->listParts([
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
]);
$this->s3->completeMultipartUpload([
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id,
'MultipartUpload' => [
"Parts" => $partsModel["Parts"],
],
]);
return $partsModel->toArray();
}
/**
* 终止上传
*
* @param string $key
* @param string $id
*
* @return bool
*
* @author hanmeimei
*/
public function abort(string $key, string $id)
{
$this->s3->abortMultipartUpload([
'Bucket' => self::BUCKET,
'Key' => $key,
'UploadId' => $id
]);
return true;
}
/**
* 获取图片路径前缀
*
* @return string
*
* @author hanmeimei
*/
private function getPrefix()
{
$prefix = env('APP_DEBUG') ? 'develop/video/' : 'video/';
return $prefix . auth()->user()->id . '/' . date('Ym') . '/';
}
/**
* 生成文件名
*
* @param string|NULL $name
*
* @return string
*
* @author hanmeimei
*/
private function generateFileName(string $name)
{
return uniqid() . strrchr($name, '.');
}
/**
* Upload constructor.
*/
private function __construct()
{
$this->s3 = new S3Client([
'version' => 'latest',
'region' => 'ap-northeast-1',
'profile' => 's3'
]);
}
/**
* Disable Clone
*/
private function __clone()
{
//
}
/**
* Disable Serialization
*/
private function __sleep()
{
//
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
想问下这种业务,一定要后端分片吗?有没有前端分片的方案,直传aws。
如果要是定时分片上传楼主有没有思路?