商会系统里的在线文件预览:PDF/Word/Excel 三种实现与选型(Node + 前端实战)

商会系统里有个功能是会员上传资料:营业执照、资质证书、合同扫描件、财务报表。会员传上来,秘书处在后台要看。

一开始我们的做法很简单------后台列表里给个下载链接,点一下下载到本地,用本机的软件打开。这个做法能用,但有几个麻烦:一是干事每次看一份文件都要下载,桌面上堆一堆;二是下载下来的文件散在各个人的电脑里,版本对不上;三是有些文件涉及会员企业的敏感信息,下载到本地不好管控。

后来改成在线预览。这篇把三种格式的预览方案和踩过的坑记录一下。

先说需求边界

我们的场景有三个特点。第一,格式集中在三种:PDF、Word、Excel。第二,文件不大,单个通常几 MB,但也有几十 MB 的财务报表。第三,预览要控权限------不是谁都能看,而且链接不能长期有效。

基于这三条,我们排除了几个方案。Office Online 和 Google Docs Viewer 这类在线服务需要文件能被公网访问,会员资料不能放公网,排除。纯前端用 iframe 套 PDF 的方式,PDF 可以,Word 和 Excel 不行,覆盖不全,也排除。

还有一个方案是后端统一转成 PDF 再预览,用 LibreOffice 之类的工具做无头转换。这个方案的优点是前端只需要处理 PDF 一种格式,逻辑简单。缺点是转换耗时长,一个几十页的 Word 转 PDF 要几秒到十几秒,会员传完文件不能立刻看,得等。而且这类工具对复杂排版的还原度一般,转换前后版式会有差异。

我们最终没选这个方案,主要卡在"等"这件事上------会员上传完资料,秘书处马上就要审核,让他等十几秒再看的体验不好。

最后的方案是分格式处理:PDF 用 pdf.js,Word 用 mammoth.js 转 HTML,Excel 用 SheetJS 转 HTML 表格。三种都在前端完成转换,后端只负责文件类型校验和临时链接签发。

后端:文件类型校验与临时链接

第一步是校验。上传时不能只看扩展名,要看文件头。

```javascript

const fileType = require('file-type');

async function validateFile(buffer, declaredExt) {

const type = await fileType.fromBuffer(buffer);

if (!type) {

throw new Error('无法识别的文件类型');

}

const allowed = {

pdf: 'application/pdf',

docx: [

'application/vnd.openxmlformats-officedocument.wordprocessingml.document'

],

xlsx: [

'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'

]

};

if (!alloweddeclaredExt || !alloweddeclaredExt.includes(type.mime)) {

throw new Error('文件实际类型与声明类型不符');

}

return type;

}

```

第二步是签临时链接。预览链接不能永久有效,我们签一个 10 分钟的有效期。

```javascript

const crypto = require('crypto');

function signPreviewUrl(fileId, userId, ttlSeconds = 600) {

const expireAt = Math.floor(Date.now() / 1000) + ttlSeconds;

const raw = `{fileId}:{userId}:${expireAt}`;

const sig = crypto

.createHmac('sha256', process.env.PREVIEW_SECRET)

.update(raw)

.digest('hex')

.slice(0, 16);

return `/api/preview/{fileId}?uid={userId}&exp={expireAt}\&sig={sig}`;

}

function verifyPreviewUrl(fileId, userId, exp, sig) {

if (Math.floor(Date.now() / 1000) > Number(exp)) return false;

const raw = `{fileId}:{userId}:${exp}`;

const expect = crypto

.createHmac('sha256', process.env.PREVIEW_SECRET)

.update(raw)

.digest('hex')

.slice(0, 16);

return expect === sig;

}

```

前端:三种格式分别处理

PDF 用 pdf.js,做成滚动翻页,一次只渲染当前页和前后各一页。

```javascript

import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js';

async function renderPdf(url, container) {

const pdf = await pdfjsLib.getDocument(url).promise;

const pages = \[\];

for (let i = 1; i <= pdf.numPages; i++) {

const page = await pdf.getPage(i);

const viewport = page.getViewport({ scale: 1.5 });

const canvas = document.createElement('canvas');

canvas.height = viewport.height;

canvas.width = viewport.width;

container.appendChild(canvas);

await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;

pages.push(canvas);

}

return pages;

}

```

Word 用 mammoth.js 转成 HTML 直接塞进容器。

```javascript

import mammoth from 'mammoth';

async function renderDocx(arrayBuffer, container) {

const result = await mammoth.convertToHtml({ arrayBuffer });

container.innerHTML = result.value;

// result.messages 里是转换告警,样式丢失会在这里提示

if (result.messages.length) {

console.warn('docx 转换告警:', result.messages);

}

}

```

Excel 用 SheetJS 读成 HTML 表格,多个 sheet 就渲染成多个表格加 tab 切换。

```javascript

import * as XLSX from 'xlsx';

function renderXlsx(arrayBuffer, container) {

const wb = XLSX.read(arrayBuffer, { type: 'array' });

container.innerHTML = '';

wb.SheetNames.forEach((name) => {

const ws = wb.Sheetsname;

const html = XLSX.utils.sheet_to_html(ws);

const wrap = document.createElement('div');

wrap.innerHTML = `<h4>{name}\{html}`;

container.appendChild(wrap);

});

}

```

踩过的坑

第一个是中文乱码。Word 转 HTML 时,如果文档里用了特殊字体,转换后中文会变成方块。解决办法是在容器上显式指定中文字体族,并且提醒会员上传时用常见字体。

第二个是大文件卡顿。一个几十页的 PDF,一次性渲染所有页会卡住。改成滚动到哪一页渲染哪一页,只保留前后各一页的 canvas,内存占用降下来不少。

第三个是 mammoth 转 HTML 会丢失复杂样式。表格合并单元格、页眉页脚这些内容转过去会变形。我们的做法是接受这个损失------预览的目的是看内容,不是还原排版,真要看排版还是得下载原文件。

第四个是权限链接被转发。临时链接虽然有时效,但在有效期内被转发出去,别人还是能打开。加上签发时的 userId 校验之后,链接换个人就失效了。

第五个是移动端适配。秘书处的干事经常用手机审核资料,手机上预览 PDF 时,canvas 渲染出来的页面在小屏上看不清。做法是默认按容器宽度缩放,再加一个双指缩放的手势支持,不做这个优化的话手机端基本没法用。

什么时候不需要这套方案

如果会员上传的文件只是给秘书处自己看,不要求在线预览,那下载到本地就够了,不用折腾转换。如果文件量很小、格式很单一(比如全是 PDF),那直接用 iframe 套原生预览更简单。这套方案的价值在于三种格式都要覆盖、而且要做权限控制的场景。

我们这边在用的商会管理系统叫未来漫城·商会互联平台,上面这套预览现在跑在会员资料审核的环节,干事在后台点开就能看营业执照和资质证书,不用下载。

相关推荐
GlobalTradercrb10 小时前
独立站SEO先写博客还是先优化产品页?看站点阶段
数据库·人工智能·google·pdf·go·seo
用户03321266636712 小时前
Python 自动化办公:如何从零快速生成结构清晰的 Excel 报表
python·excel
热爱生活的五柒13 小时前
zotero能导入已经下载好的pdf文件嘛,并且点击条目时,能直接看到下载的pdf文件嘛,我还希望能在里面做笔记
笔记·pdf
千桐科技13 小时前
qData 数据中台专业版功能解析:告别 Excel 与 SQL 拼凑,打通系统合并的可追踪数据链路
数据库·sql·excel·qdata
信可维14 小时前
AI 读 Word 表格总读错?先查合并单元格和表头这 3 处
开发语言·人工智能·c#·word·软件工程
百事牛科技15 小时前
从局部到整体:Excel限制功能的几种实用设置
windows·excel
开开心心就好16 小时前
白天晚上自动调蓝光,护眼工具亮度可调
服务器·开发语言·算法·pdf·ocr·intellij-idea·idea
asdzx6716 小时前
C# 将 HTML 转换为 PDF:基于 Chrome 的转换方案
pdf·c#·html
电子阿板18 小时前
Altium Designer 横向纵向图纸混合时,输出PDF时让每个图纸自动方向方法
pdf
andrsted18 小时前
练题簿在线免费刷题:将资料、Word 和 PDF导入小程序练习
学习·微信小程序·小程序·pdf·word·学习方法