最近,我正在开发一个Node.js的CLI,它可以帮助我的团队更容易地用Markdown写文章。该流程要求对本地的*.md文件进行解析、格式化、转换为.docx*,然后作为Google Doc文件上传到Google Drive,所有这些都需要从终端运行一条命令。
我以几种不同的方式来处理这个项目,最初希望能用无服务器函数来处理后端。在这条路上我遇到了很多死胡同,最终决定建立一个Express服务器,并最终将其托管在Heroku上。
为了将.docx文件内容从我的CLI上传到安全的后端端点,然后上传到用户认证的Google Drive上,我需要一种方法来直接从Node.js中POST multipart/form-data (无需浏览器)。这需要几个第三方库,而且要让它们都能很好地配合起来是相当困难的。互联网上没有很好的资源可以提供帮助,而且还涉及到很多试验和错误。这篇文章将解释我是如何做到的。
前提条件
为了跟上这个教程,你需要以下条件:
- 在你的机器上安装了Node.js,同时还有一个软件包管理器,例如
[npm](https://www.npmjs.com/) - 一个文本编辑器
设置你的应用程序结构
在你的终端或命令提示符中,导航到你的一般项目或开发目录,并运行以下命令:
mkdir multipart_demo
cd multipart_demo
mkdir node_app
npx express-generator express_app
cd express_app
npm install
这些命令将创建一个目录,容纳两个项目:你的Node.js应用程序和你的Express API。它们还将为Express后端提供支架(用 express-generator),并为你的新Express应用程序安装所需的依赖项。
构建你的Node.js应用程序的支架
在你的终端,导航到你的父目录multipart-demo,然后进入你的Node.js应用程序,执行以下命令:
cd ..
cd node_app
初始化一个新的Node.js应用程序:
npm init -y
安装你在这个项目中需要的第三方依赖项:
npm install form-data axios
POST 你将使用form-data 库在你的Node.js应用程序中用键/值对创建一个 "表单"。axios 将被用来将表单数据上传到你的Express应用程序。
编写代码来上传你的文件
使用你喜欢的文本编辑器,打开在你的node_app文件夹中创建的名为index.js的文件。如果这个文件没有为你创建,现在就创建它。
在这个文件中,在顶部添加以下代码:
const fs = require('fs');
const axios = require('axios');
const FormData = require('form-data');
这段代码将导入你安装的两个第三方依赖,以及允许你与文件系统互动的本地fs 模块 。
在这几行下面,添加以下函数和函数调用:
const upload = async () => {
try {
const file = fs.createReadStream('./myfile.txt');
const title = 'My file';
const form = new FormData();
form.append('title', title);
form.append('file', file);
const resp = await axios.post('http://localhost:3000/upload', form, {
headers: {
...form.getHeaders(),
}
});
if (resp.status === 200) {
return 'Upload complete';
}
} catch(err) {
return new Error(err.message);
}
}
upload().then(resp => console.log(resp));
upload() 函数是一个异步函数。在该函数内部有一个try/catch 块。这意味着该函数将 "尝试 "在try 块内做任何事情。如果在任何时候代码遇到了错误,它将立即执行catch 块,在那里你可以访问违规的错误。
它在try 块中尝试做的第一件事是创建两个变量:file 和title 。
第3行,即创建file 变量的地方,之所以突出显示,是因为你需要用你想上传的文件的路径来替换显示的文件路径。这个变量现在代表你的文件的可读流。
如果你只是想跟着做,而且心中没有特定的文件,可以创建一个名为myfile.txt的新文件,里面有一些随机的文本,并将其保存在index.js旁边的node_app文件夹中。
title 变量是你将存储你要上传的文件的标题的地方--你可以把它改成你喜欢的任何字符串值。
在这些变量的下面是创建表单本身的地方。两个键/值对被附加到表单上:一个是文件,一个是它的标题。你可以以这种方式向form 对象添加代表你的表单数据的任何数量的键/值对。
接下来,整个表单被发布到你的 Express 后台(在本教程后面你将创建一个端点),使用axios 。请注意,你正在将一个带有头信息的选项对象传递给axios.post() 方法。form-data 库通过给你一个.getHeaders() 方法来确保你的头信息被正确设置,这个方法将返回适合你的表单数据的头信息。
如果没有错误,那么一个 "上传完成 "的消息将被记录到控制台。否则,错误信息将被记录下来。
处理表单数据
现在你已经成功发送了你的文件,你需要能够在你的后台处理它。
在你的终端,导航到你的Express应用程序:
cd ..
cd express_app
Multer是一个Node.js中间件,可以处理multipart/form-data 。运行以下命令来安装multer 包:
npm install multer
在你的文本编辑器中,打开multipart_demo/express_app/routes文件夹中的index.js文件。删除该文件的内容,并将其替换为以下内容:
var express = require('express');
var router = express.Router();
const multer = require('multer');
const upload = multer({ dest: os.tmpdir() });
router.post('/upload', upload.single('file'), function(req, res) {
const title = req.body.title;
const file = req.file;
console.log(title);
console.log(file);
res.sendStatus(200);
});
module.exports = router;
在这段代码中,有几件事需要注意--重要的几行已经被突出显示。
在第5行,在导入multer 包后,你准备通过调用multer() 函数来使用Multer,向它传递一个带有dest 键的选项对象,并将返回的对象保存到一个叫做upload 的变量中。关于配置Multer的其他方法,包括将文件保存到内存而不是磁盘上,请参见文档。
在这种情况下,你要指定一个dest 的值,因为Multer需要把你上传的文件保存在某个地方。对于我的应用程序,我只是暂时需要这个文件,所以我把目的地设置为我服务器的*/tmp*文件夹。如果你需要上传的文件持续存在,你可以把它们存储在你服务器上的另一个目录中,只要你在这个对象中指定路径即可。
在第7行,在执行API端点的回调函数之前,你要调用upload 对象上的single() 方法。你可以在upload 对象上调用许多方法,这些方法适用于不同的数据配置。在这个例子中,由于你只上传一个文件,你将使用single() 方法。
此时,端点的回调函数中可用的req 对象将与你所习惯的略有不同。尽管在你提出POST 请求时没有将任何body 值附加到表单上,你的非文件表单字段的值将在req.body 。你上传的文件将可在req.file 。在上面的代码中,这两个对象都被记录在控制台中。为了看看它们是什么样子的,请测试一下这个项目。
测试该应用程序
在你的终端,确保你仍然在express_app 目录内,然后运行以下命令:
npm start
这将启动你在PORT 3000 的本地服务器。
打开一个新的终端窗口,并导航回你的Node.js项目:
cd <YOUR PARENT DIRECTORY PATH>/multipart_demo/node_app
要执行你的脚本,运行以下命令:
node index.js
如果POST 请求成功,你会在终端看到 "Upload complete "信息,否则你会看到一个错误信息:

在运行你的Express应用程序的终端中再检查一下。你会看到你的文件的标题,以及代表它的对象。你会看到,如果你把文件存储在磁盘上,会有一个path ,你可以使用这个路径来创建另一个可读流,就像我为了把文件上传到Google Drive而需要的那样:
