webuploader断点上传以及对应的断点(springboot)下载

      我的流程是前台选择上传大文件,通过webuploader插件切片文件,创建上传辅助类(MultipartFileParam)封装上传需要的name以及分片信息,md5等。上传到了我本地的d:mnt/data/下,根据redis中的文件对应的md5值去校验文件,达到断点上传的过程,文件merge(合并)完,删除临时文件目录及分片文件,然后记录文件的uuid以及路径以及文件length到数据库。断点下载参考的网上的教程,需要注意的是发送get请求的方式非Ajax请求,此处我使用了window.location.href.如有不足请多指教。

1.使用的插件

页面引入css以js(依赖jq)

2.页面代码

var file_md5   = null;   // 用于MD5校验文件
var chunk_md5 = null;
var secSend = false;
var uploader;
var file_chunks;
WebUploader.Uploader.register({
    //文件发送之前执行
    "before-send-file":"beforeSendFile",
    //在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行
    "before-send":"beforeSend",
    //在文件所有分片都上传完后,且服务端没有错误返回后执行
    "after-send-file":"afterSendFile"
},{
    beforeSendFile:function(file){
        var me = this,
            owner = this.owner,
            deferred = WebUploader.Deferred();
        log("正在计算MD5值...");
        log("<div id='md5"+file.id+"'></div>");
        uploader.md5File(file,0,1*1024*1024)
        //新版本jquery不会调用
            .progress(function(percentage) {
                $('#md5'+file.id).html((percentage*100).toFixed()+'%');
            })
            .then(function (fileMd5) {
                file_md5 = fileMd5;
                log("MD5计算完成。");
                // 检查是否有已经上传成功的分片文件
                $.post('courseImage/check', JSON.stringify({md5: file_md5}), function (data) {
                    console.log(data);
                    log(data.data);
                    if (data.data) {
                        uploader.skipFile(file);
                        secSend = true;
                        $.post('courseImage/merge', JSON.stringify({ md5: file_md5, name: file.name }), function (data) {
                            log(data.msg);
                            deferred.resolve();
                            parent.location.reload();
                        });
                    }
                    deferred.resolve();
                })
            });
        return deferred.promise();
    },
    beforeSend:function (block) {
        var me = this,
            owner = this.owner,
            blob = block.blob,
            deferred = WebUploader.Deferred();
        file_chunks = block.chunks;
        if(block.chunks>1){
            uploader.md5File(block.blob).then(function (value) {
                chunk_md5 = value;
                deferred.resolve();
            })
        }else {
            deferred.resolve();
        }
        return deferred.promise();
    },
    afterSendFile:function (file) {
        var deferred = WebUploader.Deferred();
        console.log(file_chunks>1 && !secSend);
        //判断是否要分片
        if(file_chunks>1 && !secSend){
            $.post('courseImage/merge', JSON.stringify({ md5: file_md5, name: file.name }), function (data) {
                log(data.msg);
                deferred.resolve();
                parent.location.reload();
            });
        }else {
            deferred.resolve();
        }
        return deferred.promise();
    }
})
// 创建上传
uploader = WebUploader.create({
    swf: '../../js/Uploader.swf',
    server: '/courseImage/upload',          // 服务端地址
    pick: '#picker',              // 指定选择文件的按钮容器
    resize: false,
    chunked: true,                //开启分片上传
    chunkSize: 1024 * 1024 * 1,   //每一片的大小
    chunkRetry: 3,              // 如果遇到网络错误,重新上传次数
    threads: 3,                   // [默认值:3] 上传并发数。允许同时最大上传进程数。
    auto: true, // 选完文件后,是否自动上传
    // fileNumLimit:1,
    // fileSizeLimit:1024*1024*1024*1.5,
});

// 上传提交
$("#ctlBtn").click(function () {
    log('准备上传...');
    uploader.upload();
});

// 当有文件被添加进队列的时候-md5序列化
uploader.on('fileQueued', function (file) {

    $("#filelist").append( '<div id="' + file.id + '" class="item">' +
        '<h4 class="info">' + file.name + '</h4>' +
        '<p class="state">等待上传...</p>' +
        '</div>' );

});

// 发送前在body中添加一些信息,如分块数目chunks
uploader.on('uploadBeforeSend', function( block, data ) {
    //事件里不支持这种用法
    // var deferred = WebUploader.Deferred();
    data.chunks = block.chunks;
    data.md5 = file_md5;
    console.log(file_md5+"-------------------"+block.chunks)
});

// 上传完成后触发
uploader.on('uploadSuccess', function (file,response) {
    if(secSend){
        $("#percentage_a").css("width","100%");
        $("#percentage").html("100%");
    }
    log("上传完成");
});

// 文件上传过程中创建进度条实时显示。
uploader.on('uploadProgress', function (file, percentage) {
    $("#percentage_a").css("width",parseInt(percentage * 100)+"%");
    $("#percentage").html(parseInt(percentage * 100) +"%");
});

// 上传出错处理
uploader.on('uploadError', function (file) {
    uploader.retry();
});

// 暂停处理
$("#stop").click(function(e){
    log("暂停上传...");
    uploader.stop(true);
})

// 从暂停文件继续
$("#start").click(function(e){
    log("恢复上传...");
    uploader.upload();
});

function log(html) {
    $("#log").append("<div>"+html+"</div>");
}

3.控制层

@PostMapping("/check")
@RequiresPermissions("sys:image:add")
public ResultModel checkFileMd5(@RequestBody MultipartFileParam multipartFileParam) {
    String md5 = multipartFileParam.getMd5();
    String md5InRedis = (String) stringRedisTemplate.opsForValue().get("file_md5");
    if (!StringUtils.isEmpty(md5InRedis) && md5InRedis.equals(md5)) {
        return ResultUtil.ok(true);//文件存在
    }
    return ResultUtil.ok(false);
}


@PostMapping("/upload")
@RequiresPermissions("sys:image:add")
public ResultModel importImage(MultipartFileParam mParam) {
    boolean flag = BreakPointUploadUtils.uploadImage(mParam);
    if (flag) {
        stringRedisTemplate.opsForValue().set("file_md5", mParam.getMd5()); //上传记录md5----记录到redis
    }
    return ResultUtil.ok();
}

@PostMapping("/merge")
@RequiresPermissions("sys:image:add")
public ResultModel mergeChunk(@RequestBody MultipartFileParam mParam) {
    Map<String, String> map = BreakPointUploadUtils.mergeImage(mParam);
    //清redis缓存
    stringRedisTemplate.delete("file_md5");
    if (null != map) {
        EduCourseImagesModel eduCourseImagesModel = new EduCourseImagesModel();
        eduCourseImagesModel.setCreateBy(WebUtil.getCurrentUserId());
        eduCourseImagesService.addImage(eduCourseImagesModel, map);
    }
    return ResultUtil.ok();
}

@GetMapping("/download/{id}")
@RequiresPermissions("sys:image:add")
public ResultModel downloadFile(@PathVariable Long id) {
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = requestAttributes.getRequest();
    HttpServletResponse response = requestAttributes.getResponse();
    EduCourseImagesModel eduCourseImagesModel = eduCourseImagesService.selectById(id);
    return ResultUtil.ok(BreakPointUploadUtils.downloadImage(eduCourseImagesModel, request, response));
}

4.BreakPointUploadUtils工具类

@Component(value = "breakPointUploadUtils")
public class BreakPointUploadUtils {


    private static final Logger logger = Logger.getLogger(UploadUtils.class);

    // 默认大小 50M(52428800) 改为 200m
    public static long defaultMaxSize;

    public static String defaultBaseDir;

    // 默认的文件名最大长度
    public static int DEFAULT_FILE_NAME_LENGTH = 200;

    public static final String OTHERF_PATH = "ean-upload/files/";
    public static final String BIG_FILE_PATH = "ean-upload/image/";
    public static final String MEDIA_PATH = "ean-upload/media/";


    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
            // 图片
            "bmp", "gif", "jpg", "jpeg", "png",
            // word excel powerpoint
            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
            // media
            "mp4",
            //zip
            "zip",
            // pdf
            "pdf",
            "gz",
            "tar"
    };

    @Value("${ean.upload.default-base-dir}")
    public void setDefaultBaseDir(String defaultBaseDir) {
        BreakPointUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    @Value("${ean.upload.default-max-size}")
    public void setDefaultMaxSize(long defaultMaxSize) {
        BreakPointUploadUtils.defaultMaxSize = defaultMaxSize;
    }

    private static final String datePath() {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyyMMdd");
    }

    public static final void assertAllowed(MultipartFile file, long maxSize) throws BusinessException {
        //判断文件是否存在
        if (file.isEmpty()) {
            throw new BusinessException(Constants.ResultCodeEnum.BAD_REQUEST);
        }
        //判断文件名是否超出限制大小
        int fileNamelength = file.getOriginalFilename().length();
        if (fileNamelength > UploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new BusinessException("超过文件名最大长度");
        }
        String extension = Files.getFileExtension(file.getOriginalFilename());
        //判断文件后缀名是否合法
        if (!Arrays.asList(DEFAULT_ALLOWED_EXTENSION).contains(extension)) {
            throw new BusinessException();
        }
        //判断文件是否超出限制大小
        long size = file.getSize();
        if (maxSize != -1 && size > maxSize) {
            throw new BusinessException("超出文件大小限制");
        }
    }

    public static boolean uploadImage(MultipartFileParam mParam) {
        assertAllowed(mParam.getFile(), mParam.getSize());
        boolean flag = false;
        int chunks = mParam.getChunks();
        String folder = defaultBaseDir + BIG_FILE_PATH + datePath();
        System.out.println(folder);
        if (chunks == 1 || chunks == 0) {//统一放在图片下
            File file = new File(defaultBaseDir + OTHERF_PATH + datePath() + "/" + mParam.getName());
            if (!file.isDirectory()) {
                file.mkdirs();//创建文件夹
            }
            try {
                byte[] bytes = mParam.getFile().getBytes();
                Path path = Paths.get(defaultBaseDir + OTHERF_PATH + datePath() + "/" + mParam.getName());
                java.nio.file.Files.write(path, bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            File dir = new File(folder + "/" + mParam.getName() + ".tmp");
            if (!dir.exists()) {//如果文件夹不存在
                dir.mkdirs();//创建文件夹
            }
            try {
                byte[] bytes = mParam.getFile().getBytes();
                Path path = Paths.get(folder + "/" + mParam.getName() + ".tmp/" + mParam.getChunk());
                java.nio.file.Files.write(path, bytes);
                flag = true;
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return flag;

    }

    /**
     * 断点上传
     *
     * @param mParam
     * @return
     */
    public static Map<String, String> mergeImage(MultipartFileParam mParam) {
        Map<String, String> map = new HashMap<>();
        String newFilename = UUID.randomUUID().toString().replaceAll("-", "") + "." + Files.getFileExtension(mParam.getName());
        File dir = new File(defaultBaseDir + BIG_FILE_PATH + datePath() + "/" + mParam.getName() + ".tmp");
        File file2 = null;
        if (dir.exists()) {
            File[] files = dir.listFiles();
            List<File> fileList = Arrays.asList(files);
            //按时间排序
            Collections.sort(fileList, new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    if (o1.isDirectory() && o2.isFile())
                        return -1;
                    if (o1.isFile() && o2.isDirectory())
                        return 1;
                    return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
                }
            });

            for (File file : fileList) {
                System.out.println(file.getAbsolutePath());
                try {
                    //读取小文件的输入流
                    InputStream in = new FileInputStream(file);
                    //写入大文件的输出流
                    file2 = new File(defaultBaseDir + BIG_FILE_PATH + datePath() + "/" + newFilename);
                    OutputStream out = new FileOutputStream(file2, true);
                    int len = -1;
                    byte[] bytes = new byte[1 * 1024 * 1024];
                    while ((len = in.read(bytes)) != -1) {
                        out.write(bytes, 0, len);
                    }
                    out.close();
                    in.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                file.delete();
            }
            dir.delete();
        }
        map.put("imagePath", file2.getAbsolutePath().substring(12));
        map.put("imageUUID", newFilename.substring(0, newFilename.lastIndexOf(".")));
        map.put("fileSize", String.valueOf(file2.length()));
        return map;

    }

    /**
     * 断点下载镜像文件
     *
     * @param eduCourseImagesModel
     * @param request
     * @param response
     * @return
     */
    public static boolean downloadImage(EduCourseImagesModel eduCourseImagesModel, HttpServletRequest request, HttpServletResponse response) {
        boolean flag = false;
        String fullPath = eduCourseImagesModel.getImagePath();
        File downloadFile = new File(defaultBaseDir + "/" + fullPath);
        ServletContext context = request.getServletContext();
        String mimeType = context.getMimeType(fullPath);
        if (mimeType == null) {
            // set to binary type if MIME mapping not found
            mimeType = "application/octet-stream";
        }
        // set content attributes for the response
        response.setContentType(mimeType);
        // response.setContentLength((int) downloadFile.length());
        // set headers for the response
        String headerKey = "Content-Disposition";
        String headerValue = String.format("attachment; filename=\"%s\"", downloadFile.getName());
        response.setHeader(headerKey, headerValue);
        // 解析断点续传相关信息
        response.setHeader("Accept-Ranges", "bytes");
        long downloadSize = downloadFile.length();
        long fromPos = 0, toPos = 0;
        if (request.getHeader("Range") == null) {
            response.setHeader("Content-Length", downloadSize + "");
        } else {
            // 若客户端传来Range,说明之前下载了一部分,设置206状态(SC_PARTIAL_CONTENT)
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String range = request.getHeader("Range");
            String bytes = range.replaceAll("bytes=", "");
            String[] ary = bytes.split("-");
            fromPos = Long.parseLong(ary[0]);
            if (ary.length == 2) {
                toPos = Long.parseLong(ary[1]);
            }
            int size;
            if (toPos > fromPos) {
                size = (int) (toPos - fromPos);
            } else {
                size = (int) (downloadSize - fromPos);
            }
            response.setHeader("Content-Length", size + "");
            downloadSize = size;
        }
        // Copy the stream to the response's output stream.
        RandomAccessFile in = null;
        OutputStream out = null;
        try {
            in = new RandomAccessFile(downloadFile, "rw");
            // 设置下载起始位置
            if (fromPos > 0) {
                in.seek(fromPos);
            }
            // 缓冲区大小
            int bufLen = (int) (downloadSize < 2048 ? downloadSize : 2048);
            byte[] buffer = new byte[bufLen];
            int num;
            int count = 0; // 当前写到客户端的大小
            out = response.getOutputStream();
            while ((num = in.read(buffer)) != -1) {
                out.write(buffer, 0, num);
                count += num;
                //处理最后一段,计算不满缓冲区的大小
                if (downloadSize - count < bufLen) {
                    bufLen = (int) (downloadSize - count);
                    if (bufLen == 0) {
                        break;
                    }
                    buffer = new byte[bufLen];
                }
            }
            response.flushBuffer();
            flag = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != in) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }

}

5.MultipartFileParam

public class MultipartFileParam {
    //任务ID
    private String id;
    //总分片数量
    private int chunks;
    //当前为第几块分片
    private int chunk;
    //当前分片大小
    private long size = 0L;
    //文件名
    private String name;
    //分片对象
    private MultipartFile file;
    // MD5
    private String md5;

       get set 略

6.yml大文件上传配置

注 enabled:true需要去掉,改false测试也是不可以

7.补html代码(我使用了bootstrap.min.css)

<link rel="stylesheet" href="../../css/bootstrap.min.css" media="all"/>
<link rel="stylesheet" href="../../css/webuploader.css" media="all"/>
<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="panel panel-default">
                <div class="panel-heading">上传文件</div>
                <div class="panel-body">
                    <div id="picker">选择文件</div>
                    <div id="filelist"></div>
                </div>

            </div>
        </div>
        <div class="col-md-4">
            <div class="panel panel-default">
                <div class="panel-heading">上传进度及日志</div>
                <div class="panel-body">
                    <div>
                        <div class="progress">
                            <div class="progress-bar progress-bar-warning progress-bar-striped" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%" id="percentage_a">
                                <span id="percentage">0%</span>
                            </div>
                        </div>
                    </div>
                    <div id="log" style="max-height: 600px;overflow: auto;">
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="panel panel-default">
                <div class="panel-heading">上传控制</div>
                <div class="panel-body">
                    <div id="ctlBtn" class="btn-group">
                        <input class="btn btn-primary" type="button" value="开始上传">
                    </div>
                    <div class="btn-group">
                        <input class="btn btn-danger" type="button" value="暂停" id="stop">
                    </div>
                    <div class="btn-group">
                        <input class="btn btn-success" type="button" value="继续" id="start">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script type="text/javascript" src="jquery1.11.1.min.js"></script>
<script type="text/javascript" src="webuploader.js"></script>

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值