当前位置: 首页 > news >正文

页面生成图片或PDF node-egg

没有特别的幸运,那么就特别的努力!!!

中间件:页面生成图片 node-egg

  • 涉及到技术
    • node + egg + Puppeteer
  • 解决文书智能生成多样化
  • 先看效果
  • 环境准备
    • 初始化项目
  • 目录结构
    • 核心代码
  • 完整代码
    • https://gitee.com/hammer1010_admin/node-egg

涉及到技术

node + egg + Puppeteer

官方网址:
node:https://nodejs.org/dist/v16.17.0/
egg: https://www.eggjs.org/zh-CN/
Puppeteer: https://zhaoqize.github.io/puppeteer-api-zh_CN/#/

本次使用node版本:16.17.0

解决文书智能生成多样化

场景1:
比如全国有34个省份,每个省份文书模板不一样
场景2:
条件不一样,文书生成格式布局不一样

先看效果

以百度地址为例: — https://www.baidu.com

1. 启动项目后
http://192.168.XX.XX:7400/  (浏览器访问  端开以你本机为准)2. 通过postman测试接口地址:http://192.168.XX.XX:7400/canvas/getImage
post请求 + 参数
{"url": "https://www.baidu.com"
}

效果图:
在这里插入图片描述

环境准备

操作系统:支持 macOS,Linux,Windows
运行环境:建议选择 LTS 版本,最低要求 8.x。

初始化项目

$ mkdir node-egg
$ cd node-egg
$ npm init
$ npm i egg --save
$ npm i egg-bin --save-dev
$ npm i @sentry/node events generic-pool puppeteer -D

添加 npm scripts 到 package.json:

{"name": "pdf","version": "1.0.0","description": "","main": "app.js","scripts": {"dev": "egg-bin dev"},"author": "hammer1010","license": "ISC","dependencies": {"@sentry/node": "^7.60.1","egg": "^3.17.3","events": "^3.3.0"},"devDependencies": {"egg-bin": "^6.4.1","generic-pool": "^3.9.0","puppeteer": "^20.9.0"}
}

目录结构

egg-example
├── app
│   ├── controller
│   │   └── home.js
│   │   └── XX.js
│   ├── plugins
│   │   └── puppeteer-pool.js
│   ├── service
│   │   └── canvas.js
│   └── router.js
├── config
│   └── config.default.js
└── package.json

主要就是一个puppeteer-pool 线程池,新建一个puppeteer-pool.js文件

'use strict';
const puppeteer = require('puppeteer');
const genericPool = require('generic-pool');/*** 初始化一个 Puppeteer 池* @param {Object} [options={}] 创建池的配置配置* @param {Number} [options.max=10] 最多产生多少个 puppeteer 实例 。如果你设置它,请确保 在引用关闭时调用清理池。 pool.drain().then(()=>pool.clear())* @param {Number} [options.min=1] 保证池中最少有多少个实例存活* @param {Number} [options.maxUses=2048] 每一个 实例 最大可重用次数,超过后将重启实例。0表示不检验* @param {Number} [options.testOnBorrow=2048] 在将 实例 提供给用户之前,池应该验证这些实例。* @param {Boolean} [options.autostart=false] 是不是需要在 池 初始化时 初始化 实例* @param {Number} [options.idleTimeoutMillis=3600000] 如果一个实例 60分钟 都没访问就关掉他* @param {Number} [options.evictionRunIntervalMillis=180000] 每 3分钟 检查一次 实例的访问状态* @param {Object} [options.puppeteerArgs={}] puppeteer.launch 启动的参数* @param {Function} [options.validator=(instance)=>Promise.resolve(true))] 用户自定义校验 参数是 取到的一个实例* @param {Object} [options.otherConfig={}] 剩余的其他参数 // For all opts, see opts at https://github.com/coopernurse/node-pool#createpool* @return {Object} pool*/
const initPuppeteerPool = (options = {}) => {const {max = 10,min = 2,maxUses = 2048,testOnBorrow = true,autostart = false,idleTimeoutMillis = 3600000,evictionRunIntervalMillis = 180000,puppeteerArgs = {},validator = () => Promise.resolve(true),...otherConfig} = options;const factory = {create: () =>puppeteer.launch(puppeteerArgs).then(instance => {// 创建一个 puppeteer 实例 ,并且初始化使用次数为 0instance.useCount = 0;return instance;}),destroy: instance => {instance.close();},validate: instance => {// 执行一次自定义校验,并且校验校验 实例已使用次数。 当 返回 reject 时 表示实例不可用return validator(instance).then(valid => Promise.resolve(valid && (maxUses <= 0 || instance.useCount < maxUses)));},};const config = {max,min,testOnBorrow,autostart,idleTimeoutMillis,evictionRunIntervalMillis,...otherConfig,};const pool = genericPool.createPool(factory, config);const genericAcquire = pool.acquire.bind(pool);// 重写了原有池的消费实例的方法。添加一个实例使用次数的增加pool.acquire = () =>genericAcquire().then(instance => {instance.useCount += 1;return instance;});pool.use = fn => {let resource;return pool.acquire().then(r => {resource = r;return resource;}).then(fn).then(result => {// 不管业务方使用实例成功与否都表示一下实例消费完成pool.release(resource);return result;},err => {pool.release(resource);throw err;});};return pool;
};module.exports = { initPuppeteerPool };

核心代码

'use strict';const Service = require('egg').Service;
const path = require('path');const { mkdirSyncGuard, __Time, generateGuid } = require('../../util');// const regx = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/; // 校验URL合法性
const regx = /(^(http|https):\/\/([\w\-]+\.)+[\w\-]+(\/[\w\u4e00-\u9fa5\-\.\/?\@\%\!\&=\{\}\\"\[\]\+\~\:\#\;\,]*)?)/;class CanvasService extends Service {// 截图async generateImage({ url, width, height, isMobile, deviceScaleFactor }) {const { app } = this;const fileDir = __Time(new Date()).substr(0, 10);// path.resolve() 拼接路径 ==> __dirname 获取文件所在的绝对路径const fileTempPath = path.resolve(__dirname, '../public/canvas', fileDir); // 文件临时路径mkdirSyncGuard(fileTempPath); // 递归检查目录if (!regx.test(url)) return null;try {const FileName = `${generateGuid()}.png`;const ImagePath = path.join(fileTempPath, FileName);await app.pool.use(async puppeteerInstance => {const page = await puppeteerInstance.newPage();width && height && await page.setViewport({ width, height, isMobile, deviceScaleFactor });await page.goto(url, { waitUntil: 'networkidle2' });await page.screenshot({ path: ImagePath, type: 'png', fullPage: true, width: width || 768 });await page.close();});return `canvas/${fileDir}/${FileName}`;} catch (e) {console.log(e);}}/*** 生成PDF* @param {object} param 参数*/async generatePDF({ url, width = undefined, height = undefined, isMobile = undefined, deviceScaleFactor = 1, format = undefined, margin = undefined, printBackground = true }) {const { app } = this;const fileDir = __Time(new Date()).substr(0, 10);const fileTempPath = path.resolve(__dirname, '../public/canvas', fileDir); // 文件临时路径mkdirSyncGuard(fileTempPath); // 递归检查目录if (!regx.test(url)) return null;try {const FileName = `${generateGuid()}.pdf`;const FilePath = path.join(fileTempPath, FileName);await app.pool.use(async puppeteerInstance => {const page = await puppeteerInstance.newPage();width && height && await page.setViewport({ width, height, isMobile, deviceScaleFactor });await page.goto(url, {waitUntil: 'networkidle2',});// I dont't no Whyformat && await page.pdf({ path: FilePath, omitBackground: true, displayHeaderFooter: true, printBackground: Boolean(printBackground), width: width || 768, height: height || 1400, format: format || 'letter', margin: margin || 0 });!format && await page.pdf({ path: FilePath, omitBackground: true, displayHeaderFooter: true, printBackground: Boolean(printBackground), width: width || 768, height: height || 1400, margin: margin || 0 });await page.close();});return `canvas/${fileDir}/${FileName}`;} catch (e) {console.log(e);}}
}module.exports = CanvasService;

完整代码

https://gitee.com/hammer1010_admin/node-egg

希望能帮助到大家,同时祝愿大家在开发旅途中愉快!!!

拿着 不谢 请叫我“锤” !!!

相关文章:

页面生成图片或PDF node-egg

没有特别的幸运&#xff0c;那么就特别的努力&#xff01;&#xff01;&#xff01; 中间件&#xff1a;页面生成图片 node-egg 涉及到技术node egg Puppeteer 解决文书智能生成多样化先看效果环境准备初始化项目 目录结构核心代码 完整代码https://gitee.com/hammer1010_ad…...

go常用知识点

go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct 打包一个目录下的多个包时 go build ./… go install ./… 测试时&#xff0c;命令行&#xff1a;go test . //目录下所有单元测试都会执行 go test -v 目录 //测试覆盖率 go test -cover //使用cove…...

ComPDFKit PDF SDK(支持Web、Android、IOS、Windows、Server、API、跨平台)

1. SDK、API是什么&#xff1f; SDK是软件开发工具包的缩写&#xff0c;指的是一组用于开发软件应用的工具、库和文档。SDK包含一系列的函数、类和方法&#xff0c;开发人员可以使用这些工具和资源来开发、测试和部署应用程序。SDK可以提供各种功能和技术支持&#xff0c;如图…...

使用maven容器打包java项目

docker run --rm -v /path/to/your/microservice:/app -w /app maven:latest mvn clean package 解释一下上面的命令&#xff1a; docker run&#xff1a;运行Docker容器。--rm&#xff1a;在容器运行结束后自动删除容器&#xff0c;避免堆积未使用的容器。-v /path/to/you…...

超前端相关的学习网站和一些靠谱的小工具

CSS相关 1. CSS Battle - 在线比拼 CSS https://cssbattle.dev 在线比拼 CSS &#xff0c;一个挺有趣的竞争性游戏&#xff0c;一共有12个级别&#xff0c;需要你用 HTML和 CSS 100%还原它给出的页面&#xff0c;然后再尽量减少代码&#xff0c;你也可以查看全球的排行榜&am…...

uniapp跳转到外部链接

// 一、先配置页面 {"path": "pages/webview/webview","style": {"navigationBarTitleText": ""} } // 二、编写页面 <template><web-view :src"src" /> </template><script> export def…...

初识DBT以及搭建第一个DBT工程

DBT是什么&#xff1a; 按照官方的说法&#xff0c;DBT 是一个数据转换流编排工具。个人理解就是&#xff0c;DBT是帮你编排SQL用的&#xff0c;你可以按照DBT的结构&#xff0c;构建好一个SQL的pipeline&#xff0c;然后让DBT帮你执行这个pipeline。我这里说的SQL pipeline的意…...

Python基于PyTorch实现卷积神经网络回归模型(CNN回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 卷积神经网络&#xff0c;简称为卷积网络&#xff0c;与普通神经网络的区别是它的卷积层内的神经元只覆…...

(AcWing)集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。 现在有两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿取石子&#xff0c;每次拿取的石子数量必须包含于集合 S&#xff0c;最后无法进行操作的人视为失败。 问如果两人都采用最优策略&#xff0c;先…...

ConcurrentHashMap源码详解

本文已收录于专栏 《Java》 目录 概念说明数据结构线程安全HashMap示例运行结果ConcurrentHashMap示例运行结果 涉及技术Synchronized概念特性 CAS(Compare And Swap)概念原理代码演示没有使用CAS的代码运行结果使用CAS的代码运行结果 总结提升 概念说明 ConcurrentHashMap是Ja…...

医疗流程自动化盛行,RPA成为医疗保健行业的重点应用技术

随着我们进入新的科技纪元&#xff0c;机器人流程自动化&#xff08;RPA&#xff09;正快速地改变着我们的游戏规则。简单来说&#xff0c;RPA 就是模仿人类与电子系统的互动&#xff0c;自动化执行重复性的任务和操作序列。 医疗保健领域中&#xff0c;RPA 的应用具备巨大的潜…...

Java 版 spring cloud + spring boot 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

java重试机制实现方案

本文内容是目前团队内小磊同学对重试机制实现方案的梳理总结。 从为什么需要重试的背景开始&#xff0c;到重试的场景&#xff0c;大致的一些设计思路&#xff0c;最后通过两个成熟的retry组件进行案例讲解&#xff0c;理论实战。 背景 重试是系统提高容错能力的一种手段。在一…...

参数量仅有50KB的超轻量级unet变种网络egeunet【参数和计算量降低494和160倍】医疗图像分割实践

今天看到一篇挺有意思的文章&#xff0c;做的是跟医疗图像分割相关的工作&#xff0c;但是不像之前看到的一些工作一味地去追求高精度&#xff0c;因为医疗领域本身就是一个相对特殊的行业&#xff0c;对于模型产生的结果的精确性要求是很高的&#xff0c;带来的是参数量级的庞…...

Android10 Settings系列(三)根据需求动态添加删除一级菜单、二级菜单的设置项

一 、背景 当时遇到定制需求,需要根据实际需要隐藏Settings的菜单项,于是开始了寻找方法 二 、准备工作 在看了一下源码,经过尝试后,确认生效后,就简单说明一下Settings中布局中主要组成元素 Settings中的菜单项是由 PreferenceScreen 和Preference组成的。其中Prefer…...

51单片机——串行口通信

目录 1、51单片机串口通信介绍 2、串行口相关寄存器 2.1 、串行口控制寄存器SCON和PCON 2.1.1 SCON&#xff1a;串行控制寄存器 (可位寻址) 2.1.2 PCON&#xff1a;电源控制寄存器&#xff08;不可位寻址&#xff09; 2.2、串行口数据缓冲寄存器SBUF 2.3、从机地址控制…...

洛谷题单 Part 6.7.1 矩阵

应队友要求&#xff0c;开始学线性代数&#xff0c;具体路线是矩阵 → \rightarrow →高斯消元 → \rightarrow →线性基。为多项式做个准备 P3390 【模板】矩阵快速幂 题面 板子&#xff0c;用结构体写的&#xff0c;感觉有点丑&#xff0c;一会儿看看题解有没有写得好看的 …...

Spring中c3p0与dbcp配置

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schem…...

Flutter 添加 example流程

一、已有Flutter工程&#xff08;命令&#xff09;添加 example 1、cd 工程(flutter_plugin ,是自己创建的)根目录 例: flutter create example 执行命令创建example PS&#xff1a;cd example 后执行flutter doctor 后就可以看到效果 2、如果需要指定iOS/Android 语言,请添加…...

数据治理8种方法

数据治理8种方法 8种方法&#xff0c;分别是&#xff1a;顶层设计法、技术推动法、应用牵引法、标准先行法、监管驱动法、质量管控法、利益驱动法、项目建设法。 事先声明&#xff0c;这些方法论都是向各位大佬学习来的&#xff0c;也有部分是项目中实操得来的&#xff0c;并非…...

大模型成互联网真正蜕变的标志,亦是各种新技术开始衍生的标志

以往&#xff0c;我们看到了以区块链、元宇宙为代表的诸多新物种的出现&#xff0c;但是&#xff0c;它们始终都没有逃脱仅仅只是一个概念和噱头的宿命&#xff0c;它们始终都没有走出一条可持续的发展道路。说到底&#xff0c;它们仅仅只是一个没有实现商业闭环的概念而已&…...

指针进阶详解---C语言

❤博主CSDN:啊苏要学习 ▶专栏分类&#xff1a;C语言◀ C语言的学习&#xff0c;是为我们今后学习其它语言打好基础&#xff0c;C生万物&#xff01; 开始我们的C语言之旅吧&#xff01;✈ 目录 前言&#xff1a; 一.字符指针 二.指针数组 三.数组指针 四.数组、指针参数 …...

设计模式思考,简单工厂模式和策略模式的区别?

最近学习了设计模式&#xff0c;学到简单工厂模式和策略模式的时候想&#xff0c;这两个模式不是一样嘛&#xff0c;仔细思考之后发现大体设计思路是一样的&#xff0c;但是细节却有所不一样。 简单工厂模式 简单工厂模式是一种创建型设计模式&#xff0c;它主要涉及对象的创建…...

Java - sh 脚本启动 jar 包等服务 - sh 脚本模板 - 适用于任何类似的服务启动

sh 脚本模板 该模板&#xff0c;每次运行一次都会 kill 掉原来的服务&#xff0c;然后重新启动 jar 包服务 #!/bin/bash# 定义Java进程的名称 APP_NAMEyour-app-name.jar# 定义Java进程的日志文件路径 LOG_PATH/var/log/your-app-name.log# 定义备份日志文件的目录 BACKUP_DI…...

MySQL高级篇第5章(存储引擎)

文章目录 1、查看存储引擎2、设置系统默认的存储引擎3、设置表的存储引擎3.1 创建表时指定存储引擎3.2 修改表的存储引擎 4、引擎介绍4.1 InnoDB 引擎&#xff1a;具备外键支持功能的事务存储引擎4.2 MyISAM 引擎&#xff1a;主要的非事务处理存储引擎4.3 Archive 引擎&#xf…...

openssl 命令行国密sm2的签名验签操作

快速链接: . &#x1f449;&#x1f449;&#x1f449; 个人博客笔记导读目录(全部) &#x1f448;&#x1f448;&#x1f448; 付费专栏-付费课程 【购买须知】: 密码学实践强化训练–【目录】 &#x1f448;&#x1f448;&#x1f448; 生成EC私钥&#xff1a; openssl ecp…...

开源代码分享(9)—面向100%清洁能源的发输电系统扩展规划(附matlab代码)

1.背景介绍 1.1摘要 本文提出了一种新颖的建模框架和基于分解的解决策略&#xff0c;将随机规划&#xff08;SP&#xff09;和鲁棒优化&#xff08;RO&#xff09;相结合&#xff0c;以应对协调中长期电力系统规划中的多重不确定性。从独立系统运营商&#xff08;ISO&#xff…...

为 Google Play 即将推出基于区块链的内容政策做好准备

作者 / Joseph Mills, Group Product Manager, Google Play 作为一个平台&#xff0c;Google Play 一直致力于帮助开发者将创新理念变为现实。Google Play 上托管了许多和区块链相关的应用&#xff0c;我们深知合作伙伴们希望扩展这些应用&#xff0c;并利用 NFT 等代币化数字资…...

查找-多路查找详解篇

多路查找树 多路查找树&#xff08;Multway Search Tree&#xff09;是一种高级的树形数据结构&#xff0c;它 允许每个节点有多个子节点&#xff08;通常大于等于2&#xff09;。多路查找树的每个节点 可以存储多个关键字和对应的值。分类 2-3树&#xff08;2-3 Tree&#x…...

css设置八等分圆

现需要上图样式的布局&#xff0c;我通过两张向右方的图片&#xff0c;通过定位和旋转完成了布局。 问题&#xff1a; 由于是通过旋转获取到的样式&#xff0c;实际的盒子是一个长方形&#xff0c;当鼠标移入对应的箭头时选中的可能是其他盒子&#xff0c;如第一张设计稿可以看…...