为什么用 URL + iframe,而不是自己搭转换服务
很多团队第一次做文档在线预览,第一反应是「在服务器上装一个 LibreOffice,写个转换脚本,把 Office 转成 PDF 再丢给前端」。这条路能跑通,但通常会在三个月内暴露出三个问题。
- 转换队列会堵:一份 80 页的 PPT 转换要十几秒,并发一上来任务就排队,用户看到的是转圈而不是文档。
- 还原度不够:LibreOffice 与 Office 的排版引擎不同,字体替换、分页位置、图表样式都可能偏移,公文与合同场景无法交付。
- 运维成本高:转换服务需要独立机器、需要监控队列、需要处理僵尸任务,业务系统多了一套要值班的组件。
URL + iframe 的思路是把这件事交出去:业务系统只管准备文件地址,剩下的事情——解析、渲染、水印、试读控制——全部由预览服务完成。接入方不需要安装任何组件,也不需要维护转换队列。

接入前需要准备什么
在动手之前,先确认下面三件事,可以避免大部分返工。
| 准备项 | 要求 | 说明 |
|---|---|---|
| 文件地址 | 可被服务端访问 | 公网地址最省事;内网部署时地址需在内网可达 |
| 文件格式 | 常见办公格式 | docx、xlsx、pptx、pdf、ofd、dwg、图片等均可 |
| 调用方式 | 拼接 URL 即可 | 无需 SDK、无需后端对接,前端也能直接拼 |
如果你的文件存放在对象存储(OSS、COS、S3 等),通常需要先生成一条带签名的临时访问地址,再把这条地址交给预览服务。这样既能预览,又不会把文件权限放开。
# 可以正常预览的地址形态 https://your-bucket.oss-cn-hangzhou.aliyuncs.com/contracts/2026/report.docx # 带签名的临时地址(推荐) https://your-bucket.oss-cn-hangzhou.aliyuncs.com/report.docx?Expires=... # 内网私有化部署时的地址 http://10.0.12.30:8080/files/report.docx
第一步:准备一个可被访问的文件地址
这一步不改代码,只是确认文件能被预览服务拉取到。判断方法很简单:找一台和预览服务同网络环境的机器,用 curl 拉一下这个地址,能拿到文件就说明没问题。
# 检查文件地址是否可访问(只看响应头,不下载全文件) curl -I "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/report.docx" # 期望看到 200 或 206,以及正确的 Content-Type 与 Content-Length # HTTP/1.1 200 OK # Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
如果返回 403,多半是签名过期或权限配置问题;如果返回 404,检查路径是否带上了目录前缀;如果一直卡住不返回,通常是网络策略没有放通。
第二步:拼接预览地址
预览地址的构成非常固定:固定的服务地址 + src 参数 + 你的文件地址,需要时再叠加可选参数。

| 组成 | 示例 | 是否必填 |
|---|---|---|
| 服务地址 | https://view.moxudoc.cn/ | 必填,固定不变 |
| 参数名 | ?src= | 必填,固定为 src |
| 文件地址 | https://example.com/report.docx | 必填,值需 URL 编码 |
| 其他参数 | &watermark=内部资料<=5 | 可选,按需叠加 |
用 JavaScript 拼接
// 核心就是 encodeURIComponent,避免文件地址里的 & ? 等字符破坏参数结构 const PREVIEW_BASE = 'https://view.moxudoc.cn/?src='; function buildPreviewUrl(fileUrl, options = {}) { let url = PREVIEW_BASE + encodeURIComponent(fileUrl); if (options.watermark) url += '&watermark=' + encodeURIComponent(options.watermark); if (options.lt) url += '<=' + options.lt; return url; } console.log(buildPreviewUrl('https://example.com/report.docx', { watermark: '内部资料', lt: 5 })); // https://view.moxudoc.cn/?src=https%3A%2F%2Fexample.com%2Freport.docx&watermark=...
&、? 或中文,参数就会被截断,表现是「地址明明对,但预览报文件不存在」。用 Java 拼接
public class PreviewUrlBuilder { private static final String BASE = "https://view.moxudoc.cn/?src="; public static String build(String fileUrl) { return BASE + URLEncoder.encode(fileUrl, StandardCharsets.UTF_8); } public static String build(String fileUrl, String watermark, Integer lt) { StringBuilder sb = new StringBuilder(build(fileUrl)); if (watermark != null) sb.append("&watermark=").append(URLEncoder.encode(watermark, StandardCharsets.UTF_8)); if (lt != null) sb.append("<=").append(lt); return sb.toString(); } }
第三步:嵌入到你的业务系统
拿到预览地址之后,剩下的事情就是把它放到用户能看到的地方。三种方式覆盖了绝大多数场景。

方式一:iframe 嵌入(推荐)
详情页、工单页、审批页这类「页面内就要看到文档」的场景,直接用 iframe 即可。预览服务已经处理好了自适应与移动端适配。
<iframe src="https://view.moxudoc.cn/?src=https%3A%2F%2Fexample.com%2Freport.docx" width="100%" height="720" style="border:0;border-radius:12px" allowfullscreen loading="lazy"></iframe>
高度建议给 700px 以上,太矮会让文档区变成一条缝。如果页面是弹窗形态,可以让 iframe 撑满弹窗高度,由预览服务内部自带的滚动条承担浏览。
方式二:新窗口打开
列表页的「查看」按钮更适合新窗口:用户看完关掉就回到列表,不会打断原有操作路径。
document.querySelector('.btn-view').addEventListener('click', () => { window.open(buildPreviewUrl(fileUrl), '_blank', 'noopener'); });
方式三:微信小程序 web-view
小程序里用 web-view 组件承载预览地址即可。需要注意的是,要在小程序后台把预览域名加入业务域名白名单,否则会提示「不支持打开非业务域名」。
<web-view src="{{previewUrl}}"></web-view> <!-- 页面 JS 中准备地址 --> Page({ data: { previewUrl: '' }, onLoad(options) { const fileUrl = decodeURIComponent(options.src); this.setData({ previewUrl: 'https://view.moxudoc.cn/?src=' + encodeURIComponent(fileUrl) }); } });
watermark 参数,把用户手机号后四位或昵称作为水印,截图外传时可以追溯来源。常见问题
接入文档在线预览需要后端配合吗?
不需要。预览地址可以在前端直接拼接,只要文件地址可被预览服务访问即可。如果文件存放在需要签名的对象存储里,需要后端生成一条带签名的临时地址。
文件地址必须是公网的吗?
不是。公网部署的预览服务需要公网可访问的地址;如果选择私有化部署到企业内网,使用内网地址即可,文件全程不出内网。
支持哪些文件格式的在线预览?
支持 Word(doc/docx)、Excel(xls/xlsx)、PPT(ppt/pptx)、PDF、OFD、CAD(dwg/dxf)、图片以及 ZIP 压缩包等 60 多种格式,同一套接入方式通用。
怎么防止用户下载或复制原件?
预览服务提供防复制、动态水印与试读页数控制。另外建议不要在前端暴露原始文件地址,而是由后端生成短期有效的签名地址交给预览服务。
水印可以动态设置吗?
可以。通过 watermark 参数传入任意文字,会平铺到文档上。推荐把用户手机号后四位、工号或昵称作为水印,便于外传追溯。
一个 iframe 能预览多个文件吗?
一个 iframe 对应一个文件。列表页场景建议点击后再加载 iframe,或直接使用新窗口打开,避免一屏同时发起多个预览请求。
接入大概需要多久?
如果文件地址已经可访问,接入本身通常在一小时内完成——拼接地址、放进 iframe 即可。剩下的时间主要花在业务系统的页面排版上。
私有化部署之后地址还一样吗?
接口形式完全一致,只是把服务地址从 view.moxudoc.cn 换成了企业内网的地址,前端代码不需要改。
完整参数速查
预览地址的参数不多,大部分项目只需要用到 src,其余按场景叠加即可。下表是完整清单。
| 参数 | 说明 | 示例值 |
|---|---|---|
| src | 必填。文件的公网或内网可访问地址,值需要 URL 编码 | src=https%3A%2F%2F...docx |
| m | 预览模式:0 超清 / 1 极速 / 2 SVG / 3 动画 / 4 高清(默认) | m=4 |
| lt | 试读页数限制,超出部分不渲染,常用于付费内容试看 | lt=3 |
| watermark | 动态水印文字,会平铺在文档上,适合加用户标识 | watermark=内部资料 |
| format | 输出转换,转 PDF 时传 pdf | format=pdf |
| words | 内容提取,提取全文传 all,可用于全文检索建索引 | words=all |
把常用参数组合起来,一条完整地址长这样:
https://view.moxudoc.cn/?src=https%3A%2F%2Fexample.com%2Freport.docx&m=4&watermark=内部资料<=5
src 必须放在第一位,且它的值要整体做 URL 编码,否则后面的参数会被当成文件地址的一部分。请求链路与渲染原理
理解这条链路,排查问题时能少绕很多弯路。整个过程里,业务系统只做了一件事:把地址交给浏览器。

| 环节 | 由谁完成 | 耗时占比 |
|---|---|---|
| 发起预览请求 | 浏览器 / 小程序 | 极低 |
| 拉取原始文件 | 预览服务 | 取决于源站与文件体积 |
| 解析与切片 | 预览服务 | 大文件的主要耗时来源 |
| 下发渲染数据 | 预览服务 | 按需分片,首屏很小 |
| 渲染显示 | 浏览器 | 纯前端渲染,无需插件 |
这也解释了为什么终端不需要安装任何组件:真正的重活(解析、排版、切片)都在服务端完成,浏览器只负责把结果画出来。
常见错误与排查
接入阶段遇到的报错高度集中,下面这些覆盖了九成以上的情况。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 提示文件不存在 | 对象存储权限未开放、签名地址过期,或地址未做 URL 编码 | 用 curl -I 确认地址可直接访问 |
| 页面一片空白 | 父页面的 CSP 或 X-Frame-Options 禁止 iframe 嵌入 | 检查响应头,或改用新窗口打开 |
| 一直转圈打不开 | 文件过大或源站响应慢 | 改用极速模式 m=1,走异步按需加载 |
| 中文文件名乱码 | 拼接时未做 URL 编码 | 统一用 encodeURIComponent 处理 |
| 参数不生效 | 文件地址里含 & 未编码,截断了参数 | 对 src 的值整体编码 |
| 小程序提示非业务域名 | 预览域名未加入业务域名白名单 | 在小程序后台添加预览域名 |
排查顺序建议固定成一条线:先验地址、再验参数、最后验嵌入方式。把文件地址单独丢到浏览器里能直接下载,说明源站没问题;能下载但预览报错,就重点查参数编码。
性能与安全建议
接入完成只是开始,下面这些实践能让预览在生产环境里更稳、更安全。
- 大文件优先用极速模式:
m=1采用异步按需加载,100MB 以上的合同与图纸首屏可以压到 1 秒内。 - 给 iframe 留足高度:建议 700px 以上并允许全屏,太矮会让用户频繁滚动。
- 列表页用懒加载:iframe 加上
loading="lazy",避免一屏十个预览同时发起请求。 - 水印带上用户标识:把手机号后四位、工号或昵称作为 watermark,截图外传时可以追溯。
- 用试读控制付费内容:
lt参数限制可看页数,先看后买。 - 涉密场景走私有化:把预览服务部署到内网,文件不出企业边界,具体见私有化部署方案。
小结
文档在线预览的接入成本,主要不在技术上,而在「要不要自己维护一套转换服务」这个决策上。选择 URL 拼接的方式,业务系统只需要做一件事——把地址拼出来交给浏览器。
如果你正在评估接入方案,建议先用示例文档跑通一遍流程:在线体验 可以直接上传或输入地址验证效果,确认无误后再按本文的步骤接进业务系统。



