【Node.js】文本与 pdf 的相互转换
pdf 转文本
主要使用 pdf-parse 这个库,直接识别提取我们 pdf 文件中的文字。
const express = require("express");
const fs = require("fs");
const PDFParser = require("pdf-parse");
const cors = require("cors");const pdfFilePath = "./xxx_实习生.pdf";const app = express();// 解决跨域问题
app.use(cors());
app.use(express.static(__dirname));
app.use(express.json());app.post("/form2pdf", (req, res) => {console.log(req.body);const str = "";// 1. 读取pdf文件fs.readFile(pdfFilePath, (err, pdfBuffer) => {if (err) {console.log(err);return;}PDFParser(pdfBuffer).then((pdfData) => {const pages = pdfData.text.split("\n\n");console.log(pages);let str;for (let i = 1; i < pages.length; i++) str = str + pages[i];console.log(str);});});
});app.listen(3000, () => {console.log("Server is running on port 3000");
});
文本转 pdf
主要使用 pdfkit 这个库将文本转为pdf文件。
const express = require("express");
const fs = require("fs");
const cors = require("cors");
const PDFDocument = require("pdfkit");const app = express();// 解决跨域问题
app.use(cors());
app.use(express.static(__dirname));
app.use(express.json());app.post("/form2pdf", (req, res) => {console.log(req.body);const str = "";// 使用 pdfkit 生成pdf// 创建 PDF 文档const doc = new PDFDocument({margin: 50, // 控制上下左右留白});// 输出到文件// 如果有同名的 直接覆盖doc.pipe(fs.createWriteStream("output.pdf"));// ========== Header ==========function addHeader(doc) {doc.fontSize(20).text(req.body.name, { align: "center" }).moveDown();doc.moveTo(50, doc.y) // 从左边开始画横线.lineTo(550, doc.y) // 到右边.stroke();doc.moveDown();}// ========== Footer ==========function addFooter(doc) {const range = doc.bufferedPageRange(); // 所有页for (let i = range.start; i < range.start + range.count; i++) {doc.switchToPage(i);doc.fontSize(10).fillColor("gray").text(`第 ${i + 1} 页`, 0, doc.page.height - 50, {align: "center",});}}// ========== Body ==========function addBody(doc) {doc.fontSize(12);doc.text(req.body.name + " " + req.body.sex + " " + req.body.age, {align: "left",lineGap: 4,});}// 添加内容addHeader(doc);addBody(doc);// 结束前添加 footer(多页也有效)doc.end();doc.on("end", () => {addFooter(doc);});// 把后端的pdf地址返回给前端res.json({code: 200,data: "http://localhost:3000/output.pdf",});res.send("PDF generated successfully!");
});app.listen(3000, () => {console.log("Server is running on port 3000");
});
浏览器缓存带来的小问题
之前自己写 demo,遇到一个小问题,可能刚刚入门的小白一时找不到原因,特此记录下:
- 后端生成pdf文件,但是名称都相同(而且由于前端通过 iframe 渲染 pdf 的url 地址,url
地址也相同)- 尽管内容不同,但是浏览器发现相同的名称pdf(静态文件),会继续使用缓存,导致后端pdf虽然重新覆盖生成,但是前端浏览器中的 iframe 渲染的 pdf 还是旧的(缓存中的)。
解决方法是:
<iframe :src="url" width="100%" height="1200px"></iframe>const onSubmit = () => {axios.post("http://localhost:3000/form2pdf", form).then((res) => {url.value = res.data.data + "?t=" + Date.now();console.log(url);});
};
添加一个时间戳来阻止浏览器继续使用缓存,以保证每次重新请求都可以渲染最新的pdf 文件数据。
- 浏览器为什么会缓存 PDF?
静态资源(如 PDF、图片、JS、CSS)默认会被浏览器缓存。
这是因为 Express 的 express.static()
中间件会自动为静态资源设置缓存相关的 HTTP 头(如 Cache-Control
),让浏览器下次访问同样的 URL 时直接用本地缓存,加快加载速度、减少服务器压力。
只要 URL 一样,且没有特殊的禁止缓存设置,浏览器就会优先用缓存。
如果在 /form2pdf
路由里加了:
res.setHeader("Cache-Control", 'no-store');
但这个 header 只作用于 /form2pdf
这个接口的响应,对 output.pdf
这个静态文件的响应没有影响。
output.pdf
的响应是由 express.static(__dirname)
处理的,和接口响应头无关。
- 为什么加参数能解决?
当你访问 output.pdf?t=xxx
时,浏览器认为这是一个全新的资源(即使实际文件一样),所以会重新请求服务器,不会用缓存。
- 如何让静态 PDF 不被缓存?
可以让 Express 静态资源响应时加上禁止缓存的 header:
app.use(express.static(__dirname, {setHeaders: (res, path) => {if (path.endsWith('.pdf')) {res.setHeader('Cache-Control', 'no-store');}}
}));
这样浏览器每次都会重新请求 PDF 文件。