NestJS 上传文件中文名乱码
背景
在使用 NestJS 写上传接口时,发现通过multipart/form-data上传文件的文件名是中文时,服务器读取到的是乱码,示例如下:
上传云海.jpg,服务器返回:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | {"code": 0,
 "message": "请求成功",
 "content": {
 "picture": {
 "extension": ".jpg",
 "name": "äºæµ·.jpg",
 "origin": "http://localhost:3000/static/picture/2023/07/1690774175213.jpg",
 "size": 1831902,
 "id": 65,
 "mid": 0,
 "link_id": "0",
 "status": 1,
 "created_at": "2023-07-30T19:29:35.224Z",
 "updated_at": "2023-07-30T19:29:35.224Z"
 }
 }
 }
 
 | 
使用英文名就是正常的,并且,使用 Apifox 测试接口,也是正常的。
环境
- 
Node:v18.16.1 
- 
NestJS:10.1.7 
使用的 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 中提供的方法和搜索引擎中提供的方法是类似的:
| 1
 | file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8');
 | 
在 NestJS 中,我们可以自定义一个管道 Pipe 来处理这个问题。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
 @Injectable()
 export class FileNameEncodePipe implements PipeTransform {
 transform(value: Express.Multer.File, metadata: ArgumentMetadata) {
 value.originalname = Buffer.from(value.originalname, 'latin1').toString('utf8');
 return value;
 }
 }
 
 
 | 
回到浏览器测试,发现正常了。
但是 Apifox 测试又不行了。因此需要稍加改造:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | 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;
 }
 }
 
 | 
这里是对原文件名进行一个正则判断,如果原来的文件名可以通过latin1方式正确解码出来,就不处理。
至此,浏览器和 Apifox 测试都正常了。
后记
目前这个问题在不修改源码的情况下只能自己处理了,或者等包作者更新。如果你有更好的解决方法,欢迎交流。