项目服务端用的是 SpringBoot + SpringCloud + SpringDataJPA,前端用的是 Vue,有一个功能需要下载从监控中截取的视频(mp4),该视频由另一程序截取好放在某一目录下,使用 nginx 进行访问跳转,例如:视频:http://xx.xx.xx.xx:81/videoPath
、web: http://xx.xx.xx.xx
。
let link = document.createElement('a')
link.setAttribute('download', this.videoPath)
link.style.display = 'none'
link.href = this.url
document.body.appendChild(link)
link.click();
使用该种方式直接将视频给打开了,没有下载视频,开始我以为是 download 属性写错了,后来发现是 url 指向第三方资源,download 属性失效了。
百度了一圈解决方案之后发现了 downloadjs 插件,觉得挺不错然后后开始了踩坑。安装 npm install downloadjs --save
import download from 'downloadjs'
function() {
download(this.url);
}
emmmm… 不出意外,又掉坑里了,他说我跨域了!好嘛,那就解决跨域,nginx 来一波,成功弹出下载框了,看到秒下完,顿时感觉不对。一看只有几 K,不死心的点开看了下,接受了下图的嘲讽。
经过一番查询没找到有效解决方案,加上时间问题我决定直接在后端写个 IO 来搞定下载问题。
@RestController
@RequestMapping("/download")
public class Download {
@PersistenceContext
private EntityManager entityManager;
@Value("${param.url.disk}")
public String disk;
@RequestMapping("/video")
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
// 参数合法性
if (Objects.isNull(id) || StringUtils.isEmpty(id)) {
return;
}
BaseDAOImpl<Info, Long> infoDao = new BaseDAOImpl<>(Info.class, entityManager);
Info info = infoDao.findOne(Long.parseLong(id));
String filename = info.getVideoPath();
String realPath = disk + info.getPicturePath() + File.separatorChar + filename;
FileInputStream fis = new FileInputStream(realPath);
// 设置response的响应头
String mimeType = servletContext.getMimeType(filename);
response.setHeader("content-type",mimeType);
String agent = request.getHeader("user-agent");
filename = DownLoadUtils.getFileName(agent, filename);
response.setHeader("content-disposition","attachment;filename=" + filename);
ServletOutputStream sos = response.getOutputStream();
byte[] buff = new byte[1024 * 8];
int len = 0;
while((len = fis.read(buff)) != -1){
sos.write(buff,0,len);
}
fis.close();
}
}
operateEvent(data) {
down("/weChat/download/video" , {id: data.id}).then(response => {
this.saveAs(new Blob([response.data], { type: 'text/plain;charset=UTF-8' }), filename);
});
},
// 导出文件函数
saveAs (obj, fileName) {
let ele = document.createElement('a');
ele.download = fileName || '下载';
ele.href = URL.createObjectURL(obj); // 绑定a标签
ele.style.display = 'none';
document.body.appendChild(ele); // 兼容火狐浏览器
ele.click();
setTimeout(function () { // 延时释放
URL.revokeObjectURL(obj); // 用 URL.revokeObjectURL() 来释放这个object URL
document.body.removeChild(ele); // 兼容火狐浏览器
}, 100);
},
成功弹出下载框,但是!!!后缀居然是 txt,直接给我干蒙了,什么鬼,为什么会是 txt 文件,经过一番 debug 后,发现混进来一个奇怪的东西。
data居然乱码了。仔细查找一番后发现,在项目中我们封装了 HTTP 请求组件,他是这个坑的根源 ~ 有木有发现少了啥,没有写返回类型 ╯︿╰
export function fetch(url, data = {}) {
return new Promise((resolve, reject) => {
//debugger;
axios.defaults.headers["Authorization"] = "Bearer " + sessionStorage.getItem('token');
axios.defaults.headers["ClientType"] = global_variable.Wise.ClientType;
axios({url: base + url, method: 'GET', params: data})
.then(response => {
resolve(response);
})
.catch(error => {
reject(error);
})
});
}
赶紧写上一个给加上 responseType: ‘blob’,测试通过。对于毕业一年在一家小公司既写前端又写后端的我来说,真的是一把辛酸泪。
export function down(url, data = {}) {
return new Promise((resolve, reject) => {
axios.defaults.headers["Authorization"] = "Bearer " + sessionStorage.getItem('token');
axios.defaults.headers["ClientType"] = global_variable.Wise.ClientType;
axios({url: base + url, method: 'GET', params: data, responseType: 'blob'})
.then(response => {
resolve(response);
})
.catch(error => {
reject(error);
})
});
}