商会系统里的在线文件预览: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 套原生预览更简单。这套方案的价值在于三种格式都要覆盖、而且要做权限控制的场景。

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

相关推荐
ygwelcome3 小时前
视频收割机-VideoHarvester架构功能介绍
pdf·音视频
北城笑笑3 小时前
Vue 104 ,AI + ECharts + Word:大模型数据可视化报告生成实战(前端导出图文并茂 Word 文档)
前端·vue.js·word·echarts
asdzx674 小时前
Python 解析 Excel 数据、图片与图表的实现方案
python·excel
慧都小妮子4 小时前
GcExcel 服务端 Excel 组件:无需安装 Office 的 Java/.NET 报表方案
java·.net·excel·gcexcel·服务端excel组件
2601_967760784 小时前
PDF转图片分辨率怎么选?几款工具实测
pdf
runningshark5 小时前
Lecture: Simple Present vs Present Continuous PDF
pdf
The Future is mine17 小时前
Excel如何只允许对部分表格内容进行修改
excel
2601_9620994618 小时前
Python xlwt设置excel单元格字体及格式
python·excel·xlwt·样式设置·单元格格式
weixin_416667961 天前
Stirling-PDF是一款完全开源免费、本地部署的全能PDF处理工具
pdf