文章目录
- 简介
- 简单文件下载
- 通过模拟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);
}
上面这种方式:
- 优点是简单
- 缺点是错误不友好,出错了,没有提示信息
如果希望错误信息友好一点,可以通过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个不好处理的问题:
- 跨域
- 文件流压缩
解决方案:
- 跨域问题主要是浏览器限制,可以通过后端添加header Access-Control-Allow-Origin或者直接设置浏览器处理
- 文件压缩可以使用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个:
- 服务端返回response的header Access-Control-Allow-Origin值类似于*
- 设置浏览器
设置服务器的方式很多,以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中设置:
设置这些的目的只有一个就是然response中有Access-Control-Allow-Origin
除了设置服务端,还可以设置浏览器,以Chrome为例:
设置浏览器的启动参数:
–disable-web-security --user-data-dir=D:\ChromeDevData