JavaScript下载文件(简单模式、跨域问题、文件压缩)

embedded/2024/10/18 7:51:02/

文章目录

  • 简介
  • 简单文件下载
    • 通过模拟form表单提交
    • 通过XMLHttpRequest方式
  • 跨域(oss)下载并压缩文件

简介

相信各位开发朋友都遇到过下载的文件的需求,有的非常简单,基本链接的形式就可以。

有的就比较复杂,涉及跨域和压缩文件,例如,文件在OSS中,有的oss不支持压缩文件,要下10个文件就得弹10个下载出来。

业务老师多半是没有办法接受这种情况,怎么处理呢?

这就涉及到跨域获取文件并压缩文件了。

本文会介绍一下简单下载和下载OSS文件并压缩。

简单文件下载

首先我们看一些2种简单下载方式

通过模拟form表单提交

function downloadRemoteFile(url,materialId) {var body = document.getElementsByTagName('body')[0];var form = document.createElement('form');form.method = 'POST';form.action = url;var param = document.createElement('input');param.type = "hidden";param.name = "materialId";param.value = materialId;form.appendChild(param);body.appendChild(form);form.submit();body.removeChild(form);
}

上面这种方式:

  1. 优点是简单
  2. 缺点是错误不友好,出错了,没有提示信息

如果希望错误信息友好一点,可以通过XMLHttpRequest方式。

通过XMLHttpRequest方式

function downloadRemoteFileXMLHttpRequest(url,materialId) {console.log("${downloadUrl}" + " " + materialId);var xhr = new XMLHttpRequest();xhr.open("POST", url, true);xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")xhr.onload = function () {if (xhr.status === 200) {if (xhr.response == null || xhr.response == "" || xhr.response == undefined) {alert("下载文件出错,请检查文件是否存在:" + materialId);return;}// 获取判断Content-Type// var contentType = xhr.getResponseHeader("Content-Type");var blob = new Blob([xhr.response], { type: "application/octet-stream" });var fileName = getFileNameFromResponseHeader(xhr);var link = document.createElement("a");link.href = window.URL.createObjectURL(blob);link.download = fileName;link.click();link.remove();window.URL.revokeObjectURL(link.href);} else {alert("下载响应异常");console.log(xhr);}};xhr.send("materialId=" + materialId);
}function getFileNameFromResponseHeader(xhr) {var contentDisposition = xhr.getResponseHeader("content-disposition")var matchResult = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);if (matchResult != null && matchResult[1]) {return decodeURIComponent(matchResult[1].replace(/['"]/g, ""));}return "default-name";
}

通过XMLHttpRequest方式就灵活很多,虽然还是模拟了a链接,但是能先处理响应。

例如,下载后端可以能有一些预检查,如果预检查都没有通过,那么可能返回的就不是Blob文件,而是一个json。

通过XMLHttpRequest方式就可以通过判断ContentType内容,来获取文件流、或是json的内容,从而把错误信息比较友好的展示给用户。

对于后端下载接口,可能更好的方式是把信息写到header中,这样对于前端来说就能更好统一处理逻辑。

response.addHeader("success", "false");
response.addHeader("message", URLEncoder.encode("该数据您无权下载", StandardCharsets.UTF_8));

后端不同处理方式(仅仅演示,实际操作最后统一逻辑):

@RequestMapping("/download")
public void download(HttpServletResponse response, @RequestParam("fid") Long fid) throws IOException {if (fid < 0) {response.setContentType("application/json");PrintWriter writer = response.getWriter();Result<Void> result = ResultHelper.getFailResult("文件不存在");writer.write(JSON.toJSONString(result));return;}if (fid < 100) {response.addHeader("success", "false");String message = "该数据您无权下载";response.addHeader("message", URLEncoder.encode(message, StandardCharsets.UTF_8));return;}try (FileInputStream fis = new FileInputStream("F:\\tmp\\季报统计表.xlsx");OutputStream os = response.getOutputStream()) {String fileName = URLEncoder.encode("季报统计表.xlsx",StandardCharsets.UTF_8);response.reset();response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=" + fileName);response.addHeader("success", "true");IOUtils.copy(fis, os);}
}

跨域(oss)下载并压缩文件

下载并压缩文件主要有2个不好处理的问题:

  1. 跨域
  2. 文件流压缩

解决方案:

  1. 跨域问题主要是浏览器限制,可以通过后端添加header Access-Control-Allow-Origin或者直接设置浏览器处理
  2. 文件压缩可以使用jszip

完整示例

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>跨域下载</title></head><body><button onclick="download()">下载</button></body>
</html>
<script type="text/javascript" src="js/jszip.js"></script>
<script>function fetchFile(url, zip) {window.fetch(url, {method: "GET",// mode: 'no-cors',mode: 'cors' // 允许跨越}).then(response => {// no-cors 这里不执行return response.blob();}).catch(error => {console.log(error);});}// 下载文件function downloadFile(url, fileName) {const a = document.createElement('a')a.style.display = 'none'a.href = urla.download = fileNamedocument.body.appendChild(a)a.click()document.body.removeChild(a)}function download() {const zip = new JSZip();// 从远程服务器获取文件const urls = ['http://127.0.0.1:8087/file/download2'];let map = new Map();let promises = [];var index = 0;urls.forEach(function(value, index, array) {// cors 表示可以跨越,服务端必须设置Access-Control-Allow-Origin//no-cors fetch不会执行then,拿不到文件var result = fetch(value, {mode: 'cors'}).then(response => response.blob());promises.push(result);map.set(index++, value);});Promise.all(promises).then(results => {var flag = true;if (results.every(result => result !== undefined)) {flag = true;} else {flag = flase;}if(flag){index = 0;results.forEach(blob => {// promise 结果是按顺序返回的var url = map.get(index++);var filename = getFileName(url);zip.file(filename, blob, {binary: true});});zip.generateAsync({type: "blob"}).then(function(content) {const url = window.URL.createObjectURL(content);downloadFile(url, "result.zip");});}else{alert("有文件下载失败请重试!");}}).catch(error => console.error('Error:', error));}function getFileName(url) {var url = url.substring(url.lastIndexOf("/") + 1);var idx = url.lastIndexOf("?");if (idx > 0) {url = url.substring(0, idx);}return decodeURI(url);}
</script>

文件压缩

首先下载jszip的最新版本,注意使用新版本(3.10.x)和老版本的api差别较大。

jszip下载

function zip() {const zip = new JSZip();// 添加一个文本文件zip.file("Hello.txt", "Hello World\n");// 添加一个json文件let jsonData = {};const blob = new Blob([JSON.stringify(jsonData, null, 4)], {type: 'application/json'});zip.file('notes.json', blob);// 添加文件夹const folder = zip.folder("subdir");// 在文本文件中添加一个文本文件folder.file("Hi.txt", "Hi\n");// 除了blob类型,还可以设置为base64zip.generateAsync({type: "blob"}).then(function(content) {const url = window.URL.createObjectURL(content);downloadFile(url, "result.zip");});
}// 下载文件
function downloadFile(url, fileName) {const a = document.createElement('a')a.style.display = 'none'a.href = urla.download = fileNamedocument.body.appendChild(a)a.click()document.body.removeChild(a)
}

跨域设置

如果你看到一个类似于下面的错误,那是因为浏览器限制,不允许跨域。

跨域错误

Access to fetch at ‘http://127.0.0.1:8087/file/download2’ from origin ‘http://127.0.0.1:8848’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

解决这个问题的方法有2个:

  1. 服务端返回response的header Access-Control-Allow-Origin值类似于*
  2. 设置浏览器

设置服务器的方式很多,以springboot为例:

可以通过设置Filter:

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;import java.io.IOException;@Component
public class CorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletResponse res = (HttpServletResponse) response;res.setHeader("Access-Control-Allow-Origin", "*");res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
//        res.setHeader("Access-Control-Allow-Headers", "Authorization");res.setHeader("Access-Control-Allow-Headers", "*");chain.doFilter(request, response);}
}

可以设置WebMvcConfigurer:

package vip.meet.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE").allowCredentials(true).allowedHeaders("*");}
}

可以直接在单个请求中设置:response.setHeader(“Access-Control-Allow-Origin”, “*”);

OSS中设置:

oss<a class=跨域设置" />

设置这些的目的只有一个就是然response中有Access-Control-Allow-Origin

response Access-Control-Allow-Origin

除了设置服务端,还可以设置浏览器,以Chrome为例:

浏览器<a class=跨域设置" />

设置浏览器的启动参数:

–disable-web-security --user-data-dir=D:\ChromeDevData


http://www.ppmy.cn/embedded/127013.html

相关文章

grpc的python使用

RPC 什么是 RPC &#xff1f; RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;是一种计算机通信协议&#xff0c;允许一个程序&#xff08;客户端&#xff09;通过网络向另一个程序&#xff08;服务器&#xff09;请求服务&#xff0c;而无需了解…

【web安全】——常见框架漏洞

1.ThinkPHP框架漏洞 thinkphp是一个国内轻量级的开发框架&#xff0c;采用phpapache&#xff0c;在更新迭代中&#xff0c;thinkphp也经常爆出各种漏洞&#xff0c;thinkphp一般有thinkphp2、thinkphp3、thinkphp5、thinkphp6版本&#xff0c;前两个版本已经停止更新&#xff…

缓存常用的三种读写策略

在现代软件系统中&#xff0c;缓存的使用至关重要&#xff0c;它可以极大地提高系统的性能和响应速度。本文将详细介绍缓存常用的三种读写策略&#xff1a;Cache Aside Pattern&#xff08;旁路缓存模式&#xff09;、Read/Write Through Pattern&#xff08;读写穿透&#xff…

从零开始搭建一个node.js后端服务项目

目录 一、下载node.js及配置环境 二、搭建node.js项目及安装express框架 三、集成nodemon&#xff0c;实现代码热部署 四、Express 应用程序生成器 一、下载node.js及配置环境 网上很多安装教程&#xff0c;此处就不再赘述了 版本信息 C:\Users\XXX>node -v v20.15.0…

为什么人工智能用 Python?

人工智能领域倾向于使用Python&#xff0c;主要归因于Python的多个显著优势&#xff1a; 简洁性与可读性&#xff1a;Python的语法设计简洁明了&#xff0c;代码易于阅读和理解&#xff0c;这对于涉及复杂算法和逻辑的人工智能项目尤为重要。它降低了编程门槛&#xff0c;使得…

中级课程RHCE

RHCE 一、复习RHCSA1.1 系统安装1.1.1 安装虚拟机1.1.2 第一个快照1.1.3 第二个快照vi编辑器1.1.4 看网关网卡 1.2 文件管理1.3 目录管理1.4 用户管理1.5 权限管理1.6 存储管理1.6.1 标准分区管理实验&#xff1a;1.6.2 逻辑卷管理实验&#xff1a;1.6.3 交换空间管理实验 …

Vue前端框架的基础配置

登录界面添加背景图 通过在登录界面的vue文件中&#xff0c;设置div标签的background-image属性&#xff0c;加载背景图 <style scoped> .myvue{width:100%;height: 750px; //添加背景图的地址background-image: url(../assets/oa.jpeg);background-size: cover;backgr…

docker详解介绍+基础操作 (四)容器镜像

一.镜像结构和原理 Docker 镜像是 Docker 技术的核心组成部分之一&#xff0c;它用于封装应用程序及其依赖项&#xff0c;以便在任何支持 Docker 的环境中运行。了解 Docker 镜像的结构和原理对于有效使用 Docker 至关重要。以下是对 Docker 镜像结构和原理的详细介绍。 Dock…