You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 13:35:18