使用XMLHttpRequest实现文件异步下载

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 {@Autowiredprivate 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.rhkb.cn/news/84283.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud Gateway过滤器GlobalFilter详解

一、过滤器的场景 在springCloud架构中&#xff0c;网关是必不可少的组件&#xff0c;它用于服务路由的转发。对客户端进行屏蔽微服务的具体细节&#xff0c;客户端只需要和网关进行交互。所以网关顾名思义&#xff0c;就是网络的一个关卡。它就是一座城的城门守卫。所以这个守…

Spring(11) Bean的生命周期

目录 一、简介二、Bean的流程1.BeanDefinition2.Bean 的生命周期 三、代码验证1.User 实体类2.MyBeanPostProcessor 后置处理器3.SpringConfig 扫描包配置4.UserTest 测试类5.测试结果6.模拟AOP增强 三、总结 一、简介 首先&#xff0c;为什么要学习 Spring 中 Bean 的生命周期…

腾讯云轻量应用服务器端口打开方法

腾讯云轻量应用服务器端口放行在哪设置&#xff1f;在防火墙中可以开启端口号&#xff0c;腾讯云轻量应用服务器端口怎么开通&#xff1f;在轻量服务器管理控制台的防火墙中开启端口&#xff0c;如果是CVM云服务器在安全组中开通&#xff0c;腾讯云服务器网以轻量应用服务器开通…

网络安全进阶学习第十二课——SQL手工注入3(Access数据库)

文章目录 注入流程&#xff1a;1、判断数据库类型2、判断表名3、判断列名4、判断列数1&#xff09;判断显示位 5、判断数据长度6、爆破数据内容 注入流程&#xff1a; 判断数据库类型 ——> 判断表名 ——> 判断列名 ——> 判断列名长度 ——> 查出数据。 asp的网…

Python:Spider爬虫工程化入门到进阶(1)创建Scrapy爬虫项目

Python&#xff1a;Spider爬虫工程化入门到进阶系列: Python&#xff1a;Spider爬虫工程化入门到进阶&#xff08;1&#xff09;创建Scrapy爬虫项目Python&#xff1a;Spider爬虫工程化入门到进阶&#xff08;2&#xff09;使用Spider Admin Pro管理scrapy爬虫项目 本文通过简…

MES系统在机器人行业生产管理种的运用

机器人的智能水平也伴随技术的迭代不断攀升。 2021年的春晚舞台上&#xff0c;来自全球领先工业机器人企业abb的全球首款双臂协作机器人yumi&#xff0c;轻松自如地表演了一出写“福”字&#xff0c;赢得了全国观众的赞叹。 在汽车装配领域&#xff0c;一台机器人可以自主完成一…

ElasticSearch:项目实战(1)

es环境搭建参考&#xff1a;ElasticSearch&#xff1a;环境搭建步骤_Success___的博客-CSDN博客 需求&#xff1a; 用户输入关键可搜索文章列表 关键词高亮显示 文章列表展示与home展示一样&#xff0c;当用户点击某一篇文章&#xff0c;可查看文章详情 思路&#xff1a; …

《Java-SE-第三十三章》之函数式编程

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

webpack复习

webpack webpack复习 webpack基本配置 拆分配置 - 公共配置 生产环境配置 开发环境配置 使用merge webpack-dev-server 启动本地服务 在公共中引入babel-loader处理es6 webpack高级配置 多入口文件 enty 入口为一个对象 里面的key为入口名 value为入口文件路径 例如 pa…

xxx酒业有限责任公司突发环境事件应急预案WORD

导读&#xff1a;原文《xxx酒业有限责任公司突发环境事件应急预案word》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 目 录 1 总则 1.1 编制目的 1.2 编制…

【Git】 git push origin master Everything up-to-date报错

hello&#xff0c;我是索奇&#xff0c;可以叫我小奇 git push 出错&#xff1f;显示 Everything up-to-date 那么看看你是否提交了message 下面是提交的简单流程 git add . git commit -m "message" git push origin master 大多数伙伴是没写git commit -m "…

在elementUI的表格(table)内嵌入svg图标

参考文档&#xff1a; https://element.eleme.cn/#/zh-CN/component/table demo效果图如下&#xff08;在表格的类型列中添加一个对应类型的svg图标&#xff09;&#xff1a; 本文主要关注以下两点&#xff1a; elementUI的表格&#xff08;table&#xff09;的自定义列模…

用python做一个小游戏代码,用python制作一个小游戏

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;如何用python编写一个简单的小游戏&#xff0c;用python做一个小游戏代码&#xff0c;今天让我们一起来看看吧&#xff01; 今天呢&#xff0c;给大家展示一下Python有趣的小地方&#xff0c;展示给大家看看&#xff0c…

QMainwindow窗口

QMainwindow窗口 菜单栏在二级菜单中输入中文的方法给菜单栏添加相应的动作使用QMenu类的API方法添加菜单项分隔符也是QAction类 工具栏状态栏停靠窗口 菜单栏 只能有一个, 位于窗口的最上方 关于顶级菜单可以直接在UI窗口中双击, 直接输入文本信息即可, 对应子菜单项也可以通…

【C++学习手札】new和delete看这一篇就够了!

​ 食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f340;本文前置知识&#xff1a; C类 ♈️今日夜电波&#xff1a; Prover—milet 1:21 ━━━━━━️&#x1f49f;──────── 4:01 …

安卓如何卸载应用

卸载系统应用 首先需要打开手机的开发者选项&#xff0c;启动usb调试。 第二步需要在电脑上安装adb命令&#xff0c;喜欢的话还可以将它加入系统path。如果不知道怎么安装&#xff0c;可以从这里下载免安装版本。 第三步将手机与电脑用数据线连接&#xff0c;注意是数据线&a…

Linux mysql5.7开启 binlog

查看 mysql是否开启 binlog。 查看命令&#xff1a; show variables like %log_bin%; log_bin OFF 是关闭的状态。 编辑my.cnf配置文件 vim /etc/my.cnf 默认的配置文件内容&#xff1a; 增加下面内容 server_id 1 binlog_format ROW log-bin mysql_log_bin 重启mysq…

C++友元函数和友元类的使用

1.友元介绍 在C中&#xff0c;友元&#xff08;friend&#xff09;是一种机制&#xff0c;允许某个类或函数访问其他类的私有成员。通过友元&#xff0c;可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用&#xff0c;例如在类之间共享数据或…

pdf怎么删除不要的页面?这几种删除方法了解一下

pdf怎么删除不要的页面&#xff1f;在处理pdf文档时&#xff0c;我们经常会遇到需要删除某些页面的情况。一些多余或无关的页面可能会对文档的整体结构造成混乱&#xff0c;甚至会影响文档的可读性。此外&#xff0c;删除多余页面还可以减小文件大小&#xff0c;方便存储和传输…

每天五分钟机器学习:梯度下降算法和正规方程的比较

本文重点 梯度下降算法和正规方程是两种常用的机器学习算法,用于求解线性回归问题。它们各自有一些优点和缺点,下面将分别对它们进行详细的讨论。 区别 1. 梯度下降算法是一种迭代的优化算法,通过不断迭代调整参数来逼近最优解。它的基本思想是根据目标函数的梯度方向,沿…