概述
在企业级应用开发场景下,非常常见的一个需求就是多格式文档的预览,包括但不限于:TXT、CSV、PDF、Word、PPT、Excel等。通常情况下,这些文件会上传到MinIO、阿里云OSS或腾讯云COS。
根据个人知识所限,MinIO只能预览TXT、CSV、PDF,并不能预览Office三件套,更别谈其他各种各样的文件格式。
那商业化文件存储平台呢?
OSS
亲自尝试,阿里云OSS预览Word文档,需绑定IMM智能媒体项目
点击【绑定】
点击【授权】
可惜,OSS免费开通,IMM需付费使用;本着写技术博客的严谨精神,还是开通体验一下
点击桶(Bucket)内的.docx文件,自动下载线性代数.htm文件,浏览器打开此文件,加载几分钟还出来。
商业平台能力,就这样????
另外,再说一个很搞笑的Bug(非常明显的问题):
Bucket列表为空,创建一个preview-test的桶,结果提示我已存在,我他妈为啥要关心这个名称被其他用户占用???OSS作为SaaS应用,而SaaS最基本的概念就是多租户,不同用户创建相同的Bucket名称,有什么问题吗?
上面说到有2个方案,方案1是图片方案,猜测是基于截图来实现,限于时间因素,未经验证。有空补上。
关于IMM的介绍:可绑定智能媒体管理产品,来做数据分析预处理
- 调用方式:
OSS域名/sample_object?x-oss-process=imm/action,param_value - 接口实例:
OSS域名/family.jpg?x-oss-process=imm/detectface
IMM基于x-oss-process推出全新的文档处理、音视频处理等能力,支持样式、异步任务、批处理、触发器等多种使用方式。
COS
上传文件后,有【预览】入口
点击预览,果不其然,也是收费服务
点击【开启服务】
预览效果
开源
商业化平台能力是ok的,但涉及到企业内部安全隐私,数据泄密等问题,最好还是使用本地化部署方案。本文收集几个基于浏览器的文件预览开源项目。
kkFileView
官网,一个基于Java+Spring Boot的万能文件预览开源(GitHub,14.4K Star,3.3K Fork)项目,允许用户在浏览器端实时查看文件内容。
支持的文件类型:
- 支持doc、docx、xls、xlsx、xlsm、ppt、pptx、csv、tsv、dotm、xlt、xltm、dot、dotx、xlam、xla等Office办公文档;
- 支持wps、dps、et、ett、wpt等国产WPS Office办公文档;
- 支持odt、ods、ots、odp、otp、six、ott、fodt、fods等Open Office、Libre Office办公文档;
- 支持vsd、vsdx等Visio流程图文件;
- 支持wmf、emf等Windows系统图像文件;
- 支持psd等Photoshop软件模型文件;
- 支持pdf、ofd、rtf等文档;
- 支持xmind软件模型文件;
- 支持bpmn工作流文件;
- 支持eml邮件文件;
- 支持epub图书文档;
- 支持obj、3ds、stl、ply、gltf、glb、off、3dm、fbx、dae、wrl、3mf、ifc、brep、step、iges、fcstd、bim等3D模型文件;
- 支持dwg、dxf等CAD模型文件;
- 支持txt、xml(渲染),md(渲染),java、php、py、js、css等所有纯文本;
- 支持zip、rar、jar、tar、gzip、7z等压缩包;
- 支持jpg、jpeg、png、gif、bmp、ico、jfif、webp等图片预览(翻转,缩放,镜像);
- 支持tif、tiff图信息模型文件;
- 支持tga图像格式文件;
- 支持svg矢量图像格式文件;
- 支持mp3、wav、mp4、flv等音视频格式文件;
- 支持avi、mov、rm、webm、ts、rm、mkv、mpeg、ogg、mpg、rmvb、wmv、3gp、ts、swf等视频格式转码预览;
极易上手,使用HTTP接口访问,不需要和应用集成,具有跨系统跨语言使用的特性。提供Windows/Linux版本发行包、自定义配置文件、和一键启动/停止脚本等,极大方便部署使用,官方提供Docker镜像。
因此仅需单独将kkFileView部署好后,文件交由minio存储和维护,然后提供文件的url地址既可实现在线预览,如下图所示:
部署
gitclone https://github.com/kekingcn/kkFileView.gitcdkkfileviewerdockerbuild-tkkfileview:v5.0.0.dockerrun-d-p8012:8012--namekkfileview kkfileview:v5.0.0打开:http://localhost:8012,开始体验。
集成MinIO
添加依赖:implementation 'io.minio:minio:8.5.17'
application.yaml配置:
minio:endpoint:http://xxxxx:9000accessKey:xxxxsecretKey:xxxxxbucketName:testMinIO配置类:
@ConfigurationpublicclassMinioConfig{@Value("${minio.endpoint}")privateStringendPoint;@Value("${minio.accessKey}")privateStringaccessKey;@Value("${minio.secretKey}")privateStringsecretKey;@Value("${minio.bucketName}")privateStringbucketName;@BeanprotectedMinioClientminioClient(){returnMinioClient.builder().endpoint(endPoint).credentials(accessKey,secretKey).build();}}工具类:
@ComponentpublicclassMinioUtils{@Value("${minio.bucketName}")privateStringbucketName;@ResourceprivateMinioClientminioClient;publicApiResultuploadFile(MultipartFilefile)throwsAppException{StringfileName=System.currentTimeMillis()+file.getOriginalFilename();try(InputStreamfi=file.getInputStream()){PutObjectArgsputObjectArgs=PutObjectArgs.builder().bucket(bucketName).contentType(file.getContentType()).object(fileName).stream(fi,fi.available(),-1).build();minioClient.putObject(putObjectArgs);}catch(Exceptione){thrownewAppException("文件上传失败"+e.getMessage());}returnApiResult.ok(fileName);}publicApiResultgetPreviewUrl(StringobjectName)throwsAppException{try{GetPresignedObjectUrlArgsurlArgs=GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build();returnApiResult.ok(minioClient.getPresignedObjectUrl(urlArgs));}catch(Exceptione){thrownewAppException("获取预览链接失败"+e.getMessage());}}}controller接口类:
@RestController@RequestMapping("/file")publicclassFileOperationController{@ResourceprivateMinioUtilsminioUtils;@PostMapping("/upload")publicApiResultupload(MultipartFilefile)throwsAppException{returnminioUtils.uploadFile(file);}@GetMapping("/getPreviewUrl")publicApiResultgetPreviewUrl(StringfileName)throwsAppException{returnminioUtils.getPreviewUrl(fileName);}}File Viewer
官网,全能、纯前端、开源(GitHub,1.8K Star,185 Fork)商用友好的文件预览器。基于浏览器运行,覆盖Office、PDF、OFD、Typst、CAD、3D、压缩包、邮件、绘图、电子书、Markdown、代码、图片、音频、视频等常见业务附件。官方文档,在线Demo。
| 功能 | 价值 |
|---|---|
| 全能纯前端文件预览器 | 文件在浏览器端完成解析和展示,业务系统可用一套组件覆盖绝大多数附件预览需求 |
| 完全开源,Apache-2.0 | 协议商用友好,适合企业内部系统、SaaS产品、低代码平台和二开集成 |
| 覆盖几乎所有常见附件场景 | 合同、档案、工单、知识库、工程图纸、邮箱附件、代码审阅、培训资料、压缩包归档都能进入统一预览体验 |
| 优质且高效的代码链路 | 按文件类型异步加载重型依赖,PDF.js、CAD WASM、docx-preview、epub.js、libarchive等只在需要时进入页面 |
| 可扩展性完整清晰 | @file-viewer/core负责底层预览能力,Vue、React、纯Web、jQuery、Svelte等组件入口保持统一参数、事件和操作语义 |
适用场景
| 场景 | 推荐能力组合 |
|---|---|
| 合同审阅 | Word/PDF/OFD+水印+打印+下载前置校验+文档比对 |
| 档案归档 | PDF/OFD/Office+生命周期回调+搜索定位+HTML导出 |
| 邮箱附件归档 | EML/MSG/MBOX+附件继续预览+原文下载 |
| 工程图纸 | DWG/DXF/DWF/DWFx/XPS+CAD WASM私有化+图层/结构面板 |
| 知识库 | Markdown/代码/PDF/Word+全文搜索+文本切片+来源定位 |
| 低代码平台 | Pure Web/Vue/React组件+统一options+动态文件源 |
| SaaS中后台 | 主题固定为light+权限前置操作+统一工具栏 |
| 内网部署 | 静态资源自托管+Docker/静态目录+Worker/WASM路径配置 |
核心架构
| 架构设计 | 对开发者的价值 |
|---|---|
@file-viewer/core作为纯TypeScript底座 | 格式矩阵、渲染协议、生命周期、搜索、缩放、打印、导出、水印等能力集中沉淀,框架层更轻 |
| 多生态标准组件拆分 | Vue、React、纯Web、jQuery、Svelte等入口都只依赖core,参数和事件语义保持一致 |
| 渲染链路按需异步加载 | 命中文件类型后才加载PDF.js、CAD WASM、docx-preview、epubjs、libarchive等重型依赖 |
| Demo、文档、Release、静态部署体系完整 | 官网、文档站、在线Demo、文档比对页、Docker/静态产物、npm包和Release下载物形成完整分发闭环 |
开发者可从最小组件开始接入,再逐步打开水印、搜索、打印、权限前置校验、WASM私有化、文档比对和AI溯源等能力。
扩展名支持参考packages/core/src/formats.ts,并由pnpm verify:format-support校验:194个扩展名映射,23条预览链路。不包含内部错误兜底处理器。
| 类别 | 扩展名示例 | 渲染链路 | 适合场景与边界 |
|---|---|---|---|
| Word/模板 | docx、docm、dotx、dotm、doc、dot | docx-preview、msdoc-viewer | 合同、制度、报告、历史Word附件。复杂版式优先保持页面阅读效果 |
| Excel/表格 | xlsx、xlsm、xlsb、xls、csv、ods、fods、numbers | styled-exceljs+虚拟表格 | 业务台账、导出报表、多sheet文件。表格链路以交互查看为主 |
| PowerPoint | pptx、pptm、potx、ppsx | @aiden0z/pptx-renderer | 课件、方案、汇报材料。浏览器近似渲染,复杂动画建议转PDF留档 |
pdf | PDF.js | 合同、归档件、说明书。支持页导航、目录、缩放、搜索、打印和HTML导出 | |
| OFD | ofd | DLTech21/ofd.js | 发票、政企归档。按需加载OFD解析链路 |
| Typst | typ、typst | Typst WASM+SVG | 直接预览Typst源文件,适合技术文档和排版稿 |
| CAD/图纸 | dwg、dxf、dwf、dwfx、xps | @flyfish-dev/cad-viewer+WASM/Worker | 工程图纸、设备图、DWF/DWFx/XPS审阅。私有部署需配置WASM/Worker路径 |
| 3D模型 | glb、gltf、obj、stl、ply、fbx、dae、3mf、usd等 | Three.js | 产品模型、工程资产、简单三维附件。复杂CAD/BIM可先转换 |
| 压缩包 | zip、7z、rar、tar、gz、xz、iso、jar、apk、cbz等 | libarchive.jsWorker+兼容降级 | 查看压缩包目录,按需解压内部文档并继续预览 |
| 邮件 | eml、msg、mbox | postal-mime、msgreader | 邮件归档、客服工单、附件审阅。正文隔离渲染,附件可继续预览 |
| 绘图 | excalidraw、drawio、dio | 官方Excalidraw/diagrams.net链路 | 流程图、草图、架构图,只读预览 |
| 电子书 | epub、umd | epubjs、UMD parser | 内部手册、电子书、历史移动阅读文件 |
| Markdown/代码 | md、json、js、ts、tsx、vue、py、java、sql、log等 | Markdown+highlight.js | 知识库、配置、源码、日志,安全地按源码展示 |
| 图片/设计资产 | png、jpg、svg、webp、heic、psd、ai、eps | 浏览器图片、按需解析器 | 品牌素材、截图、设计附件。AI/EPS做安全摘要或PDF-backed预览 |
| 音视频 | mp3、ogg、flac、midi、mp4、webm、m3u8 | 原生媒体、hls.js、MIDIparser | 通话录音、培训视频、媒体附件 |
| 数据资产 | sqlite、parquet、avro、wasm、webarchive、字体文件 | sql.js、hyparquet、avsc、FontFace | 快速查看结构、表、schema、导入导出信息 |
组件生态围绕core提供多条生产可用入口。新项目推荐优先使用标准包名,历史包名继续保持兼容。
技术栈
| 技术栈 | 推荐包名 | 兼容包名 | 适合场景 |
|---|---|---|---|
| Core | @file-viewer/core | 无 | 自研组件、底层能力、格式识别、生命周期和操作API |
| Vue 3 | @file-viewer/vue3 | @flyfish-group/file-viewer3、file-viewer3 | Vue3中后台、知识库、合同系统 |
| Vue 2.7 | @file-viewer/vue2.7 | @flyfish-group/file-viewer | 存量Vue2.7项目 |
| Vue 2.6 | @file-viewer/vue2.6 | 无 | 老Vue2.6项目 |
| React 18/19 | @file-viewer/react | @flyfish-group/file-viewer-react | React中后台、SaaS、设计系统 |
| React 16.8/17 | @file-viewer/react-legacy | 无 | 老React项目 |
| 纯Web/JS | @file-viewer/web | @flyfish-group/file-viewer-web | 原生页面、Web Components外壳、微前端、script标签 |
| jQuery | @file-viewer/jquery | 无 | 老后台、CMS、传统页面 |
| Svelte | @file-viewer/svelte | 无 | Svelte应用和组件库 |
对于独立静态预览站,可把自托管Demo页面放入业务iframe做隔离展示。新业务集成建议优先使用上面的原生组件或mountViewer,调试体验和事件回调会更直接。
实战
安装:
pnpmadd@file-viewer/vue3# React、纯Web、jQuery、Svelte分别安装对应包pnpmadd@file-viewer/reactpnpmadd@file-viewer/webpnpmadd@file-viewer/jquerypnpmadd@file-viewer/svelteVue3示例:
<script setup lang="ts">importFileViewerfrom'@file-viewer/vue3'</script><template><div style="height: 100vh"><FileViewer url="/files/demo.pdf"/></div></template>Vue组件会自动带上样式,父容器需要明确高度,这是所有预览器最容易被忽略的基础条件。
React示例:
importFileViewerfrom'@file-viewer/react'exportfunctionPreview(){return(<div style={{height:'100vh'}}><FileViewer url="/files/contract.docx"/></div>)}纯JS/Script标签:
import{mountViewer}from'@file-viewer/web'constcontroller=mountViewer(document.getElementById('viewer')!,{url:'/files/demo.pdf',options:{theme:'light'}})传统页面也可使用IIFE产物,通过file-viewer-copy-assets把viewer assets发布到自己的静态目录,再用全局对象挂载。
URL最简单,适合文件地址能被浏览器直接访问的场景:
<FileViewer url="https://cdn.example.com/report.pdf"/>本地上传、鉴权下载和SDK返回二进制时,推荐传File:
constfile=newFile([blob],'contract.pdf',{type:blob.type})ArrayBuffer也建议先包装成带扩展名的File:
constbuffer=awaitsdk.downloadAttachment(id)constfile=newFile([buffer],'report.xlsx')文件名很关键,预览器会根据扩展名选择渲染链路。遇到/download?id=123这种无后缀地址,推荐业务侧先下载,再补一个准确文件名。
鉴权文件下载推荐方式
constresponse=awaitfetch('/api/files/123/preview',{credentials:'include',headers:{Authorization:`Bearer${token}`}})constblob=awaitresponse.blob()constfile=newFile([blob],'sealed-contract.ofd',{type:blob.type})这样权限仍然由业务系统控制,预览器只负责浏览器端解析和展示。
CAD、压缩包、PDF、Typst、SQLite、DOCX Worker等链路会按需使用Worker或WASM。内网、CSP严格、CDN子路径部署时,可显式配置资源地址:
constoptions={pdf:{workerUrl:'/file-viewer/vendor/pdf/pdf.worker.mjs',streaming:'same-origin',rangeChunkSize:64*1024},cad:{wasmPath:'/file-viewer/wasm/cad/',workerUrl:'/file-viewer/wasm/cad/dwg-worker.js',dwfWasmUrl:'/file-viewer/wasm/cad/dwfv-render.wasm'},archive:{workerUrl:'/file-viewer/vendor/libarchive/worker-bundle.js',workerTimeoutMs:30000,cache:true},typst:{compilerWasmUrl:'/file-viewer/wasm/typst/typst_ts_web_compiler_bg.wasm',rendererWasmUrl:'/file-viewer/wasm/typst/typst_ts_renderer_bg.wasm'},data:{sqlWasmUrl:'/file-viewer/wasm/sql/sql-wasm.wasm'}}加载、卸载、打印、下载、导出、缩放都可接入业务审计和权限控制。
constoptions={hooks:{onLoadStart(ctx){console.log('开始加载',ctx.type,ctx.filename)},onLoadComplete(ctx){console.log('加载完成',ctx.duration)},onUnloadStart(ctx){console.log('开始卸载',ctx.reason)},onUnloadComplete(ctx){console.log('卸载完成',ctx.filename)}},asyncbeforeOperation(ctx){if(ctx.operation==='print'){returnawaitcheckPrintPermission(ctx.filename)}returntrue}}水印、打印、下载、导出HTML
constoptions={theme:'light',toolbar:{position:'bottom-right',download:true,print:true,exportHtml:true,zoom:true},watermark:{text:'内部资料',opacity:0.16,rotate:-24,color:'#0f766e'}}打印会尽量只输出主体文档,不带Demo外壳。PDF和Word会按页面尺寸处理;Excel等虚拟表格链路会根据能力动态隐藏打印按钮,避免输出只有当前视口。
预览器提供searchDocument()、nextSearchResult()、previousSearchResult()、collectDocumentAnchors()、scrollToLine()、scrollToAnchor()和getDocumentTextChunks()等能力。
业务侧可把文本切片写入自己的向量库或审计系统,再通过锚点回到原文位置。File Viewer 本身只提供结构、定位和高亮能力,不绑定任何云端模型。
文档比对是独立页面,适合合同版本、PDF与源文件、PPTX版本差异、Markdown与导出稿对照:https://demo.file-viewer.app/compare.html
可预置左右文件:
/compare.html?left=/example/pdf.pdf&right=/example/word.docx