SpringBoot + Vue 前后端分离项目下载视频文件踩坑记录

时间:2022-07-26
本文章向大家介绍SpringBoot + Vue 前后端分离项目下载视频文件踩坑记录,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

☞ 背景

  项目服务端用的是 SpringBoot + SpringCloud + SpringDataJPA,前端用的是 Vue,有一个功能需要下载从监控中截取的视频(mp4),该视频由另一程序截取好放在某一目录下,使用 nginx 进行访问跳转,例如:视频:http://xx.xx.xx.xx:81/videoPath、web: http://xx.xx.xx.xx

☞ 坑一:a 标签

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 插件

  百度了一圈解决方案之后发现了 downloadjs 插件,觉得挺不错然后后开始了踩坑。安装 npm install downloadjs --save

import download from 'downloadjs'

function() {
	download(this.url);
}

  emmmm… 不出意外,又掉坑里了,他说我跨域了!好嘛,那就解决跨域,nginx 来一波,成功弹出下载框了,看到秒下完,顿时感觉不对。一看只有几 K,不死心的点开看了下,接受了下图的嘲讽。

☞ 坑三:responseType: blob

  经过一番查询没找到有效解决方案,加上时间问题我决定直接在后端写个 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);
      })
  });
}