@kne/fastify-puppeteer 1.1.9 → 1.1.11

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.
@@ -1,5 +1,10 @@
1
1
  const fp = require('fastify-plugin');
2
2
  const { decode } = require('plantuml-encoder');
3
+ const { pdfFile, photoFile, zipFile, filename, encodedContent } = require('./schema/response');
4
+
5
+ const PDF_TAG = '无头浏览器-PDF生成';
6
+ const PHOTO_TAG = '无头浏览器-网页截图';
7
+ const CACHE_HINT = '相同参数的结果会缓存在本地目录,命中缓存时直接返回已生成的文件';
3
8
 
4
9
  module.exports = fp(async (fastify, options) => {
5
10
  const { services } = fastify.puppeteer;
@@ -9,16 +14,19 @@ module.exports = fp(async (fastify, options) => {
9
14
  {
10
15
  onRequest: options.authenticate,
11
16
  schema: {
12
- description: '接口说明',
17
+ tags: [PDF_TAG],
13
18
  summary: 'html生成pdf文件流',
19
+ description: `用无头浏览器加载 HTML 内容并生成PDF;${CACHE_HINT}`,
14
20
  body: {
15
21
  type: 'object',
16
22
  required: ['content'],
17
23
  properties: {
18
24
  options: pdfSchema,
19
- content: { type: 'string' }
25
+ content: { type: 'string', description: '要渲染的 HTML 内容' },
26
+ filename
20
27
  }
21
- }
28
+ },
29
+ response: pdfFile
22
30
  }
23
31
  },
24
32
  async (request, reply) => {
@@ -30,15 +38,19 @@ module.exports = fp(async (fastify, options) => {
30
38
  fastify.get(
31
39
  `${options.prefix}/parseHtmlToPdf`,
32
40
  {
33
- description: '接口说明',
34
- summary: 'html生成pdf文件流',
35
- query: {
36
- type: 'object',
37
- required: ['content'],
38
- properties: {
39
- options: pdfSchema,
40
- content: { type: 'string' }
41
- }
41
+ schema: {
42
+ tags: [PDF_TAG],
43
+ summary: 'html生成pdf文件流',
44
+ description: `GET 方式把 HTML 渲染为 PDF,content 需先用 plantuml-encoder 压缩编码;${CACHE_HINT}。注意:该路由未做参数校验,也未挂 authenticate 鉴权`,
45
+ query: {
46
+ type: 'object',
47
+ properties: {
48
+ options: { description: 'PDF 生成参数,结构同 POST /parseHtmlToPdf 的 options(按 querystring 嵌套格式传递);该路由不校验也不填充默认值' },
49
+ content: { description: `要渲染${encodedContent}` },
50
+ filename
51
+ }
52
+ },
53
+ response: pdfFile
42
54
  }
43
55
  },
44
56
  async (request, reply) => {
@@ -53,8 +65,9 @@ module.exports = fp(async (fastify, options) => {
53
65
  {
54
66
  onRequest: options.authenticate,
55
67
  schema: {
56
- description: '接口说明',
68
+ tags: [PDF_TAG],
57
69
  summary: '批量将html生成pdf的zip文件流',
70
+ description: `把多段 HTML 分别渲染为PDF并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;${CACHE_HINT}`,
58
71
  body: {
59
72
  type: 'object',
60
73
  required: ['contentList'],
@@ -62,12 +75,15 @@ module.exports = fp(async (fastify, options) => {
62
75
  options: pdfSchema,
63
76
  contentList: {
64
77
  type: 'array',
78
+ description: '要渲染的 HTML 内容列表,每一项生成一个文件',
65
79
  items: {
66
80
  type: 'string'
67
81
  }
68
- }
82
+ },
83
+ filename
69
84
  }
70
- }
85
+ },
86
+ response: zipFile('PDF')
71
87
  }
72
88
  },
73
89
  async (request, reply) => {
@@ -85,8 +101,9 @@ module.exports = fp(async (fastify, options) => {
85
101
  {
86
102
  onRequest: options.authenticate,
87
103
  schema: {
88
- description: '接口说明',
104
+ tags: [PDF_TAG],
89
105
  summary: '批量将html生成pdf的zip文件流',
106
+ description: `把多段 HTML 分别渲染为PDF并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;GET 方式的 HTML 内容需先用 plantuml-encoder 压缩编码;${CACHE_HINT}`,
90
107
  query: {
91
108
  type: 'object',
92
109
  required: ['contentList'],
@@ -94,12 +111,15 @@ module.exports = fp(async (fastify, options) => {
94
111
  options: pdfSchema,
95
112
  contentList: {
96
113
  type: 'array',
114
+ description: `要渲染的 HTML 内容列表,每一项都是需先用 plantuml-encoder 的 encode 压缩编码的 HTML`,
97
115
  items: {
98
116
  type: 'string'
99
117
  }
100
- }
118
+ },
119
+ filename
101
120
  }
102
- }
121
+ },
122
+ response: zipFile('PDF')
103
123
  }
104
124
  },
105
125
  async (request, reply) => {
@@ -117,8 +137,9 @@ module.exports = fp(async (fastify, options) => {
117
137
  {
118
138
  onRequest: options.authenticate,
119
139
  schema: {
120
- description: '接口说明',
140
+ tags: [PHOTO_TAG],
121
141
  summary: '批量将html生成png的zip文件流',
142
+ description: `把多段 HTML 分别渲染为图片并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;${CACHE_HINT}`,
122
143
  body: {
123
144
  type: 'object',
124
145
  required: ['contentList'],
@@ -126,12 +147,15 @@ module.exports = fp(async (fastify, options) => {
126
147
  options: photoSchema,
127
148
  contentList: {
128
149
  type: 'array',
150
+ description: '要渲染的 HTML 内容列表,每一项生成一个文件',
129
151
  items: {
130
152
  type: 'string'
131
153
  }
132
- }
154
+ },
155
+ filename
133
156
  }
134
- }
157
+ },
158
+ response: zipFile('图片')
135
159
  }
136
160
  },
137
161
  async (request, reply) => {
@@ -149,8 +173,9 @@ module.exports = fp(async (fastify, options) => {
149
173
  {
150
174
  onRequest: options.authenticate,
151
175
  schema: {
152
- description: '接口说明',
176
+ tags: [PHOTO_TAG],
153
177
  summary: '批量将html生成png的zip文件流',
178
+ description: `把多段 HTML 分别渲染为图片并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;GET 方式的 HTML 内容需先用 plantuml-encoder 压缩编码;${CACHE_HINT}`,
154
179
  query: {
155
180
  type: 'object',
156
181
  required: ['contentList'],
@@ -158,12 +183,15 @@ module.exports = fp(async (fastify, options) => {
158
183
  options: photoSchema,
159
184
  contentList: {
160
185
  type: 'array',
186
+ description: `要渲染的 HTML 内容列表,每一项都是需先用 plantuml-encoder 的 encode 压缩编码的 HTML`,
161
187
  items: {
162
188
  type: 'string'
163
189
  }
164
- }
190
+ },
191
+ filename
165
192
  }
166
- }
193
+ },
194
+ response: zipFile('图片')
167
195
  }
168
196
  },
169
197
  async (request, reply) => {
@@ -181,16 +209,19 @@ module.exports = fp(async (fastify, options) => {
181
209
  {
182
210
  onRequest: options.authenticate,
183
211
  schema: {
184
- description: '接口说明',
212
+ tags: [PHOTO_TAG],
185
213
  summary: 'html生成png文件流',
214
+ description: `用无头浏览器加载 HTML 内容并生成图片;${CACHE_HINT}`,
186
215
  body: {
187
216
  type: 'object',
188
217
  required: ['content'],
189
218
  properties: {
190
219
  options: photoSchema,
191
- content: { type: 'string' }
220
+ content: { type: 'string', description: '要渲染的 HTML 内容' },
221
+ filename
192
222
  }
193
- }
223
+ },
224
+ response: photoFile
194
225
  }
195
226
  },
196
227
  async (request, reply) => {
@@ -208,16 +239,19 @@ module.exports = fp(async (fastify, options) => {
208
239
  {
209
240
  onRequest: options.authenticate,
210
241
  schema: {
211
- description: '接口说明',
242
+ tags: [PHOTO_TAG],
212
243
  summary: 'html生成png文件流',
244
+ description: `用无头浏览器加载 HTML 内容并生成图片;GET 方式的 HTML 内容需先用 plantuml-encoder 压缩编码;${CACHE_HINT}`,
213
245
  query: {
214
246
  type: 'object',
215
247
  required: ['content'],
216
248
  properties: {
217
249
  options: photoSchema,
218
- content: { type: 'string' }
250
+ content: { type: 'string', description: `要渲染${encodedContent}` },
251
+ filename
219
252
  }
220
- }
253
+ },
254
+ response: photoFile
221
255
  }
222
256
  },
223
257
  async (request, reply) => {
@@ -235,16 +269,19 @@ module.exports = fp(async (fastify, options) => {
235
269
  {
236
270
  onRequest: options.authenticate,
237
271
  schema: {
238
- description: '接口说明',
272
+ tags: [PDF_TAG],
239
273
  summary: 'url生成pdf文件流',
274
+ description: `用无头浏览器打开网页地址(等待网络空闲)后生成PDF;${CACHE_HINT}`,
240
275
  body: {
241
276
  type: 'object',
242
277
  required: ['url'],
243
278
  properties: {
244
279
  options: pdfSchema,
245
- url: { type: 'string' }
280
+ url: { type: 'string', description: '要打开的网页地址' },
281
+ filename
246
282
  }
247
- }
283
+ },
284
+ response: pdfFile
248
285
  }
249
286
  },
250
287
  async (request, reply) => {
@@ -259,16 +296,19 @@ module.exports = fp(async (fastify, options) => {
259
296
  {
260
297
  onRequest: options.authenticate,
261
298
  schema: {
262
- description: '接口说明',
299
+ tags: [PDF_TAG],
263
300
  summary: 'url生成pdf文件流',
301
+ description: `用无头浏览器打开网页地址(等待网络空闲)后生成PDF;${CACHE_HINT}`,
264
302
  query: {
265
303
  type: 'object',
266
304
  required: ['url'],
267
305
  properties: {
268
306
  options: pdfSchema,
269
- url: { type: 'string' }
307
+ url: { type: 'string', description: '要打开的网页地址' },
308
+ filename
270
309
  }
271
- }
310
+ },
311
+ response: pdfFile
272
312
  }
273
313
  },
274
314
  async (request, reply) => {
@@ -278,21 +318,54 @@ module.exports = fp(async (fastify, options) => {
278
318
  }
279
319
  );
280
320
 
321
+ fastify.post(
322
+ `${options.prefix}/parseUrlToPdfBatch`,
323
+ {
324
+ onRequest: options.authenticate,
325
+ schema: {
326
+ tags: [PDF_TAG],
327
+ summary: 'url批量生成pdf的zip文件流',
328
+ description: `逐个打开网页地址生成PDF并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;${CACHE_HINT}`,
329
+ body: {
330
+ type: 'object',
331
+ required: ['urlList'],
332
+ properties: {
333
+ options: pdfSchema,
334
+ urlList: { type: 'array', items: { type: 'string' }, description: '要打开的网页地址列表,每一项生成一个文件' },
335
+ filename
336
+ }
337
+ },
338
+ response: zipFile('PDF')
339
+ }
340
+ },
341
+ async (request, reply) => {
342
+ const filename = await services.parseUrlToPdfBatch({
343
+ urlList: request.body.urlList,
344
+ options: request.body.options
345
+ });
346
+ request.body.filename && reply.header('Content-Disposition', `attachment; filename=${encodeURIComponent(request.body.filename)}`);
347
+ return reply.sendFile(filename, { root: options.root });
348
+ }
349
+ );
350
+
281
351
  fastify.get(
282
352
  `${options.prefix}/parseUrlToPdfBatch`,
283
353
  {
284
354
  onRequest: options.authenticate,
285
355
  schema: {
286
- description: '接口说明',
287
- summary: 'url生成pdf文件流',
356
+ tags: [PDF_TAG],
357
+ summary: 'url批量生成pdf的zip文件流',
358
+ description: `逐个打开网页地址生成PDF并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys;${CACHE_HINT}`,
288
359
  query: {
289
360
  type: 'object',
290
361
  required: ['urlList'],
291
362
  properties: {
292
363
  options: pdfSchema,
293
- urlList: { type: 'array', items: { type: 'string' } }
364
+ urlList: { type: 'array', items: { type: 'string' }, description: '要打开的网页地址列表,每一项生成一个文件' },
365
+ filename
294
366
  }
295
- }
367
+ },
368
+ response: zipFile('PDF')
296
369
  }
297
370
  },
298
371
  async (request, reply) => {
@@ -310,17 +383,20 @@ module.exports = fp(async (fastify, options) => {
310
383
  {
311
384
  onRequest: options.authenticate,
312
385
  schema: {
313
- description: '接口说明',
386
+ tags: [PHOTO_TAG],
314
387
  summary: 'url生成png文件流',
388
+ description: `用无头浏览器打开网页地址(等待网络空闲)后生成图片;传 selector 时只截取该元素区域;${CACHE_HINT}`,
315
389
  body: {
316
390
  type: 'object',
317
391
  required: ['url'],
318
392
  properties: {
319
393
  options: photoSchema,
320
- url: { type: 'string' },
321
- selector: { type: 'string' }
394
+ url: { type: 'string', description: '要打开的网页地址' },
395
+ selector: { type: 'string', description: 'CSS 选择器;传入时等待该元素出现并只截取其区域,不传截取整个页面' },
396
+ filename
322
397
  }
323
- }
398
+ },
399
+ response: photoFile
324
400
  }
325
401
  },
326
402
  async (request, reply) => {
@@ -339,17 +415,20 @@ module.exports = fp(async (fastify, options) => {
339
415
  {
340
416
  onRequest: options.authenticate,
341
417
  schema: {
342
- description: '接口说明',
418
+ tags: [PHOTO_TAG],
343
419
  summary: 'url生成png文件流',
420
+ description: `用无头浏览器打开网页地址(等待网络空闲)后生成图片;传 selector 时只截取该元素区域;${CACHE_HINT}`,
344
421
  query: {
345
422
  type: 'object',
346
423
  required: ['url'],
347
424
  properties: {
348
425
  options: photoSchema,
349
- url: { type: 'string' },
350
- selector: { type: 'string' }
426
+ url: { type: 'string', description: '要打开的网页地址' },
427
+ selector: { type: 'string', description: 'CSS 选择器;传入时等待该元素出现并只截取其区域,不传截取整个页面' },
428
+ filename
351
429
  }
352
- }
430
+ },
431
+ response: photoFile
353
432
  }
354
433
  },
355
434
  async (request, reply) => {
@@ -368,17 +447,20 @@ module.exports = fp(async (fastify, options) => {
368
447
  {
369
448
  onRequest: options.authenticate,
370
449
  schema: {
371
- description: '接口说明',
372
- summary: 'url生成png文件流',
450
+ tags: [PHOTO_TAG],
451
+ summary: 'url批量生成png的zip文件流',
452
+ description: `逐个打开网页地址生成图片并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys。注意:当前实现实际调用的是 url 生成 PDF,压缩包内为 PDF 文件且 selector 不生效;${CACHE_HINT}`,
373
453
  body: {
374
454
  type: 'object',
375
455
  required: ['urlList'],
376
456
  properties: {
377
457
  options: photoSchema,
378
- urlList: { type: 'array', items: { type: 'string' } },
379
- selector: { type: 'string' }
458
+ urlList: { type: 'array', items: { type: 'string' }, description: '要打开的网页地址列表,每一项生成一个文件' },
459
+ selector: { type: 'string', description: 'CSS 选择器,只截取该元素区域(当前实现未生效,见接口说明)' },
460
+ filename
380
461
  }
381
- }
462
+ },
463
+ response: zipFile('图片')
382
464
  }
383
465
  },
384
466
  async (request, reply) => {
@@ -397,17 +479,20 @@ module.exports = fp(async (fastify, options) => {
397
479
  {
398
480
  onRequest: options.authenticate,
399
481
  schema: {
400
- description: '接口说明',
401
- summary: 'url生成png文件流',
482
+ tags: [PHOTO_TAG],
483
+ summary: 'url批量生成png的zip文件流',
484
+ description: `逐个打开网页地址生成图片并打包为 zip 返回,数量不能超过插件配置 maxCacheKeys。注意:当前实现实际调用的是 url 生成 PDF,压缩包内为 PDF 文件且 selector 不生效;${CACHE_HINT}`,
402
485
  query: {
403
486
  type: 'object',
404
487
  required: ['urlList'],
405
488
  properties: {
406
489
  options: photoSchema,
407
- urlList: { type: 'array', items: { type: 'string' } },
408
- selector: { type: 'string' }
490
+ urlList: { type: 'array', items: { type: 'string' }, description: '要打开的网页地址列表,每一项生成一个文件' },
491
+ selector: { type: 'string', description: 'CSS 选择器,只截取该元素区域(当前实现未生效,见接口说明)' },
492
+ filename
409
493
  }
410
- }
494
+ },
495
+ response: zipFile('图片')
411
496
  }
412
497
  },
413
498
  async (request, reply) => {
@@ -0,0 +1,13 @@
1
+ // 接口均通过 reply.sendFile 返回文件流,响应 schema 只写说明、不声明类型,避免干扰输出
2
+ const fileResponse = description => ({ 200: { description } });
3
+
4
+ const DISPOSITION = ';请求传 filename 时附带 Content-Disposition: attachment 下载头';
5
+ const pdfFile = fileResponse(`PDF 文件流(application/pdf)${DISPOSITION}`);
6
+ const photoFile = fileResponse(`图片文件流,格式由 options.type 决定,默认 png${DISPOSITION}`);
7
+ const zipFile = kind => fileResponse(`zip 压缩包文件流,按输入顺序包含每一项生成的${kind}${DISPOSITION}`);
8
+
9
+ // filename 原本未声明、无类型约束,这里只补说明不加 type
10
+ const filename = { description: '下载文件名;传入后响应附带 Content-Disposition: attachment 头,不传则浏览器按文件流直接展示' };
11
+ const encodedContent = '的 HTML 内容,需先用 plantuml-encoder 的 encode 压缩编码后放入 query';
12
+
13
+ module.exports = { fileResponse, pdfFile, photoFile, zipFile, filename, encodedContent };
package/libs/services.js CHANGED
@@ -7,6 +7,7 @@ const createDeferred = require('@kne/create-deferred');
7
7
 
8
8
  const pdfSchema = {
9
9
  type: 'object',
10
+ description: 'PDF 生成参数,透传给 puppeteer page.pdf;不传 options 时按 A4、打印背景、显示页眉页脚生成,传了 options 则未填的项按各字段 default 填充',
10
11
  properties: {
11
12
  scale: {
12
13
  type: 'number',
@@ -61,6 +62,7 @@ const pdfSchema = {
61
62
  },
62
63
  margin: {
63
64
  type: 'object',
65
+ description: '页边距',
64
66
  properties: {
65
67
  top: {
66
68
  oneOf: [{ type: 'number' }, { type: 'string' }],
@@ -105,10 +107,11 @@ const pdfSchema = {
105
107
  description: '生成带标签的 PDF(增强可访问性)',
106
108
  default: false
107
109
  },
108
- waitForMaxTime: { type: 'number' },
109
- waitForVisible: { type: 'boolean' },
110
+ waitForMaxTime: { type: 'number', description: '等待 waitForSelector 元素的超时时间(毫秒),默认 10000;仅 url 类接口有效' },
111
+ waitForVisible: { type: 'boolean', description: '等待 waitForSelector 元素时是否要求元素可见;仅 url 类接口有效' },
110
112
  waitForSelector: {
111
- oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }]
113
+ oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }],
114
+ description: '打开网页后、生成前需等待出现的 CSS 选择器(单个或数组);注意当前实现读取的是 waitForSelectors,该参数实际未生效'
112
115
  },
113
116
  timezone: {
114
117
  type: 'string',
@@ -117,6 +120,7 @@ const pdfSchema = {
117
120
  },
118
121
  viewport: {
119
122
  type: 'object',
123
+ description: '浏览器视口尺寸,不传使用插件配置 pageWidth / pageHeight',
120
124
  properties: {
121
125
  width: {
122
126
  type: 'number',
@@ -136,6 +140,7 @@ const pdfSchema = {
136
140
 
137
141
  const photoSchema = {
138
142
  type: 'object',
143
+ description: '截图参数,透传给 puppeteer page.screenshot;不传 options 时按 png、仅可视区域截图,传了 options 则未填的项按各字段 default 填充(如 fullPage 为 true)',
139
144
  properties: {
140
145
  type: {
141
146
  type: 'string',
@@ -190,10 +195,11 @@ const photoSchema = {
190
195
  type: 'boolean',
191
196
  description: '是否捕获视口外的内容(仅当 fullPage 为 true 时有效)'
192
197
  },
193
- waitForMaxTime: { type: 'number' },
194
- waitForVisible: { type: 'boolean' },
198
+ waitForMaxTime: { type: 'number', description: '等待 waitForSelector 元素的超时时间(毫秒),默认 10000;仅 url 类接口有效' },
199
+ waitForVisible: { type: 'boolean', description: '等待 waitForSelector 元素时是否要求元素可见;仅 url 类接口有效' },
195
200
  waitForSelector: {
196
- oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }]
201
+ oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }],
202
+ description: '打开网页后、生成前需等待出现的 CSS 选择器(单个或数组);注意当前实现读取的是 waitForSelectors,该参数实际未生效'
197
203
  },
198
204
  timezone: {
199
205
  type: 'string',
@@ -202,6 +208,7 @@ const photoSchema = {
202
208
  },
203
209
  viewport: {
204
210
  type: 'object',
211
+ description: '浏览器视口尺寸,不传使用插件配置 pageWidth / pageHeight',
205
212
  properties: {
206
213
  width: {
207
214
  type: 'number',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-puppeteer",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "用于操作无头浏览器截图保存pdf等",
5
5
  "main": "index.js",
6
6
  "scripts": {