当前位置: 首页 > 后端技术 > Node.js

使用Puppeteer导出声音分享PPT

时间:2023-04-03 16:12:09 Node.js

Status声音分享是基于ThinkJS开发的在线制作PPT平台。SoundShare制作的PPT支持代码高亮、图片上传、特效等功能,同时您可以在SoundShare中收藏自己喜欢的PPT,分类管理自己的PPT。其中有一个PDF导出功能,可以将自己制作的PPT导出成PDF保存在本地。功能实现比较简单,只提供一个页面,需要用户手动打印成PDF。该方案存在一些问题:由于使用了iframe懒加载,卸载后的iframe无法正常显示。该解决方案只能打印所有页面的初始状态。如果页面有切换动画,可能会丢失部分PPT信息。需要用户手动操作,增加了使用难度。如果前端生成PDF,基本可以解决这些问题,但是开发量比较大,存在效率问题。如果PPT页面有多个iframe,PDF生成时间过长,用户会等待很长时间,显然是不合适的。最后还是决定在服务端生成PDF,Puppeteer才开始尝试。Puppeteer什么是Puppeteer?官方的解释是:Puppeteer是一个Node库,它提供了一个高级API来通过DevToolsProtocol控制Chrome或Chromium。Puppeteer默认无头运行,但可以配置为运行完整(非无头)Chrome或Chromium。简而言之,本产品是一个提供高层API的节点库,可以通过devtool在headless模式下控制Chrome或Chromium,在headless模式下可以模拟任何人为操作。通过它我们可以实现:生成页面的截图或PDF。获取SPA(单页应用程序)并生成预渲染内容(又名“SSR”(服务器端渲染))。自动化表单提交、UI测试、键盘输入等。...通过Puppeteer,我们可以直接使用Chrome将我们需要的内容导出为PDF。与以往的实现方式相比,具有以下优点:无需用户手动操作,由服务器生成PDF,直接通过邮件发送给用户。PPT中的动画可以通过模拟用户翻页的动作来触发,然后以首尾两个PDF的形式显示,不会丢失PPT内容。不需要考虑image/iframe跨域等问题。可以说Puppeteer完美的解决了我们第一阶段导出PDF的问题。解决方案我们的基本实现思路是:打开一个普通的PPT播放页面,获取需要打印的DOM元素,翻页。重复第一步,直到到达最后一页。清空页面内容,将前两步得到的页面内容依次填充到当前页面(为什么要按顺序填写,后面会有说明)。上述方案实现对应的部分代码如下:通过Puppeteer打开指定页面。//测试时建议将headless设置为false,这样可以直观的看到页面效果this.browser=awaitpuppeteer.launch({headless:this.isDebug});this.page=awaitthis.browser.newPage();等待这个.page.goto('https://xxxxx.com',{waitUntil:'networkidle2'});打开页面后,可以通过Puppeteer模拟用户的翻页操作,缓存每次翻页后需要打印的DOM元素字符串。letcanNext;leti=0;constcontent={};do{canNext=awaitthis.page.$('.navigate-right.enabled');constiframes=awaitthis.page.$$('.PluginPage.presentiframe').length;content[i++]={iframe:iframes,domStr:awaitthis.page.$eval('.RevealViewPort',el=>el.outerHTML)}if(canNext){awaitthis.page.点击('.navigate-right');//等待翻页动画awaitthis.page.waitFor(1000);}}while(canNext);得到所有待打印页面的DOM后,替换原来的页面内容。因为$evaluate方法不支持调用外部变量,所以只能通过传参的方式使用。this.page.evaluate(domStr=>document.body.innerHTML=domStr,content);调用生成PDF的API。this.page.pdf({path:path.join(think.ROOT_PATH,'runtime/xxx.pdf'),format:'A4',landscape:true,printBackground:true//如果要显示背景,这个属性应设置为true})使用nodemailer向用户发送电子邮件。这一步如果要使用本地SMTP服务,请使用nodemailer2.7.5版本,此版本后该功能已被删除。lettransporter=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:'【SoundShare】xxx',attachments:[{filename:'xxx.pdf',path:路径.join(think.ROOT_PATH,'runtime/xxx.pdf'),contentType:'application/pdf'}]})开发过程中需要注意的问题用户登录使用Puppeteer打开页面相当于启动一个新的浏览器实例,页面中的seession和cookie为空。用于打印的页面需要用户信息,所以我们登录超级管理账号进行打印操作。这个功能可以通过ThinkJS中的中间件来实现。访问页面时,检查参数判断是否为打印打开的页面,如果是,则登录超级管理账号。//打开指定页面时,检查以下参数判断是否使用超管登录module.exports=options=>{returnasync(ctx,next)=>{const{token,ctime}=ctx.query;constmd5Str=tokenGenerator();if(md5Str===token){awaitctx.session('userInfo',adminUser);返回下一个();};};Puppeteer启动如果服务器运行在root权限下,启动Puppeteer时添加--no-sandbox参数,否则Chrome/Chromium会启动失败。有关详细信息,请参阅以root身份运行而不支持-不支持无沙箱。在Linux上以root用户身份使用Chrome时,也会出现此权限问题。this.browser=awaitpuppeteer.launch({args:['--no-sandbox']});iframe无法加载声音分享支持页面内嵌的iframe,打印时遇到问题。如果页面同时插入太多的iframe,后面的iframe会直接卡住,不再加载。因此,最好分批或逐个插入iframe,设置10秒加载iframe。如果要对iframe进行精确控制,也可以使用API??等待iframe完全加载后再进行后续操作。for(leti=0;i{constdivDom=document.createElement('div');divDom.innerHTML=content;document.body.appendChild(divDom.childNodes[0])},page.domStr);如果(page.iframe)awaitthis.page.waitFor(10000*page.iframe);}