执子念

文件预览架构选型:SaaS、服务端转码与浏览器解析的边界和混合路由

企业附件预览经常从一个组件需求开始,最后却变成架构问题。

同一份文件可以交给在线 SaaS、送入自建转码集群,也可以下载到浏览器本地解析。三个方案都能显示“预览中”,但原文件经过的网络、计算成本、版式边界和失败责任完全不同。

本文给出一套可以直接用于评审的选型方法,并实现一个不会把规则散落在页面组件里的混合路由。

三种文件预览路线的边界、优势与代价

一、三种方案分别把成本放在哪里

在线 SaaS

文件或可访问 URL 进入第三方服务,由供应商负责渲染引擎、字体、升级和容量。它适合快速上线、跨组织协作和团队不想维护转换引擎的场景。

必须提前确认文件能否出域、临时文件何时删除、签名 URL 是否可能泄露、审计能否关联用户与文件,以及供应商不可用时怎样降级。

服务端转码

原文件进入自己的转换服务,生成 PDF、图片或分块页面。固定操作系统、字体和引擎后,高还原和统一输出更容易验收。

代价是转换队列、进程隔离、超时、毒文件、产物缓存、过期清理和安全补丁都需要长期维护。它不是“一台 LibreOffice 服务器”这么简单。

浏览器解析

文件进入用户终端,由 JavaScript、Worker 或 WASM 读取和渲染。它减少了转换任务与中间产物,适合只读、内网和隐私敏感场景。

代价转移到了浏览器兼容性、终端内存和运行时资产交付。所谓纯前端,只说明计算位置,不代表没有网络和部署工作。

二、五个评审问题

文件能否离开安全域

这是第一道排除题。不能进入第三方网络的文件,不应先接 SaaS 再补合规。浏览器解析也要治理缓存、Blob、打印和下载权限。

是否要求固定分页

“看内容”与“像素级一致”是两种验收。复杂字体、图表、嵌入对象和历史格式通常更适合固定环境的服务端引擎。协同编辑则应评估完整编辑器,而不是扩展只读预览器。

文件和终端有多重

不要只看文件大小。扫描 PDF、图片密集 PPTX 和大表格的资源曲线不同。浏览器路线要在最低配置终端测峰值内存,服务端路线要测队列等待和单任务资源上限。

谁负责长期维护

SaaS 维护供应商关系与迁移;服务端维护集群与渲染环境;浏览器端维护兼容性、Worker、WASM、字体和真实文件回归。没有零成本选项。

失败后走哪条路

需要明确超限转码、不支持下载、高还原切换专业工具,以及用户离开页面时取消任务和释放资源。错误也应区分下载、类型识别、运行时加载、解析和渲染。

三、把路由写成可测试的业务规则

浏览器、服务端转码与 SaaS 的混合路由

下面的实现同时返回路线与原因,便于记录审计信息,也便于在真实数据出现后调整阈值。

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。项目仍在持续收敛真实文件与终端兼容问题,本文没有把只读预览描述成编辑器或万能转换方案。

posted on 2026-08-10 17:49  执子念  阅读(19)  评论(0)    收藏  举报

导航