文件下载功能

背景

随着 Web 应用的不断发展,文件下载功能成为许多应用中不可或缺的一部分。无论是文档、图片还是其他类型的文件,能够方便地从服务器下载文件对于提升用户体验至关重要。

原理

文件下载功能的基本原理是通过 HTTP 请求从服务器获取文件数据,然后在客户端触发文件下载。在 Vue 中,可以通过以下几种方式实现文件下载:

  • 直接链接下载:通过 <a> 标签的 href 属性指向文件的 URL,用户点击链接即可下载文件。

  • 后端 API 下载:通过 AJAX 请求从后端 API 获取文件数据,使用 Blob 对象创建一个临时的 URL,再通过 <a> 标签触发下载。

示例

1. 直接链接下载

介绍

直接链接下载是最简单的方法,适用于文件已经存在于服务器上的情况。用户点击链接后,浏览器会自动发起请求并下载文件。

<template>
  <div>
    <h1>直接链接下载</h1>
    <a href="/path/to/file.pdf" download="file.pdf">下载文件</a>
  </div>
</template>

<script>
export default {
    
    
  name: "DirectDownload"
};
</script>

2. 后端 API 下载

介绍

后端 API 下载适用于需要通过后端逻辑处理文件的情况。前端通过 AJAX 请求从后端 API 获取文件数据,动态创建 <a> 标签触发下载。

前端代码示例:

<template>
  <div>
    <h1>后端 API 下载</h1>
    <button @click="downloadFile">下载文件</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
    
    
  name: "ApiDownload",
  data() {
    
    
    return {
    
    
      fileName: "大文件.docx"
    }
  },
  methods: {
    
    
    async downloadFile() {
    
    
      try {
    
    
        const response = await axios.get(`/preview?fileName=${
      
      this.fileName}`, {
    
    
          responseType: 'blob'
        });
        // 从响应头里面获取文件名
        let fileName = this.getFileName(response);

        const url = window.URL.createObjectURL(new Blob([response.data]));
        const a = document.createElement('a');
        a.href = url;
        a.download = fileName;
        a.click();
        window.URL.revokeObjectURL(url);
      } catch (error) {
    
    
        console.error('下载失败:', error);
      }
    },
    getFileName(res) {
    
    
      // 获取响应头里的 content-disposition 值 例: attachment; filename=%E5%A4%A7%E6%96%87%E4%BB%B6.docx
      const contentDisposition = res.headers.get('content-disposition');
      if (!contentDisposition) return null;
      // 获取编码后的文件名
      let fileName = contentDisposition.split('attachment; filename=')[1]
      // 解码文件名
      return decodeURIComponent(fileName)
    },
  }
};
</script>

node 服务端代码:

页面通过在请求中传递 fileName 查询参数来下载指定文件,服务端会首先检查文件是否存在,然后设置正确的 MIME 类型和 Content-Disposition 头部,以确保文件能够正确下载。

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const app = express();
// 定义文件夹路径
const mergedDir = path.join(__dirname, 'merged');

// 文件下载接口
app.get('/download', (req, res) => {
    
    
    const {
    
     fileName } = req.query;
    const filePath = path.join(mergedDir, fileName);

    fs.access(filePath, fs.constants.F_OK, (err) => {
    
    
        if (err) {
    
    
            return res.status(404).json({
    
     error: '文件不存在' });
        }

        const stats = fs.statSync(filePath);

        if (stats.isFile()) {
    
    
            const contentType = getContentType(fileName);
            res.setHeader('Content-Type', contentType);
            // 对 fileName 进行编码
            const encodedFileName = encodeURIComponent(fileName);
            res.setHeader('Content-Disposition', `attachment; filename=${
      
      encodedFileName}`);
            fs.createReadStream(filePath).pipe(res);
        } else {
    
    
            res.status(400).json({
    
     error: '不是一个文件' });
        }
    });
});

// 获取文件的 MIME 类型
function getContentType(fileName) {
    
    
    const ext = path.extname(fileName).toLowerCase();
    switch (ext) {
    
    
        case '.js':
            return 'application/javascript';
        case '.json':
            return 'application/json';
        case '.html':
            return 'text/html';
        case '.css':
            return 'text/css';
        case '.txt':
            return 'text/plain';
        case '.png':
            return 'image/png';
        case '.jpg':
        case '.jpeg':
            return 'image/jpeg';
        case '.gif':
            return 'image/gif';
        case '.pdf':
            return 'application/pdf';
        case '.doc':
            return 'application/msword';
        case '.docx':
            return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
        case '.ppt':
            return 'application/vnd.ms-powerpoint';
        case '.pptx':
            return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
        case '.xlsx':
            return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
        default:
            return 'application/octet-stream';
    }
}


// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    
    
    console.log(`Server is running on port ${
      
      PORT}`);
});

注意

Content-Disposition 头部用于指示浏览器如何处理接收到的内容,它可以帮助浏览器决定是将文件显示在浏览器中(内联显示)还是提示用户下载文件。

  • inline:内联显示文件

    // 对 fileName 进行编码
    const encodedFileName = encodeURIComponent(fileName);
    res.setHeader('Content-Disposition', `inline; filename=${
            
            encodedFileName}`);
    
  • attachment:指示浏览器提示用户下载文件。

    // 对 fileName 进行编码
    const encodedFileName = encodeURIComponent(fileName);
    res.setHeader('Content-Disposition', `attachment; filename=${
            
            encodedFileName}`);
    

设置 Content-Disposition 时,拼接的文件名需使用 encodeURIComponent 编码,解决特殊字符或中文在 HTTP 头部中可能会引起解析错误或安全问题。

总结

在 Web 应用中实现文件下载功能的两种常见方法:直接链接下载和后端 API 下载。直接链接下载适用于文件已存在于服务器上的简单场景,而后端 API 下载则适用于需要通过后端逻辑处理文件的复杂场景。通过设置 Content-Disposition 头部,可以控制文件的显示或下载行为,并使用 encodeURIComponent 编码文件名以确保特殊字符和中文字符在 HTTP 头部中正确解析,提升系统的安全性和兼容性。

猜你喜欢

转载自blog.csdn.net/a123456234/article/details/143135069