(四)文件分片上传(异步)

前言

继续学习文件大文件上传,本次采用html5+jquery实现对文件分片
后台需新加接口


一、前端页面修改

1) 采用h5 file.slice(start,end)实现对大文件分片.

在这里插入图片描述

2) 构建form表单准备需要的入参.

入参需要构建file,name,chunks,chunk,guid
在这里插入图片描述

3) 使用ajax发送异步请求.

后台接口:/fileUpload/fileSyn
在这里插入图片描述

note:
finished(data)回调函数,需要根后台定义好返回数据类型.此处文件分片,合成完成后,前端会回显上传成功后的fileId(guid).

4) 前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

    <link rel="stylesheet" type="text/css" href="/webuploader/bootstrap.css">
    <link rel="stylesheet" type="text/css" href="/webuploader/webuploader.css">
    <link type="text/css" href="/webuploader/bootstrap-theme.css">
    <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript" src="webuploader/webuploader.min.js"></script>
    <script type="text/javascript" src="webuploader/bootstrap.min.js"></script>

    <style>
        .rg_layout{
            width: 500px;
            height: 120px;
            border: 8px solid #EEEEEE;
            background-color: #d9edf7;
            /*让div水平居中*/
            margin: auto;
            padding: auto;

        }

        .rg_left{
            /*border: 1px solid red;*/
            float: left;
            margin: 15px;
        }
        .rg_right{
            /*border: 1px solid red;*/
            float: right;
            margin: 15px;
        }
    </style>

    <script >
        var page = {
            init:function () {
                $("#ctlBtn_syn").click($.proxy(this.upload,this));
            },
            upload:function () {
                var guid = Math.random().toString().substring(2,8)

                var file = $("#file_syn")[0].files[0]; //文件对象
                if (file){
                    $("#ctlBtn_syn").attr({"disabled":"disabled"});
                    $("#output").text("文件正在上传...");

                    var name = file.name,        //文件名
                        size = file.size;        //总大小

                    var shardSize = 20 * 1024 * 1024,    //以25MB为一个分片
                        shardCount = Math.ceil(size / shardSize);  //总片数

                    for(var i = 0;i < shardCount;++i){
                        //计算每一片的起始与结束位置
                        var start = i * shardSize,
                            end = Math.min(size, start + shardSize);
                        //构造一个表单,FormData是HTML5新增的
                        var form = new FormData();
                        form.append("file", file.slice(start,end));  //slice方法用于切出文件的一部分
                        form.append("name", name);
                        form.append("chunks", shardCount);  //总片数
                        form.append("chunk", i);        //当前是第几片
                        form.append("guid", guid);        //当前是第几片

                        //Ajax提交
                        $.ajax({
                            url: "../fileUpload/fileSyn",
                            type: "POST",
                            data: form,
                            async: true,        //异步
                            processData: false,  //jquery不要对form进行处理
                            contentType: false,  //指定为false才能形成正确的Content-Type
                            success: function(data){
                                //成功后的事件
                                finished(data)
                            }
                        });
                    }
                }else {
                    alert("请选择要上传的文件")
                }
            }
        }



        $(function () {
            // 利用h5实现分片上传, ajax实现异步
            page.init();

            var guid
            var uploader = WebUploader.create({
                // swf文件路径
                swf : 'webuploader/Uploader.swf',
                // 文件接收服务端。
                // server : 'http://localhost:8080/fileUploaderServlet',
                server : "/fileUpload/file",
                // 选择文件的按钮。可选。
                // 内部根据当前运行是创建,可能是input元素,也可能是flash.
                pick : {id:'#picker'},
                chunked: true,  //分片处理
                chunkSize: 50 * 1024 * 1024, //每片10M
                threads:1,//上传并发数。允许同时最大上传进程数。
                formData:{'guid':guid},
                // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
                resize : false

            });
            // 当有文件被添加进队列的时候
            uploader.on('fileQueued', function(file) {
                $("#thelist").empty();
                $("#thelist").append('<h4 id="info" class="info">' + file.name + '</h4>');
            });

            // 当文件开始上传的时候
            uploader.on('uploadStart', function() {
                guid = uploader.options.formData.guid = Math.random().toString().substring(2,8);

                $("#info").text("文件正在上传....")
            });

            //
            uploader.on('uploadFinished',function () {
                $("#info").empty();
                $("#thelist").append('<h4 id="info" class="info">' + '文件上传完成' + '</h4>');
            });

           $("#ctlBtn").on ('click',function () {
               if ($(this).hasClass('disabled')){

                   alert("disabled");
                   return false;
               }

               uploader.upload();
           })
        });
        function finished(data) {
            if (data.code=="1"&& data.msg=="success"){
                $("#ctlBtn_syn").removeAttr("disabled");
                $("#output").text("文件上传完成;fileId="+data.data);
                var file = document.getElementById('file_syn');
                file.value = '';
            }
        }
    </script>

</head>
<body>

<div id="uploader" class="rg_layout">
        <div class="rg_left">
            <div id="picker">选择文件</div>
            <div id="thelist" class="uploader-list"></div>
        </div>

        <div class="rg_right">
            <button id="ctlBtn" class="btn btn-default">开始上传</button>
        </div>
</div>
<div id="uploader_syn" class="rg_layout">
    <div class="rg_left">
        <input type="file" id="file_syn">
        <br>
        <span id="output"></span>
    </div>

    <div class="rg_right">
        <button id="ctlBtn_syn">开始上传</button>
    </div>
</div>

</body>
</html>

5) UI

在这里插入图片描述

二、后台新增接口

1) 引入commons-io 依赖

在这里插入图片描述

        <!-- 引入commons-io 依赖 -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>

2) 新增接口

URL:/fileUpload/fileSyn
Method:POST
FormData: file,name,chunks,chunk,guid
在这里插入图片描述

3) 业务层实现

和同步上传代码几乎差不多,差异主要是写入数据时使用了IOUtils.copy(inStream,outStream); 简写了代码
在这里插入图片描述
前端使用ajax发送异步请求,后台会引入并发问题,以及什么时候开始合并文件
解决方案:可以采用缓存,来统计分片完成次数, 当统计次数等于总片数即可开始合并
创建:
private static Map<String,Integer> cache = new ConcurrentHashMap<>()和
private static synchronized boolean checkChunk (String key,int chunks)方法
在这里插入图片描述
检查:checkChunk为true开始合并文件
在这里插入图片描述

4) 业务层代码

package com.service.management.service;

import com.service.management.base.Result;
import com.service.management.utils.IoUtils;
import com.service.management.vo.BreakPointFile;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@Service
public class FileUploadService {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadService.class);

    public Result uploadFile(BreakPointFile breakPointFile) {
        Result result = Result.success(breakPointFile.getName());

        String fileId = breakPointFile.getGuid();
        Integer chunk =
                breakPointFile.getChunk() == null ? 0 : breakPointFile.getChunk();
        Integer chunks =
                breakPointFile.getChunks() == null ? 1 : breakPointFile.getChunks();

        File fileDir = null;

        try {

            fileDir = new File("F:/tempFile/" + fileId);
            if (!fileDir.isDirectory()) {
                fileDir.mkdirs();
            }
            File file = new File(fileDir, chunk + "");
            if (!file.exists()) {
                file.createNewFile();
            }

            outPutSliceFile(file,breakPointFile);

            LOGGER.info("分片上传({}/{})" ,chunk+1,chunks);
        } catch (Exception e) {
            e.printStackTrace();
            Result.error(e);
        }

        if (chunks.intValue() == chunk + 1) {
            LOGGER.info("分片完成!");
            LOGGER.info("开始合并...");

            InputStream in = null;
            OutputStream out = null;
            try {
                String path = fileDir.getAbsolutePath() + "/" + breakPointFile.getName();

                File[] files = fileDir.listFiles();
                List<File> fileList = Arrays.asList(files);
                List<File> list = fileList.stream()
                        .sorted(Comparator.comparing(this::getSortName))
                        .collect(Collectors.toList());

                out = new FileOutputStream(path);
                for (File file : list) {
                    in = new FileInputStream(file);
                    int len = 0;
                    byte[] bt = new byte[1024];
                    while (-1 != (len = in.read(bt))) {
                        out.write(bt, 0, len);
                    }

                    in.close();
                }
                // 删除上传的分片文件
                 deletTempFIles(fileList);


            } catch (Exception e) {
                e.printStackTrace();
                Result.error(e);
            } finally {
                IoUtils.Close(in);
                IoUtils.Close(out);
            }
            LOGGER.info("合并完成!");
            LOGGER.info("文件上传完成! fieId = " + fileId);

        }

        return result;
    }

    private void deletTempFIles(List<File> fileList) {
        Iterator<File> iterator = fileList.iterator();
        iterator.forEachRemaining(file -> {
            file.delete();
        });
    }

    private void outPutSliceFile(File file, BreakPointFile breakPointFile) throws IOException {

        OutputStream outSliceFile = new FileOutputStream(file);

        byte[] bytes = breakPointFile.getFile().getBytes(); //保存文件

        outSliceFile.write(bytes);

        IoUtils.Close(outSliceFile);
    }

    private int getSortName(File file) {

        int i = Integer.parseInt(file.getName());
        return i;
    }

    private static Map<String,Integer> cache = new ConcurrentHashMap<>();

    private static synchronized boolean checkChunk (String key,int chunks){
        Integer counter = cache.get(key);
        if (counter == null){
            counter = 0;
        }
        counter++;
        cache.put(key,counter);

        return chunks == counter.intValue();
    }

    public Result uploadFileSyn(BreakPointFile breakPointFile) {
        Result result = Result.error(breakPointFile.getName());

        String fileId = breakPointFile.getGuid();
        Integer chunk =
                breakPointFile.getChunk() == null ? 0 : breakPointFile.getChunk();
        Integer chunks =
                breakPointFile.getChunks() == null ? 1 : breakPointFile.getChunks();

        File fileDir = null;

        try{
            fileDir = new File("F:/tempFileSyn/" + fileId);
            if (!fileDir.isDirectory()) {
                fileDir.mkdirs();
            }
            File file = new File(fileDir, chunk + "");
            if (!file.exists()) {
                file.createNewFile();
            }

            InputStream inStream = breakPointFile.getFile().getInputStream();
            OutputStream outStream = new FileOutputStream(file);
            IOUtils.copy(inStream,outStream);
            IoUtils.Close(inStream);
            IoUtils.Close(outStream);
            LOGGER.info("分片上传({}/{})" ,chunk+1,chunks);

            if (checkChunk(fileId,chunks)){
                LOGGER.info("分片完成!");
                LOGGER.info("开始合并...");
                mergeFile(breakPointFile, fileId, fileDir);
                result = Result.success(breakPointFile.getGuid());
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return result;

    }

    private void mergeFile(BreakPointFile breakPointFile, String fileId, File fileDir) {

        InputStream in = null;
        OutputStream out = null;
        try {
            String path = fileDir.getAbsolutePath() + "/" + breakPointFile.getName();

            File[] files = fileDir.listFiles();
            List<File> fileList = Arrays.asList(files);
            List<File> list = fileList.stream()
                    .sorted(Comparator.comparing(this::getSortName))
                    .collect(Collectors.toList());

            out = new FileOutputStream(path);
            for (File file1 : list) {
                in = new FileInputStream(file1);
                IOUtils.copy(in,out);
                in.close();
            }
            // 删除上传的分片文件
            deletTempFIles(fileList);

        } catch (Exception e) {
            e.printStackTrace();
            Result.error(e);
        } finally {
            IoUtils.Close(in);
            IoUtils.Close(out);
        }
        LOGGER.info("合并完成!");
        LOGGER.info("文件上传完成! fieId = " + fileId);
    }
}

总结

本次完成大文件分片异步上传 .
前端技术:JQ + H5 + ajax

int类型 == Integer类型比较时,一定一定要使用Integer.intValue()进行比较
注意常量池

感谢您看完这篇文章,本人也在学习和摸索中,如有问题,欢迎指正和交流

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值