使用iron-router,创建一个服务器端路由来提供 PDF。
Router.route '/api/pdf/:_id',
name : 'generatePDF'
where: 'server'
action: ->
document= Documents.findOne @params._id
sanitize = Meteor.npmRequire 'sanitize-filename'
filename = sanitize(document.title).replace(/\s+/g, '-')
@response.writeHead 200,
'Content-Type': 'application/pdf'
'Content-Disposition': "attachment; filename=#{filename}.pdf"
markdown = document.content
Async.runSync (done)->
Meteor.npmRequire('markdown-pdf')
.from.string(markdown)
.to.buffer (err, buffer)->
done null, new BufferStream buffer
.result.pipe @response
上面的路由接收_id 作为路由参数。这用于从Documents 集合中检索关联的document。然后通过清理document.title 将所有空格替换为连字符来生成PDF filename。
现在将响应标头设置为强制浏览器将 PDF 下载为带有经过清理的 filename 的文件。
PDF 是使用 markdown-pdf 包从 document.content 降价生成的。这个过程因两个问题而变得复杂:
生成 PDF 的调用本质上是异步的,因此需要回调。这需要通过将其包装在 Meteor 的 Async.runSynch 方法中来转换为同步调用。这将返回一个具有我们可以使用的result 属性的对象。
markdown-pdf 包有一个 to.buffer 方法,它返回一个包含生成的 PDF 的 buffer。这使我们可以将所有内容保存在代码中,并且无需将临时文件保存到服务器。要将这个buffer 通过管道传输到response,我们需要将其转换为流。我使用帮助器 BufferStream 对象为我执行此操作(见下文)
有了这条路线,我只需在显示模板的某处放置一个“下载为 PDF”按钮(下面的代码是由 Bootstrap 3 类设置为按钮的 Jade 链接)
a.btn.btn-primary(href='{{pathFor "generatePDF"}}' target='_blank') Download as PDF
最后,这是 BufferStream 辅助类:
stream = Meteor.npmRequire "stream"
class @BufferStream extends stream.Readable
constructor: (@source, @offset = 0) ->
throw new Meteor.Error 'InvalidBuffer', 'BufferStream source must be a buffer.' unless Buffer.isBuffer(@source)
super
@length = @source.length
_read: (size) ->
if @offset < @length
@push @source.slice @offset, @offset + size
@offset += size
@push null if @offset >= @length