第三节 使用Web Uploader文件分片上传

亮子 2022-12-09 06:32:01 10492 0 0 0

1、下载组件

1)、直接下载

# 下载地址
http://fex.baidu.com/webuploader/download.html

# github下载地址
https://github.com/fex-team/webuploader/releases

2)、自己编译打包

  • 环境依赖
git命令行工具
node & npm命令行工具
grunt (npm install grunt-cli -g)
  • 编译代码
# 1、克隆代码
git clone https://github.com/fex-team/webuploader.git

# 2、安装node依赖
npm install

# 3、执行grunt dist,此动作会在dist目录下面创建合并版本的js, 包括通过uglify压缩的min版本。
grunt dist

图片alt

2、前端页面index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
    <!--引入CSS-->
    <link rel="stylesheet" type="text/css" href="webuploader-0.1.5/webuploader.css">

    <!--引入JS-->
    <script type="text/javascript" src="jquery/jquery-3.6.1.min.js"></script>
    <script type="text/javascript" src="webuploader-0.1.5/webuploader.js"></script>
</head>
<body>

<div id="uploader" class="wu-example">
    <!--用来存放文件信息-->
    <div id="thelist" class="uploader-list"></div>
    <div class="btns">
        <div id="picker">选择文件</div>
        <button id="ctlBtn" class="btn btn-default">开始上传</button>
    </div>
</div>
<script type="text/javascript">
    $(function() {
        //开始上传按钮
        var $btn = $('#ctlBtn');
        //文件信息显示区域
        var $list = $('#thelist');
        //当前状态
        var state = 'pending';
        //初始化Web Uploader
        var uploader = WebUploader.create({
            // swf文件路径
            swf: '/webuploader-0.1.5/Uploader.swf',
            // 文件接收服务端。
            //server: 'http://www.hangge.com/upload.php',
            server:'/fileupload',
            // 选择文件的按钮。可选。
            // 内部根据当前运行是创建,可能是input元素,也可能是flash.
            pick: '#picker',
            //设置文佳上传的类型格式
            // accept: {  //不建议使用,使用时选择文件div失效
            //   title: 'file',
            //  extensions: 'xls,xlsx,word,doc,ppt,docx,rtf,ppt,txt,pptx,pdf',
            //  mimeTypes: '.xls,.xlsx,.word,.doc,.ppt,.docx,.rtf,.ppt,.txt,.pptx,.pdf'
            // },
            // 设置分片
            chunked: true,
            // 设置分片大小为5M
            chunkSize: 5242880
        });

        // 当有文件被添加进队列的时候(选择文件后调用)
        uploader.on( 'fileQueued', function( file ) {
            $list.append( '<div id="' + file.id + '" class="item">' +
                '<h4 class="info">' + file.name + '</h4>' +
                '<p class="state">等待上传...</p>' +
                '</div>' );
        });

        // 文件上传过程中创建进度条实时显示。
        uploader.on( 'uploadProgress', function( file, percentage ) {
            var $li = $( '#'+file.id );
            $li.find('p.state').text('上传中(' + parseInt(percentage * 100) + '%)');
        });

        // 文件上传成功后会调用
        uploader.on( 'uploadSuccess', function( file ) {
            $( '#'+file.id ).find('p.state').text('已上传');
            savefilemanager(file);
        });

        // 文件上传失败后会调用
        uploader.on( 'uploadError', function( file ) {
            $( '#'+file.id ).find('p.state').text('上传出错');
        });

        // 文件上传完毕后会调用(不管成功还是失败)
        uploader.on( 'uploadComplete', function( file ) {
            $( '#'+file.id ).find('.progress').fadeOut();
        });

        // all事件(所有的事件触发都会响应到)
        uploader.on( 'all', function( type ) {
            if ( type === 'startUpload' ) {
                state = 'uploading';
            } else if ( type === 'stopUpload' ) {
                state = 'paused';
            } else if ( type === 'uploadFinished' ) {
                state = 'done';
            }

            if ( state === 'uploading' ) {
                $btn.text('暂停上传');
            } else {
                $btn.text('开始上传');
            }
        });

        function savefilemanager(file) {
            console.log('filename='+file.name)

            let param = {};
            param.id = file.id;
            param.name = file.name;
            param.size = file.size;
            param.type = file.type;
            param.ext = file.ext;
            param.lastModifiedDate = file.lastModifiedDate;
            param.md5 = file.md5;

            $.ajax({
                url: "/uploadFileEnd",
                data: JSON.stringify(param),
                method: "post",
                dataType: "json",
                contentType: 'application/json',
                success: function (data) {
                    console.log(data)
                    if (data.success) {
                        $.messager.alert("系统提示","添加成功","info");

                    } else {
                        $.messager.alert("系统提示","添加失败","error");
                    }
                }
            });

        };

        // 开始上传按钮点击事件响应
        $btn.on( 'click', function() {
            if ( state === 'uploading' ) {
                uploader.stop();
            } else {
                uploader.upload();
            }
        });
    });
</script>
</body>
</html>

3、后端代码

  • 实体类
package com.shenma2005.vo;

import lombok.Data;

import java.io.Serializable;

/**
 * @author 军哥
 * @version 1.0
 * @description: FileUploadEndVo
 * @date 2022/12/9 10:02
 */

@Data
public class FileUploadEndVo implements Serializable {
    private String id;
    private String name;
    private Integer size;
    private String type;
    private String ext;
    private String md5;
    private String lastModifiedDate;
}
  • 接口类
package com.shenma2005.controller;

import com.shenma2005.vo.FileUploadEndVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;

/**
 * @author 军哥
 * @version 1.0
 * @description: Web Uploader 分片上传接口
 * @date 2022/12/9 8:19
 */

@Controller
@Slf4j
@RequestMapping(value = "/")
public class IndexController {

    @Autowired
    private RedisTemplate redisTemplate;

    final String uploadDir = "D:\\temp\\upload";

    @GetMapping(value = "/index")
    public String index() {
        return "index";
    }

    /**
     * 接收上传的分片文件
     * @param request
     * @param response
     * @return
     * @throws Exception
     */

    @RequestMapping("/fileupload")
    @ResponseBody
    public String doulefileupload(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String id = request.getParameter("id");
        String name = request.getParameter("name");
        String type = request.getParameter("type");
        String lastModifiedDate = request.getParameter("lastModifiedDate");
        String size = request.getParameter("size");
        String chunks = request.getParameter("chunks");
        String chunk = request.getParameter("chunk");

        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("file");

        long size1 = file.getSize();
        String originalFilename = file.getOriginalFilename();
        System.out.println(""+size1+originalFilename);

        // 更新上传文件信息
        String uploadFileKey = "UPLOAD_" + id + "_" + size;
        log.info("uploadFileKey1="+uploadFileKey);
        redisTemplate.opsForHash().put(uploadFileKey, "id", id);
        redisTemplate.opsForHash().put(uploadFileKey, "name", name);
        redisTemplate.opsForHash().put(uploadFileKey, "type", type);
        redisTemplate.opsForHash().put(uploadFileKey, "lastModifiedDate", lastModifiedDate);
        redisTemplate.opsForHash().put(uploadFileKey, "size", size);
        redisTemplate.opsForHash().put(uploadFileKey, "chunks", chunks);
        redisTemplate.opsForHash().put(uploadFileKey, "chunk", chunk);

        // 准备目录
        String storeDir = uploadDir + File.separator + uploadFileKey;
        log.info("storeDir1="+storeDir);

        File fileFolder = new File(storeDir);
        if (!fileFolder.exists()) {
            boolean mkdirs = fileFolder.mkdirs();
            log.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", storeDir, mkdirs);
        }

        // 存储文件
        String tempName = UUID.randomUUID().toString();
        String fileName = storeDir + File.separator + tempName;
        log.info("fileName1="+fileName);


        File dest = new File(fileName);
        file.transferTo(dest);

        // 上传成功,设置成功标识
        String storeFileKey = "STORE_" + id + "_" + size;
        log.info("storeFileKey1={},chunk={}", storeFileKey,chunk);
        redisTemplate.opsForHash().put(storeFileKey, chunk, fileName);

        return name;
    }

    /**
     * 所有分片上传成功,开始问卷合并
     * @param fileUploadEndVo
     * @return
     */

    @PostMapping(value = "/uploadFileEnd")
    @ResponseBody
    public String uploadFileEnd(@RequestBody FileUploadEndVo fileUploadEndVo) {
        System.out.println(""+fileUploadEndVo.toString());

        try {
            // 检查文件是否全部上传完成
            String uploadFileKey = "UPLOAD_" + fileUploadEndVo.getId() + "_" + fileUploadEndVo.getSize();
            log.info("uploadFileKey="+uploadFileKey);

            String chunks = (String)redisTemplate.opsForHash().get(uploadFileKey, "chunks");
            log.info("chunks2="+chunks);


            Boolean isFinish = true;
            String storeFileKey = "STORE_" + fileUploadEndVo.getId() + "_" + fileUploadEndVo.getSize();

            for (int index = 0; index < Integer.valueOf(chunks); index++) {

                log.info("storeFileKey2="+storeFileKey);

                if(!redisTemplate.opsForHash().hasKey(storeFileKey, ""+index)) {
                    isFinish = false;
                    log.error("storeFileKey={},key={}", storeFileKey, ""+index);
                    break;
                }
            }
            if(!isFinish) {
                return "ERROR";
            }

            // 合并文件
            String storeFile = uploadDir + File.separator + UUID.randomUUID().toString() + "." + fileUploadEndVo.getExt();
            File resultFile = new File(storeFile);
            BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(resultFile));
            int bufSize = 1024*4;
            byte[] buffer = new byte[bufSize];

            for (int index = 0; index < Integer.valueOf(chunks); index++) {
                String tempFile = (String)redisTemplate.opsForHash().get(storeFileKey, ""+index);

                BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(tempFile));
                int readcount;
                while ((readcount = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, readcount);
                }
                inputStream.close();
                Files.delete(Paths.get(tempFile));
            }
            outputStream.close();

            // 删除临时文件:直接删除目录
            String storeDir = uploadDir + File.separator + uploadFileKey;
            Files.deleteIfExists(Paths.get(storeDir));

            // 存入数据库,并删除缓存文件
            redisTemplate.delete(uploadFileKey);
            redisTemplate.delete(storeFileKey);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }



        return fileUploadEndVo.getName();
    }
}

4、修改配置文件

## 文件上传大小限制
spring.servlet.multipart.max-request-size=100MB
spring.servlet.multipart.max-file-size=100MB

参考文章