实际需求
在实际的项目开发过程中,文件上传有时候,我们只需要读取文件的内容,而不需要保存到本地。因为可能生产环境的服务器并不归开发人员管,你可能连【写】的权限都没有。所以读取文件这种操作,一般是,文件上传 -> 保存到buffer中 -> 用streamBuffer来接收数据。今天要讲的是,node.js中文件上传的一个模块【multer】使用流的方式来读取数据。
前置知识
我们需要用到 multer中的MemoryStorage
,它可以将文件作为一个buffer保存到内存中;然后我们用node中的stream来接收这段buffer,并解析字节。
实现步骤
- 在express的route文件中,导入multer,并将storage设置成memoryStorage
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
-
在对应的上传url中,添加文件上传的请求头拦截
app.route('/api/test/uploadFile').post(upload.single("file"), TestController.uploadFile);
这里的
upload.single("file")
的作用就是将上传的文件以buffer的形式保存到请求参数里面的file属性中。在这里需要补充一点,如果你的上传需要做权限校验,那权限校验的方法应该写在
upload.single("file")
后面。由于前端的文件上传的请求头content-type
是multipart/form-data
,而一般的数据传输的请求头的content-type是application/json
。如果先去校验权限的话,会出现无法解析到session数据,无法获取请求头或者request中的数据;如果先调用权限校验方法,则文件上传的数据会因为请求转发而丢失。所以这时候最恰当的做法是,先接收文件的二进制数据,再设置请求头从而获取到request中的数据进行权限校验。 -
在TestController中用流将数据从字节转换成具体的字符,实现文件读取。
const uploadFile = async (req, res) => { // 先检查文件的buffer是否存在,文件数据有没有保存到Buffer中 if (_.isEmpty(req.file)) { return res.status(400).send({ result: 'error', message: 'file is empty.' }); } let fileData = []; // 定义一个数据,用于保存文件的内容 let len = 0; // 保存流的长度 const bufferStream = new stream.PassThrough(); // 声明一个PassThrough流,将输入字节传到输出 bufferStream.end(req.file.buffer); // 初始化的流,一定为空,这时候让它读取文件的流 // 当有数据,就写入到数组中 bufferStream.on('data', (chunk) => { fileData.push(chunk); len += chunk.length; }); // 这个流到尽头了 bufferStream.on('end', async () => { // 将文件流的内容合并起来,并从字节转成String字符 let content = Buffer.concat(fileData, len).toString(); // 下面做一些具体的读取文件业务操作,你可以调用你的service层,这样会比较合理 ...... // 你需要在这里将响应结果返回,写在外面是没用的 return res.json({result: 'SUCCESS'}); } // 这个只有在流的读写操作错误的时候才会触发 bufferStream.on('error', (error) => { console.log(error); } }
看到这里你可能会觉得很疑惑,为什么我要把流写在这里,不写在service层,还要在流里面将结果返回?因为这个流的事件监听是异步的,这个异步你完全不知道它什么时候返回,更没有用Promise或者await去等待它的结果。事件监听没有回调函数,如果你需要 读取数据 -> 处理数据 -> 返回处理后的数据,也就是你要返回数据给前端,那你就只能将流和事件监听写在Controller这里。
如果不需要什么返回值,我只要你读取数据,然后给我保存就行了,我Controller不等这流的处理结果是什么,先随便响应点什么给前端意思意思就行了。那写在service层就完美了。