我正在尝试在nodejs中实现一个模块(刚刚开始在nodejs中工作),其中有以下要求
当前用于休息api的框架是"表达":" ~4.2.0"和multer文件上传。
现在我在 app.js
中配置了如下所示的multerapp.use(multer({
onFileUploadData : function(file, data){
console.log('onFileUploadData Called with data - '+ data);
}
}));
在我的路线文件中,我有一个如下所示的帖子端点
app.post('/sample.csv',lead.processCSV);
此路由正在从下面的ajax调用中调用
$.ajax({
xhrFields: {withCredentials: true},
url: '/sample.csv',
type: 'POST',
success: function (data) {
$scope.handleResponse(data);
},
error: function (error, xhr) {
angular.element('#csvUploadBusyIcon').hide();
alert('Oops! Upload failed');
},
data: formData,
cache: false,
contentType: false,
processData: false
});
现在我想获取csv文件的内容,即当所有内容都已加载后,我应该处理 lead.processCSV 方法。
我是否还需要csv文件的任何其他模块,或者在我的情况下multer就足够了?
任何正确方向的建议/指导都会有所帮助。提前致谢。
答案 0 :(得分:20)
有一个很棒的节点项目给了我很多帮助。你应该check it out 我们将要使用的是他们的csv-parse模块。它能够将流作为输入并逐行读取而不会阻塞事件循环,因此基本上在处理文件时,服务器不会被卡住,其他请求仍然可以正常处理。
由于您说您刚刚开始使用nodejs,因此您应该快速搜索并了解中间件在请求处理过程中的工作方式。 作为请求处理的简化,中间件是一个函数(req,res,next)。使用req,您可以获得请求数据。使用res,您可以发送响应,然后将req和res对象发送到下一个中间件。这样您就可以处理部分请求,并且流的最后一个中间件将向客户端发送响应(例如res.send(200))
Multer({...})调用返回一个中间件函数。当请求到达此中间件时,multer将尝试下载用户在post请求中发送的任何文件。当你说app.use(Multer({...}))时,你要求multer尝试从任何包含文件的帖子请求中下载文件。如果并非所有路由都希望上传文件,则存在安全风险。
好的,就是说,这是我为处理您的用例而编写的示例代码:
//Important Security advice:
//don't add multer as a middleware to all requests.
//If you do this, people will be able to upload files
//in ALL YOUR 'post' handlers!!!
var Multer = require('multer');
var Parse = require('csv-parse');
var fs = require('fs')
function parseCSVFile(sourceFilePath, columns, onNewRecord, handleError, done){
var source = fs.createReadStream(sourceFilePath);
var linesRead = 0;
var parser = Parse({
delimiter: ',',
columns:columns
});
parser.on("readable", function(){
var record;
while (record = parser.read()) {
linesRead++;
onNewRecord(record);
}
});
parser.on("error", function(error){
handleError(error)
});
parser.on("end", function(){
done(linesRead);
});
source.pipe(parser);
}
//We will call this once Multer's middleware processed the request
//and stored file in req.files.fileFormFieldName
function parseFile(req, res, next){
var filePath = req.files.file.path;
console.log(filePath);
function onNewRecord(record){
console.log(record)
}
function onError(error){
console.log(error)
}
function done(linesRead){
res.send(200, linesRead)
}
var columns = true;
parseCSVFile(filePath, columns, onNewRecord, onError, done);
}
//this is the route handler with two middlewares.
//First: Multer middleware to download file. At some point,
//this middleware calls next() so process continues on to next middleware
//Second: use the file as you need
app.post('/upload', [Multer({dest:'./uploads'}), parseFile]);
我希望这有帮助。确保了解路由中间件如何在节点中工作:它们是高质量代码的关键。
马塞尔
答案 1 :(得分:0)
我有一个类似的请求来处理csv文件,我尝试实现您的解决方案:它可以工作,但是只要我将其与控制台日志一起使用即可。我试图将'record'变量存储在一个名为'results'的数组上,但是我只得到了一个空数组[],在显示了这个空数组之后,我收到了console.log响应,其中显示了已解析的CSV数据。
所以这似乎是一个同步问题。.我的意思是,csv文件的处理需要一段时间。因此,我尝试压缩您的代码并将其转换为Promise,然后执行它。因此,在执行了诺言之后,就可以使用我的数组了。
title, type, value, category
Loan, income, 1500, Others
Website Hosting, outcome, 50, Others
Ice cream, outcome, 3, Food
注意:与您的情况有些不同:我从死记号'/ import收到一个文件。我正在使用Insomnina Designer应用程序发送包含一个名为importFile的文件的多部分表单正文
注意:我导入了您使用的相同库,并且也使用了中间件的概念
注意:在这种情况下,我只需要一个文件,所以我使用了multer({dest:'./upload'}).single('importFile')。也可以使用.any()。
注意:我使用的是打字稿,因此对于JS来说,在某些变量声明后删除即可:@type,例如
注意:我离开了选项1-仅使用数组,而选项2-使用对象。
const results: object[] = [];
becomes:
const results = [];
我们去看代码:
import { Router, Request, Response } from 'express';
import csv from 'csv-parse';
import multer from 'multer';
import fs from 'fs';
// used on option 2 due typescript
interface CSVTransactionDTO {
title: string;
value: number;
type: 'income' | 'outcome';
category: string;
}
app.post(
'/import', // route name
multer({ dest: './upload' }).single('importFile'), // middleware to download one file (csv)
async (request: Request, response: Response) => {//last middleware with CSV parsing with arrow function
const filePath = request.file.path;
let rowCounter = 0;
const results: string[] = [];// option 1
const newTransactions: CSVTransactionDTO[] = [];// option 2
function parseCSVPromise(): Promise<void> {
return new Promise((resolve, reject) => {
const ConfigCSV = {
// delimiter:';',//other delimiters different from default = ','
from_line: 2, // data starts here
trim: true, // ignore white spaces immediately around the delimiter (comma)
};
fs.createReadStream(filePath)
.pipe(csv(ConfigCSV))
.on('data', /* async */ row => {
rowCounter += 1;// counter of how many rows were processed
// console.log(data); // just test
results.push(row); // Option1 - The simplest way is to push a complete row
const [title, type, value, category] = row;// Option2, process it as an object
newTransactions.push({title, type, value, category});// Option2, process it as an object
})
.on('error', error => {
reject(error);
throw new Error('Fail to process CSV file');
})
.on('end', () => {
resolve();// ends the promise when CSV Parse send 'end' flag
});
});
}
await parseCSVPromise(); // now using the created promise - await finishing parsingCSV
console.log('option1', results);// option1
console.log('option2',newTransactions);// option2
return response.json({ resultsCounter, results }); // For testing only - interrupting the rote execution
// continue processing results and send it to dataBase...
//await fs.promises.unlink(filePath); // optionally you can delete the file parsed/processed
option1响应:
[
[ 'Loan', 'income', '1500', 'Others' ],
[ 'Website Hosting', 'outcome', '50', 'Others' ],
[ 'Ice cream', 'outcome', '3', 'Food' ]
]
option2响应:
[
{ title: 'Loan', type: 'income', value: '1500', category: 'Others' },
{ title: 'Website Hosting', type: 'outcome', value: '50', category: 'Others' },
{ title: 'Ice cream', type: 'outcome', value: '3', category: 'Food' }
]