文件下载 普通文件下载 + 前端获取后端返回的文件流并下载

8 篇文章 0 订阅

参考资料

  1. 前端接受后端文件流并下载的几种方法
  2. ajax 请求二进制流 图片 文件 XMLHttpRequest 请求并处理二进制流数据 之最佳实践
  3. ajax请求二进制流进行处理(ajax异步下载文件)
  4. response.setHeader()的用法
  5. response.setContentType()的作用及参数


要点

  1. response.setContentType("application/octet-stream"); 指明要返回的内容为二进制的流
  2. response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(文件名.后缀, "UTF-8")); 指明下载的文件名称
    Content-Disposition是Content-Type的扩展,告诉浏览器弹窗下载框,而不是直接在浏览器里展示文件。因为一般浏览器对于它能够处理的文件类型,如txt,pdf 等,它都是直接打开展示,而不是弹窗下载框。

一. 普通文件下载

  • 普通文件下载只能直接触发url进行下载,不能使用Ajax

1.1 原生servlet的HttpServletResponse方式下载

前端

<a href="/test15/fileDownLoad">下载文件</a>

后端

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

@Controller
@RequestMapping("/test15")
public class Test15Controller {

    @Resource
    private HttpServletResponse response;

    @GetMapping("/fileDownLoad")
    public void fileDownload() throws IOException {

        // 保存在本地磁盘中的文件
        File file = new File("C:\\Users\\用户名\\Desktop\\hexo安装.txt");

        /*
        	⏹1. response.setContentType的作用是使客户端浏览器,区分不同种类的数据
        	并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
			⏹2. application/octet-stream表示返回二进制流,表示不知道下载文件类型,具有通用性
		*/ 
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        /*
        	Content-Disposition 的作用:
        		文件下载时会告诉浏览器要下载的文件名称和类型
        	URLEncoder.encode 的作用是为了防止中文文件名乱码
		*/
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
		
		// 使用工具类直接将文件的字节复制到响应输出流中
        FileCopyUtils.copy(new FileInputStream(file), response.getOutputStream());
    }
}

1.2 Spring的ResponseEntity方式下载

前端

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <a id="downloadFile" th:href="@{/test19/testResponseEntity1}">文件下载-普通的方式</a>
</div>
</body>
</html>

后端

  • 通过构造springframework的ResponseEntity类的方式返回数据
  • 可以指定字节码的返回值,响应值,状态码

在这里插入图片描述

import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;

import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Controller
@RequestMapping("/test19")
public class Test19Controller {

    @GetMapping("/testResponseEntity1")
    public ResponseEntity<byte[]> testResponseEntity1() throws Exception {

        // 🧐🧐🧐读取本地的文件
        String filePath = "/temp/A110120119/测试文件.text";
        ClassPathResource readFile = new ClassPathResource(filePath);

        // 🐳🐳🐳设置响应头,把文件名称放入响应头中,确保文件可下载
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Disposition", "attachment;filename=" + URLEncoder.encode(readFile.getFilename(), "UTF-8"));
        // 🐳🐳🐳设置内容类型为「application/octet-stream」二进制流
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

        // 获取File对象
        File file = readFile.getFile();
        Path path = Paths.get(file.toURI());
        // 获取File对象的字节码文件
        byte[] bytes = Files.readAllBytes(path);

        /*
         * 💪💪💪表示返回一个字节码类型的响应
         * 同时设置了响应头和状态码
         * */
        return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        
		// 👍👍👍还可以使用下面这种链式调用的方式
		// return ResponseEntity.ok().headers(headers).body(bytes);
    }
}

下载成功后,可以在Response Headers响应头中看到我们设置的Content-DispositionContent-Type

在这里插入图片描述

二. 文件流下载-原生ajax之XMLHttpRequest

前端

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <button id="btn">下载本地的文本</button>
</body>
<script th:src="@{js/jquery.min.js}"></script>
<script>
	// 点击按钮下载,将本地的文件以流的形式返回前端,然后进行下载
    $("#btn").on("click", function() {
    	
        // 构造XMLHttpRequest对象
        var xmlRequest = new XMLHttpRequest();
        // 发送get请求
        xmlRequest.open("GET", "/test15/fileDownLoad", true);
        // 设置响应类型
        xmlRequest.responseType = "blob";
        // 发送请求
        xmlRequest.send([]);
        
        // 请求获得响应之后,触发下面的回调函数
        xmlRequest.onload = function(oEvent) {
            // 当时满足下面的状态码的时候,视为下载成功
            if ((xmlRequest.status >= 200 && xmlRequest.status < 300) || xmlRequest.status === 304) {

                // 从xmlRequest对象中获取响应的内容
                var content = xmlRequest.response;

                /*
                	从xmlRequest的响应头中获取文件名字符串
                	因为我们将文件以流的形式返回前端,返回的文件没有文件名
                	因此在后端处理的时候,我们将文件名放到响应头中
                	然后在前端从响应头中获取文件名
				*/ 
                var dispositionStr = xmlRequest.getResponseHeader('content-disposition');
                if (dispositionStr == null || dispositionStr === "") {
                    alert("下载失败!");
                    return;
                } 
                
				// 获取文件名
                let dispositionArr = dispositionStr.split("=");
                // 我们的文件名可能含有汉字,因此在后端进行了UTF-8编码处理,此处进行解码
                let fileName = decodeURIComponent(dispositionArr[1]);

                // 利用response的响应内容构造一个Blob对象(通过Blob对象进行下载)
                var blob = new Blob([content]);

                // 创建一个隐藏的用来下载文件的a标签
                var elink = document.createElement('a');
                elink.download = fileName;
                elink.style.display = 'none';

                /*
                	将blob文件对象转换为内存中对象,并将生成的对象赋给隐藏的a标签
                	bolb对象会暂时存储在客户端的内存中,
                	使用URL.createObjectURL()方法可以创建指向内存文件对象的临时url
                	使用createObjectURL可以节省性能并更快速,只不过需要在不使用的情况下手动释放内存
                	FileReader.readAsDataURL返回文件的base64字符串,比blob url消耗更多内存,但是在不用的时候会自动从内存中清除(通过垃圾回收机制)
				*/ 
                const src = URL.createObjectURL(blob);
                elink.href = src;
                document.body.appendChild(elink);

                // 模拟点击下载事件,进行下载
                elink.click();
                
                // 点击之后,移除我们定义的隐藏a标签
                document.body.removeChild(elink);

                // 移除文件对象
                URL.revokeObjectURL(src)
            }
        }
    });
</script>
</html>

后端

  • 参照普通文件下载的后端

三. 文件流下载-jQuery的Ajax

⭕后台-controller

添加@ResponseBody注解,防止 java.lang.IllegalStateException: getOutputStream() has already been called for this response 异常.如果使用@Autowired的方式注入HttpServletResponse对象,但是不添加@ResponseBody注解话,就会出现该异常.

@Controller
@RequestMapping("/test2")
public class Test2Controller {

    @Autowired
    private Test2Service service;

    @GetMapping("/init")
    public ModelAndView init() {

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("test2");
        return  modelAndView;
    }

    @PostMapping("/fileDownload")
    @ResponseBody
    public void fileDownload(@RequestBody Test2Form form) throws Exception {

        service.fileDownload(form);
    }
}

⭕后台-service

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import java.text.MessageFormat;

@Service
public class Test2Service {

    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    private HttpServletResponse response;

    public void fileDownload(Test2Form form) throws Exception {

        String fileId = form.getFileId();
        String fileName = form.getFileName();

        // 格式化拼接资源的相对路径
        String filePath = MessageFormat.format("classpath:temp/{0}/{1}", fileId, fileName);
        // 使用ResourceLoader获取项目中的资源,防止项目打包之后找不到资源
        Resource resource = resourceLoader.getResource(filePath);

        if (!resource.exists()) {
            // 抛出异常,前台Ajax在error方法中对异常进行处理,获取响应头中的异常信息
            response.setHeader("errorInfo", URLEncoder.encode(fileName + "不存在!", "UTF-8"));
            throw new RuntimeException();
        }

        // 获取资源,指定相应信息
        File file = resource.getFile();
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename*=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
        response.setContentLength((int) file.length());

        // 要下载的文件不大的话,直接使用工具类将文件拷贝到响应流中即可
        FileCopyUtils.copy(new FileInputStream(file), response.getOutputStream());
    }
}

在这里插入图片描述

⭕前台

㊙关键在于dataType和xhrFields的属性指定上,指定对属性才能返回blob文件流

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点击下载文件</button>
</body>
<script src="/js/public/jquery-3.6.0.min.js"></script>
<script th:inline="javascript">

    function doAjax_FileDownload(url, param, callback) {

        $.ajax({
            url: url,
            type: "post",
            data: JSON.stringify(param),
            // 向服务器发送的数据类型
            contentType: 'application/json;charset=utf-8',
            // 💢💢💢指定服务器返回的类型,因为我们要返回文件流,所以类型为二进制数据
            dataType: "binary",
            // 💢💢💢原生 XMLHttpRequest 的属性,设置响应类型为blob,接收文件流
            xhrFields: {
                'responseType': 'blob'
            },
            success: function (result, status, xhr) {

                // 可通过XMLHttpRequest对象,获取响应头
                console.log(xhr);

                // 浏览器兼容
                const download_URL = (window.URL || window.webkitURL).createObjectURL(result);

                // 创建a标签,模拟点击下载
                const a_link = document.createElement('a');
                a_link.href = download_URL;
                // 利用了a标签的download属性,指定文件名称
                a_link.download = param.fileName;
                document.body.appendChild(a_link);
                a_link.click();

                setTimeout(function () {
                    // 移除内存中的临时文件路径和为下载而创建的a标签
                    URL.revokeObjectURL(download_URL);
                    a_link.remove();
                }, 10000);
            },
            error: function (xhr, textStatus, errorMessage) {

                // 从响应头中获取异常信息,如果包含中文的话会乱码因此 后台URLEncoder.encode() + 前台decodeURIComponent() 防止乱码
                const errorInfo = decodeURIComponent(xhr.getResponseHeader("errorInfo"));

                // 对错误信息进行展示
                alert(errorInfo);
            }
        });
    }

    $("#btn").click(() => {

        const url = "http://localhost:8080/test2/fileDownload";
        const fileInfo = {
            fileName: '测试文件.text',
            fileId: 'A110120119'
        };
        doAjax_FileDownload(url, fileInfo, function () {});
    });
</script>
</html>

效果
在这里插入图片描述
在这里插入图片描述

四. 文件流下载-原生ajax之fetch

重要代码为await response.blob()

详情可参考这篇博客
https://blog.csdn.net/feyehong/article/details/124974601

五. 下载数据库中保存的文件

详情可参考这篇博客
https://blog.csdn.net/feyehong/article/details/117653690

  • 42
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端下载 Excel 文件后端需要返回文件,可以按照以下步骤进行处理: 1. 后端根据前端请求参数,生成 Excel 文件,并将文件返回前端。 2. 前端通过 AJAX 发送请求,请求后端生成 Excel 文件的接口。 3. 后端接收到请求后,使用相关的库(如 Apache POI)生成 Excel 文件,并将文件返回前端。 4. 前端接收后端返回文件后,使用 Blob 对象创建一个 URL,并将该 URL 赋给一个 a 标签的 href 属性。 5. 前端再使用 JavaScript 触发该 a 标签的 click 事件,即可下载 Excel 文件。 以下是一个示例的后端代码,使用了 Spring Boot 和 Apache POI 库: ```java @GetMapping("/downloadExcel") public ResponseEntity<byte[]> downloadExcel() throws IOException { // 创建 Excel 工作簿 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); // 创建表头 Row header = sheet.createRow(0); header.createCell(0).setCellValue("姓名"); header.createCell(1).setCellValue("年龄"); // 创建数据行 Row dataRow = sheet.createRow(1); dataRow.createCell(0).setCellValue("张三"); dataRow.createCell(1).setCellValue(20); // 将 Excel 文件写入字节数组输出 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); workbook.write(outputStream); // 设置响应头 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename=test.xlsx"); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 返回响应实体 return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK); } ``` 在前端中,可以使用以下代码实现下载: ```javascript axios.get('/downloadExcel', { responseType: 'blob' }).then(res => { const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = 'test.xlsx' a.click() URL.revokeObjectURL(url) }) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值