我在themeteorchef上找到了关于在Meteor服务器端创建PDF文件然后将它们发送回客户端的"Rendering PDFs with React components" 教程。我并不真的需要PDF文件,而是使用docx文件,并认为在使用 officegen
创建docx文件时,我可能会采用类似的方法我创建了非常相似的服务器端模块,它从客户端的输入生成docx文件,然后尝试将它们转换为base64字符串,然后将其发送到客户端。但是,永远不会创建base64字符串。
这是模块:
let myModule;
const getBase64String = (loc) => {
try {
const file = fs.readFileSync(loc);
return new Buffer(file).toString('base64');
} catch (exception) {
myModule.reject(exception);
}
}
const generateBase64Docx = (path, fileName) => {
try {
myModule.resolve({fileName, base64: getBase64String(path+fileName)});
fs.unlink(loc);
} catch (exception) {
myModule.reject(exception);
}
}
const formatComponentAsDocx = (props, fileName) => {
try {
var docxFile = officegen({
'type': 'docx',
'orientation': 'portrait',
'title': props.title,
});
var pObj = docxFile.createP();
pObj.addText(props.body);
var path = './';
output = fs.createWriteStream(path+fileName);
docxFile.generate(output);
return path;
} catch (exception) {
myModule.reject(exception);
}
}
const handler = ({props, fileName}, promise) => {
myModule = promise;
const path = formatComponentAsDocx(props, fileName);
if (path) {
generateBase64Docx(path, fileName);
}
}
export const generateComponentAsDocx = (options) => {
return new Promise((resolve, reject) => {
return handler(options, { resolve, reject });
});
};

这里的问题是 fs.readFileSync 部分。它总是返回空缓冲区,这就是为什么文件永远不会转换为base64字符串而且也永远不会发送回客户端的原因。为什么?文件本身始终在服务器上创建,并且始终可以找到。
如果我将const file = fs.readFileSync(loc);
部分更改为例如此
fs.readFile(loc, (err, data) => {
if(err) myModule.reject(err);
console.log(JSON.stringify(data));
}
我可以在数据中看到一些数据,但对于整个文件来说还不够。
我在这里做错了什么?我错过了什么吗?
答案 0 :(得分:2)
您的问题是docxFile.generate(output);
不同步。因此,虽然您的本地路径存在(它是由fs.createWriteStream()
调用创建的),但它是空的,而您的同步 fs.readFileSync
正在捕获该空文件。
您应订阅docxFile
的{{1}}事件以捕获文件生成结束:
finalize
因此,重写代码:
docxFile.on('finalize, function (writtenBytes) {
// do you work with generated file here
});
答案 1 :(得分:2)
您需要等到officegen
生成的文件完成后才能尝试从其中取出base64。这是您需要做出的最小改变。我不建议等待finalize
生成的officegen
事件,因为此事件为buggy。我建议等待输出流的finish
事件。但是,您显示的代码存在其他问题:
由于您有使用它后立即取消链接文件的代码,因此我推断您不需要文件。因此,您只需在内存中创建数据并从中获取base64
字符串。
myModule
的整个过程非常糟糕。如果我的一位同事提出了这样的代码,那么就会交换强有力的话语。是的, 不好。将整个代码库转换为使用promises更好。
整个模块可以简化为以下内容。我已对此代码进行了一些测试,但我并未声称它可以处理所有可能性。
import * as stream from "stream";
import officegen from "officegen";
function formatComponentAsDocx(props) {
return new Promise((resolve, reject) => {
// There's no need to wrap this in try...catch only to call reject. If any
// exception is raised in this function, the promise is automatically
// rejected.
const docxFile = officegen({
'type': 'docx',
'orientation': 'portrait',
'title': props.title,
});
const pObj = docxFile.createP();
pObj.addText(props.body);
// We record the output in our own buffer instead of writing to disk,
// and reading later.
let buf = Buffer.alloc(0);
const output = new stream.Writable({
write(chunk, encoding, callback) {
buf = Buffer.concat([buf, chunk]);
callback();
},
});
docxFile.generate(output, {
// Do propagate errors from officegen.
error: reject,
});
// We don't use the "finalize" event that docxFile.generate would emit
// because it is buggy. Instead, we wait for the output stream to emit
// the "finish" event.
output.on('finish', () => {
resolve(buf);
});
});
}
export function generateComponentAsDocx({ props }) {
return formatComponentAsDocx(props).then((data) => {
return { base64: data.toString("base64") };
});
};
答案 2 :(得分:-2)
readFileSync是同步的,因此它不会处理承诺。
https://nodejs.org/api/fs.html#fs_fs_readfilesync_file_options
Synchronous version of fs.readFile. Returns the contents of the file.
您可能想要使用fs.readFile。
https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback
The callback is passed two arguments (err, data), where data is the contents of the file.
If no encoding is specified, then the raw buffer is returned.