Node.js服务器实现PDF付费前预览:限制页数并防完整文件下载
解决方案:限制PDF预览页数+防完整文件泄露+动态处理优化
一、动态生成预览PDF(不落地文件系统)
你当前方案的核心问题是生成本地预览文件,面对大量请求时会出现文件冲突、存储冗余。可以直接通过hummus.js的流输出,把生成的预览PDF直接写入响应流,无需创建本地文件:
const { createReadStream } = require('fs'); const hum = require('hummus'); const { PassThrough } = require('stream'); app.get('/preview/:pdfId', async (req, res) => { try { // 1. 从数据库根据pdfId查询文件存储路径(替换为你的实际查询逻辑) const pdfRecord = await yourDbModel.findOne({ id: req.params.pdfId }); if (!pdfRecord) return res.status(404).send('PDF不存在'); const sourcePdfPath = pdfRecord.filePath; // 例如 './uploads/xxx.pdf' // 2. 创建可读流读取源PDF,用PassThrough流作为输出目标 const sourceStream = createReadStream(sourcePdfPath); const outputStream = new PassThrough(); // 3. 用hummus处理PDF,生成指定页数的预览 const readPdf = hum.createReader(sourceStream); const writePdf = hum.createToStreamWriter(outputStream); const copyingContext = writePdf.createPDFCopyingContext(readPdf); // 只复制前2页(可根据需求调整预览页数) const previewPageCount = Math.min(2, readPdf.getPagesCount()); for (let i = 0; i < previewPageCount; i++) { copyingContext.appendPDFPageFromPDF(i); } writePdf.end(); // 4. 将预览流直接pipe到响应 res.contentType('application/pdf'); outputStream.pipe(res); } catch (err) { console.error('生成预览失败:', err); res.status(500).send('生成预览出错'); } });
二、数据库与文件系统的路径联动处理
- 接口通过
pdfId作为参数,而非直接暴露文件路径,避免路径泄露 - 从数据库查询对应PDF的实际存储路径,再读取文件处理,完全解耦数据库记录与文件系统存储逻辑
三、防止用户获取完整文件的多层防护
1. 渲染层防护
- 不要在前端加载完整PDF文件,仅通过后端预览接口返回的流,用pdfjs渲染预览内容
- 禁用打印、右键菜单:
// 拦截打印请求 window.addEventListener('beforeprint', (e) => { e.preventDefault(); alert('仅付费用户可打印完整文档'); }); // 禁用右键菜单 document.addEventListener('contextmenu', (e) => e.preventDefault());
2. 传输层防护
- 给预览接口添加权限校验:仅未付费用户能访问预览接口,已付费用户直接返回完整文件
- 添加限流机制,防止恶意爬虫批量获取预览页拼接完整PDF
- 响应头禁用缓存:
res.setHeader('Cache-Control', 'no-store, no-cache'),避免用户从缓存获取完整文件
3. 内容层防护
- 在预览PDF的每页添加水印,即使用户截图也无法直接使用,可通过hummus.js生成预览时添加:
// 在生成预览页后添加水印 const pageContext = writePdf.startPageContentContext(); pageContext.writeText('预览版·付费获取完整文档', 150, 300, { font: writePdf.getFontForFile('./fonts/your-font.ttf'), size: 16, color: '#AAAAAA', rotation: 45 }); writePdf.endPageContentContext(pageContext);
四、可扩展性优化
- 缓存预览结果:对访问量高的PDF,将生成好的预览PDF缓存到Redis或文件系统,同时添加缓存失效机制(原PDF更新时清除对应缓存)
- 异步生成预览:用户上传PDF后异步生成并存储预览文件,后续请求直接返回缓存的预览文件,提升响应速度
内容的提问来源于stack exchange,提问作者Natnael
相关产品推荐
相关产品推荐

