基于Node.js Express实现文件下载前预览的技术求助
文件下载前预览功能实现指南(Node.js + Express)
核心思路
根据不同文件类型采用对应的解析/返回策略,前端再匹配类型渲染预览内容。以下是分类型的具体实现方案:
1. 依赖安装
先安装所需工具库:
npm install fs-extra mammoth xlsx
2. 后端路由实现(Express)
创建专门的预览路由,处理不同类型文件的请求:
const express = require('express'); const router = express.Router(); const fs = require('fs-extra'); const mammoth = require('mammoth'); const XLSX = require('xlsx'); // 文本类文件预览(.txt/.md/.json等) router.get('/preview/text/:filename', async (req, res) => { const filePath = `./uploads/${req.params.filename}`; // 替换为你的实际文件存储路径 try { const content = await fs.readFile(filePath, 'utf8'); res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.send(content); } catch (err) { res.status(404).send('文件不存在或读取失败'); } }); // 图片类文件预览(.jpg/.png/.gif等) router.get('/preview/image/:filename', (req, res) => { const filePath = `./uploads/${req.params.filename}`; res.sendFile(filePath, (err) => { if (err) res.status(404).send('图片不存在'); }); }); // PDF文件预览(返回文件流,前端用PDF.js渲染) router.get('/preview/pdf/:filename', (req, res) => { const filePath = `./uploads/${req.params.filename}`; res.setHeader('Content-Type', 'application/pdf'); const stream = fs.createReadStream(filePath); stream.pipe(res); stream.on('error', () => res.status(404).send('PDF文件不存在')); }); // DOCX文件预览(转换为HTML返回) router.get('/preview/docx/:filename', async (req, res) => { const filePath = `./uploads/${req.params.filename}`; try { const result = await mammoth.convertToHtml({path: filePath}); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send(result.value); } catch (err) { res.status(404).send('DOCX文件不存在或解析失败'); } }); // XLSX文件预览(转换为JSON,前端生成表格) router.get('/preview/xlsx/:filename', async (req, res) => { const filePath = `./uploads/${req.params.filename}`; try { const workbook = XLSX.readFile(filePath); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); res.json(jsonData); } catch (err) { res.status(404).send('XLSX文件不存在或解析失败'); } }); module.exports = router;
记得在主Express文件中注册该路由:
const previewRouter = require('./routes/preview'); app.use('/', previewRouter);
3. 前端预览渲染
根据文件类型调用对应接口,渲染不同的预览内容:
<!DOCTYPE html> <html> <head> <title>文件预览</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script> <style> .preview-container { margin: 20px; padding: 20px; border: 1px solid #eee; border-radius: 4px; } .text-preview { white-space: pre-wrap; line-height: 1.6; } .image-preview img { max-width: 100%; height: auto; border-radius: 4px; } .pdf-preview canvas { display: block; margin: 10px 0; border: 1px solid #eee; } .table-preview table { border-collapse: collapse; width: 100%; margin-top: 10px; } .table-preview th, .table-preview td { border: 1px solid #ddd; padding: 8px; text-align: left; } .table-preview th { background-color: #f5f5f5; } </style> </head> <body> <div class="preview-container" id="previewContainer"></div> <script> // 示例:替换为实际的文件信息(可从后端接口获取) const targetFile = { name: 'report.docx', type: 'docx' // 可选值:text, image, pdf, docx, xlsx }; function renderPreview() { const container = document.getElementById('previewContainer'); container.innerHTML = '<p>加载中...</p>'; switch(targetFile.type) { case 'text': fetch(`/preview/text/${targetFile.name}`) .then(res => res.text()) .then(content => { container.innerHTML = `<div class="text-preview">${content}</div>`; }) .catch(() => container.textContent = '文本预览加载失败'); break; case 'image': container.innerHTML = ` <div class="image-preview"> <img src="/preview/image/${targetFile.name}" alt="${targetFile.name}" onerror="this.parentNode.textContent='图片加载失败'"> </div> `; break; case 'pdf': container.innerHTML = '<div class="pdf-preview"></div>'; const pdfContainer = container.querySelector('.pdf-preview'); pdfjsLib.getDocument(`/preview/pdf/${targetFile.name}`).promise .then(pdf => { pdfContainer.innerHTML = `<p>共 ${pdf.numPages} 页</p>`; for(let i = 1; i <= pdf.numPages; i++) { pdf.getPage(i).then(page => { const canvas = document.createElement('canvas'); const viewport = page.getViewport({scale: 1.2}); canvas.height = viewport.height; canvas.width = viewport.width; page.render({canvasContext: canvas.getContext('2d'), viewport}); pdfContainer.appendChild(canvas); }); } }) .catch(() => container.textContent = 'PDF预览加载失败'); break; case 'docx': fetch(`/preview/docx/${targetFile.name}`) .then(res => res.text()) .then(html => { container.innerHTML = html; }) .catch(() => container.textContent = 'DOCX预览加载失败'); break; case 'xlsx': fetch(`/preview/xlsx/${targetFile.name}`) .then(res => res.json()) .then(data => { if(!data.length) { container.textContent = '表格无数据'; return; } // 生成表格 const headers = Object.keys(data[0]); let tableHtml = '<div class="table-preview"><table><thead><tr>'; headers.forEach(header => tableHtml += `<th>${header}</th>`); tableHtml += '</tr></thead><tbody>'; data.forEach(row => { tableHtml += '<tr>'; headers.forEach(header => tableHtml += `<td>${row[header] || ''}</td>`); tableHtml += '</tr>'; }); tableHtml += '</tbody></table></div>'; container.innerHTML = tableHtml; }) .catch(() => container.textContent = 'XLSX预览加载失败'); break; default: container.textContent = '不支持该类型文件的预览'; } } renderPreview(); </script> </body> </html>
4. 关键注意事项
- 文件路径权限:确保Express服务对文件存储目录有读取权限,避免因权限问题导致读取失败。
- 大文件处理:对于大体积的文本/文档文件,建议使用流式读取(
fs.createReadStream)替代一次性读取,防止内存溢出。 - 类型校验:可在后端添加文件类型校验逻辑,仅允许预设类型的文件请求预览,提升安全性。
- 样式兼容:DOCX转HTML可能丢失部分复杂样式,若需保留完整格式,可考虑使用第三方服务或更专业的解析库。
内容的提问来源于stack exchange,提问作者Badr-Eddine El mostaine
相关产品推荐
相关产品推荐

