当我们在前端发送文件的时候,首先把文件的数据整合起来,在业务层汇聚到接口层,接口层使用参数接收一个或多个文件:
export function useUploadFile() {
return useMutation({
mutationFn: async ({
bucket,
files,
}: {
bucket: string;
files: File[];
}) => {
const formData = new FormData();
files.forEach((file) => {
formData.append('files', file, encodeURIComponent(file.name));
});
const res = await http.post<UploadFileApiResponse>(
`${uploadFilePath}/${bucket}`,
formData,
{
headers: {
'Accept-Charset': 'utf-8',
},
}
);
return res;
},
});
}
在上面的代码中,接收的是多个文件,使用File[]类型进行约束。
拿到文件后,先new一个FormData,用来发送文件,当文件的发送“容器”。那么多个文件就需要把它们都放入“容器”里才行。使用forEach进行放入。
使用formdata的append函数即可添加。注意append函数的第一个参数,那是文件的描述符号,可以随便写,当然,开发过程中需要遵守开发标准,后端接收的时候用什么描述符号,就填什么。
最关键的是第三个参数,第三个参数是调用了encodeURIComponent函数,这个函数会把字符转换为URL-encoded UTF-8 , 也就是URL编码的utf8格式。
那么就需要说一下nestjs的文件接收的编码转换的坑了。
使用nestjs接收文件时,使用:
@Post('upload/:bucket')
@UseInterceptors(FilesInterceptor('files', 100))
async uploadFile(
@Param('bucket') bucket: string,
@UploadedFiles() files: Express.Multer.File[],
) {}
这样的方式接收文件。
问题在于它接收的时候,会把文件名从utf8转为ISO-8859-1 (Latin-1 ) 编码,这导致文件的中文名会变成:“çµå¶é”这样的字符。
所以为了避免这样的问题,就需要
formData.append('files', file, encodeURIComponent(file.name));
在这里调用encodeURIComponent对原始的文件名进行编码,这样到了后端,文件的编码形式就不会改变。
后端使用文件名时,如下方法即可:
files.map(async (f) => {
// 解码前端发送的url编码形式,正确的将文件名传输过来
const newfilename = decodeURIComponent(f.originalname);
// 为minIO的每个文件添加一个单独的uuid,防止文件名重名,在获取文件信息以及下载文件时予以清除
const UUID = randomUUID();
const safeFilename = newfilename.replace(/[“”#]/g, '').trim();
this.minioService.validateFile(f);
// 保存文件信息到mysql数据库
await this.fileService.saveFile(
safeFilename,
UUID,
f.mimetype,
f.size,
);
// 分片上传文件到 MinIO
await this.minioService.uploadFile(UUID, f, bucket);
// 更新任务的已上传文件数
const task = uploadTasks.get(taskId);
if (task) task.uploadedFiles++;
}),
);
可以看到在第三行,调用了decodeURIComponent对文件的原始名称进行解码,即可得到正确的文件名。