聲享是一個基於 ThinkJS 開發的在線製做 PPT 平臺。聲享製做的 PPT 支持代碼高亮、圖片上傳、神奇效果等功能,同時你能夠在聲享收藏本身喜歡的 PPT 、對本身的 PPT 進行分類管理。其中有一個 PDF 導出的功能,能夠將本身製做的 PPT 導出成 PDF 保存到本地。前端
功能實現比較簡單,只是提供了一個頁面,用戶須要手動去打印成 PDF。這個方案存在一些問題:node
若是是前端來生成 PDF,這些問題基本能夠獲得解決,可是開發量比較大並且存在一個效率問題。若是 PPT 頁面存在多個 iframe,PDF 的生成時間過長會讓用戶長時間等待,明顯不太合適。最終仍是決定服務端來生成 PDF,纔有了後來 Puppeteer 的嘗試。linux
什麼是Puppeteer呢?官方給的解釋是:git
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.github
簡而言之,這貨是一個提供高級 API 的 node 庫,可以經過 devtool 控制 headless 模式的 Chrome 或者 Chromium,它能夠在 headless 模式下模擬任何的人爲操做。經過它咱們能夠實現:web
經過 Puppeteer,咱們能夠直接使用 Chrome 把咱們須要的內容導出爲 PDF。對比之前的實現方式有如下優勢:chrome
能夠說 Puppeteer 完美的解決來咱們一期 PDF 導出存在的問題。跨域
咱們基本的實現思路是:瀏覽器
對應上述方案實現的部分代碼以下:緩存
// 測試時建議headless設置爲false,以即可以直觀看到頁面效果
this.browser = await puppeteer.launch({headless: this.isDebug});
this.page = await this.browser.newPage();
await this.page.goto('https://xxxxx.com', { waitUntil:'networkidle2' });
複製代碼
let canNext;
let i = 0;
const content = {};
do {
canNext = await this.page.$('.navigate-right.enabled');
const iframes = await this.page.$$('.PluginPage.present iframe').length;
content[i++] = {
iframe: iframes,
domStr: await this.page.$eval('.RevealViewPort', el => el.outerHTML)
}
if (canNext) {
await this.page.click('.navigate-right');
// 等待翻頁動畫
await this.page.waitFor(1000);
}
} while (canNext);
複製代碼
this.page.evaluate(domStr => document.body.innerHTML = domStr, content);
複製代碼
this.page.pdf({
path: path.join(think.ROOT_PATH, 'runtime/xxx.pdf'),
format: 'A4',
landscape: true,
printBackground: true //若是要顯示背景,此屬性要設置爲true
})
複製代碼
let transporter = nodemailer.createTransport({
host: 'smtp.ym.163.com',
port: 994,
secure: true,
auth: {
user: 'xxx@xxx.com',
pass: 'xxx'
}
});
transporter.sendMail({
from: 'xxx@xxx.com',
to: 'xxx@xxx.com',,
subject: '【聲享】xxx',
attachments: [{
filename: 'xxx.pdf',
path: path.join(think.ROOT_PATH, 'runtime/xxx.pdf'),
contentType: 'application/pdf'
}]
})
複製代碼
// 打開指定頁面時經過校驗後面參數判斷是否以超管登陸
module.exports = options => {
return async (ctx, next) => {
const { token, ctime } = ctx.query;
const md5Str = tockenGenerator();
if (md5Str === token) {
await ctx.session('userInfo', adminUser);
}
return next();
};
};
複製代碼
若是服務端是運行在 root 權限下,在啓動 Puppeteer 時要添加 --no-sandbox 參數,不然 Chrome/Chromium 會啓動失敗。詳情見 Running as root without — no-sandbox is not supported。這個權限問題在linux以root用戶使用 Chrome 的時候一樣適用。
this.browser = await puppeteer.launch({args:['--no-sandbox']});
複製代碼
聲享支持頁面內嵌入 iframe,在打印的時候碰到一個問題。若是同時在頁面上插入 iframe 過多,後面的 iframe 會直接卡住再也不加載。因此 iframe 最好分批插入或者一個一個插入,同時設定10秒來加載iframe。 若是想精確控制 iframe 也可使用 API 等待 iframe 徹底加載再執行後續操做。
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
await this.page.$evaluate(content => {
const divDom = document.createElement('div');
divDom.innerHTML = content;
document.body.appendChild(divDom.childNodes[0])
}, page.domStr);
if (page.iframe) await this.page.waitFor(10000 * page.iframe);
}
複製代碼