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

server/2024/10/17 18:40:53/

文章目录

  • 简介
  • 简单文件下载
    • 通过模拟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/server/132552.html

相关文章

【保姆级教程】DolphinScheduler本地部署与远程访问详细步骤解析

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

解决leetcode第3309题连接二进制表示可形成的最大数值

难度&#xff1a;中等 问题描述&#xff1a; 给你一个长度为 3 的整数数组 nums。 现以某种顺序连接数组nums 中所有元素的二进制表示 &#xff0c;请你返回可以由这种方法形成的 最大数值。 注意&#xff1a;任何数字的二进制表示不含前导零。 示例 1: 输入: nums [1,2…

ITSS服务经理认证有什么用?

在当今数字化快速发展的时代&#xff0c;ITSS 服务项目经理扮演着至关重要的角色。 ITSS 服务项目经理&#xff0c;不仅需要具备扎实的技术知识&#xff0c;还得拥有卓越的管理能力和出色的沟通技巧。他们宛如一座桥梁&#xff0c;连接着技术团队与客户需求&#xff0c;确保服…

PostgreSQL学习笔记六:模式SCHEMA

模式&#xff08;Schema&#xff09; PostgreSQL中的模式&#xff08;Schema&#xff09;是一个命名的数据库对象集合&#xff0c;包括表、视图、索引、数据类型、函数、存储过程和操作符等。模式的主要作用是组织和命名空间数据库对象&#xff0c;使得同一个数据库中可以包含…

2024双十一值得购买的好物有哪些?看完这五款好物让你不后悔!

随着一年一度的双十一购物狂欢节即将拉开帷幕&#xff0c;作为一名热衷于分享购物心得的博主&#xff0c;我今天特别想在这里为大家详细介绍五款我个人非常期待入手的好物。这些产品都是经过我精心挑选和试用的&#xff0c;我相信它们不仅能够满足我的需求&#xff0c;同样也能…

数字后端零基础入门系列 | Innovus零基础LAB学习Day1

一 Floorplan 数字IC后端设计如何从零基础快速入门&#xff1f;(内附数字IC后端学习视频&#xff09; Lab5-1这个lab学习目标很明确——启动Innovus工具并完成设计的导入。 在进入lab之前&#xff0c;我们需要进入我们的FPR工作目录。 其中ic062为个人服务器账户。比如你端…

【LeetCode】每日一题 2024_10_16 最小元素和最大元素的最小平均值(排序、模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;最小元素和最大元素的最小平均值 连续两天的简单题了&#xff0c;我有预感&#xff0c;明天的每日一题估计要来大的了 代码与解题思路 今天的题目算是标准的简单模拟题&#xff0c; 需要…

985研一学习日记 - 2024.10.17

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00√ 2、健身1个多小时 今天练了二头和背部&#xff0c;明天练胸和三头 3、LeetCode刷了3题 旋转图像&#xff1a…