背景
基于SpringBoot+Vue先后端分离项目中进行文件下载
SpringBoot版本:2.0.3.RELEASE
vue版本:2.5.2前端
本博客中前端实现文件下载的方式有3种方式以下:vue
经过a连接下载(须要绕过安全校验框架的token验证);
axios+Blob发送post请求实现下载(通过安全校验框架的登陆或者token验证,可是下载复杂类型文件异常,尽能够支持.txt或者csv文件);
XMLHttpRequest+Blob发送post请求实现下载(通过安全校验框架的登陆或者token验证,能够下载全部的文件)。
1.后台
1.1后台下载文件的工具类
该工具类型的主要处理逻辑是设置响应头属性,将待下载文件转换为字节流写入HttpServletResponse 实例中。java
Content-type:说明了实体主体内对象的媒体类型;不一样文件对应的value ,而下面代码中使用的application/octet-stream则表示以流方式下载文件,能够匹配全部类型的文件。
Content-Disposition:下载文件的一个标识字段,filename属性值是下载获得的文件的文件名;
Content-Length:该资源的大小,单位为字节。
package com.mark.common.utils;ios
import com.mark.common.exception.Campuso2oException;
import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Encoder;web
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;spring
/**
* @Description: 下载文件的工具类
* @Author: Mark
* @CreateDate: 2019/2/23 16:29
* @Version: 2.0
* @Copyright : 豆浆油条我的非正式工做室
*/
public class DownloadFileUtil {apache
/**
* 下载文件
* @param originalFileName :下载文件的原始文件名
* @param file :下载的文件
* @param response :相应对象
*/
public static void downloadFile(String originalFileName, File file, HttpServletResponse response, HttpServletRequest request) {
// 数据校验
checkParam(originalFileName,file);json
//相应头的处理
//清空response中的输出流
response.reset();
//设置文件大小
response.setContentLength((int) file.length());
//设置Content-Type头
response.setContentType("application/octet-stream;charset=UTF-8");
//设置Content-Disposition头 以附件形式解析
String encodedFilename = getEncodedFilename(request, originalFileName);
response.addHeader("Content-Disposition", "attchment;filename=" + encodedFilename);axios
//未来文件流写入response中
FileInputStream fileInputStream = null;
ServletOutputStream outputStream = null;
try {
//获取文件输入流
fileInputStream = new FileInputStream(file);
//建立数据缓冲区
byte[] buffers = new byte[1024];
//经过response中获取ServletOutputStream输出流
outputStream = response.getOutputStream();
int length;
while ((length = fileInputStream.read(buffers)) > 0) {
//写入到输出流中
outputStream.write(buffers, 0, length);
}后端
} catch (IOException e) {
e.printStackTrace();
} finally {
//流的关闭
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下载文件的参数的校验,若是参数不合法则抛出自定义异常
* @param originalFileName :文件原始文件名
* @param file :待下载的文件
*/
private static void checkParam(String originalFileName, File file) {
if(StringUtils.isBlank(originalFileName)){
throw new Campuso2oException("输入的文件原始文件名为空");
}
if(file == null || !file.exists() ){
throw new Campuso2oException("待在下载的文件不存在!");
}
}
/**
* 获取URL编码后的原始文件名
* @param request :客户端请求
* @param originalFileName :原始文件名
* @return :
*/
private static String getEncodedFilename(HttpServletRequest request, String originalFileName) {
String encodedFilename = null;
String agent = request.getHeader("User-Agent");
if(agent.contains("MSIE")){
//IE浏览器
try {
encodedFilename = URLEncoder.encode(originalFileName, "utf-8");
encodedFilename = encodedFilename.replace("+", " ");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}else if(agent.contains("Firefox")){
//火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
encodedFilename = "=?utf-8?B?" + base64Encoder.encode(originalFileName.getBytes(StandardCharsets.UTF_8))+"?=";
}else{
//其余浏览器
try {
encodedFilename = URLEncoder.encode(originalFileName, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return encodedFilename;
}
}
1.2.Controller层的调用测试
package com.mark.web.common.controller;
import com.mark.common.utils.DownloadFileUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
/**
* @Description: 通用文件下载类
* @Author: Kingsley
* @CreateDate: 2019/2/23 20:22
* @Version: 2.0
* @Copyright : 豆浆油条我的非正式工做室
*/
@RestController()
@RequestMapping("/common/file/")
public class FileDownloadController {
/**
* 下载文件的demo
*/
@RequestMapping(value = "download")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
File file = new File("C:\\Users\\Administrator\\Desktop\\bak\\test.jpg");
String fileName = "测试文件下载.png";
DownloadFileUtil.downloadFile(fileName,file,response,request);
}
}
2.前端下载
2.1前端使用a链接标签下载
a标签的download属性
<a href="http://localhost:8080/common/file/download" download="true">下载文件</a>
1
下载结果演示
左边为原始文件,右边为下载的前端演示以及下载成功后的获得的文件。下载完成后比较两个文件的大小,发现是一致的!
问题
以上能够实现经过a连接下载文件。但这种下载文件的有一个安全问题,经过a连接下载是没法进行token验证的,若是后台有实现了权限验证框架,例如Shiro。那么这种下载方式是须要在Shiro中设置url放行。
2.2优化1-axios发送post请求下载(有bug)
前端vue框架中居于axios组件经过post请求实现文件的下载,这样前端会被后台的权限验证拦截,验证token信息。
注意响应类型须要设置为blob ,Blob对象使用说明
/**
* 文件的下载
* @param url : 请求的Url
* @param data : 请求的数据
* @param errorHandler : 请求发生异常的回调函数
*/
loadLoadFile:(url, data, errorHandler) =>{
axios.post(url,data,{
headers :{
responseType: 'blob'
}
}).then(res=>{
const blob = new Blob([res.data])
let url = window.URL.createObjectURL(blob)
//建立一个a标签元素,设置下载属性,点击下载,最后移除该元素
let link = document.createElement('a')
link.href = url
link.style.display = 'none'
//res.headers.fileName 取出后台返回下载的文件名
const downlaodFileName = decodeURIComponent(res.headers.filename)
console.log(res.headers)
link.setAttribute('download',downlaodFileName)
link.click()
window.URL.revokeObjectURL(url)
}).catch(errorHandler)
},
工具须要在响应头信息中添加一个自定义的下载文件名
注意:虽而后台设置文件名的key为fileName,请求返回客户端的时候会转为小写即filename
//添加下载文件名
response.addHeader("fileName",encodedFilename);
1
2
结果演示
从下载结果中能够看出虽然能够下载文件,可是下载后的文件的大小比原始的文件的大小不一致。更重要的通过测试发现若是下载文件为文档、图片、表格等文件会出现乱码或者文件打不开的状况。搜索一堆网上的博客文档,提到多是axios下载的锅,响应获得文件流已是乱码不正确的了。基于axios请求下载文件还没找到更好的解决方法。换个方式呢?下面经过原始XHR请求能够解决这个问题。
2.3优化2:使用XMLHttpRequest发送post请求
关于XMLHttpRequest的使用
window.URL.createObjectURL()和 window.URL.revokeObjectURL() 的使用说明
/** * 经过XMLHttpRequest发送post请求下载文件 *@param url : 请求的Url * @param data : 请求的数据 */ XHRLoadLoadFile:(url, data)=>{ let xhr = new XMLHttpRequest() xhr.open('post',url) //若是须要请求头中这是token信息能够在这设置 xhr.setRequestHeader('Content-Type','application/json;charset=UTF-8') xhr.responseType = 'blob' xhr.send(JSON.stringify(data)) xhr.onreadystatechange = function(){ if(xhr.readyState ===4 && xhr.status === 200){ const blob = new Blob([xhr.response]) let url = window.URL.createObjectURL(blob) //建立一个a标签元素,设置下载属性,点击下载,最后移除该元素 let link = document.createElement('a') link.href = url link.style.display = 'none' //取出下载文件名 const fileName = xhr.getResponseHeader('filename') const downlaodFileName = decodeURIComponent(fileName) link.setAttribute('download',downlaodFileName) link.click() window.URL.revokeObjectURL(url) } } }, 结果演示:终于能够正常下载了