PDF 生成(6)— 服务化、配置化

news2025/2/24 8:34:28

当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注点赞收藏评论

新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。

回顾

前面我们分别通过 PDF 生成(1)— 开篇、PDF 生成(2)— 生成 PDF 文件、PDF 生成(3)— 封面、尾页、PDF 生成(4)— 目录页、PDF 生成(5)— 内容页支持由多页面组成 五篇来讲解 PDF 生成的整个方案,到目前为止,整套方案基本完成了:

  • 我们通过 PDF 文件合并技术让一份 PDF 文件包含封面、内容页和尾页三部分
  • 通过在内容页的开始位置动态插入 HTML 锚点、页面缩放、锚点元素高度计算、换页高度补偿等技术让 PDF 文件拥有了包含准确页码 + 页面跳转能力的目录页
  • 通过多页面合并技术 + 样式沙箱解决了用户在复杂 PDF 场景下前端代码维护问题,让用户的开发更自由、更符合业务逻辑

至此,PDF 生成的能力齐了,但怎么给用户使用呢?这就是本文要解决的问题了。

简介

前面我们花了大量的精力来完善整个 PDF 生成方案,现在从 PDF 生成角度来说,能力已经齐备,但整个服务以及相关配置都运行在本地,没办法直接给用户使用。

所以本文我们就将 PDF 生成能力通过服务化暴露给用户,相关资源配置化来适配不同的用户。

服务化

通过为项目引入 Koa 框架来对外提供服务。

  • 安装 koa 和 @koa/router,npm i koa @koa/router
  • 新建/server/koa-server.mjs文件

/server/koa-server.mjs:

import Koa from 'koa'
import KoaRouter from '@koa/router'
import { generatePDF } from './index.mjs'

const app = new Koa()
const router = new KoaRouter()

// 当用户请求 http://localhost:3000 时,触发 generatePDF() 函数生成 PDF 文件
router.get('/', function(ctx) {
  generatePDF()

  ctx.body = {
    errno: 0,
    data: [],
    msg: '正在生成 PDF 文件'
  }
})

app.use(router.routes())

app.listen(3000, () => {
  console.log('koa-server start at 3000 port')
})

/server/index.mjs 导出 generatePDF 方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过 node 或 nodemon 执行 /server/koa-server.mjs,然后在浏览器直接访问http://localhost:3000会看到 PDF 生成服务开始运行,并生成 PDF 文件。这样,我们的 PDF 生成能力就实现了对外的服务化

配置化

目前可以发现,PDF 文件的目录页配置、前端页面的 URL 等信息都是写死在代码中的,我们需要将这些信息以接入方为维度进行统一维护,并以服务的形式暴露给 PDF 生成服务。

  • 安装 axios,用来请求配置服务npm i axios
  • 分别对/server/koa-server.mjs/server/index.mjs进行如下改造

/server/koa-server.mjs

import Koa from 'koa'
import KoaRouter from '@koa/router'
import { generatePDF } from './index.mjs'
import axios from 'axios'

const app = new Koa()
const router = new KoaRouter()

// 当用户请求 http://localhost:3000 时,触发 generatePDF() 函数生成 PDF 文件
router.get('/', async function (ctx) {
  const appId = ctx.query.appId
  const { data: configData } = await axios.get(`http://localhost:3000/get-pdf-config?appId=${appId}`)
  // 异常情况
  if (configData.errno) {
    ctx.body = configData
    return
  }

  const { data } = configData
  generatePDF(data)

  ctx.body = {
    errno: 0,
    data: [],
    msg: '正在生成 PDF 文件'
  }
})

// 获取指定 appId 所对应的配置信息
router.get('/get-pdf-config', function (ctx) {
  const pdfConfig = {
    // 为接入方分配唯一的 uuid
    '59edaf80-ca75-8699-7ca7-b8121d01d136': {
      name: 'PDF 生成服务测试',
      // 目录页配置
      dir: [
        { title: '锚点 1', id: 'anchor1' },
        { title: '锚点 2', id: 'anchor2' },
        { title: '第二个内容页 —— 锚点 1', id: 'second-content-page-anchor1' },
        { title: '第二个内容页 —— 锚点 2', id: 'second-content-page-anchor2' },
      ],
      // 接入方的前端页面链接
      pageInfo: {
        // 封面
        "cover": "file:///Users/liyongning/studyspace/generate-pdf/fe/cover.html",
        // 内容页
        "content": [
          "file:///Users/liyongning/studyspace/generate-pdf/fe/exact-page-num.html",
          "file:///Users/liyongning/studyspace/generate-pdf/fe/second-content-page.html"
        ],
        // 尾页
        "lastPage": "file:///Users/liyongning/studyspace/generate-pdf/fe/last-page.html"
      },
      // ... 还可以增加其他配置
    }
  }

  const appId = ctx.query.appId || ''
  if (!pdfConfig[appId]) {
    ctx.body = {
      errno: 100,
      data: [],
      msg: '无效的 appId,请联系服务提供方申请接入'
    }
    return
  }

  ctx.body = {
    errno: 0,
    data: pdfConfig[appId],
    msg: 'success'
  }
})

app.use(router.routes())

app.listen(3000, () => {
  console.log('koa-server start at 3000 port')
})

增加了配置服务/get-pdf-config,并在 PDF 生成服务中调用,获取配置内容,并将配置内容传递给了generatePDF方法。

/server/index.mjs

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
image.png
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PDF 的目录页配置、封面、内容页、尾页均改成了使用配置服务传递过来的数据,我们在浏览器访问http://localhost:3000/?appId=59edaf80-ca75-8699-7ca7-b8121d01d136即可生成 PDF 文件,如果访问时没有传 appId 会收到异常提示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好了,配置化就讲到这里了,就像代码中提到的一样,所有和配置相关的信息都可以通过配置服务来维护,可根据自己的需求来进行扩充。

并发控制(队列)

现在我们的 PDF 生成能力以服务的形式对外提供,并通过配置服务来维护接入方信息。经过一段时间的推广后,接入的用户越来越多,服务的调用量越来越大,这时候就会遇到服务稳定性的问题。

每个请求我们都会启动一个浏览器,一台 2核 4G内存的机器,三四个并发基本上就超负荷运行了,如果同时有更多的请求过来,直接就宕机了。所以,我们需要为服务增加一个并发控制。思路如下:

  • 给服务增加一个任务队列,这个队列可以通过 kafka 实现,也可以通过 redis 来实现,最差也可以程序自己维护一个单机版的内存队列(不推荐)
  • 每个请求进来时,先入队
  • 当队列中监听到有任务存在时,从队列中取出一个任务然后执行,这个取任务的频率可以由程序自己控制

这样,程序就不会因为请求量过大,而导致机器宕机。基于队列我们也可以做任务失败重试。

任务分类

服务又稳定的运行了一段时间,有天又收到了一个接入申请,这个接入方的使用场景是不定期的生成几千几万份报告,然后将这些报告打包发给销售,让销售进一步跟进用户。

这个需求很合理,但是会对我们现有的服务造成影响,试想,如果这个任务一旦启动,短时间就会在队列中堆积几万个待执行的任务,要消费完这些任务可能需要好几个小时甚至一整天,这会影响其他任务的执行,后入队的任务一直排在队尾,迟迟得不到执行。

这时候,我们就需要对任务进行分类,将任务分为实时和非实时,实时任务进入实时队列,非实时任务进入非实时队列,程序有优先消费实时队列中的任务,当实时队列为空时去消费非实时队列的任务,当两个队列都为空时,程序停止。

其他优化

本系列的重点是演示 PDF 生成的核心思路和逻辑,所以有些地方的代码写的比较简单,比如没有做很好的模块化拆分、异常处理等,但这些完全不影响对整体架构的理解。

技术架构中我们还有一些能力没有实现,比如:

  • PDF 文件上传 S3,并将下载地址回传给接入方
  • 服务的安全校验,可以设置复杂的校验,也可以通过简单的参数签名来做,根据使用场景来决定
  • 告警推送,比如 PDF 文件生成异常告警、PDF 文件下载链接推送入群或发给个人等

剩下的这些功能都依赖一些内网服务,所以这里就没有一一演示了,只提供一些思路,大家可以根据自己的实际情况有选择性的学习和迭代。

部署问题

项目开发结束后,一般都需要部署到服务器上,这时候你可能会遇到一些困难,比如:

  • 启动项目后,会发现有如下报错,其原因是服务器上缺少相关安装包,具体可查看 鼓掌排除 下的 Chrome 无法在 linux 上启动

img

  • 如果遇到如下错误,是因为 nss 库版本过低,可通过 rpm -q nss 命令查看已安装的库信息,然后使用 yum update nss 进行升级

  • 这会儿,服务应该起来了,但执行的时候发现又报错了,这时候需要禁用沙箱,可以查看 鼓掌排除 的 设置 Chrome Linux 沙箱

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 这时候 PDF 终于生成了,但可能会发现 — 乱码了,这是字符集问题,即服务器上没有对应的字体库,具体操作参考下面的字体库章节

img

字体库

如果生成的 PDF 文件出现了乱码问题,是因为服务器缺少字体库文件,我们需要为服务器增加相应的字体。比如我们使用的是 PingFang 和思源黑体,去找设计同学要一份字体文件,然后拷贝到 /usr/share/fonts 目录下,其中涉及到如下命令:

  • fc-list | grep 'PingFang SC' 查看是否有该字体库
  • 字体库配置文件 /etc/fonts/fonts.config,打开后发现,这就是为什么把新增字体文件放 /usr/share/fonts 目录的原因

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 新增字体文件后执行 fc-cache -f -v 清空字体缓存,并会生成新的字体缓存

总结

到这里,本文就结束了,我们来简单总结一些:

  • 我们通过 Koa 框架,将 PDF 生成能力以服务的形式对外暴露
  • 通过配置化服务来维护接入方的一些信息,比如业务名称、目录页的配置、PDF 文件封面、内容页、尾页对应的 URL 等,配置化服务配置的内容有很多,根据场景自行扩充
  • 通过队列来做并发控制,保证服务的稳定性
  • 通过对任务进行分类(实时和非实时),来保证实时任务的及时消费,非实时任务的稳定消费
  • 最后给大家提了一些其他可迭代的点,比如文件上传、下载地址回传、服务安全校验、告警推送等

系列总结

如果你完整的阅读了整个系列,那么首先应该为自己鼓掌,毕竟又是成长的一段时间,另外一定要进行实操,光看不实践,学习效果还是会打一定的折扣。接下来我们就对本系列进行一个简单的回顾总结:

  • 首先我们在 PDF 生成(1)— 开篇 中讲解了 PDF 生成的技术背景、方案选型和决策,以及整个方案的技术架构图,所以后面的几篇一直都是在实现整套技术架构
  • PDF 生成(2)— 生成 PDF 文件 中我们通过 puppeteer 来生成 PDF 文件,并讲了自定义页眉、页脚的使用和其中的。本文结束之后 puppeteer 在 PDF 文件生成场景下的能力也基本到头了,所以,接下来的内容就全是基于 puppeteer 的增量开发了,也是整套架构的核心难点
  • PDF 生成(3)— 封面、尾页 通过 PDF 文件合并技术让一份 PDF 文件包含封面、内容页和尾页三部分。
  • PDF 生成(4)— 目录页 通过在内容页的开始位置动态插入 HTML 锚点、页面缩放、锚点元素高度计算、换页高度补偿等技术让 PDF 文件拥有了包含准确页码 + 页面跳转能力的目录页
  • PDF 生成(5)— 内容页支持由多页面组成 通过多页面合并技术 + 样式沙箱解决了用户在复杂 PDF 场景下前端代码维护问题,让用户的开发更自由、更符合业务逻辑
  • PDF 生成(6)— 服务化、配置化 就是本文了,本系列的最后一篇,以服务化的方式对外提供 PDF 生成能力,通过配置服务来维护接入方的信息,通过队列来做并发控制和任务分类
  • 代码仓库 欢迎 Star

感谢大家花时间阅读,希望大家能从本系列学到对自己有用的知识,不论是 PDF 生成本身,还是整个思考迭代过程,亦或者是其中的某些点。


当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注点赞收藏评论

新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1889814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 15 应用适配默认全屏的行为变更(Android V的新特性)

简介 Android V 上默认会使用全面屏兼容方式,影响应用显示,导致应用内跟导航标题重合,无法点击上移的内容。 默认情况下,如果应用以 Android 15(API 级别 35)为目标平台,在搭载 Android 15 的设…

鸿蒙应用笔记

安装就跳过了,一直点点就可以了 配置跳过,就自动下了点东西。 鸿蒙那个下载要12g个内存,大的有点吓人。 里面跟idea没区别 模拟器或者真机运行 真机要鸿蒙4.0,就可以实机调试 直接在手机里面跑,这个牛逼&#xf…

现代智能宠物喂食器方案定制

现代智能宠物喂食器不仅具备定时喂食功能,帮助宠物主人管理宠物的饮食时间和食量,还加入了录音功能和摄像头,使得宠物主人即使不在家也能与宠物保持互动,并实时监控宠物的状况。此外,一些产品还具备紧急预警功能&#…

数据分析:基于聚类的LASSO预测模型包----clustlasso

介绍 clustlasso是结合lasso和cluster-lasso策略的R包,并发表在Interpreting k-mer based signatures for antibiotic resistance prediction。 标准交叉验证lasso分类或回归流程如下: 选择交叉验证数据集(数据分割)&#xff1…

【计算机网络】网络层(作业)

【一】 1、某主机的 IP 地址为 166.199.99.96/19。若该主机向其所在网络发送广播 IP 数据报, 则目的地址可以是(D)。 A. 166.199.99.255B. 166.199.96.255C. 166.199.96.0D. 166.199.127.255 解析: 166.199.99.96/19166.199.0…

YOLOv5初学者问题——用自己的模型预测图片不画框

如题,我在用自己的数据集训练权重模型的时候,在训练完成输出的yolov5-v5.0\runs\train\exp2目录下可以看到,在训练测试的时候是有输出描框的。 但是当我引用训练好的best.fangpt去进行预测的时候, 程序输出的图片并没有描框。根据…

nginx转发的问题

我在项目配置的时候遇到一个问题: 配置了域名转发,且配置了https nginx配置如下: server {listen 443 ssl;server_name yourdomain.com;ssl_certificate /path/to/your/certificate.crt;ssl_certificate_key /path/to/your/private.key;loca…

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01 环境搭建验证码倒计时短信服务邮件服务验证码短信形式:邮件形式: 异常机制MD5参考 环境搭建 C:\Windows\System32\drivers\etc\hosts 192.168.…

2024年软件测试面试题,精选100道,内附文档。。。

测试技术面试题 1、我现在有个程序,发现在 Windows 上运行得很慢,怎么判别是程序存在问题还是软硬件系统存在问题? 2、什么是兼容性测试?兼容性测试侧重哪些方面? 3、测试的策略有哪些? 4、正交表测试用…

lua中判断2个表是否相等

当我们获取 table 长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度,而且还会出现奇怪的现象。例如:t里面有3个元素,但是因为最后一个下表是5和4,却表现出不一…

mac M2芯片系统版本macOS Sonoma14.4.1 Navicat Premium意外退出问题,报错:Translated Report (Full Report Below)

前言 Mac电脑正在使用的navicat客户端突然闪退了!!!! 之前用的好好的。做了可能影响navicat客户端闪退的事情就是把电脑系统升级到了macOS Sonoma14.4.1。后悔莫及~ 现象:navicat能正常创建连接&#xff0…

大数据处理引擎选型之 Hadoop vs Spark vs Flink

随着大数据时代的到来,处理海量数据成为了各个领域的关键挑战之一。为了应对这一挑战,多个大数据处理框架被开发出来,其中最知名的包括Hadoop、Spark和Flink。本文将对这三个大数据处理框架进行比较,以及在不同场景下的选择考虑。…

【AI是在帮助开发者还是取代他们?】AI与开发者:合作与创新的未来

目录 前言一、AI工具现状(一)GitHub Copilot(二)TabNine 二、AI对开发者的影响(一)影响和优势(二)新技能和适应策略(三)保持竞争力的策略 三、AI开发的未来&a…

CAS操作

CAS 全称:Compare and swap,能够比较和交换某个寄存器中的值和内存中的值,看是否相等,如果相等,则把另外一个寄存器中的值和内存进行交换. (这是一个伪代码,所以这里的&address实际上是想要表示取出address中的值) 那么我们可以看到,CAS就是这样一个简单的交换操作,那么…

为什么网上商店需要翻译成其他语言

网上商店不仅仅是一个可以买到商品的网站。它是一个完整的电子商务平台,为来自世界各地的用户提供购买所需物品的机会。但是,为了让这些用户舒适地使用网站,需要高质量的翻译和本地化。 本地化是指产品或服务适应特定文化或市场的过程。它包…

app单页下载页源码带管理后台

新版带后台管理APP应用下载页,自动识别安卓苹果下载页,带管理后台,内置带3套App下载模板带中文模板/英文模板随时切换。 app单页下载页源码带管理后台

从头开始构建 RAG 的 LLM 代理:综合指南

GPT-3、GPT-4 等 LLM 及其开源版本经常难以检索最新信息,有时会产生幻觉或不正确的信息。 检索增强生成 (RAG)是一种将 LLM 的强大功能与外部知识检索相结合的技术。RAG 使我们能够将 LLM 响应建立在事实、最新的信息之上,从而显著提高 AI 生成内容的准…

java基础:流程控制

一、用户交互Scanner (一)基础 1、概念:基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入。java.util.Scanner 是 Java5的新特征,我们可以通过Scanne…

MySQL安装与环境配置

1.打开安装程序 2.默认配置,如下二三图 3.配置密码 4.等待安装完毕 5.检查 6.配置环境变量 7.从控制台登录检测

Let‘s Encrypt 申请免费 SSL 证书(每隔60天自动更新证书)

文章目录 官网文档简介安装 Nginxacme.sh生成证书智能化生成证书 安装证书查看已安装证书更新证书 官网 https://letsencrypt.org/zh-cn/ 文档 https://letsencrypt.org/zh-cn/docs/ 简介 Let’s Encrypt 是一个非营利组织提供的免费SSL/TLS证书颁发机构,旨在促…