这个话题对我来说是一个非常愉快的话题。 在许多Web应用程序中,接受用户输入并将单个记录保存到数据库中是很常见的。 但是,当您的用户(或您)想要在单个命令中执行多次插入时,该怎么办?
输入本文,它将演示如何创建CSV模板和上载CSV文件的表单,以及如何将CSV解析为Mongoose模型并将其保存到MongoDB数据库。
本文假定您对Mongoose及其与MongoDB的交互方式有基本的了解。 如果您不这样做,我建议您先阅读我的MongoDB简介(关于MongoDB和Node.js) 。 本文介绍了如何通过创建从其创建模型的强类型模式来使Mongoose与MongoDB进行交互。 如果您已经对猫鼬有了很好的了解,那么让我们继续吧。
入门
首先,让我们实例化一个新的Node.js应用程序。 在命令提示符中,导航到要托管Node.js应用程序的位置,然后执行以下命令:
mkdir csvimport
cd csvimport
npm init
我保留了所有默认值,因此我的应用程序将从index.js
开始。 在创建和解析CSV文件之前,首先需要完成一些初始设置。 我想使它成为一个Web应用程序; 为此,我将使用Express包来处理所有基本的服务器设置。 在命令提示符下,通过运行以下命令来安装Express:
npm install express --save
由于此Web应用程序将通过Web表单接受文件,因此我还将使用Express子包Express File Upload。 让我们现在也安装它:
npm install express-fileupload --save
现在,我已经完成了足够的初始配置,以设置我的Web应用程序并创建一个基本的Web页面,该页面将创建我的文件上传表单。
这是设置我的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文件上载库,将我的Web应用程序配置为使用文件上载,并侦听端口80。此示例还使用Express在“ /”处创建了一条路由,这将是我的Web的默认登录页面。应用。 此路由返回一个index.html
文件,该文件包含允许用户上传CSV文件的Web表单。 就我而言,我正在本地计算机上运行,因此当我访问http:// localhost时 ,将看到在下一个示例中创建的表单。
这是我的index.html
页面,该页面创建用于上传CSV文件的表单:
<!DOCTYPE html>
<html lang="en">
<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文件包含两个重要内容:
- 指向“ / template”的链接,单击该链接将下载一个CSV模板,该模板可以填充要导入的信息。
-
encType
设置为multipart/form-data
,输入字段的file
类型接受扩展名为“ csv”的文件。
您可能已经注意到,HTML引用了Author模板。 如果您阅读了《猫鼬简介》一文,则创建了一个Author Schema。 在本文中,我将重新创建该架构,并允许用户将一组作者批量导入到我的MongoDB数据库中。 让我们看一下Author Schema。 但是,在执行此操作之前,您可能已经猜到了-我们需要安装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;
创建了“作者模式”和“模型”之后,让我们进行切换,并集中精力创建可通过单击模板链接下载的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”创建路由。 此路由将在template.js
文件中调用get
函数。
在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);
我本来想使用这种动态方法,但是当我不想在Schema到CSV模板中包含多个属性时,它会变得有些复杂。 在这种情况下,我的模板不包含_id
和created
属性,因为这些属性将通过代码填充。 但是,如果您没有要排除的字段,则动态方法也将起作用。
创建CSV模板
定义了字段数组后,我使用json2csv
包从JavaScript对象创建CSV模板。 此csv
对象将是此路由的结果。
最后,使用Express服务器中的res
属性,我设置了两个标头属性,这些属性将强制下载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路由的创建与模板路由类似,该模板路由从upload.js
文件调用post
函数。 无需将这些功能放在单独的文件中; 但是,我喜欢为这些路由创建单独的文件,因为它有助于保持代码的美观和井井有条。
提交资料
最后,让我们创建一个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文件时填充该数组。 该数组将用于将数据保存到数据库。
现在,通过利用fromString
函数调用fast-csv
库。 此函数接受CSV文件作为字符串。 我已经从authorFile.data
属性中提取了字符串。 data
属性包含我上传的CSV文件的内容。
我为fast-csv
函数提供了两个选项: headers
和ignoreEmpty
。 这些都设置为true
。 这告诉库,CSV文件的第一行将包含标题,并且空行应被忽略。
通过配置选项,我设置了两个侦听器函数,这些函数在触发data
事件和end
事件时被调用。 CSV文件的每一行都会调用一次data
事件。 此事件包含已解析数据JavaScript对象。
我将此对象更新为包括具有新ObjectId
的Author Schema的_id
属性。 然后将该对象添加到authors
数组。
CSV文件完全解析后,将触发end
事件。 在事件回调函数中,我在Author模型上调用create
函数,将authors
数组传递给它。
如果尝试保存数组时发生错误,则会引发异常;否则,将抛出异常。 否则,将向用户显示一条成功消息,指示已经上传了多少作者并将其保存到数据库中。
如果您想查看完整的源代码,我已经使用代码创建了一个GitHub存储库 。
结论
在我的示例中,我仅上传了几条记录。 如果您的用例需要能够上载数千条记录,则最好将记录保存在较小的块中。
这可以通过几种方法完成。 如果要实现它,我建议更新data
回调函数以检查authors数组的长度。 当数组超出定义的长度(例如100)时,请在数组上调用Author.create
函数,然后将数组重置为空。 然后,这将以100为一组保存记录。确保将最终的create
调用保留在end
回调函数中以保存最终的记录。
请享用!