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

news2024/11/24 13:32:42

文章目录

  • 简介
  • 简单文件下载
    • 通过模拟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 = url
		a.download = fileName
		document.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类型,还可以设置为base64
    zip.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 = url
    a.download = fileName
    document.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 {
    @Override
    public 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 {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .allowedHeaders("*");
    }
}

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

OSS中设置:

oss跨域设置

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

response Access-Control-Allow-Origin

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

浏览器跨域设置

设置浏览器的启动参数:

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

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

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

相关文章

简易登录注册;测试类;postman测试;

项目是如何创建的&#xff0c;最简易的登陆注册功能是怎么实现的&#xff0c;数据库不能明文存放密码&#xff0c;密码经过了怎么样的处理存入数据库 前端使用nodejs18 后端项目需要等待maven加载完相关依赖&#xff0c;后端使用java17 1后端 1.1 创建项目所需要的数据库 内…

安宝特方案 | AR技术在轨交行业的应用优势

随着轨道交通行业不断向智能化和数字化转型&#xff0c;传统巡检方式的局限性日益凸显。而安宝特AR眼镜以其独特的佩戴方式和轻便设计&#xff0c;为轨道交通巡检领域注入了创新活力&#xff0c;提供了全新的解决方案。 01 多样化佩戴方法&#xff0c;完美适应户外环境 安宝特…

骨传导耳机哪个牌子最好?五大爆品骨传导耳机全维度拆解推荐!

随着科技的不断进步&#xff0c;消费者对电子产品的需求也在日益增长&#xff0c;特别是在音频领域&#xff0c;骨传导耳机作为一项革新性的技术&#xff0c;正逐渐成为市场的新宠。不同于传统的空气传导耳机&#xff0c;骨传导耳机通过振动颅骨直接将声音传递至内耳&#xff0…

vivo全新AI战略“蓝心智能”发布 原系统5亮相开发者大会

​10月10日&#xff0c;2024 vivo开发者大会在深圳国际会展中心举办&#xff0c;大会主题为“同心同行”。会上&#xff0c;vivo正式发布全新AI战略——“蓝心智能”&#xff0c;同时带来全面升级的自研蓝心大模型矩阵、原系统5&#xff08;OriginOS 5&#xff09;、蓝河操作系…

开发环境搭建之JAVA多个JDK版本安装

由于项目需要安装多个版本JDK、所以在此记录一下安装过程&#xff1a; 下载JDK1.8 11 17 等多个版本 简单粗暴一看就会、直接从官网下载exe安装包、然后配置环境变量即可 JDK1.8 JDK11 JDK17 安装完成之后如下图&#xff1a; 环境变量配置 右击“我的电脑”属性、找到…

计算机网络实验一:组建对等网络

实验一 组建对等网络 实验要求&#xff1a; 1. 组建对等网络&#xff0c;会在命令行使用ipconfig&#xff0c;两网络能够相互ping通&#xff0c;尝试netstat 命令 2. 建立局域网共享文件夹 3. 安装packet tracer&#xff0c;模拟组建对等网并测试对等网 1、组建对等网络 连…

RandLA-Net PB 模型 测试

tensorflow ckpt 模型 转换 pb 模型, 测试模型是否正确, 后续实现 c++ 部署。 Code: https://github.com/QingyongHu/RandLA-Net 测试PB 模型 RandLANetConvert.py import tensorflow.compat.v1 as tf tf.disable_v2_behavior

第十三章 Redis短信登录实战(基于Redis)

目录 一、概述 1.1. Session复制 1.2. 使用Redis 二、基于Redis实现共享Session登录 2.1. 实现思路 2.2. 功能实现的主要代码 2.2.1. 用户业务接口 2.2.2. 用户业务接口实现类 2.2.3. 用户控制层 2.2.4. 登录拦截器 2.2.5. 拦截器配置类 2.3. 优化登录拦截器 完…

JVM系列(一) -浅谈虚拟机的成长史

一、摘要 众所周知&#xff0c;Java 经过多年的发展&#xff0c;已经从一门单纯的计算机编程语言&#xff0c;发展成了一套成熟的软件解决方案。从互联网到企业平台&#xff0c;Java 是目前使用最广泛的编程语言。 以下这段内容是来自 Java 的官方介绍&#xff01; 从笔记本电…

程序设计基础I-实验7 函数(函数题)

6-1 sdut-C语言实验-计算组合数 计算组合数。C(n,m),表示从n个数中选择m个的组合数。 计算公式如下&#xff1a; 若&#xff1a;m0&#xff0c;C(n,m)1 否则&#xff0c; 若 n1&#xff0c;C(n,m)1 否则&#xff0c;若mn&#xff0c;C(n,m)1 否则 C(n,m) C(n-1,m-1) C(n-1,m…

linux下新增加一块sata硬盘并使用

1&#xff09;确认新硬盘能被正确识别到 2&#xff09;对新硬盘进行分区 说明&#xff1a;fdisk指令中输入“m”&#xff0c;可以看到详细的指令含义。 3&#xff09;确认新创建的分区 5&#xff09;格式化新创建的分区 6&#xff09;挂载新分区并使用

网优学习干货:王者荣耀游戏用户体验洞察及质差识别(2)

王者荣耀卡顿特点 影响时延的因素 手游定界定位解决方案 基于“9段法”进行卡顿问题分解 通过数据关联->体验定标->优化提升&#xff0c;改善手游卡顿 无线侧通过“面”和“点”优化改善空口时延 参数及互操作策略优化提升业务感知 传输优化准确定位管道问题——无TWAM…

kafka快速入门系统学习示例

1.主要名词 Broker&#xff1a;消息中间件处理节点&#xff0c;⼀个Kafka节点就是⼀个broker&#xff0c;⼀个或者多个 Broker可以组成⼀个Kafka集群。 partition&#xff1a;通过partition将⼀个topic中的消息分区来存储。这样的好处有多个&#xff1a;分区存储&#xff0…

ArgoWorkflow教程(六)---无缝实现步骤间参数传递

之前我们分析了&#xff0c;Workflow、WorkflowTemplate 、template 3 者之间如何传递参数。 本文主要分析同一个 Workflow 中的不同 step 之间实现参数传递&#xff0c;比如将上一个步骤的输出作为下一个步骤的结果进行使用&#xff08;而非以文件方式传递&#xff09;。 1. …

引领零售未来:年销售额500亿的运动品牌如何实现多渠道会员管理?

通过技术创新和全渠道整合&#xff0c;提升运动品牌影响力、用户粘性和市场竞争力。 运动热情高涨持续释放消费潜力&#xff0c;全球知名运动品牌2024财年营收超500亿美元&#xff0c;保持七个季度连续增长。 而随着电商的崛起和消费习惯的变化&#xff0c;会员数量激增且分布…

Android实现ViewPager剧中放大效果

效果图 实现方式核心思想是自定义PageTransformer继承ViewPager.PageTransformer&#xff0c;精确控制每一个page的动效。 PageTransformer的transformPage方法并不会区分当前的page是哪一个&#xff0c;所以需要我们自己去识别&#xff0c;我的方法是每个page添加一个text显…

Nuxt.js 应用中的 page:transition:finish 钩子详解

title: Nuxt.js 应用中的 page:transition:finish 钩子详解 date: 2024/10/10 updated: 2024/10/10 author: cmdragon excerpt: page:transition:finish 是 Nuxt.js 中的一个事件钩子,专门用于处理页面过渡效果结束后的逻辑。这一钩子在页面过渡的 onAfterLeave 事件之后…

qt+opengl 实现纹理贴图,平移旋转,绘制三角形,方形

1 首先qt 已经封装了opengl&#xff0c;那么我们就可以直接用了&#xff0c;这里面有三个函数需要继承 virtual void initializeGL() override; virtual void resizeGL(int w,int h) override; virtual void paintGL() override; 这三个函数是实现opengl的重要函数。 2 我们…

【NLP自然语言处理】03 - 使用Anaconda创建新的环境/pycharm切换环境

NLP基础阶段&#xff1a;创建新的虚拟环境 第一步&#xff1a;查看有多少个虚拟环境 conda env list 第二步&#xff1a;创建一个新的虚拟环境&#xff0c;起个名字&#xff1a;nlpbase 打开anconda prompt终端&#xff0c;输入命令: conda create -n nlpbase python3.10 第三步…

基于xml配置文件的Spring事务

在项目中对事务属性通常传播属性&#xff0c;回滚属性&#xff0c;隔离级别&#xff0c;超时属性都取默认值&#xff0c;只有只读属性会如下的配置&#xff1a; 什么意思&#xff1a;Service层你的类里的方法&#xff0c;以get&#xff0c;find&#xff0c;select等开头的方法是…