# file-server **Repository Path**: batman2016/file-server ## Basic Information - **Project Name**: file-server - **Description**: 大文件分片上传断点续传 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-12-31 - **Last Updated**: 2021-12-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![file upload](https://img2.baidu.com/it/u=2552198785,2502288129&fm=26&fmt=auto) ### 1.目标 javaweb环境下,提高大文件上传下载效率 ### 2.解决的核心问题 - 分片上传 - 断点续传 - 大文件秒传 - 分片下载 - 断点下载 - 编码问题 ### 3.技术栈 - springboot - webuploader ### 4.分片上传 ![分片上传](https://note.youdao.com/yws/public/resource/f85effa14ef9549aed160e4098beba1a/xmlnote/WEBRESOURCE3bfd8f0928b8d253b418a1a56f423635/21551) > 前端将要上传的大文件,分成多个小文件,分别上传。后端成功接收各个小文件后,组合成和原来一样的大文件 - 前端使用webuploader组件进行大文件拆分,上传。上传的重要参数为 参数 | 注释 ---|--- name | 文件名 chunks | 一共有几个分片 chunk | 当前是第几个分片 md5 | 当前文件的md5值 - 后端把每个分片存入硬盘中的临时文件夹。不推荐放在内存,文件大的话,内存会爆。临时文件的命令方式:chunk + name ``` if (name!=null) { if (schunk!=null) { tempFileName = schunk + "_" + tempFileName ; } File tempFile = new File(uploadPath,tempFileName); //断点续传:比如说网络中断重新上传 if (!tempFile.exists()) { item.write(tempFile); } } ``` - 当最后一个分片上传成功,把所有分片进行组合 ``` //最后一个分片 if (schunk != null && schunk.intValue() ==schunks.intValue() -1) { //组合后的文件 File file = new File(uploadPath,name); //组合文件需要的缓冲流 os = new BufferedOutputStream(new FileOutputStream(file)); //文件合并 for (int i = 0; i < schunks; i++) { //根据文件命名获取每个分片 File tempFile = new File(uploadPath,i + "_" + name); //嘴后一个分配上传后,进行文件合并,但是此时多线程情况下并不保证其它分片已经来了 int j = 0 ; while (!tempFile.exists() && j < 1000) { //文件不存在,进入循环 Thread.sleep(100); //有的分配如果始终传不上来,这边不能始终进行阻塞 j++ ; } //读取这个分片,获取byte数组 byte[] bytes = FileUtils.readFileToByteArray(tempFile); //写入缓冲流 os.write(bytes); os.flush(); //删除临时文件 tempFile.delete(); } os.flush(); } ``` ### 4.断点续传 > 文件上传过程中,如果碰到网络故障,可以从已经上传的部分开始继续上传未完成的部分,而没有必要从头开始上传。用户可以节省时间,提高速度,在写分片文件中,有一个判断: ``` if (!tempFile.exists()) { item.write(tempFile); } ``` > 如果该分片临时文件存在,则不重新写入。一个大文件分成多个分片上传,遇到网络等故障,存在有些分片上传成功,有些分片还没上传。故障解除后,文件重新上传,webuploader会进行相同的分片上传,此时如果之前已经写入硬盘的分片不再重新写入,只写入还未写入的分片,进而实现了断点续传 ### 5.大文件秒传 - 通过分片上传断点续传多线程处理,大大提高文件上传效率 - 记录每个文件的md5值,如果相同的文件上传,只进行相关业务处理,不再进行上传操作(实际上没有传,所以做到秒传) ### 6.分片下载 ![分片下载](https://note.youdao.com/yws/public/resource/f85effa14ef9549aed160e4098beba1a/xmlnote/WEBRESOURCEd71c19e178435c1d9a0119fd7824a0ae/21554) - 服务端 1. 根据请求头Range判断客户端是否要进行分片下载,需要分片下载,range的值格式为:bytes=100-1000,表示要下载分片的头尾 ``` request.getHeader("Range") ``` 2. 根据InputStream的skip方法跳到分片的其实位置,然后根据分片大小,读取分片,写入OutputStream,返回给客户端。 ``` is.skip(pos); byte[] buffer = new byte[1024]; int length = 0 ; while (sum < rangeLen){ length = is.read(buffer,0,(rangeLen-sum)<=buffer.length?(int)(rangeLen-sum):buffer.length); sum = sum + length ; os.write(buffer,0,length); } ``` - 客户端 1. 先下载一个很小的分片,从请求头里面获取文件的大小和文件名 2. 根据文件大小,计算出共需要分几个分片进行下载,每个分片分配一个线程下载,用线程池执行 ``` FileInfo fileInfo = download(0, 10, -1, null); if (fileInfo != null) { long fSize = fileInfo.fSize; System.out.println("all size="+fSize); String fName = fileInfo.fName; long pages = fSize / PER_PAGE ; // <= for (long i = 0; i <= pages; i++) { pool.submit(new DownLoad(i*PER_PAGE, (i+1)*PER_PAGE -1, i, fName)); } } ``` 3.文件合并,和分配上传的文件合并类似 ### 7.断点下载 > 在分片的下载中,判断这个分片是否已经存在并且这个分片的大小是否是单分片的大小。 ``` //file.exists() 文件是否存在 //page != -1 该分片不是探测分片 //file.length() == PER_PAGE 这是个完整的分片 if (file.exists() && page != -1 && file.length() == PER_PAGE) { return null ; } ``` > 如果该分片已经完成下载,则不重新下载。一个大文件分成多个分片下载,遇到网络等故障,存在有些分片下载成功,有些分片下载还没完成。故障解除后,文件重新下载,此时如果之前已经下载完成的分片不再重新下载,只下载还未完成下载的分片,进而实现了断点下载 ### 8.相关材料 - [源码](https://gitee.com/huangguoye001/file-server.git) - [参考材料](https://www.bilibili.com/video/BV1EP4y1h7uY?p=3&spm_id_from=pageDriver)