团检报告生成方案遇到的一些问题

『23年02月19日』

之前做了一套团检报告生成方案 - 上篇博文。期间有遇到一些问题,今天有空归纳总结了一下,整套方案的运行效果还是很理想的,当然我们的业务访问量其实不大,不然有可能会暴露出更多的问题,特别是 puppeteer 是很耗内存资源的,在访问量大的情况下,可能要去重点优化 puppeteer 的使用效率问题,但以现在公司的业务基本不用太担心这个问题。

问题 1: 团检报告的数据中中文字符会乱码

我在 node 起了一个 next app 服务,其中包含 /pages/* 下的页面资源和 /api/* 下的 接口资源。

接口资源如下:

  • POST /api/html2pdf
  • GET /api/healthcheck

/api/html2pdf 用来生成将指定的 html 页面转成 PDF 文档,接收以下参数:

1
2
3
4
5
{
"url": "https://www.baidu.com",
"fileName": "百度首页pdf文档",
"extraHeaders": "团检报告json stringify后的数据"
}

页面资源如下:

  • teamreport

访问 http://localhost/teamport 会返回 Next SSR 的 html 文档,这是一份带有图表,表格,插图等的体检报告。其中报告里的数据通过在 puppeteer 的 setExtraHeader 取得page.setExtraHeader,整体流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1. POST /api/html2pdf

body = {
"url": "https://www.baidu.com",
"fileName": "百度首页pdf文档",
"extraHeaders": JSON.stringify(体检报告数据)
}

2. html2pdf接口收到请求后launch puppeteer并且打开一个新页面
const page =browser.newPage();

3. 设置page的extraHeader
page.setExtraHeaders({data: extraHeaders});

3. 打开teamreport
page.goTo('https://localhost:3000/teamreport')

4. 在团检报告中接收extraHeaders

function getServerSideProps(req) {
const { data } = req.header;
return {
props: data
}
}

const App = (data) {

// data是整份团检报告的json数据
return (渲染体检报告);
}

由于团检报告 json 数据中是会包含一些中文字符的,比如一些医学名称,例如高血压、高血脂等等。在 api/html2pdf拿到的 extraHeader 还是正常的 JSON.stringify 后的文本,中文也是正常显示。但是当整份数据需要通过 page.setExtraHeaders 的形式给到 puppeteer 打开的 page 时,在 page 拿到的数据中的中文会乱码,想必是 puppeteer 在 page.setExtraHeaders 的时候对特殊字符做了一些转译。

解决方法:对 JSON.stringify 后的文本(extraHeaders)进行 再 encodeURI 处理,然后才 setExtraHeaders

1
2
3
4
5
encodeURI(JSON.stringify(reportData));

// teamreport 页面
const text = decodeURI(req.header.extraHeaders);
JSON.parse(text);

这样就“消灭”了中文字符,在新打开的 page 里再通过 decodeURI 对 encode 的特殊字符进行解码,中文特殊字符就能正常通过 page header 的形式给到 page 了。但也因此产生了问题 2

问题 2:puppeteer.page.setExtraHeaders()设置的 header 文本过长
当体检报告的数据(>10kb)过大的时候,有可能超过 HTTP header 的最大限制,而 HTTP header 支持的最大长度会根据 web 服务器不同而有不同的限制。项目中使用 encodeURI 去对中文字符进行转译也会额外增加 header 的长度。

所以,使用 header 并不适合用来传输大量数据。

解决方案 1:
使用 node 全局变量,页面渲染完后即销毁。