使用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
相关文章:

使用XMLHttpRequest实现文件异步下载
1、问题描述 我想通过异步的方式实现下载文化,请求为post请求。一开始我打算用ajax。 $.ajax({type:post,contentType:application/json,url:http://xxx/downloadExcel,data:{data:JSON.stringify(<%oJsonResponse.JSONoutput()%>)},}).success(function(dat…...

Lombok 的安装与使用
文章目录 一、什么是 Lombok1.1 Lombok 的概念1.2 为什么使用 Lombok1.3 Lombok 的相关注解 二、Lombok 的安装2.1 引入依赖2.2 安装插件 三、Lombok 的使用案例四、Lombok 的原理 一、什么是 Lombok 1.1 Lombok 的概念 Lombok(“Project Lombok”)是一…...

springBean生命周期解析
本文基于Spring5.3.7 参考: kykangyuky Spring中bean的生命周期 阿斌Java之路 SpringBean的生命周期, 杨开振 JavaEE互联网轻量级框架整合开发 黑马程序员 JavaEE企业级应用开发教程 马士兵 Spring源码讲解 一. SpringBean生命周期流程图 二. 示例代码 …...

人工智能轨道交通行业周刊-第54期(2023.7.31-8.6)
本期关键词:BIM智能运维、铁水联运、编组站美容、鸿蒙4.0、LK-99完全悬浮 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…...

Docker Compose 使用方法
目录 前言 安装 Docker Compose Ubuntu 安装与更新 Red Hat 安装与更新 验证是否安装 Docker Compose 创建 docker-compose.yml 文件 创建一个MySQL 与 tomcat 示例 使用Docker Compose启动服务 前言 Docker Compose 是一个工具,旨在帮助定义和 共享多容器…...

HTML 初
前言 HTML的基本骨架 HTML基本骨架是构建网页的最基本的结果。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…...

IPv6地址分类,EUI-64转换规则
1、可聚合的单全球单播地址Global Unique Address: Aggregate global unicast address,前3位是001,即2000::/3,目前IANA已经将一部分可聚合全球单播进行了专门使用,如:2001::/16用于IPV6互联网,…...

Nginx安装部署
什么是Nginx? Nginx(发音同engine x)是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代 理服务器以及电子邮件(IMAP/POP3)代理服务器。 Nginx 因具有高并发(特別是静态资源)、 占用系统资…...

物联网|按键实验---学习I/O的输入及中断的编程|读取I/O的输入信号|中断的编程方法|轮询实现按键捕获实验-学习笔记(13)
文章目录 实验目的了解擒键的工作原理及电原理图 STM32F407中如何读取I/O的输入信号STM32F407对中断的编程方法通过轮询实现按键捕获实验如何利用已有内工程创建新工程通过轮询实现按键捕获代码实现及分析1 代码的流程分析2 代码的实现 Tips:下载错误的解决 实验目的 了解擒键…...

Hadoop-HDFS的Namenode及Datanode(参考Hadoop官网)
HDFS有什么特点,被设计做什么 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。有一下几个特点: HDFS是一个高度容错性的系统,具有高容错、高可靠性、高扩展性的特点,适合部…...
C:通过alarm发送信号
可以通过alarm定时发送SIGALRM信号: #include <unistd.h> unsigned int alarm(unsigned int seconds); alarm()函数用来在seconds秒之后安排发送一个SIGALRM信号,如果seconds为0,将取消所有已设置的闹钟请求。alarm()函数的返回值是以前…...

如何将 dubbo filter 拦截器原理运用到日志拦截器中?
业务背景 我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。 实现的方式有很多种,我们分别来看一下。 拓展阅读 java 注解结合 spring aop 实现自动输出日志 java 注解结合 spring aop 实现日志traceId唯一标识 java 注解结合 spring ao…...

【java】【maven】【基础】MAVEN安装配置介绍
目录 1 下载 2 安装-windows为例 3 配置环境变量 3.1 JAVA_HOME 3.2 MAVEN_HOME 3.3 PATH 3.4 验证 4 MAVEN基础概念 4.1 仓库概念 4.2 坐标概念 4.2.1 打开网址 4.2.2 输入搜索内容junit 4.2.3 找到对应API名称点击 4.2.4 点击对应版本 4.2.5 复制MAVEN坐标 4.3 配置…...

【C语言进阶】指针的高级应用(下)
文章目录 一、指针数组与数组指针1.1 指针数组与数组指针的表达式 二、函数指针2.1 函数指针的书写方式 三、二重指针与一重指针3.1 二重指针的本质3.2 二重指针的用法3.3 二重指针与数组指针 总结 一、指针数组与数组指针 (1)指针数组的实质是一个数组,这个数组中存…...
【uniapp APP隐藏顶部的电量,无线,时间状态栏与导航栏】
uniapp APP隐藏顶部的电量,无线,时间状态栏 如下代码配置(在一个页面设置这个段代码,所有页面都会消失) onShow() {// #ifdef APP-PLUS// 隐藏顶部电池,时间等信息 plus.navigator.setFullscreen(true);//隐藏虚拟按…...
微信小程序前后页面传值
微信小程序前后页面传值 从前一个页面跳转到下一个页面,如何传递参数?从后一个页面返回前一个页面,如何回调参数? 向后传值 从前一个页面跳转到下一个页面并传值。 前页面:在跳转链接中添加参数并传递 wx.navigat…...
没有jodatime,rust里怎么比较两个日期(时间)的大小呢?
关注我,学习Rust不迷路!! 在 Rust 中,比较两个日期的大小有多种方法。以下是列举的四种常见方法: 1. 使用 PartialOrd trait: use chrono::prelude::*;fn main() {let date1 NaiveDate::from_ymd(2022,…...

【雕爷学编程】Arduino动手做(186)---WeMos ESP32开发板18
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&#x…...
C语言假期作业 DAY 14
一、选择题 1、有以下函数,该函数的功能是( ) int fun(char *s) {char *t s;while(*t);return(t-s); } A: 比较两个字符的大小 B: 计算s所指字符串占用内存字节的个数 C: 计算s所指字符串的长度 D: 将s所指字符串复制到字符串t中 答案解析 …...

Maven-生命周期及命令
关于本文 ✍写作原因 之前在学校学习的时候,编写代码使用的项目都是单体架构,导入开源框架依赖时只需要在pom.xml里面添加依赖,点一下reload按钮即可解决大部分需求;但是在公司使用了dubbo微服务架构之后发现只知道使用reload不足…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...

UE5 音效系统
一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类,将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix,将上述三个类翻入其中,通过它管理每个音乐…...
标注工具核心架构分析——主窗口的图像显示
🏗️ 标注工具核心架构分析 📋 系统概述 主要有两个核心类,采用经典的 Scene-View 架构模式: 🎯 核心类结构 1. AnnotationScene (QGraphicsScene子类) 主要负责标注场景的管理和交互 🔧 关键函数&…...