技术储备
- egg ,我们要启动一个node服务来提供接口;
- Puppeteer ,用来生成pdf;
- umi或其他前端页面框架,用来开发页面
- nodejs、html、css、javaScript;
技术介绍
做了一个医药项目,需求是要把患者每天的打卡记录生成为pdf文件,方便患者打印出来给带给医生看;所以就有了这个方案;
技术介绍:
- egg 官网;
egg 是阿里出品的一款 node.js 后端 web 框架,基于 koa 封装,并做了一些约定。
学习这个链接;对这个项目就够用了; - Puppeteer [官网](https://pptr.dev/)
Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。
Puppeteer 默认以headless 模式运行,但是可以通过修改配置文件运行“有头”模式。 - umi 官网
Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
技术实现
- 创建生成pdf的核心流程
'use strict';
const dayjs = require('dayjs');
const { Cluster } = require('puppeteer-cluster');
const { tools } = require('./index');
const sleep = async(time) => {
return new Promise(resolve => {
setTimeout(_ => {
resolve()
}, time)
})
}
const launchOptions = {
headless: true,
ignoreHTTPSErrors: true, // 忽略证书错误
waitUntil: 'networkidle2',
defaultViewport: {
width: tools.interceptWidth,
height: tools.interceptHeight,
deviceScaleFactor: tools.deviceScaleFactor
},
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C101 ios/4.10.0',
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-xss-auditor', // 关闭 XSS Auditor
'--no-zygote',
'--no-sandbox',
'--disable-setuid-sandbox',
'--allow-running-insecure-content', // 允许不安全内容
'--disable-webgl',
'--disable-popup-blocking',
'–no-first-run', // 没有设置首页。在启动的时候,就会打开一个空白页面。
'–single-process' // 单进程运行
]
};
const pagePool = async () => {
const cluster = await Cluster.launch({
concurrency: Cluster.CONCURRENCY_PAGE, // 单Chrome多tab模式
maxConcurrency: 20, // 并发的workers数
retryLimit: 2, // 重试次数
// skipDuplicateUrls: true, // 不爬重复的url
// monitor: true, // 显示性能消耗
puppeteerOptions: launchOptions,
});
await cluster.task(async ({ page, data: { log, url, renderData }, }) => {
return new Promise(async (resolve, reject) => {
let startTime = dayjs().valueOf()
// 查看H5页面的控制台输出日志
page.on('console', (msg) => {
for (let i = 0; i < msg.args().length; ++i) {
// console.log(`${i}: ${msg.args()[i]}`);
if (`${msg.args()[i]}`.includes('RENDER')) {
log.send({ msg: `api/pdf, render =======> msg ${msg.args()[i]}` })
}
if (`${msg.args()[i]}`.includes('ERROR')) {
log.error({ msg: `api/pdf, render =======> error msg ${msg.args()[i]}` })
}
}
});
try {
log.send({
msg: 'api/pdf, puppeteer launchs',
data: {
launchs: true
}
})
// 向H5页面注入数据
const dataString = JSON.stringify(renderData)
page.evaluateOnNewDocument(`
Object.defineProperty(window, 'renderData', {
get() {
return ${dataString}
}
})
`)
await page.goto(url)
log.send({ msg: `api/pdf, page go to ${url} use ${dayjs().valueOf() - startTime} ms` })
startTime = dayjs().valueOf()
await page.waitForFunction(() => {
console.log(`RENDER: ======> from waitForFunction ${window.renderData}`)
console.log(`RENDER: ${window.routerBase}, path: ${window.location.pathname}`)
if (!window.routerBase) return true
return window.isRenderDone == true
}, { polling: 500, timeout: 3 * 1000 })
log.send({ msg: `api/pdf, page render use ${dayjs().valueOf() - startTime} ms` })
startTime = dayjs().valueOf()
// 处理首页没有页首
await page.addStyleTag({
content: `
@page:first { margin-top: 0; }
`,
});
const buffer = await page.pdf({
width: tools.paperWidth,
height: tools.paperHeight,
headerTemplate: `
<div style="color: rgb(18, 25, 36); width: 100%; border-bottom: 1px solid #121924; padding-right: 16px; font-family: PingFangSC-Medium, PingFang SC; display: inline-block; line-height: 15px; padding-bottom: 7px; margin-top: -7px;">
<span style="font-size: 14px; font-weight: 500; float: left;">PDF头部</span>
<span style="font-size: 10px; float: right; magrin-right: 10px; ">${dayjs().format('YYYY-MM-DD hh:mm')}</span>
<strong style="font-size: 10px; float: right; ">生成时间:</strong>
</div>
`,
displayHeaderFooter: true,
printBackground: true,
omitBackground: true,
preferCSSPageSize: true,
margin: {
top: '50px',
bottom: '50px',
left: 0,
right: 0,
},
footerTemplate: `<div style=" font-size: 10px; padding-top: 5px; text-align: center; width: 100%;
background:transparent !important; background-color:rgba(0,0,0,0) !important; font-family: PingFangSC-Medium, PingFang SC;">
第<span class="pageNumber"></span>/<span class="totalPages"></span>页
</div>`
})
resolve({ buffer, ext: 'pdf' })
} catch (e) {
log.error({
msg: 'api/pdf, htmlToPDF function error', error: e,
})
reject(e)
}
});
});
return cluster;
}
module.exports = pagePool;
- 因保密限制,具体的H5代码和egg代码就不再提供,按照常规实现即可;
网友评论