问题
服务端使用的是NestJS,在处理上传需求的时候,发现multipart/form-data
上传文件的文件名是中文时,服务器读取到的是乱码

英文是正常的,中文是乱码的
定位问题
环境
- Node: 22.14.0
- NetsJS: 10.4.2
Express 的 Multer 包。
代码排查
由于上传文件使用的是 Multer ,因此找到了 Github 上面 Multer 的代码仓库,然后在 Issues 中搜索,发现了类似的情况:
Issue with UTF-8 characters in filename · Issue #1104 · expressjs/multer (github.com)
这个 issue 中提到了另一个包 busboy,Multer 就是通过这个包来解析 FormData 的,原来是这个 busboy 的问题,在该仓库的 Issues 中也能发现有人提了这个问题:
Parsing fails if filename contains UTF-8 characters · Issue #20 · mscdex/busboy (github.com)
busboy 将配置项defParamCharset
的默认值从utf8
改为了latin1
,从而导致了这个问题(居然是一个 patch 更新导致很多人的代码出问题了然后就被指责了),需要手动设置一下。问题是这个 busboy 是由 Multer 负责创建的,同时 Multer 并没有提供defParamCharset
这个选项,更别说在内部使用了 Multer 的 NestJS 了。
解决问题
目前 issue 中提供的方法和搜索引擎中提供的方法是类似的:
ts
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8');
在 NestJS 中,我们可以自定义一个管道 Pipe 来处理这个问题。
首先定义一个管道common/pips/file-name-encode.pipe.ts
ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class FileNameEncodePipe implements PipeTransform {
transform(value: Express.Multer.File, metadata: ArgumentMetadata) {
if (!/[^\u0000-\u00ff]/.test(value.originalname)) {
value.originalname = Buffer.from(value.originalname, 'latin1').toString(
'utf8',
);
}
return value;
}
}
然后在controller就可以用了
ts
import { FileNameEncodePipe } from '@/common/pipes/file-name-encode.pipe';
async uploadFile(
@UploadedFile(
new FileNameEncodePipe(),
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 30 * 1024 * 1024 }),
new FileTypeValidator({ fileType: 'application/pdf' }),
],
}),
)
file: Express.Multer.File,
) {
return this.uploadService.handleFileUpload(file);
}
这里是对原文件名进行一个正则判断,如果原来的文件名可以通过latin1
方式正确解码出来,就不处理。
至此,浏览器和 Apifox 测试都正常了。