如何在Meteor中执行服务器端文件处理操作?

我在服务器上使用GridFS存储Word(.docx)文件。 我希望能够通过使用docx-builder NPM软件包将文档合并到一个Word文件中。

以下是我如何上传文件:

Meteor.methods({ uploadFiles: function (files) { check(files, [Object]); if (files.length < 1) throw new Meteor.Error("invalid-files", "No files were uploaded"); var documentPaths = []; _.each(files, function (file) { ActivityFiles.insert(file, function (error, fileObj) { if (error) { console.log("Could not upload file"); } else { documentPaths.push("/cfs/files/activities/" + fileObj._id); } }); }); return documentPaths; } }) 

我怎么能在服务器端做这个? 我只能做这个服务器端,因为我使用的包需要的fs包不能执行客户端。

以下是我目前正在尝试解决此问题的方法。 从客户端,我调用下面的方法(这是声明为Meteor.method ):

 print: function(programId) { // Get the program by ID. var program = Programs.findOne(programId); // Create a new document. var docx = new docxbuilder.Document(); // Go through all activities in the program. program.activityIds.forEach(function(activityId) { // Create a temporary server side folder to store activity files. const tempDir = fs.mkdtempSync('/tmp/'); // Get the activity by ID. var activity = Activities.findOne(activityId); // Get the document by ID. var document = ActivityFiles.findOne(activity.documents.pop()._id); // Declare file path to where file will be read. const filePath = tempDir + sep + document.name(); // Create stream to write to path. const fileStream = fs.createWriteStream(filePath); // Read from document, write to file. document.createReadStream().pipe(fileStream); // Insert into final document when finished writinf to file. fileStream.on('finish', () => { docx.insertDocxSync(filePath); // Delete file when operation is completed. fs.unlinkSync(filePath); }); }); // Save the merged document. docx.save('/tmp' + sep + 'output.docx', function (error) { if (error) { console.log(error); } // Insert into Collection so client can access merged document. Fiber = Npm.require('fibers'); Fiber(function() { ProgramFiles.insert('/tmp' + sep + 'output.docx'); }).run(); }); } 

但是,当我从客户端的ProgramFiles集合下载最终文档时,该文档是一个空的Word文档。

这里怎么了?


我已经将@ FrederickStark的答案纳入了我的代码。 现在只是停留在这一部分。


这是另一个尝试:

 'click .merge-icon': (e) => { var programId = Router.current().url.split('/').pop(); var programObj = Programs.findOne(programId); var insertedDocuments = []; programObj.activityIds.forEach(function(activityId) { var activityObj = Activities.findOne(activityId); var documentObj = ActivityFiles.findOne(activityObj.documents.pop()._id); JSZipUtils.getBinaryContent(documentObj.url(), callback); function callback(error, content) { var zip = new JSZip(content); var doc = new Docxtemplater().loadZip(zip); var xml = zip.files[doc.fileTypeConfig.textPath].asText(); xml = xml.substring(xml.indexOf("<w:body>") + 8); xml = xml.substring(0, xml.indexOf("</w:body>")); xml = xml.substring(0, xml.indexOf("<w:sectPr")); insertedDocuments.push(xml); } }); JSZipUtils.getBinaryContent('/assets/template.docx', callback); function callback(error, content) { var zip = new JSZip(content); var doc = new Docxtemplater().loadZip(zip); console.log(doc); setData(doc); } function setData(doc) { doc.setData({ // Insert blank line between contents. inserted_docs_formatted: insertedDocuments.join('<w:br/><w:br/>') // The template file must use a `{@inserted_docs_formatted}` placeholder // that will be replaced by the above value. }); doc.render(); useResult(doc); } function useResult(doc) { var out = doc.getZip().generate({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); saveAs(out, 'output.docx'); } 

查看docx-builder的文档,它只支持从文件系统读取docx文件。 调用document.url()的问题是它提供了一个可以通过http访问的url,而不是文件系统上的path。

因此,要使用GridFS,您需要首先将文件写入临时文件夹,然后docx-builder才能读取它们。

 import fs from 'fs'; import { sep } from 'path'; const tempDir = fs.mkdtempSync('/tmp/' + sep); program.activityIds.forEach(function(activityId) { var activity = Activities.findOne(activityId); console.log(activity); var document = ActivityFiles.findOne(activity.documents.pop()._id); documents.push(document); // Build a file path in the temp folder const filePath = tempDir + sep + document.name(); // Write the document to the file system using streams const fileStream = fs.createWriteStream(filePath); document.createReadStream().pipe(fileStream); // When the stream has finished writing the file, add it to your docx fileStream.on('finish', () => { console.log(filePath); docx.insertDocxSync(filePath); // Delete the file after you're done fs.unlinkSync(filePath); }); }); 

我怀疑你可以做同步使用fs.writeFileSync(filePath, document.data)但不知道,所以没有在例子中使用它。

或者,您可以查找可以支持从stream或缓冲区读取的docx包,然后您将不需要临时文件。

我只能做这个服务器端,因为我使用的包需要的fs包不能执行客户端。

虽然当前使用的docx-builder库依赖于fs (因此在Node环境中),但是可以直接使用它的依赖性docxtemplater并仅在客户端(浏览器)一侧使用它。

非常像docx-builder工作,你从一个“标准”模板文件开始,你可以合并本地docx文件,然后生成生成的docx文件,并保存在本地或发送到您的服务器。

实现你正在做的事情(即合并docx文件),但在客户端的基本步骤是:

  1. 在客户端检索文件,如果它们已经存在于networking中,或让用户通过文件typesinput和HTML5文件API从本地文件系统中select文件:
 <input type="file" id="fileInput" /> 
  1. 阅读文件内容并提取它的正文,正如在docx-builder所做的那样:
 var insertedDocsFormatted = []; // If the file is locally provided through File type input: document.getElementById('fileInput').addEventListener('change', function (evt) { var file = evt.currentTarget.files[0], fr = new FileReader(); fr.onload = function () { insertDocContent(fr.result); }; fr.readAsArrayBuffer(file); }); // Or if the file already exists on a server: JSZipUtils.getBinaryContent(url, function (err, content) { insertDocContent(content); }); function insertDocContent(content) { var zip = new JSZip(content), doc = new Docxtemplater().loadZip(zip); var xml = zip.files[doc.fileTypeConfig.textPath].asText(); // Inspired from https://github.com/raulbojalil/docx-builder xml = xml.substring(xml.indexOf("<w:body>") + 8); xml = xml.substring(0, xml.indexOf("</w:body>")); xml = xml.substring(0, xml.indexOf("<w:sectPr")); // Keep for later use. insertedDocsFormatted.push(xml); } 
  1. 一旦所有要合并的文件都被处理,加载起始模板文件
 // 'template.docx' is a static file on your server (eg in `public/` folder) // But it could even be replaced by a user locally provided file, // as done in step 2 above. JSZipUtils.getBinaryContent('template.docx', callback); function callback(err, content) { var zip = new JSZip(content); var doc = new Docxtemplater().loadZip(zip); setData(doc); } 
  1. join内容并使用格式定义数据键,以便将其插入到模板文件中,然后渲染文档:
 function setData(doc) { doc.setData({ // Insert blank line between contents. inserted_docs_formatted: insertedDocsFormatted.join('<w:br/><w:br/>') // The template file must use a `{@inserted_docs_formatted}` placeholder // that will be replaced by the above value. }); doc.render(); useResult(doc); } 
  1. 提示“另存为”对话框或将文件(blob)发送到您的服务器。
 function useResult(doc) { var out = doc.getZip().generate({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); saveAs(out, 'output.docx'); } 

在线演示: https : //ghybs.github.io/docx-builder-demo/ (根本不需要服务器端处理,纯粹的客户端逻辑)

源代码: https : //github.com/ghybs/docx-builder-demo

图书馆:

  • JSZip解压缩docx文件
  • JSZipUtils读取文件为二进制文件(可以input到JSZip)
  • docxtemplater来操作一个docx文件
  • 用于“另存为”对话框的FileSaver.js (可选)
Interesting Posts