文件预览架构选型:SaaS、服务端转码与浏览器解析的边界和混合路由
企业附件预览经常从一个组件需求开始,最后却变成架构问题。
同一份文件可以交给在线 SaaS、送入自建转码集群,也可以下载到浏览器本地解析。三个方案都能显示“预览中”,但原文件经过的网络、计算成本、版式边界和失败责任完全不同。
本文给出一套可以直接用于评审的选型方法,并实现一个不会把规则散落在页面组件里的混合路由。

一、三种方案分别把成本放在哪里
在线 SaaS
文件或可访问 URL 进入第三方服务,由供应商负责渲染引擎、字体、升级和容量。它适合快速上线、跨组织协作和团队不想维护转换引擎的场景。
必须提前确认文件能否出域、临时文件何时删除、签名 URL 是否可能泄露、审计能否关联用户与文件,以及供应商不可用时怎样降级。
服务端转码
原文件进入自己的转换服务,生成 PDF、图片或分块页面。固定操作系统、字体和引擎后,高还原和统一输出更容易验收。
代价是转换队列、进程隔离、超时、毒文件、产物缓存、过期清理和安全补丁都需要长期维护。它不是“一台 LibreOffice 服务器”这么简单。
浏览器解析
文件进入用户终端,由 JavaScript、Worker 或 WASM 读取和渲染。它减少了转换任务与中间产物,适合只读、内网和隐私敏感场景。
代价转移到了浏览器兼容性、终端内存和运行时资产交付。所谓纯前端,只说明计算位置,不代表没有网络和部署工作。
二、五个评审问题
文件能否离开安全域
这是第一道排除题。不能进入第三方网络的文件,不应先接 SaaS 再补合规。浏览器解析也要治理缓存、Blob、打印和下载权限。
是否要求固定分页
“看内容”与“像素级一致”是两种验收。复杂字体、图表、嵌入对象和历史格式通常更适合固定环境的服务端引擎。协同编辑则应评估完整编辑器,而不是扩展只读预览器。
文件和终端有多重
不要只看文件大小。扫描 PDF、图片密集 PPTX 和大表格的资源曲线不同。浏览器路线要在最低配置终端测峰值内存,服务端路线要测队列等待和单任务资源上限。
谁负责长期维护
SaaS 维护供应商关系与迁移;服务端维护集群与渲染环境;浏览器端维护兼容性、Worker、WASM、字体和真实文件回归。没有零成本选项。
失败后走哪条路
需要明确超限转码、不支持下载、高还原切换专业工具,以及用户离开页面时取消任务和释放资源。错误也应区分下载、类型识别、运行时加载、解析和渲染。
三、把路由写成可测试的业务规则

下面的实现同时返回路线与原因,便于记录审计信息,也便于在真实数据出现后调整阈值。
type Route = 'browser' | 'converter' | 'saas'
interface AttachmentProfile {
confidential: boolean
collaboration: boolean
fixedLayout: boolean
browserSupported: boolean
bytes: number
deviceMemoryGb?: number
}
export function selectRoute(file: AttachmentProfile): {
route: Route
reason: string
} {
if (file.collaboration && !file.confidential) {
return { route: 'saas', reason: '需要协同与跨组织分享' }
}
if (file.fixedLayout || !file.browserSupported) {
return { route: 'converter', reason: '需要固定输出或格式不适合浏览器解析' }
}
const weakDevice = (file.deviceMemoryGb ?? 4) <= 2
const largeFile = file.bytes > 200 * 1024 * 1024
if (weakDevice && largeFile) {
return { route: 'converter', reason: '文件超过终端内存预算' }
}
return { route: 'browser', reason: '只读文件可留在现有业务边界内处理' }
}
为这段规则补测试时,不要只测三条正常分支。还应覆盖未知终端内存、格式识别失败、租户禁止第三方传输和阈值边界。
import { describe, expect, it } from 'vitest'
describe('selectRoute', () => {
it('keeps confidential read-only files out of SaaS', () => {
expect(selectRoute({
confidential: true,
collaboration: false,
fixedLayout: false,
browserSupported: true,
bytes: 8 * 1024 * 1024,
}).route).toBe('browser')
})
it('moves large files on weak devices to conversion', () => {
expect(selectRoute({
confidential: true,
collaboration: false,
fixedLayout: false,
browserSupported: true,
bytes: 300 * 1024 * 1024,
deviceMemoryGb: 2,
}).route).toBe('converter')
})
})
四、浏览器解析不是零部署
重型预览链路往往包含 Worker、WASM、字体、CMap 和 vendor 资源。主 JavaScript 构建成功,并不说明这些资源也已经发布。
File Viewer v2.2.5 当前把 208 个已注册扩展名映射到 25 条预览链路。这是扩展名路由矩阵,不是 208 个独立渲染器。各能力的运行时可以按需加载,并与业务应用一起托管在内网。
createFullAssetOptions('/static/file-viewer/')
// pdf -> worker / cmap / wasm / fonts
// presentation -> worker / vendor runtime
// cad -> worker / wasm
// archive -> worker / wasm

最近一次门禁验证覆盖了 24 个 renderer 包、40 个核心资产,以及 CAD 运行时在根路径、子路径和自定义目录中的版本镜像。生产验收至少要检查:
运行时与主包来自同一版本;
站点部署在子路径时资源 URL 不回到根路径;
断开公网后仍能加载 Worker、WASM 和字体;
切换文件或离开页面后旧任务被取消;
超限与不支持文件进入明确的兜底路线。
五、用 20 份真实文件代替功能表
准备一组脱敏样本,覆盖最大体积、最复杂版式、最弱终端、最严格权限和最差网络。对三条路线记录首屏时间、峰值内存、总下载量、还原偏差、失败位置和人工处理成本。
真正稳妥的答案通常不是三选一:常见只读附件走浏览器解析,超大或高还原文件进入自建转码,确需协同的场景再使用合规 SaaS。三条路线共用鉴权、审计、错误分层和资源释放协议。
文中浏览器自托管路线的实现和资产校验脚本可以在开源仓库核对:flyfish-dev/file-viewer。项目仍在持续收敛真实文件与终端兼容问题,本文没有把只读预览描述成编辑器或万能转换方案。
浙公网安备 33010602011771号