@kne/fastify-puppeteer 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,54 @@
13
13
  npm i --save @kne/fastify-puppeteer
14
14
  ```
15
15
 
16
+
17
+ ### 概述
18
+
19
+ ### 项目简介
20
+
21
+ fastify-puppeteer 是一个基于 Fastify 的插件,它集成了 Puppeteer 来提供强大的网页内容转换功能。该插件可以将 HTML 内容或 URL 转换为 PDF 文档或图片,支持单个转换和批量转换操作。
22
+
23
+ ### 主要特性
24
+
25
+ - HTML 转 PDF:将 HTML 内容直接转换为 PDF 文档
26
+ - HTML 转图片:将 HTML 内容转换为图片
27
+ - URL 转 PDF:将指定 URL 的网页转换为 PDF 文档
28
+ - URL 转图片:将指定 URL 的网页转换为图片
29
+ - 批量处理:支持批量转换多个 HTML 内容或 URL
30
+ - 并发控制:内置任务队列管理,控制最大并发任务数量
31
+ - 缓存机制:支持文件缓存,提高转换效率
32
+
33
+ ### 技术架构
34
+
35
+ #### 核心组件
36
+
37
+ - **Fastify 插件**:提供 HTTP 接口和静态文件服务
38
+ - **Puppeteer**:负责网页渲染和内容转换
39
+ - **任务队列**:管理并发任务,防止系统过载
40
+ - **文件缓存**:缓存转换结果,提高响应速度
41
+
42
+ #### 工作流程
43
+
44
+ 1. 接收转换请求(HTML内容或URL)
45
+ 2. 通过任务队列管理器控制并发
46
+ 3. 使用 Puppeteer 打开页面并进行渲染
47
+ 4. 执行转换操作(PDF或图片)
48
+ 5. 返回转换结果
49
+
50
+ ### 配置选项
51
+
52
+ - **maxTaskSize**:最大并发任务数,默认为 100
53
+ - **puppeteerOptions**:Puppeteer 启动选项,用于配置浏览器行为
54
+ - **cacheOptions**:文件缓存配置,控制缓存行为
55
+
56
+ ### 使用场景
57
+
58
+ - 网页存档:将网页内容保存为 PDF 文档
59
+ - 报表生成:将动态内容转换为 PDF 报表
60
+ - 网页截图:捕获网页内容生成图片
61
+ - 批量处理:批量转换多个网页或HTML内容
62
+
63
+
16
64
  ### 示例
17
65
 
18
66
  #### 示例代码
@@ -21,7 +69,225 @@ npm i --save @kne/fastify-puppeteer
21
69
 
22
70
  ### API
23
71
 
24
- | 属性名 | 说明 | 类型 | 默认值 |
25
- |-----|----|----|-----|
26
- | | | | |
72
+ ### API 接口文档
73
+
74
+ 本文档详细描述了 fastify-puppeteer 插件提供的所有 API 接口。
75
+
76
+ #### 接口概览
77
+
78
+ | 接口名称 | 请求方法 | 请求路径 | 描述 |
79
+ |---------|---------|---------|------|
80
+ | parseHtmlToPdf | POST | /parseHtmlToPdf | 将 HTML 内容转换为 PDF 文件 |
81
+ | parseHtmlToPdfBatch | POST | /parseHtmlToPdfBatch | 批量将 HTML 内容转换为 PDF 文件(打包为 ZIP) |
82
+ | parseHtmlToPhoto | POST | /parseHtmlToPhoto | 将 HTML 内容转换为图片 |
83
+ | parseHtmlToPhotoBatch | POST | /parseHtmlToPhotoBatch | 批量将 HTML 内容转换为图片(打包为 ZIP) |
84
+ | parseUrlToPdf | POST | /parseUrlToPdf | 将 URL 网页转换为 PDF 文件 |
85
+ | parseUrlToPdfBatch | POST | /parseUrlToPdfBatch | 批量将 URL 网页转换为 PDF 文件(打包为 ZIP) |
86
+ | parseUrlToPhoto | POST | /parseUrlToPhoto | 将 URL 网页转换为图片 |
87
+ | parseUrlToPhotoBatch | POST | /parseUrlToPhotoBatch | 批量将 URL 网页转换为图片(打包为 ZIP) |
88
+
89
+ #### 接口详情
90
+
91
+ ##### HTML 转换接口
92
+
93
+ ###### parseHtmlToPdf
94
+
95
+ | 项目 | 说明 |
96
+ |------|------|
97
+ | 接口描述 | 将 HTML 内容转换为 PDF 文件 |
98
+ | 请求方法 | POST |
99
+ | 请求路径 | /parseHtmlToPdf |
100
+ | 认证方式 | 根据配置的 authenticate 函数 |
101
+ | 请求参数 | JSON 对象 |
102
+
103
+ **请求参数详情:**
104
+
105
+ | 参数名 | 类型 | 必填 | 描述 |
106
+ |-------|------|------|------|
107
+ | content | string | 是 | 需要转换的 HTML 内容 |
108
+ | options | object | 否 | PDF 生成选项,参考 Puppeteer API |
109
+
110
+ **响应结果:**
111
+ - 返回生成的 PDF 文件流
112
+
113
+ ###### parseHtmlToPdfBatch
114
+
115
+ | 项目 | 说明 |
116
+ |------|------|
117
+ | 接口描述 | 批量将 HTML 内容转换为 PDF 文件(打包为 ZIP) |
118
+ | 请求方法 | POST |
119
+ | 请求路径 | /parseHtmlToPdfBatch |
120
+ | 认证方式 | 根据配置的 authenticate 函数 |
121
+ | 请求参数 | JSON 对象 |
122
+
123
+ **请求参数详情:**
124
+
125
+ | 参数名 | 类型 | 必填 | 描述 |
126
+ |-------|------|------|------|
127
+ | contentList | array | 是 | 需要转换的 HTML 内容列表 |
128
+ | options | object | 否 | PDF 生成选项,参考 Puppeteer API |
129
+
130
+ **响应结果:**
131
+ - 返回包含所有生成 PDF 的 ZIP 文件流
132
+
133
+ ###### parseHtmlToPhoto
134
+
135
+ | 项目 | 说明 |
136
+ |------|------|
137
+ | 接口描述 | 将 HTML 内容转换为图片 |
138
+ | 请求方法 | POST |
139
+ | 请求路径 | /parseHtmlToPhoto |
140
+ | 认证方式 | 根据配置的 authenticate 函数 |
141
+ | 请求参数 | JSON 对象 |
142
+
143
+ **请求参数详情:**
144
+
145
+ | 参数名 | 类型 | 必填 | 描述 |
146
+ |-------|------|------|------|
147
+ | content | string | 是 | 需要转换的 HTML 内容 |
148
+ | options | object | 否 | 图片生成选项,参考 Puppeteer API |
149
+
150
+ **响应结果:**
151
+ - 返回生成的图片文件流
152
+
153
+ ###### parseHtmlToPhotoBatch
154
+
155
+ | 项目 | 说明 |
156
+ |------|------|
157
+ | 接口描述 | 批量将 HTML 内容转换为图片(打包为 ZIP) |
158
+ | 请求方法 | POST |
159
+ | 请求路径 | /parseHtmlToPhotoBatch |
160
+ | 认证方式 | 根据配置的 authenticate 函数 |
161
+ | 请求参数 | JSON 对象 |
162
+
163
+ **请求参数详情:**
164
+
165
+ | 参数名 | 类型 | 必填 | 描述 |
166
+ |-------|------|------|------|
167
+ | contentList | array | 是 | 需要转换的 HTML 内容列表 |
168
+ | options | object | 否 | 图片生成选项,参考 Puppeteer API |
169
+
170
+ **响应结果:**
171
+ - 返回包含所有生成图片的 ZIP 文件流
172
+
173
+ ##### URL 转换接口
174
+
175
+ ###### parseUrlToPdf
176
+
177
+ | 项目 | 说明 |
178
+ |------|------|
179
+ | 接口描述 | 将 URL 网页转换为 PDF 文件 |
180
+ | 请求方法 | POST |
181
+ | 请求路径 | /parseUrlToPdf |
182
+ | 认证方式 | 根据配置的 authenticate 函数 |
183
+ | 请求参数 | JSON 对象 |
184
+
185
+ **请求参数详情:**
186
+
187
+ | 参数名 | 类型 | 必填 | 描述 |
188
+ |-------|------|------|------|
189
+ | url | string | 是 | 需要转换的网页 URL |
190
+ | options | object | 否 | PDF 生成选项,参考 Puppeteer API 和 URL 转换特有选项 |
191
+
192
+ **响应结果:**
193
+ - 返回生成的 PDF 文件流
194
+
195
+ ###### parseUrlToPdfBatch
196
+
197
+ | 项目 | 说明 |
198
+ |------|------|
199
+ | 接口描述 | 批量将 URL 网页转换为 PDF 文件(打包为 ZIP) |
200
+ | 请求方法 | POST |
201
+ | 请求路径 | /parseUrlToPdfBatch |
202
+ | 认证方式 | 根据配置的 authenticate 函数 |
203
+ | 请求参数 | JSON 对象 |
204
+
205
+ **请求参数详情:**
206
+
207
+ | 参数名 | 类型 | 必填 | 描述 |
208
+ |-------|------|------|------|
209
+ | urlList | array | 是 | 需要转换的网页 URL 列表 |
210
+ | options | object | 否 | PDF 生成选项,参考 Puppeteer API 和 URL 转换特有选项 |
211
+
212
+ **响应结果:**
213
+ - 返回包含所有生成 PDF 的 ZIP 文件流
214
+
215
+ ###### parseUrlToPhoto
216
+
217
+ | 项目 | 说明 |
218
+ |------|------|
219
+ | 接口描述 | 将 URL 网页转换为图片 |
220
+ | 请求方法 | POST |
221
+ | 请求路径 | /parseUrlToPhoto |
222
+ | 认证方式 | 根据配置的 authenticate 函数 |
223
+ | 请求参数 | JSON 对象 |
224
+
225
+ **请求参数详情:**
226
+
227
+ | 参数名 | 类型 | 必填 | 描述 |
228
+ |-------|------|------|------|
229
+ | url | string | 是 | 需要转换的网页 URL |
230
+ | selector | string | 否 | 页面元素选择器,指定截图区域。如果提供,将只截取该元素区域 |
231
+ | options | object | 否 | 图片生成选项,参考 Puppeteer API 和 URL 转换特有选项 |
232
+
233
+ **响应结果:**
234
+ - 返回生成的图片文件流
235
+
236
+ ###### parseUrlToPhotoBatch
237
+
238
+ | 项目 | 说明 |
239
+ |------|------|
240
+ | 接口描述 | 批量将 URL 网页转换为图片(打包为 ZIP) |
241
+ | 请求方法 | POST |
242
+ | 请求路径 | /parseUrlToPhotoBatch |
243
+ | 认证方式 | 根据配置的 authenticate 函数 |
244
+ | 请求参数 | JSON 对象 |
245
+
246
+ **请求参数详情:**
247
+
248
+ | 参数名 | 类型 | 必填 | 描述 |
249
+ |-------|------|------|------|
250
+ | urlList | array | 是 | 需要转换的网页 URL 列表 |
251
+ | selector | string | 否 | 页面元素选择器,指定截图区域。如果提供,将只截取该元素区域 |
252
+ | options | object | 否 | 图片生成选项,参考 Puppeteer API 和 URL 转换特有选项 |
253
+
254
+ **响应结果:**
255
+ - 返回包含所有生成图片的 ZIP 文件流
256
+
257
+ ### 选项参数说明
258
+
259
+ #### PDF 生成选项
260
+
261
+ PDF 生成选项参考 Puppeteer 的 [page.pdf()](https://pptr.dev/api/puppeteer.page.pdf) 方法参数,常用选项包括:
262
+
263
+ | 选项名 | 类型 | 描述 |
264
+ |-------|------|------|
265
+ | format | string | 纸张格式,如 'A4', 'Letter' 等 |
266
+ | width | string | 纸张宽度,如 '8.5in' |
267
+ | height | string | 纸张高度,如 '11in' |
268
+ | margin | object | 页面边距,包含 top, right, bottom, left 属性 |
269
+ | printBackground | boolean | 是否打印背景图形 |
270
+ | landscape | boolean | 是否使用横向打印 |
271
+
272
+ #### 图片生成选项
273
+
274
+ 图片生成选项参考 Puppeteer 的 [page.screenshot()](https://pptr.dev/api/puppeteer.page.screenshot) 方法参数,常用选项包括:
275
+
276
+ | 选项名 | 类型 | 描述 |
277
+ |-------|------|------|
278
+ | type | string | 图片格式,如 'png', 'jpeg' |
279
+ | quality | number | 图片质量 (0-100),仅适用于 JPEG |
280
+ | fullPage | boolean | 是否捕获完整页面 |
281
+ | clip | object | 裁剪区域,包含 x, y, width, height 属性 |
282
+ | omitBackground | boolean | 是否隐藏默认白色背景 |
283
+
284
+ #### URL 转换特有选项
285
+
286
+ 以下选项适用于 parseUrlToPdf、parseUrlToPdfBatch、parseUrlToPhoto 和 parseUrlToPhotoBatch 接口:
287
+
288
+ | 选项名 | 类型 | 描述 |
289
+ |-------|------|------|
290
+ | waitForSelectors | array | 等待页面中特定元素出现的选择器数组,在继续处理前会等待这些元素加载完成 |
291
+ | waitForVisible | boolean | 是否等待元素可见,默认为 false,仅检查元素是否存在于 DOM 中 |
292
+ | waitForMaxTime | number | 等待元素出现的最大时间(毫秒),默认为 10000(10秒) |
27
293
 
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const fp = require('fastify-plugin');
2
- const path = require('path');
2
+ const path = require('node:path');
3
3
  const packageJson = require('./package.json');
4
4
  const merge = require('lodash/merge');
5
5
 
@@ -11,6 +11,8 @@ module.exports = fp(
11
11
  prefix: `/api/v${packageJson.version.split('.')[0]}/puppeteer`,
12
12
  maxCacheKeys: 1000,
13
13
  maxTaskSize: 100,
14
+ pageWidth: 1366,
15
+ pageHeight: 768,
14
16
  puppeteerOptions: {},
15
17
  authenticate: []
16
18
  },
@@ -21,8 +23,10 @@ module.exports = fp(
21
23
  require('@fastify/static'),
22
24
  Object.assign({}, options.static, {
23
25
  root: options.root,
26
+ prefix: `${options.prefix}/static`,
24
27
  index: false,
25
- list: false
28
+ list: false,
29
+ decorateReply: false
26
30
  })
27
31
  );
28
32
 
@@ -1,4 +1,5 @@
1
1
  const fp = require('fastify-plugin');
2
+ const path = require('node:path');
2
3
  module.exports = fp(async (fastify, options) => {
3
4
  const { services } = fastify.puppeteer;
4
5
  fastify.post(
@@ -20,7 +21,7 @@ module.exports = fp(async (fastify, options) => {
20
21
  },
21
22
  async (request, reply) => {
22
23
  const filename = await services.parseHtmlToPdf({ html: request.body.content, options: request.body.options });
23
- return reply.sendFile(filename);
24
+ return reply.sendFile(filename, { root: options.root });
24
25
  }
25
26
  );
26
27
 
@@ -51,7 +52,7 @@ module.exports = fp(async (fastify, options) => {
51
52
  htmlList: request.body.contentList,
52
53
  options: request.body.options
53
54
  });
54
- return reply.sendFile(filename);
55
+ return reply.sendFile(filename, { root: options.root });
55
56
  }
56
57
  );
57
58
 
@@ -82,7 +83,7 @@ module.exports = fp(async (fastify, options) => {
82
83
  htmlList: request.body.contentList,
83
84
  options: request.body.options
84
85
  });
85
- return reply.sendFile(filename);
86
+ return reply.sendFile(filename, { root: options.root });
86
87
  }
87
88
  );
88
89
 
@@ -108,7 +109,7 @@ module.exports = fp(async (fastify, options) => {
108
109
  html: request.body.content,
109
110
  options: request.body.options
110
111
  });
111
- return reply.sendFile(filename);
112
+ return reply.sendFile(filename, { root: options.root });
112
113
  }
113
114
  );
114
115
 
@@ -131,7 +132,7 @@ module.exports = fp(async (fastify, options) => {
131
132
  },
132
133
  async (request, reply) => {
133
134
  const filename = await services.parseUrlToPdf({ url: request.body.url, options: request.body.options });
134
- return reply.sendFile(filename);
135
+ return reply.sendFile(filename, { root: options.root });
135
136
  }
136
137
  );
137
138
 
@@ -157,7 +158,7 @@ module.exports = fp(async (fastify, options) => {
157
158
  urlList: request.body.urlList,
158
159
  options: request.body.options
159
160
  });
160
- return reply.sendFile(filename);
161
+ return reply.sendFile(filename, { root: options.root });
161
162
  }
162
163
  );
163
164
 
@@ -180,12 +181,12 @@ module.exports = fp(async (fastify, options) => {
180
181
  }
181
182
  },
182
183
  async (request, reply) => {
183
- const file = await services.parseUrlToPhoto({
184
+ const filename = await services.parseUrlToPhoto({
184
185
  url: request.body.url,
185
186
  selector: request.body.selector,
186
187
  options: request.body.options
187
188
  });
188
- return reply.sendFile(filename);
189
+ return reply.sendFile(filename, { root: options.root });
189
190
  }
190
191
  );
191
192
 
@@ -209,11 +210,11 @@ module.exports = fp(async (fastify, options) => {
209
210
  },
210
211
  async (request, reply) => {
211
212
  const filename = await services.parseUrlToPhotoBatch({
212
- urlList: request.body.url,
213
+ urlList: request.body.urlList,
213
214
  selector: request.body.selector,
214
215
  options: request.body.options
215
216
  });
216
- return reply.sendFile(filename);
217
+ return reply.sendFile(filename, { root: options.root });
217
218
  }
218
219
  );
219
220
  });
@@ -1,5 +1,5 @@
1
1
  const puppeteer = require('puppeteer');
2
- const createDeferred = require('./createDeferred');
2
+ const createDeferred = require('@kne/create-deferred');
3
3
 
4
4
  module.exports = async options => {
5
5
  options = Object.assign({ maxTaskSize: 100 }, options);
@@ -9,7 +9,7 @@ module.exports = async options => {
9
9
  task: async callback => {
10
10
  return await deferred(async () => {
11
11
  const page = await browser.newPage();
12
- await page.setViewport({ width: 1366, height: 768 });
12
+ await page.setViewport({ width: options.pageWidth, height: options.pageHeight });
13
13
  const output = await (async () => {
14
14
  try {
15
15
  return {
@@ -2,7 +2,7 @@ const setup = require('./setup');
2
2
  (async () => {
3
3
  const puppeteerPage = await setup({
4
4
  puppeteerOptions: {
5
- executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
5
+ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
6
6
  }
7
7
  });
8
8
 
package/libs/services.js CHANGED
@@ -73,11 +73,19 @@ module.exports = fp(async (fastify, options) => {
73
73
  });
74
74
  });
75
75
 
76
- const parseUrlToPdf = memoCache('parseUrlToPdf', async ({ url, options }) => {
76
+ const parseUrlToPdf = memoCache('parseUrlToPdf', async ({ url, options = {} }) => {
77
77
  return await puppeteerPage.task(async ({ page }) => {
78
78
  await page.goto(url, {
79
79
  waitUntil: 'networkidle2'
80
80
  });
81
+ await Promise.all(
82
+ (options.waitForSelectors || []).map(waitForSelector => {
83
+ return page.waitForSelector(waitForSelector, {
84
+ visible: options.waitForVisible || false,
85
+ timeout: options.waitForMaxTime || 10000
86
+ });
87
+ })
88
+ );
81
89
  return await page.pdf(
82
90
  Object.assign(
83
91
  {},
@@ -98,11 +106,19 @@ module.exports = fp(async (fastify, options) => {
98
106
  });
99
107
  });
100
108
 
101
- const parseUrlToPhoto = memoCache('parseUrlToPhoto', async ({ url, selector, options }) => {
109
+ const parseUrlToPhoto = memoCache('parseUrlToPhoto', async ({ url, selector, options = {} }) => {
102
110
  return await puppeteerPage.task(async ({ page }) => {
103
111
  await page.goto(url, {
104
112
  waitUntil: 'networkidle2'
105
113
  });
114
+ await Promise.all(
115
+ (options.waitForSelectors || []).map(waitForSelector => {
116
+ return page.waitForSelector(waitForSelector, {
117
+ visible: options.waitForVisible || false,
118
+ timeout: options.waitForMaxTime || 10000
119
+ });
120
+ })
121
+ );
106
122
  if (selector) {
107
123
  await page.locator(selector).wait();
108
124
  const element = await page.$(selector);
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@kne/fastify-puppeteer",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "用于操作无头浏览器截图保存pdf等",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "init": "husky",
8
8
  "dev": "nodemon ./tests/server.js",
9
9
  "start": "node ./index.js",
10
- "doc": "node ./open-api.js",
11
10
  "build:md": "npx @kne/md-doc",
12
11
  "start:md": "npx @kne/md-doc --watch",
13
12
  "prettier": "prettier --config .prettierrc --write '{libs/**/*,index}.{js,jsx,ts,tsx,json,css,scss}'",
@@ -37,12 +36,14 @@
37
36
  "url": "https://github.com/kne-union/fastify-puppeteer/issues"
38
37
  },
39
38
  "homepage": "https://github.com/kne-union/fastify-puppeteer#readme",
39
+ "peerDependencies": {
40
+ "@kne/fastify-namespace": "*",
41
+ "fastify-plugin": ">=5"
42
+ },
40
43
  "dependencies": {
41
- "@fastify/static": "^7.0.4",
42
- "@kne/fastify-namespace": "^0.1.1",
44
+ "@fastify/static": "^8.2.0",
45
+ "@kne/create-deferred": "^0.1.1",
43
46
  "compressing": "^1.10.1",
44
- "fastify": "^4.27.0",
45
- "fastify-plugin": "^4.5.1",
46
47
  "file-type": "^16.5.4",
47
48
  "fs-extra": "^11.2.0",
48
49
  "lodash": "^4.17.21",
@@ -53,11 +54,12 @@
53
54
  "uuid": "^10.0.0"
54
55
  },
55
56
  "devDependencies": {
56
- "@fastify/swagger": "^8.14.0",
57
+ "@kne/fastify-namespace": "^1.1.2",
58
+ "fastify": "^5.4.0",
59
+ "fastify-plugin": "^5.0.1",
57
60
  "husky": "^9.0.11",
58
61
  "nodemon": "^3.1.3",
59
62
  "prettier": "^3.2.5",
60
- "qs": "^6.12.1",
61
- "sqlite3": "^5.1.7"
63
+ "qs": "^6.14.0"
62
64
  }
63
65
  }
@@ -1,30 +0,0 @@
1
- const createDeferred = size => {
2
- const queue = [];
3
- let runningCount = 0;
4
- const checkRun = () => {
5
- if (queue.length === 0) {
6
- runningCount = 0;
7
- return;
8
- }
9
- if (runningCount < size) {
10
- const callback = queue.shift();
11
- runningCount += 1;
12
- callback().finally(() => {
13
- runningCount -= 1;
14
- checkRun();
15
- });
16
- }
17
- };
18
- return callback => {
19
- return new Promise(resolve => {
20
- queue.push(() => {
21
- const promise = Promise.resolve().then(() => callback());
22
- resolve(promise);
23
- return promise;
24
- });
25
- checkRun();
26
- });
27
- };
28
- };
29
-
30
- module.exports = createDeferred;