使用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.mzph.cn/news/25471.shtml

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

相关文章

Linux远程连接mysql 出错plugin caching_sha2_password could not be loaded:

问题描述&#xff1a; 今天使用SQLyog远程连接mysql时出错plugin caching_sha2_password could not be loaded问题。 但在本地cmd 进入命令行窗口&#xff1a;输入命令连接远程连接mysql&#xff0c;发现可以顺利连接。 主要问题是 MySQL可视化工具&#xff08;如&#xff1a…

FL Studio Producer Edition 21 v21.0.3 Build 3517 Windows/mac官方中文版

FL Studio Producer Edition 21 v21.0.3 Build 3517 Windows FL Studio Producer Edition 21 v21.0.3 Build 3517 Windows/mac官方中文版是一个完整的软件音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。它代表了 25 多年的创新发展&#xff0c;将您创作、编曲、录…

python 合并多个excel文件

使用 openpyxl 思路&#xff1a; 读取n个excel的文件&#xff0c;存储在一个二维数组中&#xff0c;注意需要转置。将二维数组的数据写入excel。 安装软件&#xff1a; pip install openpyxl源代码&#xff1a; import os import openpyxl # 将n个excel文件数据合并到一个…

Android SystemServer中Service的创建和启动方式(基于Android13)

Android SystemServer创建和启动方式(基于Android13) SystemServer 简介 Android System Server是Android框架的核心组件&#xff0c;运行在system_server进程中&#xff0c;拥有system权限。它在Android系统中扮演重要角色&#xff0c;提供服务管理和通信。 system …

一个.NET开发的Web版Redis管理工具

今天给大家推荐一款web 版的Redis可视化工具WebRedisManager&#xff0c;即可以作为单机的web 版的Redis可视化工具来使用&#xff0c;也可以挂在服务器上多人管理使用的web 版的Redis可视化工具。 WebRedisManager基于SAEA.Socket通信框架中的SAEA.RedisSocket、SAEA.WebApi两…

【100天精通python】Day23:正则表达式,基本语法与re模块详解示例

目录 专栏导读 1 正则表达式概述 2 正则表达式语法 2.1 正则表达式语法元素 2.2 正则表达式的分组操作 3 re 模块详解与示例 4 正则表达式修饰符 专栏导读 专栏订阅地址&#xff1a;https://blog.csdn.net/qq_35831906/category_12375510.html 1 正则表达式概述 python 的…

Prometheus + Grafana安装

Prometheus是一款基于时序数据库的开源监控告警系统&#xff0c;非常适合Kubernetes集群的监控。Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态&#xff0c;任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做…

并查集维护额外信息,算法思路类似前缀和,结构类似扑克接龙

一、链接 240. 食物链 二、题目 动物王国中有三类动物 A,B,CA,B,C&#xff0c;这三类动物的食物链构成了有趣的环形。 AA 吃 BB&#xff0c;BB 吃 CC&#xff0c;CC 吃 AA。 现有 NN 个动物&#xff0c;以 1∼N1∼N 编号。 每个动物都是 A,B,CA,B,C 中的一种&#xff0c;…

总结950

7:00起床 7:30~8:00复习单词300个&#xff0c;记忆100个 8:10~9:30数学660&#xff0c;只做了10道题&#xff0c;发现对各知识点的掌握程度不一。有些熟练&#xff0c;有些生疏 9:33~10:25计算机网络课程1h 10:32~12:02继续660&#xff0c;也不知道做了几道 2:32~4:00数据…

12.物联网操作系统之多任务核心

一。列表及列表项概念以及应用 1.freeRTOS列表介绍 列表项都是由链表生成&#xff0c;想要了解列表项&#xff0c;首先应该把上述的链表都要搞懂。 这是列表项的组件列表。 2.列表及列表项的定义 列表是双向链表构成&#xff0c;原因是双向链表的插入与删除效率高&#xff0c…

【Spring】使用注解的方式获取Bean对象(对象装配)

目录 一、了解对象装配 1、属性注入 1.1、属性注入的优缺点分析 2、setter注入 2.1、setter注入的优缺点分析 3、构造方法注入 3.1、构造方法注入的优缺点 二、Resource注解 三、综合练习 上一个博客中&#xff0c;我们了解了使用注解快速的将对象存储到Spring中&#x…

Android:自己写一个简单记事本

一、前言&#xff1a;我的app是点击加号跳转到另一个界面 那么我遇到的问题的是点击加号是一个从一个Fragment跳转到另一个Fragment跳转失败。 二、解决方案&#xff1a; //相应控件的监听里面实现跳转FragmentManager fragmentManagergetFragmentManager();fragmentManager.b…

网络可靠性之链路聚合

网络的可靠性 网络的可靠性指当设备或者链路出现单点或者多点故障时保证网络服务不间断的能力网络的可靠性是可以从单板、设备、链路多个层面实现。 链路聚合 以太网链路聚合&#xff1a; 通过将多个物理接口捆绑成为一个逻辑接口&#xff0c;可以再不进行硬件升级的条件下&a…

css滚动条样式指南

css滚动条样式指南 滚动条是网页设计中经常被忽视的元素。虽然它看起来像是一个小细节&#xff0c;但它在网站导航中起着至关重要的作用。默认的滚动条可能看起来不合适&#xff0c;有损整体美观。本文将介绍如何使用 CSS 自定义滚动条。 在 Chrome、Edge 和 Safari 中设置滚…

基于Azure OpenAI Service 的知识库搭建实验⼿册

1.概要 介绍如何使⽤Azure OpenAI Service 的嵌⼊技术&#xff0c;创建知识库&#xff1b;以及创建必要的资源组和资源&#xff0c;包括 Form Recognizer 资源和 Azure 翻译器资源。在创建问答机器⼈服务时&#xff0c;需要使⽤已部署模型的 Azure OpenAI 资源、已存在的…

SAP-MM-发票校验的重复校验功能

路径&#xff1a;SPRO-物料管理-后勤发票校验-收入发票-设置重复发票检查 按公司代码设置重复检查&#xff0c;可以按三个方式进行检查&#xff0c;公司代码、参照、发票日期&#xff0c;如果此处未维护就是按供应商&#xff08;XK02&#xff09;的六项进行检查 但是如果两处都…

深入学习 Redis - 事务、实现原理、指令使用及场景

目录 一、Redis 事务 vs MySQL事务 二、Redis 事务的执行原理 2.1、执行原理 2.2、Redis 事务设计这么简单&#xff0c;为什么不涉及成 MySQL 那样强大呢&#xff1f; 三、Redis 事务的使用 3.1、使用场景 3.2、具体演示 开启/执行/放弃事务 watch 监控 watch 实现原理…

chapter14:springboot与安全

Spring Boot与安全视频 Spring Security, shiro等安全框架。主要功能是”认证“和”授权“&#xff0c;或者说是访问控制。 认证&#xff08;Authentication&#xff09;是建立在一个声明主体的过程&#xff08;一个主体一般指用户&#xff0c;设备或一些可以在你的应用程序中…

Django之JWT库与SimpleJWT库的使用

Django之JWT库与SimpleJWT库的使用 JWTJWT概述头部(header)载荷(payload)签名(signature) Django使用JWT说明jwt库的使用安装依赖库配置settings.py文件配置urls.py文件创建视图配置权限 SimpleJWT库的使用安装SimpleJWT库配置Django项目配置路由创建用户接口测试身份认证自定义…

【雕爷学编程】Arduino动手做(190)---MAX4466声音模块

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…