摘要:而打印所用的頁(yè)面需要用到用戶(hù)信息,所以我們登錄了一個(gè)超管帳號(hào)來(lái)執(zhí)行打印操作。在訪問(wèn)頁(yè)面的時(shí)候通過(guò)參數(shù)校驗(yàn)判斷是否是打印而打開(kāi)的頁(yè)面,如果是則登錄超管帳號(hào)。
現(xiàn)狀
聲享是一個(gè)基于 ThinkJS 開(kāi)發(fā)的在線(xiàn)制作 PPT 平臺(tái)。聲享制作的 PPT 支持代碼高亮、圖片上傳、神奇效果等功能,同時(shí)你可以在聲享收藏自己喜歡的 PPT 、對(duì)自己的 PPT 進(jìn)行分類(lèi)管理。其中有一個(gè) PDF 導(dǎo)出的功能,可以將自己制作的 PPT 導(dǎo)出成 PDF 保存到本地。
功能實(shí)現(xiàn)比較簡(jiǎn)單,只是提供了一個(gè)頁(yè)面,用戶(hù)需要手動(dòng)去打印成 PDF。這個(gè)方案存在一些問(wèn)題:
由于使用了 iframe 懶加載導(dǎo)致未加載的 iframe 無(wú)法正常顯示。
該種方案只能打印所有頁(yè)面的初始狀態(tài)。如果頁(yè)面中存在切換動(dòng)畫(huà),可能會(huì)丟失部分 PPT 信息。
需要用戶(hù)手動(dòng)操作,提高了使用難度。
如果是前端來(lái)生成 PDF,這些問(wèn)題基本可以得到解決,但是開(kāi)發(fā)量比較大而且存在一個(gè)效率問(wèn)題。如果 PPT 頁(yè)面存在多個(gè) iframe,PDF 的生成時(shí)間過(guò)長(zhǎng)會(huì)讓用戶(hù)長(zhǎng)時(shí)間等待,明顯不太合適。最終還是決定服務(wù)端來(lái)生成 PDF,才有了后來(lái) Puppeteer 的嘗試。
Puppeteer什么是Puppeteer呢?官方給的解釋是:
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.
簡(jiǎn)而言之,這貨是一個(gè)提供高級(jí) API 的 node 庫(kù),能夠通過(guò) devtool 控制 headless 模式的 Chrome 或者 Chromium,它可以在 headless 模式下模擬任何的人為操作。通過(guò)它我們可以實(shí)現(xiàn):
生成頁(yè)面的截圖或者 PDF。
抓取 SPA(單頁(yè)應(yīng)用)并生成預(yù)渲染內(nèi)容(即“SSR”(服務(wù)器端渲染))。
自動(dòng)提交表單,進(jìn)行 UI 測(cè)試,鍵盤(pán)輸入等。
...
通過(guò) Puppeteer,我們可以直接使用 Chrome 把我們需要的內(nèi)容導(dǎo)出為 PDF。對(duì)比以前的實(shí)現(xiàn)方式有以下優(yōu)點(diǎn):
不需要用戶(hù)手動(dòng)操作,服務(wù)端生成 PDF 后直接以郵件的方式發(fā)送給用戶(hù)。
PPT 中的動(dòng)畫(huà)可以模擬用戶(hù)翻頁(yè)的動(dòng)作觸發(fā),然后以初始、結(jié)束兩張 PDF 的方式展示,不會(huì)丟失 PPT 內(nèi)容。
不需要考慮圖片/ iframe 跨域等問(wèn)題。
可以說(shuō) Puppeteer 完美的解決來(lái)我們一期 PDF 導(dǎo)出存在的問(wèn)題。
解決方案我們基本的實(shí)現(xiàn)思路是:
打開(kāi)一個(gè)正常的 PPT 播放頁(yè),獲取需要打印的 DOM 元素并翻頁(yè) 。
重復(fù)第一步操作直至到最后一頁(yè) 。
清空頁(yè)面內(nèi)容并將前兩步獲得的頁(yè)面內(nèi)容依次填充到當(dāng)前頁(yè)面(為什么要依次填充會(huì)在后面解釋?zhuān)?/p>
對(duì)應(yīng)上述方案實(shí)現(xiàn)的部分代碼如下:
通過(guò) Puppeteer 打開(kāi)指定的頁(yè)面。
// 測(cè)試時(shí)建議headless設(shè)置為false,以便可以直觀看到頁(yè)面效果 this.browser = await puppeteer.launch({headless: this.isDebug}); this.page = await this.browser.newPage(); await this.page.goto("https://xxxxx.com", { waitUntil:"networkidle2" });
打開(kāi)頁(yè)面后可以通過(guò) Puppeteer 模擬用戶(hù)翻頁(yè)操作,每次翻頁(yè)后緩存需要打印的 DOM 元素字符串。
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"); // 等待翻頁(yè)動(dòng)畫(huà) await this.page.waitFor(1000); } } while (canNext);
獲取到要打印的所有頁(yè)面 DOM 后,替換掉原來(lái)的頁(yè)面內(nèi)容。因?yàn)?$evaluate 方法中不支持調(diào)用外部變量所以只能以傳參的方式使用。
this.page.evaluate(domStr => document.body.innerHTML = domStr, content);
調(diào)用生成 PDF 的 API。
this.page.pdf({ path: path.join(think.ROOT_PATH, "runtime/xxx.pdf"), format: "A4", landscape: true, printBackground: true //如果要顯示背景,此屬性要設(shè)置為true })
使用 nodemailer 發(fā)送郵件給用戶(hù)。這一步如果想使用本地的 SMTP 服務(wù)請(qǐng)用 nodemailer 的 2.7.5 的版本,此版本后這項(xiàng)功能被刪除了。
let transporter = nodemailer.createTransport({ host: "smtp.ym.163.com", port: 994, secure: true, auth: { user: "[email protected]", pass: "xxx" } }); transporter.sendMail({ from: "[email protected]", to: "[email protected]",, subject: "【聲享】xxx", attachments: [{ filename: "xxx.pdf", path: path.join(think.ROOT_PATH, "runtime/xxx.pdf"), contentType: "application/pdf" }] })開(kāi)發(fā)中需要注意的問(wèn)題
用戶(hù)登錄
使用 Puppeteer 打開(kāi)頁(yè)面相當(dāng)于你新啟動(dòng)了一個(gè)瀏覽器實(shí)例,頁(yè)面中的 seession 和 cookie 是空的。而打印所用的頁(yè)面需要用到用戶(hù)信息,所以我們登錄了一個(gè)超管帳號(hào)來(lái)執(zhí)行打印操作。在 ThinkJS 中可以通過(guò)中間件來(lái)實(shí)現(xiàn)這項(xiàng)功能。在訪問(wèn)頁(yè)面的時(shí)候通過(guò)參數(shù)校驗(yàn)判斷是否是打印而打開(kāi)的頁(yè)面,如果是則登錄超管帳號(hào)。
// 打開(kāi)指定頁(yè)面時(shí)通過(guò)校驗(yàn)后面參數(shù)判斷是否以超管登錄 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(); }; };
Puppeteer 啟動(dòng)
如果服務(wù)端是運(yùn)行在 root 權(quán)限下,在啟動(dòng) Puppeteer 時(shí)要添加 --no-sandbox 參數(shù),否則 Chrome/Chromium 會(huì)啟動(dòng)失敗。詳情見(jiàn) Running as root without — no-sandbox is not supported。這個(gè)權(quán)限問(wèn)題在linux以root用戶(hù)使用 Chrome 的時(shí)候同樣適用。
this.browser = await puppeteer.launch({args:["--no-sandbox"]});
iframe 無(wú)法加載
聲享支持頁(yè)面內(nèi)嵌入 iframe,在打印的時(shí)候碰到一個(gè)問(wèn)題。如果同時(shí)在頁(yè)面上插入 iframe 過(guò)多,后面的 iframe 會(huì)直接卡住不再加載。所以 iframe 最好分批插入或者一個(gè)一個(gè)插入,同時(shí)設(shè)定10秒來(lái)加載iframe。 如果想精確控制 iframe 也可以使用 API 等待 iframe 完全加載再執(zhí)行后續(xù)操作。
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); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/102645.html
摘要:一背景需求因?yàn)閿?shù)據(jù)包含機(jī)密信息,所以得自己搭建圖表導(dǎo)出服務(wù)器在后臺(tái)生成對(duì)應(yīng)圖表以圖片的形式導(dǎo)出保存。圖表個(gè)性化程度較高,如一些圖列是沒(méi)有的,但在前端可以利用實(shí)現(xiàn)。每周定時(shí)執(zhí)行上述生成圖表的任務(wù),保存到指定位置。 一、背景需求 1、因?yàn)閿?shù)據(jù)包含機(jī)密信息,所以得自己搭建圖表導(dǎo)出服務(wù)器;在后臺(tái)生成對(duì)應(yīng)Highcharts圖表、以圖片的形式導(dǎo)出保存。2、圖表個(gè)性化程度較高,如一些圖列是High...
摘要:可以通過(guò)的提供的直接控制模擬大部分用戶(hù)操作來(lái)進(jìn)行或者作為爬蟲(chóng)訪問(wèn)頁(yè)面來(lái)收集數(shù)據(jù)。 ??骨架屏是在頁(yè)面數(shù)據(jù)尚未加載完成前先給用戶(hù)展示出頁(yè)面的大致結(jié)構(gòu),直到請(qǐng)求數(shù)據(jù)返回后再顯示真正的頁(yè)面內(nèi)容;隨著單頁(yè)應(yīng)用( SPA )的越來(lái)越流行,單頁(yè)應(yīng)用的用戶(hù)體驗(yàn)也越來(lái)越得到前端開(kāi)發(fā)者的關(guān)注;為了優(yōu)化用戶(hù)體驗(yàn),在數(shù)據(jù)到達(dá)用戶(hù)之前,往往會(huì)在頁(yè)面上加上 loading 的效果,而現(xiàn)在,越來(lái)越多的場(chǎng)景傾向于使...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺(tái)。是谷歌開(kāi)源的基于和的自動(dòng)化測(cè)試工具,可以很方便的讓程序模擬用戶(hù)的操作,對(duì)瀏覽器進(jìn)行程序化控制。相對(duì)于,是新的開(kāi)源項(xiàng)目,而且是谷歌開(kāi)發(fā),可以使用很多新的特性。 背景 說(shuō)到爬蟲(chóng),大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯(cuò),而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺(tái)。是谷歌開(kāi)源的基于和的自動(dòng)化測(cè)試工具,可以很方便的讓程序模擬用戶(hù)的操作,對(duì)瀏覽器進(jìn)行程序化控制。相對(duì)于,是新的開(kāi)源項(xiàng)目,而且是谷歌開(kāi)發(fā),可以使用很多新的特性。 背景 說(shuō)到爬蟲(chóng),大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯(cuò),而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:獲取獲取上下文句柄執(zhí)行計(jì)算銷(xiāo)毀句柄除此之外,還可以使用意為在瀏覽器環(huán)境執(zhí)行腳本,可傳入第二個(gè)參數(shù)作為句柄,而則針對(duì)選中的一個(gè)元素執(zhí)行操作。 我們?nèi)粘J褂脼g覽器或者說(shuō)是有頭瀏覽器時(shí)的步驟為:?jiǎn)?dòng)瀏覽器、打開(kāi)一個(gè)網(wǎng)頁(yè)、進(jìn)行交互。 無(wú)頭瀏覽器指的是我們使用腳本來(lái)執(zhí)行以上過(guò)程的瀏覽器,能模擬真實(shí)的瀏覽器使用場(chǎng)景。 有了無(wú)頭瀏覽器,我們就能做包括但不限于以下事情: 對(duì)網(wǎng)頁(yè)進(jìn)行截圖保存為圖片或 ...
閱讀 2256·2021-09-24 10:31
閱讀 3910·2021-09-22 15:16
閱讀 3426·2021-09-22 10:02
閱讀 1051·2021-09-22 10:02
閱讀 1869·2021-09-08 09:36
閱讀 2020·2019-08-30 14:18
閱讀 637·2019-08-30 10:51
閱讀 1896·2019-08-29 11:08