私有 Word 文件在线预览方案(.doc/.docx 转 PDF)
前言
由于 .doc
和 .docx
Word 文件 无法在浏览器中直接预览 (尤其在私有 API 场景下),常见的 Content-Disposition: inline
并不能生效。因此,本方案通过 后端转换为 PDF 文件,并将其以文档流形式返回前端,达到可在线阅读的效果。
效果如图:
实现流程概览
- 前端请求文件(非下载模式)
- 后端判断文件扩展名是否为
.doc
或.docx
- 使用 LibreOffice 将 Word 文件转换为 PDF
- 待 PDF 生成后,通过
res.sendFile()
发送给前端 - (可选)临时 PDF 文件使用后自动删除
实现步骤
🔹 第一步:后端判断 Word 文件类型并构建 PDF 路径
js
const docxRegex = /\.(docx?)$/i; // 支持 .doc 和 .docx(忽略大小写)
if (type !== 'download' && docxRegex.test(filePath)) {
const pdfPath = filePath.replace(docxRegex, '.pdf'); // 替换为 PDF 路径
...
}
第二步:安装 LibreOffice(用于文件转换)
Ubuntu / Debian:
bash
sudo apt update
sudo apt install libreoffice -y
CentOS / RHEL:
bash
sudo yum install libreoffice -y
第三步:安装中文字体(避免 PDF 中文乱码)
推荐使用开源思源字体(Noto 字体家族)
Ubuntu / Debian:
bash
sudo apt install fonts-noto-cjk -y
CentOS / RHEL:
bash
sudo yum install google-noto-sans-cjk-ttc -y
第四步:转换 Word 文件为 PDF 并返回给前端
js
const { exec } = require('child_process');
const fs = require('fs');
exec(`libreoffice --headless --convert-to pdf "${filePath}" --outdir "${uploadDir}"`, (error, stdout, stderr) => {
if (error) {
console.error('转换文件失败:', error);
return res.status(500).json({ message: '转换文件失败' });
}
const waitForPdf = setInterval(() => {
if (fs.existsSync(pdfPath) && fs.statSync(pdfPath).size > 1000) {
clearInterval(waitForPdf);
res.setHeader('Content-Type', 'application/pdf');
res.sendFile(pdfPath, (err) => {
if (err) {
console.error('发送 PDF 文件失败:', err);
return res.status(500).json({ message: '发送失败' });
}
// 清理临时文件
fs.unlink(pdfPath, (unlinkErr) => {
if (unlinkErr) {
console.error('删除 PDF 失败:', unlinkErr);
} else {
console.log('临时 PDF 已删除');
}
});
});
}
}, 100); // 每 100ms 检查一次 PDF 文件生成状态
});
补充建议
- 你可以在服务器上缓存转换后的 PDF,避免重复转换
- 建议加入错误重试机制(比如检测失败后尝试转换 2 次)
- 如对性能有要求,可使用转换任务队列(如 bull.js)
示例:前端预览
可以获取
html
// 查看文档
async viewDocument(fileData) {
let update = {
filename: fileData.filename,
userId: this.$store.state.userInfo.userId
}
try {
// 设置 responseType 为 'blob' 来正确处理二进制数据
const response = await this.$apiRequest('get', '/ser/xxxx', update, '', {
responseType: 'blob'
});
if(response.status === 200) {
// 直接使用返回的 blob 数据
const blob = response.data;
// 创建临时 URL
const blobUrl = URL.createObjectURL(blob);
// 在新窗口中打开文件
const newWindow = window.open(blobUrl, '_blank');
// 清理临时 URL(延迟清理,确保文件能正常打开)
setTimeout(() => {
URL.revokeObjectURL(blobUrl);
}, 1000);
// 如果无法打开新窗口,提供下载选项
if (!newWindow) {
this.downloadFile(blob, fileData.name);
}
}
} catch (error) {
console.error('查看文档失败:', error);
this.$message.error('查看文档失败,请重试');
}
},
备注
注意responseType: 'blob'
在接口架构文件里配置一下。