【开发经验】java 20行代码实现断点续传

引言
随着文件的越来越大,很多场景需要断点续传功能,这个功能很友好,大文件上传了一半,下次还能接着上传,免得断点或者关机之后还需要从头开始。
断点上传思路很简单,只需要在上传时将大文件切割为多个分片,将这些小的分片进行上传。
在这里插入图片描述

实现思路

  1. 上传文件时判断是否有上传过。
  2. 如果没有上传过,则直接对文件进行切割,切割成很多分片进行上传。
  3. 如果有上传过,则判断之前上传了那些分片,只对于剩下的分片进行上传即可;

1.如何判断文件是否有上传过?

​ 通过散列函数进行文件的标识,来判断文件是否有上传过。散列函数(Hash),一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。附录中可查看散列函数的特性。

2.如何进行文件的切割?

​ 文件切割,这个就是客户端的工作了,对于一个专职后端的开发来说,做文件切割,虽然是有一点点的困难,但是简单的做一做还是必须要可以的。上代码!

  <form enctype="multipart/form-data">
    <input type="file" id="file" value="选择文件"/>
  </form>

​ 简单来一个文件上传的入口。通过ajax进行上传。

// 引入jquery
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>  

$("#file").change(function () {
               var file = $("#file").get(0).files[0];
               // 查询是否有上传过
               // 这里应该是有一个查询的逻辑的,不在做了哈
               // 就直接冲第0个分片开始传了。
               var shardIndex = 0;
               uploadFile(file,shardIndex);

  })

通过file.slice方法进行文件的切割,用来处理开始字节数和结束字节的位置。

 function uploadFile(file,shardIndex) {
     			// 声明 文件信息和分片信息。
     			// 注意:shardTotal是总分片数,即使后面有一点点字节,也要进行加1操作。避免丢了字节。
                var name = file.name,
                    size = file.size,
                    shardSize = 1024*1024*10,
                    shardTotal = Math.ceil( size /shardSize);
                if(shardIndex >=shardTotal){
                    return ;
                }
                var start = shardIndex*shardSize;
                var end = Math.min(start+shardSize,size);
     			// 文件切割
                var packet = file.slice(start,end);
                var formData = new FormData();
                formData.append("file",packet);
     //                formData.append("hashCode",md5(file));
                formData.append("hashCode","1111");  // 这里应该进行hash运算。用111代替一下
                formData.append("fileName",name);
                formData.append("size",size);
                formData.append("shardIndex",shardIndex);
                formData.append("shardSize",shardSize);
                formData.append("shardTotal",shardTotal);
     			// 通过ajax进行上传
                $.ajax({
                    url:"http://localhost:8080/file/upload",
                    type:"post",
                    cache: false,
                    data: formData,
                    processData: false,
                    contentType: false,
                    success :function (data) {
                        console.log(data);
                        //这里进行循环调用,把所有的分片循环上传
                        if(shardIndex<shardTotal){
                            shardIndex++;
                            uploadFile(file,shardIndex);
                        }
                    }
                });
            }

前后端分离太久了, 就不善于看前端代码,慢慢的更熟悉http请求信息了。请求的信息长这个样子,后来进行接收就好了。认准有二进制信息的提交。如果没有,可能是前端代码有问题了。

在这里插入图片描述

3.后端文件接收

接收文件信息

public class FileInfo {
    private String hasdCode;
    private String fileName;
    private Integer size;
    private Integer shardIndex;
    private Integer shardSize;
    private Float shardTotal;
    }
@RestController
public class FileController {
    private static HashMap<String,FileInfo> fileMap = new HashMap();
    // 模拟保存的位置
    private String savePath="E:\\data";
    @PostMapping(value = "/file/upload")
    public void fileUpload(FileInfo fileInfo, HttpServletRequest request) throws IOException {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile multipartFile = multipartRequest.getFile("file"); // 通过参数名获取指定文件
        //创建分片
        File fileblob = new File(savePath+"\\"+fileInfo.getFileName()+"."+"blob");
        System.out.println("保存分片"+fileblob.getName());
        //保存分片  后面为true 表示在已有的文件进行追加。
        FileCopyUtils.copy(multipartFile.getInputStream(),new FileOutputStream(fileblob,true));
        //判断是否全部上传完成。
        if((fileInfo.getShardIndex()+1) == Math.ceil(fileInfo.getShardTotal())){
            System.out.println("上传完成,重命名");
            File saveFile = new File(savePath+"\\"+fileInfo.getFileName());
            //重命名操作
            fileblob.renameTo(saveFile);
        }
        // 类似保存到数据库,这里简化下,直接放到map中。
        fileMap.put(fileInfo.getHasdCode(),fileInfo);
    }
    @GetMapping("/getfile/{hashCode}")
    public FileInfo getFileInfoByCode(@PathVariable("hashCode") String hashCode){
        return fileMap.get(hashCode);
    }
}

至此,断点续传的核心逻辑是完成了。但是代码是有很多需要优化。比如数据库如何进行保存数据等等。

简单的文件复制大家都直到,但是如何复制的更快,却是一个比较难的优化,毕竟java在对系统操作的时候有先天的不足。如果有兴趣的可以看下这个文章,讲述了如何更好的进行文件IO的处理。不过对于一般开发的应用程序来说,既然都用到java来操作文件了,应该也不太在乎复制效率了,这个道理貌似和”吃泡面的时候还在乎什么健康“一样。

笔者环境

spring boot 2.3.1.RELEASE JDK1.8

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>file-upload-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

附录

散列函数

​ 所有散列函数都有如下一个基本特性:如果两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果。但另一方面,散列函数的输入和输出不是一一对应的,如果两个散列值相同,两个输入值很可能是相同的,但不绝对肯定二者一定相等(可能出现哈希碰撞)。输入一些数据计算出散列值,然后部分改变输入值,一个具有强混淆特性的散列函数会产生一个完全不同的散列值。

更多散列函数信息

xia

读到这里了,如果对你有帮助就留个赞吧。

另外关注公众号java内功心法回复我要当架构即可领取java方向必备进阶资料。

  • 15
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叁滴水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值