使用XMLHttpRequest实现文件异步下载

news2024/12/25 12:25:05

1、问题描述

        我想通过异步的方式实现下载文化,请求为post请求。一开始我打算用ajax。

$.ajax({
        type:'post',
        contentType:'application/json',
        url:'http://xxx/downloadExcel',
        data:{data:JSON.stringify(<%=oJsonResponse.JSONoutput()%>)},
        }).success(function(data){
                    const blob = new Blob([data], {type: 'application/vnd.openxmlformats- 
                    officedocument.spreadsheetml.sheet'});
                    const url1=URL.createObjectURL(blob)
                    const a = document.createElement('a');
                    a.href = url1;
                    a.download = '表格.xlsx';
                    a.click(); 
                    URL.revokeObjectURL(url1);

        });

        不过ajax的返回类型不支持二进制文件流(binary)!因此ajax的异步方式无法接到后端接口返回的文件流,就无法下载文件。

jQuery.ajax() | jQuery API Documentation

2、解决方法

        改用dom原生的XMLHttpRequest。

         XMLHttpRequest的返回类型二进制数据blob,可以接到文件流。

XMLHttpRequest.responseType - Web API 接口参考 | MDN (mozilla.org)

3、代码示例

3.1、前端代码

         downloadExcel.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript" src="./js/jquery.min.js"></script>
		
		<script type="text/javascript">
			$(document).ready(function() {
				$("#btnDownload").click(function(){
					var param={name:'zhangsan',age:'20',sex:'男'};
					let xhr=new XMLHttpRequest(); 
					xhr.responseType = "blob";
					xhr.open('POST', 'http://localhost:6001/excel/downloadExcel');
					xhr.setRequestHeader('Content-Type', 'application/json');
					xhr.send(JSON.stringify(param));
					xhr.onreadystatechange = function() {
						console.log(xhr.response)
						if (xhr.status === 200) {
							var blob = xhr.response;
							if(blob){
								var downloadLink = document.createElement('a');
								downloadLink.href = URL.createObjectURL(blob);
								downloadLink.download = 'excel.xlsx'; // 设置下载的文件名
								downloadLink.click();
							}
							
						}
					}
				});
			});
		</script>
		
		<button class="btn" id="btnDownload" name="btnDownload">下载文件</button>
	</body>
</html>

 3.2、后端代码

ExcelController.java
import java.io.*;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author Wulc
 * @date 2023/7/20 16:02
 * @description
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {
    @Autowired
    private MyExcelUtils myExcelUtils;

    @PostMapping("/downloadExcel")
    @CrossOrigin //跨域
    public String downloadExcel(HttpServletResponse response, @RequestBody Map<String, Object> data) throws IOException {
        return myExcelUtils.downloadExcel(myExcelUtils.composeFile(data), response);
    }
}
MyExcelUtils.java
package com.easyexcel.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.easyexcel.bo.*;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Wulc
 * @date 2023/7/25 17:07
 * @description
 */
@Component
public class MyExcelUtils {

    public File composeFile(Map<String, Object> map) throws IOException {
        Resource resource = new ClassPathResource("/");
        String path = resource.getFile().getPath();
        String filePath = path + "excel.xlsx";
        List<PeopleBO> peopleBOList = new ArrayList<>();
        peopleBOList.add(new PeopleBO(map.get("name").toString(), map.get("age").toString(), map.get("sex").toString()));
        EasyExcel.write(filePath, PeopleBO.class).sheet().useDefaultStyle(false).needHead(true).doWrite(peopleBOList);
        return new File(filePath);
    }

    public String downloadExcel(File file, HttpServletResponse response) {
        try {
            // 获取文件名
            String filename = file.getName();
            // 获取文件后缀名
            String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
            // 将文件写入输入流
            FileInputStream fileInputStream = new FileInputStream(file);
            InputStream fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            response.reset();
            response.setCharacterEncoding("UTF-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
            response.addHeader("Content-Length", "" + file.length());
            //跨域
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.setContentType("application/octet-stream");
            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
            outputStream.write(buffer);
            outputStream.flush();
            outputStream.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            file.delete();
        }
        return "success";
    }
}

文件是能成功下载了。 

 但后端报了一个错误

 产生的原因,是因为返回了多次response了。因为一个接口只能有一个return,即有一个response响应给到调用方。但downloadExcel接口出现了两个response,但一个接口只能有一个response响应,因此另一个response就失效了,就会出现sendError()的报错。

 解决方法有三种

1、输出流不要关闭(不推荐)

因为流一旦关闭,就意味着基本上就结束了对客户端的响应了。下面的return "success"就没法返回给调用方了。又因为是@RestController,需要一个可以封装成json的返回对象,显然“流”是不能封装成json的,@RestController需要下面的return "success",但你提前把“流”关闭了,return "success"不会响应,@RestController封装不到json对象,就会报错了。

2、controller接口或者util方法改为void(推荐)

 不要return myExcelUtils.downloadExcel(myExcelUtils.composeFile(data), response);

把return去掉,直接myExcelUtils.downloadExcel(myExcelUtils.composeFile(data), response);

3、避免使用@RestController,改用@Controller

 @RestController=@Controller+@ResponseBody,而@ResponseBody会把返回值封装成json的形式返回。如果不加@ResponseBody,则底层会把返回值封装成一个ModelAndView对像。显然文件流并不能封装成json,但由于通常在输出文件流后,会把这个流关闭,因此下面那个可以封装成json对象的return返回值就不能返回了。因此就会报错了。当然除非你一直让outputStream保持打开,使response响应不关闭。但不推荐这么做,文件流还是要用完及时关闭的。因为OutputStream也属于资源,处理完了以后务必要close()关闭并释放此流有关的所有系统资源,不然会大量占用系统内存资源,大量不释放资源会导致内存溢出。

4、总结

        我们在jquery中常用的ajax其实就是对XMLHttpRequest进行了封装。ajax的底层就是XMLHttpRequest。jquery的出现主要就是为了更快捷的操作DOM,以及解决一些浏览器兼容性问题。jquery$.ajax通过对XHR(XMLHttpRequest简称XHR)封装,做了兼容性的处理,简化了使用,增加了对JSONP的支持。

        JSONP类型可以支持跨域,因为jsonp不受同源策略的影响。所谓同源策略,”源“指的是:协议名(http/https)、域名/Ip地址、端口号。不同源的客户端/服务端,在没有对方授权的情况下是不允许发送/接收对方的数据资源的,会产生“跨域”情况。

         JSONP用法举例:

        前端

<script type="text/javascript" src="./js/jquery.min.js"></script>
		<script type="text/javascript">
            $(document).ready(function() {
                $("#btnJSONP").click(function(){
					var param={name:'zhangsan',age:'20',sex:'男'};
					$.ajax({ 
						 url: 'http://localhost:6001/excel/testJsonP', // 跨域URL 
						 type:'get',
						 dataType: 'jsonp',
						 jsonp:'jsoncallback',//自定义参数名称
						 jsonpCallback: 'showData', //指定回调函数名称
						 //timeout: 5000, 
					 }).success(function(data){
					 	console.log("success:"+data)	
					 });
				});
            });
			function showData(data){
				console.info("回调showData:"+data);
			}
				
		</script>

<button class="btn" id="btnJSONP" name="btnJSONP">testJSONP</button>

         后端

 @GetMapping("/testJsonP")
 public void testJsonP(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        //前端传过来的回调函数名称
        String callback = request.getParameter("jsoncallback");
        //用回调函数名称包裹返回数据,这样,返回数据就作为回调函数的参数传回去了
        String result = callback + "('Hello World')";
        response.getWriter().write(result);
    }

可以看到前后端不同源,但不用专门设置什么,就可以实现通信。这就是JSONP的跨域。

如果不用jsonp的话,用XMLHttpRequest或者ajax的话,则要设置一下

 后端接口加上@CrossOrigin注解,设置response “Access-Control-Allow-Origin”请求头为“*”

response.addHeader("Access-Control-Allow-Origin", "*");

不然就会出现跨域报错:

 除了XHR和ajax,在前端框架中广泛Http数据通信工具:fetch、axios。fetch和XMLHttpRequest一样都是底层的原生js,只不过Fetch是基于promise设计的适用于前端框架。

而ajax和axios都是封装了XMLHttpRequest,一个适用于jquery,一个则广泛运用于各种主流前端框架:vue、react等等。

5、参考资料

ajax跨域请求错误-CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource_ajax cors错误_我是一朵蒲公英的博客-CSDN博客

 Ajax传JSON对象报错:JSON parse error: Unrecognized token ‘ids‘: was expecting (‘true‘, ‘false‘ or ‘null‘);_萌宅鹿同学的博客-CSDN博客

Can not construct instance of java.util.LinkedHashMap: no String-argument constructor/factory method_-droidcoffee-的博客-CSDN博客 【Java】解决POST表单提交报错 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported_北宫清云的博客-CSDN博客

 jQuery.ajax() | jQuery API Documentationjava 关闭输出流_Java OutputStream.close()关闭并释放输出流资源_卖糕郎的博客-CSDN博客

var,let,const三者的特点和区别_前端Vincent的博客-CSDN博客

已解决【Error】Cannot call sendError() after the response has been committed_hah杨大仙的博客-CSDN博客 springBoot文件下载出现 Cannot call sendError() after the response has been committed异常_木羊子羽的博客-CSDN博客

解决:java.lang.IllegalStateException: Cannot call sendError() after the response has been committed_java 转发 报cannot call sendredirect after the respon_郄子硕-langgeligelang的博客-CSDN博客 Controller和RestController的区别_controller与restcontroller区别_Linux资源站的博客-CSDN博客jsonp解决跨域问题_jsonp跨域_Ivymemphis的博客-CSDN博客

https://www.cnblogs.com/chiangchou/p/jsonp.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/842741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

旅游系统开源

线上旅游电子商务系统 适用业务场景&#xff1a; 线路、酒店、门票、租车、邮轮、导游、团购、签证、特产、户外、商城等。 支持B2C\B2B2C\B2B业务&#xff0c;支持微信公会号、微信小程序、PC端等等。 源码介绍&#xff1a; 1、采用WEB软件应用最广泛的PHPMYSQL语言 采用B…

4大软件测试策略的特点和区别(单元测试、集成测试、确认测试和系统测试)

四大软件测试策略分别是单元测试、集成测试、确认测试和系统测试。 一、单元测试 单元测试也称为模块测试&#xff0c;它针对软件中的最小单元&#xff08;如函数、方法、类、模块等&#xff09;进行测试&#xff0c;以验证其是否符合预期的行为和结果。单元测试通常由开发人…

单例模式(C++)

定义 保证一个类仅有一个实例&#xff0c;并提供一个该实例的全局访问点。 应用场景 在软件系统中&#xff0c;经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例&#xff0c;才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器&#xff0c;提供一种…

【Docker】Docker私有仓库的使用

目录 一、搭建私有仓库 二、上传镜像到私有仓库 三、从私有仓库拉取镜像 一、搭建私有仓库 首先我们需要拉取仓库的镜像 docker pull registry 然后创建私有仓库容器 docker run -it --namereg -p 5000:5000 registry 这个时候我们可以打开浏览器访问5000端口看是否成功&…

zookeeper和kafka

目录 一、zookeeper理论 1.1、zookeeper定义 1.2、zookeeper工作机制 1.3、zookeeper特点 1.4、zookeeper的数据结构 1.5、zookeeper应用场景 1.6、zookeeper的选举机制 二、部署Zookeeper 集群 2.1、环境准备 2.2、安装 Zookeeper 2.3、修改配置文件 2.4、配置…

编写第一个 React Native 程序

React Native 目录 使用React Native CLI命令创建的目录如下图所示&#xff1a; 重要目录说明 目录说明__tests__存放测试用例的目录.bundle / config配置文件&#xff08;一般不会用到&#xff09;android 和 IOS 文件夹这两个文件夹主要是存放安卓和 ios 相关的配置文件和…

ESP32学习笔记(52)————三轴加速度ADXL345使用(SPI方式)

一、简介 ADXL345 是一款 ADI 公司推出的基于 iMEMS 技术的超低功耗3轴加速度计&#xff0c;分辨率高(13位)&#xff0c;测量范围达 16g。数字输出数据为 16 位二进制补码格式&#xff0c;可通过 SPI(3线或4线) 或 I2C 数字接口访问。ADXL345 非常适合移动设备应用。它可以在倾…

小研究 - MySQL 分区技术在海量系统日志中的应用

随着信息技术的飞速发展&#xff0c;系统的业务功能不断扩大&#xff0c;产生的日志与日俱增&#xff0c;导致应用软件的运行速度越来越慢&#xff0c;不能很好地满足用户对软件性能的需求。基于此&#xff0c;重点研究了 MySQL 分区技术在大数据量软件日志中的应用&#xff0c…

NamedParameterJdbcTemplate类的作用和使用

NamedParameterJdbcTemplate是Spring框架提供的一个类&#xff0c;用于简化在JDBC操作中使用命名参数的过程。它是对JdbcTemplate的扩展&#xff0c;通过使用命名参数而不是传统的问号占位符&#xff0c;使SQL语句更易读和维护。 NamedParameterJdbcTemplate的作用是&#xff…

cn.hutool.core.date.DateUtil.beginOfDay 方法的作用和使用

cn.hutool.core.date.DateUtil.beginOfDay 方法的作用是返回指定日期的开始时间&#xff0c;即将时、分、秒和毫秒部分设置为 0。 使用 beginOfDay 方法时&#xff0c;可以按照以下步骤进行操作&#xff1a; 导入 cn.hutool.core.date.DateUtil 类。创建一个 java.util.Date …

PPS Tester测量原理和实施方法

怿星科技发布了新品PPS Tester&#xff0c;这是一款基于1PPS方法的时间同步精度测试设备。PPS Tester由硬件模块ETS2110和上位机软件ePPSTester构成。本文将围绕此设备的应用场景&#xff0c;介绍相关概念和设备使用方法。 什么是时间同步&#xff1f; 时间同步就是采取某项技…

涂鸦智能获Matter Non-VID Scoped PAA资质 助力开发者拥抱Matter生态

今年5月&#xff0c;全球化IoT开发者平台涂鸦智能&#xff08;NYSE: TUYA&#xff0c;HKEX: 2391&#xff09;正式生成Tuya Matter PAA密钥根&#xff0c;并于7月&#xff0c;成功通过了连接标准联盟和第三方MA机构审查而上线。自此&#xff0c;涂鸦正式成为全球同时提供支持Ma…

考研算法38天:反序输出 【字符串的翻转】

题目 题目收获 很简单的一道题&#xff0c;但是还是有收获的&#xff0c;我发现我连scanf的字符串输入都忘记咋用了。。。。。我一开始写的 #include <iostream> #include <cstring> using namespace std;void deserve(string &str){int n str.size();int…

STM32 HAL 驱动PM2.5传感器(GP2Y10AU气体检测模块)

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方式对G…

Java实现Google cloud storage 文件上传,Google oss

storage 控制台位置 创建一个bucket 点进bucket里面&#xff0c;权限配置里&#xff0c;公开访问&#xff0c;在互联网上公开&#xff0c;需要配置角色权限 新增一个访问权限 &#xff0c;账号这里可以模糊搜索&#xff0c; 角色配置 给allUser配置俩角色就可以出现 在互联…

文水爱心帮扶开展——情系困境 高温天里“送关爱”

为了困境儿童有个健康、快乐、平等、和谐的成长环境&#xff0c;在社会组织党委的领导下&#xff0c;文水县妇联、县爱心帮扶志愿者协会及中志协应急委文水服务队联合开展新时代文明实践活动--为困境儿童送关爱。 8月6日上午九点&#xff0c;在文水火车站广场&#xff0c;文水县…

解决Vue+Element UI使用el-dropdown(下拉菜单)国际化时菜单label信息没有刷新的情况

说明&#xff1a;该篇博客是博主一字一码编写的&#xff0c;实属不易&#xff0c;请尊重原创&#xff0c;谢谢大家&#xff01; 问题描述 在默认中文时&#xff0c;点击布局大小下拉菜单正常显示中文&#xff0c;此时切换至英文时&#xff0c;再次点击下拉菜单&#xff0c;还…

Linux安装mysql报错

用rpm安装mysql时报错如下&#xff1a; 解决&#xff1a; yum install -y libc.so.6 yum install -y libaio.so.1再次安装即可&#xff1a;

java,python,c++有什么区别,python java c c++区别

大家好&#xff0c;给大家分享一下java,python,c有什么区别&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 从这四种语言的难度、受欢迎度还有作用以及优点缺点给楼主做一个全面的分析&#xff0c;我们可以从中了解其区别&#xff0c;以及…

如何查询多级菜单(采用递归的方法)

应用场景 1.京东 京东的页面就是这么显示的在家用电器下面有电视.空调.洗衣机然后再电视下面又有全面屏电视.教育电视等等 2.我们的后端管理系统 我们后端在页面上显示的很多也是通过层级目录的显示出来。 如何实现 1.准备数据库 我们这里parent_id为0的为我们的一级菜单 …