私有 Word 文件在線預覽方案(.doc/.docx 轉 PDF)
前言
由于 .doc
和 .docx
Word 文件 無法在瀏覽器中直接預覽(尤其在私有 API 場景下),常見的 Content-Disposition: inline
并不能生效。因此,本方案通過 后端轉換為 PDF 文件,并將其以文檔流形式返回前端,達到可在線閱讀的效果。
效果如圖:
實現流程概覽
- 前端請求文件(非下載模式)
- 后端判斷文件擴展名是否為
.doc
或.docx
- 使用 LibreOffice 將 Word 文件轉換為 PDF
- 待 PDF 生成后,通過
res.sendFile()
發送給前端 - (可選)臨時 PDF 文件使用后自動刪除
實現步驟
🔹 第一步:后端判斷 Word 文件類型并構建 PDF 路徑
const docxRegex = /\.(docx?)$/i; // 支持 .doc 和 .docx(忽略大小寫)if (type !== 'download' && docxRegex.test(filePath)) {const pdfPath = filePath.replace(docxRegex, '.pdf'); // 替換為 PDF 路徑...
}
第二步:安裝 LibreOffice(用于文件轉換)
Ubuntu / Debian:
sudo apt update
sudo apt install libreoffice -y
CentOS / RHEL:
sudo yum install libreoffice -y
第三步:安裝中文字體(避免 PDF 中文亂碼)
推薦使用開源思源字體(Noto 字體家族)
Ubuntu / Debian:
sudo apt install fonts-noto-cjk -y
CentOS / RHEL:
sudo yum install google-noto-sans-cjk-ttc -y
第四步:轉換 Word 文件為 PDF 并返回給前端
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)
示例:前端預覽
可以獲取
// 查看文檔
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;// 創建臨時 URLconst 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'
在接口架構文件里配置一下。