效率工具 · 个人实践
OFD 转 PDF,打开浏览器就行
零安装、隐私安全、即传即转一个为国产版式文档而生的在线工具
OFD——我国自主研发的版式文档标准,正在电子发票、电子票据、电子证照等领域快速普及。但你一定遇到过这样的场景:收到一份 OFD 文件,电脑里却没有阅读器,打不开、看不了、转不出。今天介绍的这个在线工具,让这件事变得简单到只需三步。
线上地址:http://59.110.20.164/
一、OFD 的「最后一公里」之痛
OFD(Open Fixed-layout Document)是国家版式文档标准,在政务、税务、财政等领域已经成为「国标」。但现实是:
😤 阅读器难找:Windows 自带的不支持,常见的 PDF 阅读器也打不开,得专门下载安装 OFD 阅读器。
📎 分享困难:发给同事、客户,对方未必装了阅读器,沟通成本陡增。
🖨️ 打印归档不便:很多办公系统的打印、归档流程只认 PDF。
🔒 在线转换顾虑多:市面上的在线转换工具,文件上传后去哪了?会不会被留存?隐私没底。
于是就有了这个工具——一个轻量级的 OFD 在线预览与转 PDF 的 Web 应用。不装软件、不留文件、打开浏览器就能用。
二、给普通用户:三步搞定转换
普通用户
无论你是财务、行政、法务,还是偶尔收到 OFD 发票的打工人,使用流程非常直观:
1
打开网页浏览器访问 http://59.110.20.164/,无需注册、无需登录。
2
上传 OFD 文件直接把.ofd文件拖到页面中央的上传区,或者点击选择文件。支持最大 50MB。
3
预览 + 下载几秒后转换完成,页面直接在线预览 PDF(支持翻页、缩放、旋转),点击下载按钮即可保存,文件名与原始 OFD 一致。
💡 转换失败了怎么办?
如果文件损坏或格式不兼容导致转换失败,页面会显示明确的错误提示(不是一串看不懂的报错),并提供「一键重试」按钮。加密 OFD 暂不支持,后续版本会加入密码输入功能。
🔒 我的文件安全吗?
安全。文件仅用于本次转换,24 小时后自动删除,不做任何持久化存储。服务端不会保留你的文件内容,统计数据只记录转换次数,不涉及文件本身。
三、给开发者:技术架构拆解
开发人员
如果你是后端或前端工程师,可能更关心这个东西是怎么做出来的。下面从架构到关键实现逐一拆解。
3.1 整体技术栈
3.2 架构分层
整个系统是一个经典的「上传 → 异步处理 → 轮询结果」模型,关键在于任务状态机的设计。
3.3 任务状态机:六个状态撑起整个生命周期
每个 OFD 文件上传后都会创建一个任务,经历以下状态流转:
UPLOADED → QUEUED → PROCESSING → SUCCEEDED ↓ FAILED → (retry) → QUEUED 任何状态 → EXPIRED (24h 后自动清理)uploaded | ||
queued | ||
processing | ||
succeeded | ||
failed | ||
expired |
3.4 后端关键设计点
异步队列处理。上传接口立即返回任务 ID,转换任务通过@Async提交到固定大小线程池,避免大文件转换阻塞 HTTP 请求。前端通过 1.5 秒间隔轮询任务状态,收到终态后停止。
自动清理机制。独立守护线程每 5 分钟扫描工作目录,删除超过 24 小时的文件目录,同时将内存中对应任务标记为EXPIRED。这是隐私优先原则的技术保障。
统计持久化。使用AtomicLong线程安全计数,每次转换成功原子递增,同时写入stats.json文件持久化。服务重启后从文件恢复,不会丢失历史数据。
3.5 前端关键设计点
PDF.js 渲染的坑。这是整个前端最值得说的细节。PDF.js 默认使用 Range 请求和流式传输来分段加载 PDF,但在 Vite dev 代理和部分 Nginx 配置下,流式传输会被中断,导致ERR_ABORTED错误。解决方案是强制关闭这两个特性:
// 关键配置:强制完整下载 PDF getDocument({ url: previewUrl, disableRange: true, // 禁用 Range 请求 disableStream: true // 禁用流式传输 })多标签页统计同步。右上角的「累计成功转换次数」需要在多个浏览器标签页之间实时同步。方案是「轮询 + localStorage 广播」组合拳:每 3 秒轮询/stats接口,发现计数变化时写入 localStorage,其他标签页通过storage事件即时感知。即使 localStorage 不可用,轮询也能保证 3 秒内的最终一致性。
高 DPI 适配。PDF.js 的 Canvas 渲染需要处理devicePixelRatio,在高分屏上才能保持文字清晰。逻辑像素与物理像素的映射关系需要正确设置 Canvas 的width/height和 CSS 尺寸。
3.6 API 一览
/api/ofd/tasks | ||
/api/ofd/tasks/{id} | ||
/api/ofd/tasks/{id}/preview | ||
/api/ofd/tasks/{id}/download | ||
/api/ofd/tasks/{id}/retry | ||
/api/ofd/capabilities | ||
/api/ofd/stats |
四、给产品经理:设计思路与产品哲学
产品经理
这个工具没有复杂的功能堆砌,但每一个设计决策背后都有清晰的产品逻辑。
4.1 核心设计原则
| 零安装 | |
| 隐私优先 | |
| 异步处理 | |
| 容错友好 | |
| 生产可用 |
4.2 产品决策背后的思考
为什么不做批量上传?首版关闭了批量功能(batch-enabled: false)。这是一个典型的 MVP 取舍——先验证单文件转换的核心链路是否稳定可靠,再考虑批量的交互复杂度。批量不是不能做,而是不该在核心路径未验证时就做。
为什么用内存存储而不是数据库?任务记录使用ConcurrentHashMap,服务重启会丢失。这听起来是个缺点,但在这个场景下反而是优势——文件本身就是临时的、24 小时过期,任务记录的生命周期跟文件一致,用数据库反而引入了不必要的持久化复杂度。后续如果需要水平扩展,再替换为 Redis 或数据库即可。
为什么展示累计转换次数?这个右上角的小数字,看似只是统计,实则是一个轻量的社会证明(Social Proof)设计。用户看到「已有 N 次成功转换」,会产生信任感——这工具是有人在用的、是可靠的。同时多标签页实时同步的体验,也间接展示了系统的实时性。
为什么预览和下载分开?预览用Content-Disposition: inline(浏览器内嵌显示),下载用attachment(触发保存)。用户可以先看再下,避免下了打不开的尴尬。文件名保留原始 OFD 文件名,减少用户的认知负担。
4.3 错误处理的产品化思维
很多工具的报错是「转换失败」四个字,用户一脸懵。这个工具定义了 8 种错误码,每种都有对应的触发场景和友好提示:
FILE_TYPE_INVALID | ||
FILE_TOO_LARGE | ||
OFD_PARSE_FAILED | ||
UNSUPPORTED_FEATURE | ||
CONVERT_TIMEOUT | ||
SERVICE_BUSY |
这种「错误码 → 友好提示 → 可操作建议」的三层结构,是把技术细节翻译成用户语言的产品化实践。
五、部署方案:一个人也搞得定
开发人员产品经理
生产部署非常轻量,一台云服务器即可搞定:
互联网用户 │ HTTP :80 ▼ Nginx (静态资源 + /api 反代 → :8080) │ ▼ Spring Boot JAR (-Xms256m -Xmx1g) │ ▼ 文件系统 (/opt/ofd-preview-pdf/data + logs)几个关键配置点:
- systemd 托管
: Restart=on-failure,崩溃后 5 秒自动重启,开机自启 - Nginx 反代
: proxy_buffering off支持 PDF 流式读取,client_max_body_size 50m允许大文件上传 - 静态资源缓存
: expires 7d+Cache-Control: immutable,减少重复请求 - SPA 路由
: try_files $uri $uri/ /index.html,支持前端路由刷新不 404
六、已知限制与未来规划
坦诚地说,当前版本还有一些不足,但都有明确的演进方向:
立即体验
打开浏览器,上传 OFD,几秒拿到 PDF
http://59.110.20.164/
如果你也经常和 OFD 文件打交道,不妨收藏这个工具。也欢迎把它分享给身边还在为「打不开 OFD」发愁的同事。
— 技术栈:Vue 3 + Spring Boot + PDF.js —
夜雨聆风