您将要创建 的内容
这个话题对我来说真的很有趣。在许多 Web 应用程序中,接受用户输入并将单个记录保存到数据库中是很常见的。但是当您的用户(或您)想要在一个命令中执行多个插入时呢?
进入这篇文章,它将演示如何创建一个csv模板和一个表单来上传 CSV 文件,以及如何将 CSV 解析为 mongoose 模型,并将其保存到mongodb数据库中。
本文假设您对 Mongoose 以及它如何与 MongoDB 交互有基本的了解。如果您不这样做,我建议您先阅读我的 Mongoose 简介 for MongoDB 和 node.js 文章。本文描述了 Mongoose 如何通过创建强类型模式来与 MongoDB 交互,模型是从该模式中创建的。如果您已经对 Mongoose 有很好的了解,那么让我们继续。
入门
首先,让我们实例化一个新的 Node.js 应用程序。在命令提示符中,导航到要托管 Node.js 应用程序的位置并执行以下命令:
mkdir csvimport cd csvimport npm init
我保留了所有默认设置,所以我的应用程序将以index.js
. 在创建和解析 CSV 文件之前,需要先进行一些初始设置。我想让它成为一个网络应用程序;为此,我将使用 Express 包来处理所有细节的服务器设置。在命令提示符下,通过运行以下命令安装 Express:
npm install express --save
由于此 Web 应用程序将通过 Web 表单接受文件,因此我还将使用 Express 子包 Express File Upload。让我们现在也安装它:
npm install express-fileupload --save
我现在已经完成了足够的初始配置来设置我的 Web 应用程序并创建一个基本网页,该网页将创建我的文件上传表单。
这是我index.js
设置我的网络服务器的文件:
var app = require('express')(); var fileUpload = require('express-fileupload'); var server = require('http').Server(app); app.use(fileUpload()); server.listen(80); app.get('/', function (req, res) { res.sendFile(__dirname + '/index.html'); });
此示例导入 Express 和 Express File Upload 库,配置我的 Web 应用程序以使用 File Upload,并***端口 80。此示例还在“/”处使用 Express 创建了一个路由,这将是我的 Web 的默认登录页面应用。此路由返回一个index.html
文件,其中包含允许用户上传 CSV 文件的 Web 表单。就我而言,我在本地计算机上运行,所以当我访问 http://localhost时 ,我将看到我在下一个示例中创建的表单。
这是我index.html
创建用于上传 CSV 文件的表单的页面:
<!DOCTYPE html> <html> <head> <title>Upload Authors</title> </head> <body> <p>Use the form below to upload a list of authors. Click <a href="/template">here</a> for an example template.</p> <form action="/" method="post" encType="multipart/form-data"> <input type="file" name="file" accept="*.csv" /><br/><br/> <input type="submit" value="Upload Authors" /> </form> </body> </html>
这个 HTML 文件包含两个重要的东西:
单击“/模板”的链接将下载一个 CSV 模板,该模板可以填充要导入的信息。
encType
具有设置为的表单multipart/form-data
和具有类型的输入字段file
接受具有“csv”扩展名的文件。
您可能已经注意到,HTML 引用了作者模板。如果您阅读我的猫鼬简介文章,我创建了一个作者模式。在本文中,我将重新创建此 Schema 并允许用户将作者集合批量导入我的 MongoDB 数据库。让我们看一下作者模式。不过,在我们这样做之前,您可能已经猜到了——我们需要安装 Mongoose 包:
npm install mongoose --save
创建模式和模型
安装 Mongoose 后,让我们创建一个新author.js
文件来定义 Author Schema 和 Model:
var mongoose = require('mongoose'); var authorSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, name: { firstName: { type: String, required: true }, lastName: String }, biography: String, twitter: { type: String, validate: { validator: function(text) { if (text !== null && text.length > 0) return text.indexOf('https://twitter.com/') === 0; return true; }, message: 'Twitter handle must start with https://twitter.com/' } }, facebook: { type: String, validate: { validator: function(text) { if (text !== null && text.length > 0) return text.indexOf('https://www.facebook.com/') === 0; return true; }, message: 'Facebook Page must start with https://www.facebook.com/' } }, linkedin: { type: String, validate: { validator: function(text) { if (text !== null && text.length > 0) return text.indexOf('https://www.linkedin.com/') === 0; return true; }, message: 'LinkedIn must start with https://www.linkedin.com/' } }, profilePicture: Buffer, created: { type: Date, default: Date.now } }); var Author = mongoose.model('Author', authorSchema); module.exports = Author;
创建 Author Schema 和 Model 后,让我们换个思路,专注于创建 CSV 模板,单击模板链接即可下载该模板。为了帮助生成 CSV 模板,我将使用 JSON to CSV 包。现在让我们安装它:
npm install json2csv --save
我现在要更新我之前创建index.js
的文件以包含“/template”的新路由:
var template = require('./template.js'); app.get('/template', template.get);
我只包含了附加到前一个index.js
文件的模板路由的新代码。
这段代码做的第一件事是包含一个新template.js
文件(接下来要创建)并为“/template”创建一个路由。此路由将调用文件get
中的函数template.js
。
随着 Express 服务器更新以包含新路由,让我们创建新template.js
文件:
var json2csv = require('json2csv'); exports.get = function(req, res) { var fields = [ 'name.firstName', 'name.lastName', 'biography', 'twitter', 'facebook', 'linkedin' ]; var csv = json2csv({ data: '', fields: fields }); res.set("Content-Disposition", "attachment;filename=authors.csv"); res.set("Content-Type", "application/octet-stream"); res.send(csv); };
该文件首先包含以前安装的json2csv
软件包。然后我创建并导出一个get
函数。此函数接受来自 Express 服务器的请求和响应对象。
在函数内部,我创建了一个要包含在 CSV 模板中的字段数组。这可以通过以下两种方式之一来完成。第一种方法(在此示例中完成)是创建要包含在模板中的字段的静态列表。第二种方法是通过从作者模式中提取属性来动态创建字段列表。
第二种方法可以使用以下代码完成:
var fields = Object.keys(Author.schema.obj);
我本来希望使用这种动态方法,但是当我不想将架构中的多个属性包含到我的 CSV 模板中时,它会变得有点复杂。在这种情况下,我的模板不包含_id
andcreated
属性,因为这些将通过代码填充。但是,如果您没有要排除的字段,动态方法也可以使用。
创建 CSV 模板
定义了字段数组后,我使用json2csv
包从我的javascript对象创建我的 CSV 模板。这个csv
对象将是这条路线的结果。
最后,使用res
Express 服务器的属性,我设置了两个标题属性,将强制下载authors.csv
文件。
此时,如果您要运行 Node 应用程序并在 Web 浏览器中导航到http://localhost ,则 Web 表单将显示一个下载模板的链接。单击下载模板的链接将允许您在authors.csv
上传之前下载要填充的文件。
以下是填充的 CSV 文件的示例:
name.firstName,name.lastName,biography,twitter,facebook,linkedin Jamie,Munro,Jamie is a web developer and author,,, Mike,Wilson,Mike is a web developer and Node.js author,,,
此示例在上传后将创建两个作者:我自己和几年前写过一本关于 Node.js 的书的朋友。您可能会注意到,每行的末尾是三个逗号“,,,”。这样做是为了简化示例。我没有填充社交网络属性(twitter
、facebook
和linkedin
)。
拼图开始拼凑起来,形成一幅画。让我们来看看这个例子的主要内容并解析那个 CSV 文件。该index.js
文件需要一些更新才能连接到 MongoDB 并创建一个接受文件上传的新 POST 路由:
var app = require('express')(); var fileUpload = require('express-fileupload'); var mongoose = require('mongoose'); var server = require('http').Server(app); app.use(fileUpload()); server.listen(80); mongoose.connect('mongodb://localhost/csvimport'); app.get('/', function (req, res) { res.sendFile(__dirname + '/index.html'); }); var template = require('./template.js'); app.get('/template', template.get); var upload = require('./upload.js'); app.post('/', upload.post);
配置数据库连接和新的 POST 路由后,就可以解析 CSV 文件了。幸运的是,有几个很棒的库可以帮助完成这项工作。我选择使用fast-csv
可以通过以下命令安装的包:
npm install fast-csv --save
POST 路由的创建类似于post
从文件调用函数的模板路由upload.js
。不必将这些函数放在单独的文件中;但是,我喜欢为这些路由创建单独的文件,因为它有助于使代码保持良好和有条理。
提交数据
最后,让我们创建一个upload.js
文件,其中包含 post
提交之前创建的表单时调用的函数:
var csv = require('fast-csv'); var mongoose = require('mongoose'); var Author = require('./author'); exports.post = function (req, res) { if (!req.files) return res.status(400).send('No files were uploaded.'); var authorFile = req.files.file; var authors = []; csv .fromString(authorFile.data.toString(), { headers: true, ignoreEmpty: true }) .on("data", function(data){ data['_id'] = new mongoose.Types.ObjectId(); authors.push(data); }) .on("end", function(){ Author.create(authors, function(err, documents) { if (err) throw err; }); res.send(authors.length + ' authors have been successfully uploaded.'); }); };
这个文件中发生了很多事情。前三行包含解析和保存 CSV 数据所需的必要包。
接下来,post
定义并导出函数以供index.js
文件使用。在这个函数内部是魔法发生的地方。
该函数首先检查请求正文中是否包含文件。如果没有,则返回错误,指示必须上传文件。
上传文件后,对该文件的引用将保存到名为authorFile
. 这是通过访问files
数组和数组中的file
属性来完成的。该file
属性匹配index.html
我在示例中首先定义的文件输入名称的名称。
我还创建了一个authors
数组,将在解析 CSV 文件时填充该数组。该数组将用于将数据保存到数据库中。
fast-csv
现在通过利用该fromString
函数调用该库。此函数接受 CSV 文件作为字符串。我已经从authorFile.data
属性中提取了字符串。该data
属性包含我上传的 CSV 文件的内容。
我在该fast-csv
函数中包含了两个选项:headers
和ignoreEmpty
. 这些都设置为true
. 这告诉库 CSV 文件的第一行将包含标题,并且应该忽略空行。
配置好选项后,我设置了两个监听函数,当data
事件和end
事件被触发时调用。data
对于 CSV 文件的每一行,都会调用一次该事件。此事件包含已解析数据的 JavaScript 对象。
我更新了这个对象以包含_id
Author Schema 的属性和一个新的ObjectId
. 然后将该对象添加到authors
数组中。
当 CSV 文件被完全解析后,end
事件被触发。在事件回调函数内部,我调用create
Author 模型上的函数,将数组传递authors
给它。
如果尝试保存数组时发生错误,则会引发异常;否则,将向用户显示一条成功消息,指示有多少作者已上传并保存到数据库中。
如果您想查看完整的源代码,我已经使用该代码创建了一个GitHub 存储库。
结论
在我的示例中,我只上传了几条记录。如果您的用例需要能够上传数千条记录,那么将记录保存在较小的块中可能是个好主意。
这可以通过多种方式完成。如果我要实现它,我建议更新data
回调函数以检查作者数组的长度。当数组超过您定义的长度时,例如 100,调用Author.create
数组上的函数,然后将数组重置为空。然后,这会将记录保存为 100 个块。确保将最终create
调用留在end
回调函数中以保存最终记录。