You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:45:33