前言
文件上传是前端开发中常见的功能。最近为了本地化部署,需要使用 minio
(一个开源的 S3 兼容的对象存储)来替代一直使用的 阿里云 OSS
,
研究了一下关于文件上传的实现,记录一下踩的坑以及整个文件上传的过程。
文件上传的实现

出于安全考虑,标准的文件上传流程通常包含以下四个步骤:
- 前端向自己的服务端发起请求,获取文件上传所需的授权信息
- 服务端进行身份验证,验证通过后生成一个带有过期时间的预签名 URL
- 前端获取预签名 URL 后,直接通过该 URL 将文件上传至对象存储服务
- 文件上传成功后,对象存储服务可以调用预先配置的回调接口,执行回调函数
其中,阿里云 oss 与 minio 的实现略有不同,回调也略有差异,下面分别介绍。
上传方法差异
阿里云 oss
antd
的 upload
组件默认是使用 post
方法也就是表单上传来上传文件的。

在使用这种方式上传文件时,有一个重要的约定:file必须为最后一个表单域
,后面如果还有字段会直接丢弃不解析
const formData = new FormData();
formData.append('name',filename);
formData.append('policy', data.policy);
formData.append('OSSAccessKeyId', data.ossAccessKeyId);
formData.append('success_action_status', '200');
formData.append('signature', data.signature);
formData.append('key', data.dir + filename);
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
formData.append('file', file);
这是因为使用 PostObject
进行表单上传时, Content-Length
并不是必须的。
也就是说 oss
并不知道你这个文件的大小,没办法提前给你把空间分配出来让你写入。它只能一小段一小段的数据接收,收完一段存一份元数据,最后汇总。
所以,当拿到 file 字段后,就认为后面全都是数据,就会去走数据存储的流程。
minio
minio
作为一个 S3
兼容的对象存储,它支持 S3
的 API
。为了以后迁移方便,最好使用 @aws-sdk/client-s3
来上传文件。
在这个 sdk
中,关于对象上传的操作只有
PutObjectPart
: 上传分段putObject
: 普通上传
所以我们在使用 @aws-sdk/client-s3
生成预签名 url
时,只能通过 PutObjectCommand
来生成。
下面是一个生成预签名 url
的示例:
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3Client = new S3Client({
endpoint: "your-endpoint",
region: "your-region",
credentials: {
accessKeyId: "your-access-key-id",
secretAccessKey: "your-secret-access-key"
},
forcePathStyle: true
});
// 生成包含时间戳和文件ID的JWT令牌
const verificationToken = sign(
{
fileId,
userId: user?.id,
timestamp: Date.now(),
},
"your-jwt-secret-key",
{
expiresIn: '2h', // 令牌2小时后过期
algorithm: 'RS256',
}
);
const command = new PutObjectCommand({
Bucket: "your-bucket-name",
Key: key,
ContentType: 'application/octet-stream',
Metadata: {
'x:user_id': user?.id,
'x:verification_token': verificationToken,
},
});
const url = await getSignedUrl(s3Client, command, {
expiresIn: 60 * 3 // 3分钟
});
前端拿到 url
后,直接上传文件即可,如果使用的 antd
的 Upload
组件,需要修改以下地方
- 接口是
put
请求,所以需要修改method
为PUT
- 拿到
url
后,不需要通过FormData
上传,直接通过PUT
请求把file
放到body
里即可
customRequest: async (options) => {
const response = await fetch(options.action, {
headers: options.headers,
method: options.method,
body: options.file
});
options?.onSuccess?.({ ...response, file_id: options.data?.key })
}
上传回调差异
阿里云的上传回调是一个很好用功能,在执行完上传文件后,oss
会执行回调接口,并直接把回调返回的结果返回给前端,可以有效降低
前端的逻辑复杂度和网络消耗。

但是这个不是一个通用方案,如果你想在 minio
上使用上传回调功能,你需要配置对应的 webhook
功能

最后再在你的 bucket
上订阅你这个 webhook
即可
