zoukankan      html  css  js  c++  java
  • 在浏览器中异步下载文件监听下载进度

    在浏览器中异步下载文件,其实就是把服务器响应的文件先保存在内存中。然后再一次下载到磁盘。第二次下载过程,就是把内存的数据IO到磁盘,没有网络开销。速度极快。

    之所以要先保存在内存,主要是可以在下载开始之前和下载结束后可以做一些业务逻辑(例如:校验,判断),还可以监听下载的进度。

    演示

    这里演示一个Demo,在点击下载摁钮后,弹出加loading框。在读取到服务器的响应的文件后。关闭loading框。并且在控制台中输出下载的进度。

    有点像是监听文件下载完毕的意思,也只能是。从内存IO到磁盘的这个过程,JS代码,再也无法染指过程。更谈不上监听了。

    Controller

    服务端的下载实现

    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    @Controller
    @RequestMapping("/download")
    public class DownloadController {
    	
    	@GetMapping
    	public void download (HttpServletRequest request,
    							HttpServletResponse response,
    							@RequestParam("file") String file) throws IOException {
    		
    		Path path = Paths.get(file);
    		if (Files.notExists(path) || Files.isDirectory(path)) {
    			// 文件不存在,或者它是一个目录
    			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
    			return ;
    		}
    		
    		String contentType = request.getServletContext().getMimeType(file);
    		if (contentType == null) { 
    			// 如果没读取到ContentType,则设置为默认的二进制文件类型
    			contentType = "application/octet-stream";
    		}
    		
    		
    		try (BufferedInputStream bufferedInputStream = new BufferedInputStream(Files.newInputStream(path))){
    
    			response.setContentType(contentType);
    			response.setHeader("Content-Disposition", "attachment; filename=" + new String(path.getFileName().toString().getBytes("GBK"), "ISO-8859-1"));
    			
    			// 关键点,给客户端响应Content-Length头,客户端需要用此来计算下载进度
    			response.setContentLengthLong(Files.size(path));
    			
    			OutputStream outputStream = response.getOutputStream();
    			
    			byte[] buffer = new byte[8192];
    			
    			int len = 0;
    			
    			while ((len = bufferedInputStream.read(buffer)) != -1) {
    				outputStream.write(buffer, 0, len);
    			}
    			
    		} catch (IOException e) {
    			
    		}
    	}
    }
    
    

    Index.html

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="UTF-8">
    		<title>异步下载</title>
    	</head>
    	<body>
    		<input name="name" value="D:\eclipse-jee-2019-12-R-win32-x86_64.zip" placeholder="输入你要下载的文件路径" id="file" />
    		<button id="button" onclick="downlod();">开始下载</button>
    	</body>
    	<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
    	<script src="/layer/layer.js"></script>
    	<script type="text/javascript">
    		function downlod(){
    			const file = document.querySelector('#file').value;
    			if (!file){
    				alert('请输入合法的文件地址');
    			}
    			
    			// 打开加载动画
    			const index = layer.load(1, {
      				shade: [0.1,'#fff']
    			});
    			
    			const xhr = new XMLHttpRequest();
    			xhr.open('GET', '/download?file=' + encodeURIComponent(file));
    			xhr.send(null);
    			// 设置服务端的响应类型
    			xhr.responseType = "blob";
    			// 监听下载
    			xhr.addEventListener('progress', event => {
    				// 计算出百分比
    				const percent  = ((event.loaded / event.total) * 100).toFixed(2);
    				console.log(`下载进度:${percent}`);
    			}, false);
    			xhr.onreadystatechange = event => {
    				if(xhr.readyState == 4){
    					if (xhr.status == 200){
    						
    						// 获取ContentType
    						const contentType = xhr.getResponseHeader('Content-Type');
    						
    						// 文件名称
    						const fileName = xhr.getResponseHeader('Content-Disposition').split(';')[1].split('=')[1];
    						
    						// 创建一个a标签用于下载
    						const donwLoadLink = document.createElement('a');
    						donwLoadLink.download = fileName;
    						donwLoadLink.href = URL.createObjectURL(xhr.response);
    						
    						// 触发下载事件,IO到磁盘
    						donwLoadLink.click();
    						
    						// 释放内存中的资源
    						URL.revokeObjectURL(donwLoadLink.href);
    						
    						// 关闭加载动画
    						layer.close(index);
    					} else if (response.status == 404){
    						alert(`文件:${file} 不存在`);
    					} else if (response.status == 500){
    						alert('系统异常');
    					}
    				}
    			}
    		}
    	</script>
    </html>
    

    现在的ajax请求,几乎都是用ES6的fetch,支持异步,而且代码也更优雅。API设计得更合理。但是目前为止,好像fetch并没有progress事件,也就说它不支持监听上传下载的进度。所以没辙,还是得用XMLHttpRequest

    最后

    这种方式弊端也是显而易见,如果文件过大。那么内存就炸了。我觉得浏览器应该暴露一个js的接口。允许通过异步的方式直接下载文件IO到磁盘,通过回调给出下载的进度,IO的进度。

    原文:https://springboot.io/t/topic/2734

  • 相关阅读:
    洛谷 P1194 飞扬的小鸟 题解
    洛谷 P1197 星球大战 题解
    洛谷 P1879 玉米田Corn Fields 题解
    洛谷 P2796 Facer的程序 题解
    洛谷 P2398 GCD SUM 题解
    洛谷 P2051 中国象棋 题解
    洛谷 P1472 奶牛家谱 Cow Pedigrees 题解
    洛谷 P1004 方格取数 题解
    洛谷 P2331 最大子矩阵 题解
    洛谷 P1073 最优贸易 题解
  • 原文地址:https://www.cnblogs.com/kevinblandy/p/13669904.html
Copyright © 2011-2022 走看看