无插件实现大文件分片上传,断点续传

1. 简介:
本篇文章基于实际项目的开发,将介绍项目中关于大文件分片上传、文件验证、断点续传、手动重试上传等需求的使用场景及实现;

2. 项目需求
#####1. 在一个音视频的添加中,既要有音视频的简介(如音视频内容文字介绍、自定义主题名称等一些基本的信息),又要有音视频所需要的多个文件(就像电视剧,一部电视剧有多集一样)。在数据库中具体表现为一对多的关系,即一个视频对应多个文件。下文就以电视剧为例

#####2. 如果一个电视剧中,既有上百兆的,也有几十兆的视频,但是如果在不稳定的一个网络环境中,c传输大文件时,客户想先把小的视频上传了,之后再来继续传未传完的大文件声誉部分,这就需要断点续传的功能;

#####3. 电视剧中至少有一集(至少有一个文件),无文件的电视剧基本信息无效;

3. 需求分析
1. 确定电视剧基本信息(自定义名称,内容简介、演员简介、播出时间等)及文件的上传方式
- 基本信息和音视频文件分开上传(因为在原有的数据库表设计中,文件表是关联于基本信息,所以必须要有音视频主键,才能在数据库添加对应的文件信息),取得基本信息主键之后再去上传文件;
1
2. 文件断点续传中,如何分片;文件接收方式;服务器端如何判断是哪个文件的分片;如何拼接各个分片;上传过程中发生意外情况(如断网,关闭浏览器),如何处理?
- 分片方式: 在客户端进行分片;
- 服务器端接收方式:使用MultipartFile接收文件
- 服务器端确定是哪个文件的分片: 在客户端按照一定规则(UUID或其他方式)生成唯一名称,在服务器端直接找到与该名称相同的文件片段;
-  拼接文件分片: 使用NIO的方式,将分片追加到已有分片的后面;
- 上传中发生意外: 
    A.  断网: 该情况类似于暂停上传,上传到文件处于暂停状态,网络恢复,即可点击继续上传按钮,继续上传;
    B.  关闭浏览器: 在关闭时,给用户提示框,询问是否继续保存,若不保存,则根据视频基本信息表的主键的删除脏数据;
    C.  第一个文件在上传时候,被用户取消或者断网,则服务器端未修改基本信息为有效,并且也未标记该文件为有效记录,可以理解为脏数据,但不需要清理这些数据(在查询的时候,不能查出这些无效记录,可以在更新视频基本信息记录的时候,查找这些脏数据,并清理磁盘上及数据表中的记录);

4. 实现
1. 基于以上分析,搭建一个简单的web项目工程,如下图:


2. 前端主要功能实现
// 文件分割上传
        // 文件大小和分割起点
        // 注释的是本地存储实现
        var size = file.size, start = localStorage[fileid] * 1 || 0;
        //start = $("filelist_" + fileid).filesize;
        console.log("start1:"+start);
        if (size == start) {
            // 已经传过了
            fileArray.shift();
            if (delete fileArray[fileid]) console.log(fileArray.join() + "---上传成功");
            objStateElement.success(fileid, now);
            // 回调
            onsuccess.call(fileid, {});
            localStorage.clear();
            return;
        }

        var funFileSize = function() {
            if (file.flagPause == true) {
                onpause.call(fileid);
                return;
            }
            var data = new FormData();
            data.append("name", encodeURIComponent(file.name));
            data.append("fileid", fileid);
            data.append("file", file.slice(start, start + fileSplitSize));
            data.append("start", start + "");
            var p = "?name="+encodeURIComponent(file.name)+"&fileid"+fileid+"&start"+start;
            // XMLHttpRequest 2.0 请求
            var xhr = new XMLHttpRequest();
            xhr.open("post", eleForm.action, true);             
            // 上传进度中
            xhr.upload.addEventListener("progress", function(e) {
                objStateElement.backgroundSize(fileid, (e.loaded + start) / size * 100);
            }, false);
            // ajax成功后
            xhr.onreadystatechange = function(e) {
                if (xhr.readyState == 4) {
                    if (xhr.status == 200) {
                        try {
                            var json = JSON.parse(xhr.responseText);
                        } catch (e) {
                            objStateElement.error(fileid);
                            return;
                        } 
                        //var json = JSON.parse(xhr.responseText);
                        if (!json || !json.succ) {
                            objStateElement.error(fileid);
                            onerror.call(fileid, json);
                            return;
                        }

                        if (start + fileSplitSize >= size) {
                            // 超出,说明全部分割上传完毕
                            // 上传队列中清除者一项
                            fileArray.shift();
                            if (delete fileArray[fileid]) console.log(fileArray.join() + "---上传成功");
                            objStateElement.success(fileid, now);
                            // 回调
                            onsuccess.call(fileid, json);
                            localStorage.clear();
                        } else {
                            // 尚未完全上传完毕                     
                            // 改变下一部分文件的起点位置
                            start += fileSplitSize;
                            // 存储上传成功的文件点,以便出现意外的时候,下次可以断点续传
                            localStorage.setItem(fileid, start + "");                           
                            // 上传下一个分割文件
                            funFileSize();

                        }       
                    } else {
                        objStateElement.error(fileid);
                    }
                }
            };

前端向后台提交文件

    var xhr_filesize = new XMLHttpRequest();
            xhr_filesize.open("GET", "/BigFileUpload/ajaxFilesUploadServlet?filename=" + nameArray.join(), true);
            xhr_filesize.onreadystatechange = function(e) {
                if (xhr_filesize.readyState == 4) {
                    if (xhr_filesize.status == 200 && xhr_filesize.responseText) {
                        var json = JSON.parse(xhr_filesize.responseText);
                        if (json.succ && json.data) {
                            for (var key in json.data) {
                                if (json.data[key] > 0 && json.data[key] < fileArray[key].size) {                                   
                                    objStateElement.backgroundSize(key, json.data[key] / fileArray[key].size * 100);
                                    objStateElement.keep(key);
                                } 
                                $("filelist_" + key).filesize = json.data[key];
                            }
                        }
                    }
                }
            };
            xhr_filesize.send();
        }

3.后台接收文件
 /***
    * @Description: 上传流文件并保存
    * @param request
    * @param response
    * @throws ServletException
    * @throws IOException
    * @version: v1.1.0
    * @author: xiangdong.she
    * @date: Nov 9, 2017 
    *
    * Modification History:
    * Date         Author          Version            Description
    *-------------------------------------------------------------
    * Nov 9, 2017    xiangdong.she     v1.1.0               修改原因
    */
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {

        request.setCharacterEncoding("utf-8"); //设置编码

        JSONObject json = new JSONObject(); //返回的json串

        String filename = ""; //文件名称
        String path = request.getRealPath("/upload"); //获取文件需要上传到的路径

        try
        {
            List<FileItem> items = new ServletFileUpload(
                    new DiskFileItemFactory()).parseRequest(request);
            for (FileItem item : items)
            {
                if (item.isFormField())
                {

                    String fieldname = item.getFieldName();
                    String fieldvalue = "";
                    if (fieldname.equals("name"))
                    {
                        filename = fieldvalue = URLDecoder.decode(item.getString(),
                                "UTF-8");

                    }
                    else
                    {
                        fieldvalue = item.getString();
                    }

                    System.out.println("fieldname:" + fieldname
                            + "--fieldvalue:" + fieldvalue);
                    // to do list
                }
                else
                {
                    String fieldname = item.getFieldName();
                    InputStream filecontent = item.getInputStream();
                    System.out.println("fieldname:" + fieldname + "--filename:"
                            + filename + "---filecontent:" + filecontent
                            + "---path:" + path);
                    //手动写入硬盘
                    if (makeDir(path))
                    {
                        createFile(path, filename);
                    }

                    File file = new File(path + File.separator + filename);
                    FileOutputStream fos = new FileOutputStream(file, true);
                    InputStream is = item.getInputStream();
                    IOUtils.copy(is, fos);
                    is.close();
                    fos.close();

                    System.out.println("获取上传文件的总共的容量:" + item.getSize());
                }
            }
        }
        catch (FileUploadException e)
        {
            e.printStackTrace();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        json.put("succ", true);
        response.setContentType("text/plain");
        response.getWriter().write(json.toString());
    }

 


无插件实现大文件分片上传,断点续传

猜你喜欢

转载自blog.csdn.net/ftaomanman/article/details/87092239