精华内容
下载资源
问答
  • springboot 大文件上传,支持分片并发上传、断点续传、秒传,已经测试过1.2G的文件,最大支持理论无限制 博文链接:https://blog.csdn.net/haohao123nana/article/details/54692669
  • 文件分片上传

    2020-07-12 10:30:39
    文件上传,支持分片并发上传、断点续传、秒传,已经测试过1.2G的文件,最大支持理论无限制
  • 前端读取文件的大小然后文件分片传给后端,后端将每一片数据重新组合成文件。大概的过程是:前端将整个文件的md5、size(大小)、name(文件名)、ext(文件后缀)、totalchunk(分片总数)与分片文件的md5、chunk...
  • 网上找了好多分片上传,分段上传大文件的,但是构造表单时总是构造不成功,花了一周时间才找到的解决办法,后端只需按文件类型接收文件,并拼接即可。
  • 前端WebUploader后端Java大文件分片上传,实际测试可用
  • HTTP方式文件分片断点下载

    千次阅读 2019-05-11 12:05:25
    本文就通过http方式进行文件分片断点下载,进行实战说明。 HTTP之Range 在开始之前有必要了解一下相关概念及原理,即:HTTP之Range,才能更好的理解分片断点下载的原理。 什么是Range Range是一个H...

    前言

          在进行大文件或网络带宽不是很好的情况下,分片断点下载就会显得很有必要,目前各大下载工具,如:迅雷,都是很好的支持分片断点下载功能的。本文就通过http方式进行文件分片断点下载,进行实战说明。


    HTTP之Range

         在开始之前有必要了解一下相关概念及原理,即:HTTP之Range,才能更好的理解分片断点下载的原理。

    什么是Range

         Range是一个HTTP请求头,告知服务器要返回文件的哪一部分,即:哪个区间范围(字节)的数据,在 Range 中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回  416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略  Range  头,从而返回整个文件,状态码用 200 。

         因为有了HTTP中Range请求头的存在,分片断点下载,便简单了许多。

         当你正在看大片时,网络断了,你需要继续看的时候,文件服务器不支持断点的话,则你需要重新等待下载这个大片,才能继续观看。而Range支持的话,客户端就会记录了之前已经看过的视频文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个视频文件发送回客户端,以此节省网络带宽。

    Range规范

    Range: <unit>=<range-start>-
    Range: <unit>=<range-start>-<range-end>
    Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
    Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

    <unit>:范围所采用的单位,通常是字节(bytes)

    <range-start>:一个整数,表示在特定单位下,范围的起始值

    <range-end>:一个整数,表示在特定单位下,范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束。

    Range: bytes=1024-2048

    分片断点下载之实现

    以Java Spring Boot的方式来实现,核心代码如下:

    • serivce层

    package com.xcbeyond.common.file.chunk.service.impl;
    
    import com.xcbeyond.common.file.chunk.service.FileService;
    import org.springframework.stereotype.Service;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    
    /**
     * 文件分片操作Service
     * @Auther: xcbeyond
     * @Date: 2019/5/9 23:02
     */
    @Service
    public class FileServiceImpl implements FileService {
    
        /**
         * 文件分片下载
         * @param range http请求头Range,用于表示请求指定部分的内容。
         *              格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容
         * @param request
         * @param response
         */
        public void fileChunkDownload(String range, HttpServletRequest request, HttpServletResponse response) {
            //要下载的文件,此处以项目pom.xml文件举例说明。实际项目请根据实际业务场景获取
            File file = new File(System.getProperty("user.dir") + "\\pom.xml");
    
            //开始下载位置
            long startByte = 0;
            //结束下载位置
            long endByte = file.length() - 1;
    
            //有range的话
            if (range != null && range.contains("bytes=") && range.contains("-")) {
                range = range.substring(range.lastIndexOf("=") + 1).trim();
                String ranges[] = range.split("-");
                try {
                    //根据range解析下载分片的位置区间
                    if (ranges.length == 1) {
                        //情况1,如:bytes=-1024  从开始字节到第1024个字节的数据
                        if (range.startsWith("-")) {
                            endByte = Long.parseLong(ranges[0]);
                        }
                        //情况2,如:bytes=1024-  第1024个字节到最后字节的数据
                        else if (range.endsWith("-")) {
                            startByte = Long.parseLong(ranges[0]);
                        }
                    }
                    //情况3,如:bytes=1024-2048  第1024个字节到2048个字节的数据
                    else if (ranges.length == 2) {
                        startByte = Long.parseLong(ranges[0]);
                        endByte = Long.parseLong(ranges[1]);
                    }
    
                } catch (NumberFormatException e) {
                    startByte = 0;
                    endByte = file.length() - 1;
                }
            }
    
            //要下载的长度
            long contentLength = endByte - startByte + 1;
            //文件名
            String fileName = file.getName();
            //文件类型
            String contentType = request.getServletContext().getMimeType(fileName);
    
            //响应头设置
            //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Ranges
            response.setHeader("Accept-Ranges", "bytes");
            //Content-Type 表示资源类型,如:文件类型
            response.setHeader("Content-Type", contentType);
            //Content-Disposition 表示响应内容以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
            // 这里文件名换成下载后你想要的文件名,inline表示内联的形式,即:浏览器直接下载
            response.setHeader("Content-Disposition", "inline;filename=pom.xml");
            //Content-Length 表示资源内容长度,即:文件大小
            response.setHeader("Content-Length", String.valueOf(contentLength));
            //Content-Range 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
            response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());
    
            response.setStatus(response.SC_OK);
            response.setContentType(contentType);
    
            BufferedOutputStream outputStream = null;
            RandomAccessFile randomAccessFile = null;
            //已传送数据大小
            long transmitted = 0;
            try {
                randomAccessFile = new RandomAccessFile(file, "r");
                outputStream = new BufferedOutputStream(response.getOutputStream());
                byte[] buff = new byte[2048];
                int len = 0;
                randomAccessFile.seek(startByte);
                //判断是否到了最后不足2048(buff的length)个byte
                while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
                    outputStream.write(buff, 0, len);
                    transmitted += len;
                }
                //处理不足buff.length部分
                if (transmitted < contentLength) {
                    len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
                    outputStream.write(buff, 0, len);
                    transmitted += len;
                }
    
                outputStream.flush();
                response.flushBuffer();
                randomAccessFile.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (randomAccessFile != null) {
                        randomAccessFile.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    • controller层

    package com.xcbeyond.common.file.chunk.controller;
    
    import com.xcbeyond.common.file.chunk.service.FileService;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 文件分片操作Controller
     * @Auther: xcbeyond
     * @Date: 2019/5/9 22:56
     */
    @RestController
    public class FileController {
        @Resource
        private FileService fileService;
    
        /**
         * 文件分片下载
         * @param range http请求头Range,用于表示请求指定部分的内容。
         *              格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容
         * @param request   http请求
         * @param response  http响应
         */
        @RequestMapping(value = "/file/chunk/download", method = RequestMethod.GET)
        public void fileChunkDownload(@RequestHeader(value = "Range") String range,
                                      HttpServletRequest request, HttpServletResponse response) {
            fileService.fileChunkDownload(range,request,response);
        }
    }

     

    通过postman进行测试验证,启动Spring Boot后,如:下载文件前1024个字节的数据(Range:bytes=0-1023),如下:

    注:此处 实现中没有提供客户端,客户端可循环调用本例中下载接口,每次调用指定实际的下载偏移区间range。

     

    请注意响应头Accept-Ranges、Content-Range 

    Accept-Ranges: 表示响应标识支持范围请求,字段的具体值用于定义范围请求的单位,如:bytes。当发现Accept-Range 
    头时,可以尝试继续之前中断的下载,而不是重新开始。

    Content-Range: 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小],如:bytes 0-1023/2185

     

    源码:https://github.com/xcbeyond/common-utils/tree/master/src/main/java/com/xcbeyond/common/file/chunk

    (如果你觉得不错,不妨留下脚步,在GitHub上给个Star)

     

    参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Range

     

    欢迎微信扫码下面二维码,关注微信公众号【程序猿技术大咖】,进行更多交流学习!

     

    展开全文
  • Python实现大文件分片上传

    万次阅读 多人点赞 2017-08-25 00:37:11
    http协议并不是非常适合上传大文件,所以要考虑分片,即把大文件分割后再上传,而WebUploader所做的事,正是将一个大文件分片,一部分一部分的上传到服务器。WebUploader是一个前端框架,所以接收文件的部分需要我们...
    转载请注明出处:
    http://blog.csdn.net/jinixin/article/details/77545140
    


    引言


    想借着这篇文章简要谈谈WebUploader大文件上传与Python结合的实现。

    WebUploader是百度团队对大文件上传的前端实现,而后端需要根据不同的语言自己实现。这里我采用Python语言的Flask框架搭建后端,配合使用Bootstrap前端框架渲染上传进度条,效果图在文章底部。

    WebUploader官网:点这里;WebUploader API:点这里




    实施


    http协议并不是非常适合上传大文件,所以要考虑分片,即把大文件分割后再上传,而WebUploader所做的事,正是将一个大文件分片,一部分一部分的上传到服务器。在上传每个分片的http请求中,需要同时携带:

    1)该文件的唯一标识:task_id;

    2)该文件的分片总数:chunks;

    3)该分片在该文件所有分片中的位置:chunk;

    其中后两个WebUploader已经替我们自动上传了,而第一个task_id仅需要我们调用对应函数即可产生,然后再将其写入form-data。


    WebUploader是一个前端框架,所以接收文件的部分需要我们自己实现,而我选用了Python和其的Flask框架。
后端要做的是接收这一大堆分片,然后将它们重新合并成一个文件,那么有如下三个问题:


    1)如何判定某个分片上传后,是不是整个文件也上传结束了?

    WebUploader已经为我们解决了,详见下面代码。

    <script type="text/javascript">
    $(document).ready(function() {
        var task_id = WebUploader.Base.guid();        //产生task_id,唯一标识该文件
        var uploader = WebUploader.create({
            server: '/upload/accept',                 //服务器接收并处理分片的url地址
            formData: {
                task_id: task_id,                     //上传分片的http请求携带的数据
            },
        });
    
        uploader.on('uploadSuccess', function(file) { //当该文件所有分片均上传成功时调用该函数
            //上传的信息(文件唯一标识符,文件后缀名)
            var data = { 'task_id': task_id, 'ext': file.source['ext'], 'type': file.source['type'] };
            $.get('/upload/complete', data);          //ajax携带data向该url发请求
        });
    });
    </script>


    2)如何处理接收分片和将分片内容写入文件的关系?
    方案一:开一个字符串,一边接收分片,一边将分片里的内容读取出来后添加到字符串末尾;全部分片接收完毕后,再将字符串写入新文件中。
    方案二:创建一个文件,一边接收分片,一边将分片里的内容读取出来写入文件末尾。

    方案三:为每个分片创建一个新的临时文件来保存其内容;待全部分片上传完毕后,再按顺序读取所有临时文件的内容,将数据写入新文件中。


    前两个方案看似不错,但其实有些问题。方案一因等待所有分片的时间过长容易造成内存溢出;由于分片不一定是按序上传,所以方案二也不行;故只能选择方案三了。


    3)后端如何区分不同用户的文件?如何区分同个文件不同分片的先后顺序?
    通过http请求携带的task_id可以区分不同的文件。同个文件分片的先后顺序,可以通过http请求携带的chunk来区分。因此,task_id+chunk的组合可以在众多不同用户不同文件的分片中唯一标记某个分片,即某个文件的某个分片名称是task_id+chunk。


    关键代码


    
前端代码

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <script src="./static/jquery-1.11.1.min.js"></script>
        <script src="./static/bootstrap/js/bootstrap.min.js"></script>
        <script src="./static/webuploader/webuploader.min.js"></script>
        <link rel="stylesheet" type="text/css" href="./static/webuploader/webuploader.css">
        <link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css">
    </head>
    
    <body>
        <div>
            <div id="picker">请选择</div>   <!-- 上传按钮,必须指定id选择器的值 -->
            <div class="progress">         <!-- 进度条 -->
                <div class="progress-bar progress-bar-striped active" role="progressbar" style="width:0%;"></div>
            </div>
        </div>
        <script type="text/javascript">
        $(document).ready(function() {
            var task_id = WebUploader.Base.guid();        //产生task_id
            var uploader = WebUploader.create({           //创建上传控件
                swf: './static/webuploader/Uploader.swf', //swf位置,这个可能与flash有关
                server: '/upload/accept',                 //接收每一个分片的服务器地址
                pick: '#picker',                          //填上传按钮的id选择器值
                auto: true,                               //选择文件后,是否自动上传
                chunked: true,                            //是否分片
                chunkSize: 20 * 1024 * 1024,              //每个分片的大小,这里为20M
                chunkRetry: 3,                            //某分片若上传失败,重试次数
                threads: 1,                               //线程数量,考虑到服务器,这里就选了1
                duplicate: true,                          //分片是否自动去重
                formData: {                               //每次上传分片,一起携带的数据
                    task_id: task_id,
                },
            });
    
            uploader.on('startUpload', function() {       //开始上传时,调用该方法
                $('.progress-bar').css('width', '0%');
                $('.progress-bar').text('0%');
            });
    
            uploader.on('uploadProgress', function(file, percentage) { //一个分片上传成功后,调用该方法
                $('.progress-bar').css('width', percentage * 100 - 1 + '%');
                $('.progress-bar').text(Math.floor(percentage * 100 - 1) + '%');
            });
    
            uploader.on('uploadSuccess', function(file) { //整个文件的所有分片都上传成功,调用该方法
                //上传的信息(文件唯一标识符,文件名)
                var data = {'task_id': task_id, 'filename': file.source['name'] };
                $.get('/upload/complete', data);          //ajax携带data向该url发请求
                $('.progress-bar').css('width', '100%');
                $('.progress-bar').text('上传完成');
            });
    
            uploader.on('uploadError', function(file) {   //上传过程中发生异常,调用该方法
                $('.progress-bar').css('width', '100%');
                $('.progress-bar').text('上传失败');
            });
    
            uploader.on('uploadComplete', function(file) {//上传结束,无论文件最终是否上传成功,该方法都会被调用
                $('.progress-bar').removeClass('active progress-bar-striped');
            });
    
        });
        </script>
    </body>
    
    </html>

    后端代码

    @app.route('/', methods=['GET', 'POST'])
    def index():                                        # 一个分片上传后被调用
        if request.method == 'POST':
            upload_file = request.files['file']
            task = request.form.get('task_id')          # 获取文件唯一标识符
            chunk = request.form.get('chunk', 0)        # 获取该分片在所有分片中的序号
            filename = '%s%s' % (task, chunk)           # 构成该分片唯一标识符
            upload_file.save('./upload/%s' % filename)  # 保存分片到本地
        return rt('./index.html')
    
    
    @app.route('/success', methods=['GET'])
    def upload_success():                               # 所有分片均上传完后被调用
        target_filename = request.args.get('filename')  # 获取上传文件的文件名
        task = request.args.get('task_id')              # 获取文件的唯一标识符
        chunk = 0                                       # 分片序号
        with open('./upload/%s' % target_filename, 'wb') as target_file:  # 创建新文件
            while True:
                try:
                    filename = './upload/%s%d' % (task, chunk)
                    source_file = open(filename, 'rb')                    # 按序打开每个分片
                    target_file.write(source_file.read())                 # 读取分片内容写入新文件
                    source_file.close()
                except IOError:
                    break
                chunk += 1
                os.remove(filename)                     # 删除该分片,节约空间
        return rt('./index.html')


    结果

    效果图

    上传成功

    上传失败


    测试

    三台计算机,一台做服务器,分别在另两台上同时各上传一本电影,大小为2.6G与3.8G;上传完毕后,两本电影均可在服务器上正常播放。


    备注

    如果有想要源码的朋友,可以移步这里。对该项目还会不断改进,如果你感兴趣,不妨star一下,谢谢。


    展开全文
  • 关于使用阿里云oss作大文件分片上传的做法 关于大文件上传,很简单,但是做起来也会碰到各种各样的问题,刚好做了一个,现在记录下过程 需求 公司新项目要做一个大文件上传功能,将一些工程项目模型之类的东西保存...

    关于使用阿里云oss作大文件分片上传的做法

    关于大文件上传,很简单,但是做起来也会碰到各种各样的问题,刚好做了一个,现在记录下过程

    需求

    公司新项目要做一个大文件上传功能,将一些工程项目模型之类的东西保存在线上,动辄上G的文件肯定不能保存在自己的服务器上面,使用阿里云的oss 服务。期间经历了以下几个方法
    方法一:文件从客户端分片上传到服务器。服务器再进行分片上传到oss,已实现,
    缺点:
    1:文件在上传过程中占用服务器带宽。
    2:用户上传的大文件可能删除不及时,在服务器成为垃圾文件,占用系统磁盘。需要另外写定时任务脚本清除。
    优点:敏感数据不会暴露。

    方法二:web 端直接上传 ,已实现。
    缺点:临时 key, secret 会暴露给客户端,有一定风险。
    优点:不会占用服务器资源,直接用户端跟oss 交互。

    方法一 :后端保存上传

    使用webuploader插件 进行分片上传到oss
    先引入插件内容

    <script src="/public/others/jquery.js"></script>
    <script src="/public/webuploader/webuploader.js"></script>
    <link rel ="slylesheet" type="text/css" href="/public/webuploader/webuploader.css">
    

    html代码部分

    <div class="demo">
        <div id="uploadfile">
            <!--用来存放文件信息-->
            <div id="the_2655" class="uploader-list"></div>
            <div class="form-group form-inline">
                <div id="pick_2655" style="float:left">选择文件</div>  
                <button id="Btn_2655" class="btn btn-default" style="padding: 5px 10px;border-radius: 3px;">开始上传</button>
            </div>
        </div>
    </div>
    

    js 部分

    //上传文件函数
    //ids唯一ID
    //folder文件保存目录
    function uploadfiles(ids,folder) {
        $(function(){
            var $list = $("#the_"+ids);
                $btn = $("#Btn_"+ids);
            var uploader = WebUploader.create({
              resize: false, // 不压缩image
              swf: '__PUBLIC__/ueditor/third-party/webuploader/uploader.swf', // swf文件路径
              server: '{:url("admin/upload/uploadFile")}', // 文件接收服务端。
              pick: "#pick_"+ids, // 选择文件的按钮。可选
              chunked: true, //是否要分片处理大文件上传
              chunkSize:5*1024*1024, //分片上传,每片2M,默认是5M
              //fileSizeLimit: 6*1024* 1024 * 1024,    // 所有文件总大小限制 6G
              fileSingleSizeLimit: 10*1024* 1024 * 1024,    // 单个文件大小限制 5 G
              formData: {
                    folder:folder  //自定义参数
              }
              //auto: false //选择文件后是否自动上传
             // chunkRetry : 2, //如果某个分片由于网络问题出错,允许自动重传次数
              //runtimeOrder: 'html5,flash',
              // accept: {
              //   title: 'Images',
              //   extensions: 'gif,jpg,jpeg,bmp,png',
              //   mimeTypes: 'image/*'
              // }
            });
            // 当有文件被添加进队列的时候
            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 ),
                    $percent = $li.find('.progress .progress-bar');
     
                // 避免重复创建
                if ( !$percent.length ) {
                    $percent = $('<div class="progress progress-striped active">' +
                      '<div class="progress-bar" role="progressbar" style="width: 0%">' +
                      '</div>' +
                    '</div>').appendTo( $li ).find('.progress-bar');
                }
     
                $li.find('p.state').text('上传中');
     
                $percent.css( 'width', percentage * 100 + '%' );
            });
            // 文件上传成功
            uploader.on( 'uploadSuccess', function( file,response) {
                $( '#'+file.id ).find('p.state').text('已上传');
                $list.append('<input type="hidden" name="'+ids+'" value="'+response.filePath+'" />');
                //alert(response.filePath);
            });
     
            // 文件上传失败,显示上传出错
            uploader.on( 'uploadError', function( file ) {
                $( '#'+file.id ).find('p.state').text('上传出错');
            });
            // 完成上传完
            uploader.on( 'uploadComplete', function( file ) {
                $( '#'+file.id ).find('.progress').fadeOut();
            });
     
            $btn.on('click', function () {
                if ($(this).hasClass('disabled')) {
                    return false;
                }
                uploader.upload();
                // if (state === 'ready') {
                //     uploader.upload();
                // } else if (state === 'paused') {
                //     uploader.upload();
                // } else if (state === 'uploading') {
                //     uploader.stop();
                // }
            });
        });
    }
    

    注意demo 里边分片的每片文件大小是5M,php 的ini 文件里边最大限制的参数要大于5M,才能上传成功,具体怎么修改的话去百度。

    php 部分

    /**
         * 上传文件函数,如过上传不成功打印$_FILES数组,查看error报错信息
         * 值:0; 没有错误发生,文件上传成功。
         * 值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
         * 值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
         * 值:3; 文件只有部分被上传。
         * 值:4; 没有文件被上传。
         * date:2018.4.18    from:zhix.net
         */
        public function uploadFile(){
            header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
            header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
            header("Content-type: text/html; charset=gbk32");
            header("Cache-Control: no-store, no-cache, must-revalidate");
            header("Cache-Control: post-check=0, pre-check=0", false);
            header("Pragma: no-cache");
            $folder = input('folder');
            if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
                exit; // finish preflight CORS requests here
            }
            if ( !empty($_REQUEST[ 'debug' ]) ) {
                $random = rand(0, intval($_REQUEST[ 'debug' ]) );
                if ( $random === 0 ) {
                    header("HTTP/1.0 500 Internal Server Error");
                    exit;
                }
            }
            // header("HTTP/1.0 500 Internal Server Error");
            // exit;
            // 5 minutes execution time
            set_time_limit(5 * 60);
            // Uncomment this one to fake upload time
             usleep(5000);
            // Settings
              $targetDir = Env::get('root_path').'/upload/test/file_material_tmp'; //存放分片临时目录        
          if($folder){
              $uploadDir =Env::get('root_path').'/upload/test/file_material/';
          }else{
              $uploadDir = Env::get('root_path').'/upload/test/file_material/';    //分片合并存放目录
          }
    
     
            $cleanupTargetDir = true; // Remove old files
            $maxFileAge = 5 * 3600; // Temp file age in seconds
     
            // Create target dir
            if (!file_exists($targetDir)) {
                mkdir($targetDir,0777,true);
            }
            // Create target dir
            if (!file_exists($uploadDir)) {
                mkdir($uploadDir,0777,true);
            }
            // Get a file name
            if (isset($_REQUEST["name"])) {
                $fileName = $_REQUEST["name"];
            } elseif (!empty($_FILES)) {
                $fileName = $_FILES["file"]["name"];
            } else {
                $fileName = uniqid("file_");
            }
            $oldName = $fileName;
     
            $fileName = iconv('UTF-8','gb2312',$fileName);
            $filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
            // $uploadPath = $uploadDir . DIRECTORY_SEPARATOR . $fileName;
            // Chunking might be enabled
            $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
            $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;
            // Remove old temp files
            if ($cleanupTargetDir) {
                if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
                    die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory111."}, "id" : "id"}');
                }
                while (($file = readdir($dir)) !== false) {
                    $tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;
                    // If temp file is current file proceed to the next
                    if ($tmpfilePath == "{$filePath}_{$chunk}.part" || $tmpfilePath == "{$filePath}_{$chunk}.parttmp") {
                        continue;
                    }
                    // Remove temp file if it is older than the max age and is not the current file
                    if (preg_match('/\.(part|parttmp)$/', $file) && (filemtime($tmpfilePath) < time() - $maxFileAge)) {
                        unlink($tmpfilePath);
                    }
                }
                closedir($dir);
            }
            // Open temp file
            if (!$out = fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream222."}, "id" : "id"}');
            }
            if (!empty($_FILES)) {
                if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
                    die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file333."}, "id" : "id"}');
                }
                // Read binary input stream and append it to temp file
                if (!$in = fopen($_FILES["file"]["tmp_name"], "rb")) {
                    die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream444."}, "id" : "id"}');
                }
            } else {
                if (!$in = fopen("php://input", "rb")) {
                    die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream555."}, "id" : "id"}');
                }
            }
            while ($buff = fread($in, 4096)) {
                fwrite($out, $buff);
            }
            fclose($out);
            fclose($in);
            rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");
            $index = 0;
            $done = true;
            for( $index = 0; $index < $chunks; $index++ ) {
                if ( !file_exists("{$filePath}_{$index}.part") ) {
                    $done = false;
                    break;
                }
            }
     
            if ($done) {
                $pathInfo = pathinfo($fileName);
                $hashStr = substr(md5($pathInfo['basename']),8,16);
                $hashName = time() . $hashStr . '.' .$pathInfo['extension'];
                $uploadPath = $uploadDir . DIRECTORY_SEPARATOR .$hashName;
                if (!$out = fopen($uploadPath, "wb")) {
                    die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream666."}, "id" : "id"}');
                }
                //flock($hander,LOCK_EX)文件锁
                if ( flock($out, LOCK_EX) ) {
                    for( $index = 0; $index < $chunks; $index++ ) {
                        if (!$in = fopen("{$filePath}_{$index}.part", "rb")) {
                            break;
                        }
                        while ($buff = fread($in, 4096)) {
                            fwrite($out, $buff);
                        }
                        fclose($in);
                        unlink("{$filePath}_{$index}.part");
                    }
                    flock($out, LOCK_UN);
                }
                fclose($out);
                $response = [
                    'success'=>true,
                    'oldName'=>$oldName,
                    'filePath'=>$uploadPath,
    //                'fileSize'=>$data['size'],
                    'fileSuffixes'=>$pathInfo['extension'],          //文件后缀名
    //                'file_id'=>$data['id'],
                ];
                return json($response);
            }
     
            // Return Success JSON-RPC response
            die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
        }   
    

    后端接收分片,并且组合文件的方法。注意几点
    1:方法的header 头里边要加上 utf8的限制,如下,不然文件名称为中文会乱码
    2:返回的路径是绝对路径,自己根据项目需求自己截取。

        header("Content-type: text/html; charset=utf-8");
    

    文件上传部分:

    <?php
    if (is_file(__DIR__ . '/../autoload.php')) {
        require_once __DIR__ . '/../autoload.php';
    }
    if (is_file(__DIR__ . '/../vendor/autoload.php')) {
        require_once __DIR__ . '/../vendor/autoload.php';
    }
    
    use OSS\OssClient;
    use OSS\Core\OssException;
    
    // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
    $accessKeyId = "<yourAccessKeyId>";
    $accessKeySecret = "<yourAccessKeySecret>";
    // Endpoint以杭州为例,其它Region请按实际情况填写。
    $endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
    $bucket= "<yourBucketName>";
    $object = "<yourObjectName>";
    $file = "<yourLocalFile>";
    
    $options = array(
        OssClient::OSS_CHECK_MD5 => true,
        OssClient::OSS_PART_SIZE => 1,
    );
    try{
        $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
    
        $ossClient->multiuploadFile($bucket, $object, $file, $options);
    } catch(OssException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ":  OK" . "\n");
    

    注意 multiuploadFile 方法,该方法就是分片上传的具体封装的方法,阿里云分片上传是上传一个具体的文件,你自己的每一个分片阿里云是不承认的,所以不要自己尝试上传分片文件。
    具体使用方法参考链接:
    阿里云php oss分片上传描述文档

    上传结果参考文档。

    方法二 ,web前端直传

    web 直传有个问题就是需要 STS临时授权访问
    你需要了解这个玩意
    具体就是阿里云总帐号创建一个子账号,子账号被赋予了oss 的权限,然后拥有oss 权限的子账号相当于一个新的RAM角色,通过这个子账号跟角色创建时候生成的 id和 secret 可以获取一个临时token,通过这个token,前端在不获取总账号权限的情况下可以进行一些操作。具体创建子账号的流程可以看上边链接,赋予的权限选择前端要使用的,根据自己实际情况。

    走的弯路

    阿里云有给到一个获取STS临时授权访问 的接口文档,
    阿里云获取sts
    注意这里的version
    注意这里的version 2015-04-01, 各种尝试不通,报错如下:

    sts.aliyuncs.comInvalidVersionSpecified parameter Version is not valid.

    提工单,回复如下:
    在这里插入图片描述
    我dnmd,不能用你挂这里干嘛?浪费谁的时间?

    附:请求接口的方法:

    class STS
    {
        protected $url = 'https://sts.aliyuncs.com';
        protected $accessKeySecret = '1234567890qwertyuioasdfghj';
        protected $accessKeyId = 'LT11234567898';
        protected $roleArn = 'acs:ram::$accountID:role/$roleName';//指定角色的 ARN ,角色策略权限
        protected $roleSessionName = 'client1';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
        protected $durationSeconds = '1800';//指定的过期时间
        protected $type = 'xxx';//方便调用时获取不同的权限
    
        public function __construct($type)
        {
            $this->type = $type;
            $this->setRoleArn();
        }
    
        public function sts()
        {
            $action = 'AssumeRole';//通过扮演角色接口获取令牌
            date_default_timezone_set('UTC');
            $param = array(
                'Format'           => 'JSON',
                'Version'          => '2015-04-01',
                'AccessKeyId'      => $this->accessKeyId,
                'SignatureMethod'  => 'HMAC-SHA1',
                'SignatureVersion' => '1.0',
                'SignatureNonce'   => $this->getRandChar(8),
                'Action'           => $action,
                'RoleArn'          => $this->roleArn,
                'RoleSessionName'  => $this->roleSessionName,
                'DurationSeconds'  => $this->durationSeconds,
                'Timestamp'        => date('Y-m-d') . 'T' . date('H:i:s') . 'Z'
                //'Policy'=>'' //此参数可以限制生成的 STS token 的权限,若不指定则返回的 token 拥有指定角色的所有权限。
            );
            $param['Signature'] = $this->computeSignature($param, 'POST');
            $res = CurlHandle::httpPost($this->url, $param);//curl post请求
            if ($res) {
                return self::_render($res);
            } else {
                return [];
            }
        }
    
        private static function _render($res)
        {
            $res = json_decode($res, true);
            if (empty($res['Credentials'])) {
                return [];
            } else {
                return [
                    'accessKeySecret' => $res['Credentials']['AccessKeySecret'] ?? '',
                    'accessKeyId'     => $res['Credentials']['AccessKeyId'] ?? '',
                    'expiration'      => $res['Credentials']['Expiration'] ?? '',
                    'securityToken'   => $res['Credentials']['SecurityToken'] ?? '',
                ];
            }
        }
    
        protected function computeSignature($parameters, $setMethod)
        {
            ksort($parameters);
            $canonicalizedQueryString = '';
            foreach ($parameters as $key => $value) {
                $canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
            }
            $stringToSign = $setMethod . '&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
            $signature = $this->getSignature($stringToSign, $this->accessKeySecret . '&');
    
            return $signature;
        }
    
        public function getSignature($source, $accessSecret)
        {
            return base64_encode(hash_hmac('sha1', $source, $accessSecret, true));
        }
    
        protected function percentEncode($str)
        {
            $res = urlencode($str);
            $res = preg_replace('/\+/', '%20', $res);
            $res = preg_replace('/\*/', '%2A', $res);
            $res = preg_replace('/%7E/', '~', $res);
            return $res;
        }
    
        public function getRandChar($length)
        {
            $str = null;
            $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
            $max = strlen($strPol) - 1;
    
            for ($i = 0; $i < $length; $i++) {
                $str .= $strPol[rand(0, $max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
            }
    
            return $str;
        }
    
        protected function setRoleArn()
        {
            if ($this->type == '123') {//根据入参使用不同的策略,当然这里还可以有其他写法兼容更多的策略的情况
                $this->roleArn = 'acs:ram::123456789098:role/=$roleName';
            }
        }
    }
    

    正道的光

    还是要使用sdk ,不过这个sdk 是阿里云已经给封装好的该模块的sdk ,比自己从github里边下载的好使一万倍。tp 的话就放到vendor 下边就行,laravel 的话自己看,能正常引入就行。
    STS的SDK下载链接
    在这里插入图片描述
    下载完成之后引入项目

    简单的请求token 的方法,
    1:注意过期时间,默认是3600s,
    2:accessKeySecret,accessKeyId,这两个参数是你创建的子账号的参数,不是总账号的。

    <?php
    namespace app\services;
     
    class StsService
    {
        protected $url = 'https://sts.aliyuncs.com';
        protected $accessKeySecret;
        protected $accessKeyId;
        protected $roleArn;//指定角色的 ARN ,角色策略权限
        protected $roleSessionName = 'client';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
        protected $durationSeconds = '3600';//指定的过期时间
     
     
        public function __construct()
        {
            $this->accessKeySecret = config('oss_sts_accessKeySecret');
            $this->accessKeyId = config('oss_sts_accessKeyId');
            $this->roleArn = config('oss_sts_roleArn');
        }
     
     
        public function getStsOuah()
        {
     
            require_once VENDOR_PATH.'aliyuncs/sts-server/aliyun-php-sdk-core/Config.php';
     
            $iClientProfile = \DefaultProfile::getProfile("cn-hangzhou", $this->accessKeyId, $this->accessKeySecret);
     
            $client = new \DefaultAcsClient($iClientProfile);
     
            $request = new \Sts\Request\V20150401\AssumeRoleRequest();
     
            $request->setRoleSessionName("client_name");
     
            $request->setRoleArn($this->roleArn);
     
    //        $request->setPolicy(VENDOR_PATH.'aliyuncs/sts-server/policy/bucket_write_policy.txt');
     
            $request->setDurationSeconds($this->durationSeconds);
     
            $response = $client->doAction($request);
     
            $rows = array();
     
            $body = $response->getBody();
     
            $content = json_decode($body);
     
            if ($response->getStatus() == 200){
                $rows['statusCode'] = 200;
                $rows['accessKeyId'] = $content->Credentials->AccessKeyId;
                $rows['accessKeySecret'] = $content->Credentials->AccessKeySecret;
                $rows['expiration'] = $content->Credentials->Expiration;
                $rows['securityToken'] = $content->Credentials->SecurityToken;
            }else{
                $rows['statusCode'] = 500;
                $rows['errorCode'] = $content->Code;
                $rows['errorMessage'] = $content->Message;
            }
     
            return $rows;
        }
     
    }
    

    返回的数据,需要把这些数据传给前端

       Array
    (
        [statusCode] => 200
        [accessKeyId] => STS.NT3aC8UXWJydcuuoyreGfvWxJ
        [accessKeySecret] => 59vT1iGHaJtEiyDwpEN9125ZgBJ4eShfUbtnXiGvQirb
        [expiration] => 2020-06-06T06:11:20Z
        [securityToken] => CAIS9QF1q6Ft5B2yfSjIr5eGKvmMuId2/buzYVPEi3k2achKmZLTqDz2IHlPdHlrBOwesv8/lGFY7fwblqJ4T55IQ1Dza8J148z5Be4Lo8yT1fau5Jko1beHewHleTOZsebWZ+LmNqC/Ht6md1HDkAJq3LL+bk/Mdle5MJqP+/UFB5ZtKWveVzddA8pMLQZPsdITMWCrVcygKRn3mGHdfiEK00he8TonsPXhn5fEskSF3QOhlbAvyt6vcsT+Xa5FJ4xiVtq55utye5fa3TRYgxowr/4t3PwfpGuf4orNUwgBuE/fKYnd/thuKh5kYLM6Gr71cwRkHn730xqAARfcIG9I3u0E7TJzfmTvcIIIpogdqzjed27c1JvZAoAxHu+6XgIBgrbcLds0q2InwBPKWF4S0nfKnI9kXPn1CXtKdjQmgLW0cRz0w9/OfbxTIX9lb2HmtWABxvLVAgQtHJRb4o6xxgkQy5pBs3BeHYJ1yY9NZCDu5zW2OGC03MqZ
    )
    

    前端代码:
    本人 不是专业前端,样式凑合着看吧。

    html

    <div id="up_wrap"></div>
    <div class="form-group">
        <input type="file" id="file" multiple="multiple" />
    </div>
    <div class="form-group">
        <input type="button" class="btn btn-primary" id="file-button" value="Upload" />
        <input type="button" class="btn btn-primary" id="Continue-button" value="Continue" />
    </div>
    

    js

    <script src="http://gosspublic.alicdn.com/aliyun-oss-sdk-6.0.2.min.js"></script>
    <script type="text/javascript">
        var appServer = 'http://www.tele.bh/med/home/sts';//获取ststoken的接口,这边这个地址是我本地的。你们的接口地址自己应该清楚
        var bucket = '你新建的bucket名字';
        var region = 'oss-cn-hangzhou';//前面新建bucket时选择过的。
        var uid = 'x';//用户标识。这个根据自己情况自己定
        var Buffer = OSS.Buffer;
        console.log(OSS);
        //获取授权STSToken,并初始化client
        var applyTokenDo = function (func) {
            var url = appServer;
            return $.get(url).then(function (result) {
                var res = JSON.parse(result);
                var creds = res.data.Credentials;
                console.log(creds);
                var client = new OSS({
                    region: region,
                    accessKeyId: creds.AccessKeyId,
                    accessKeySecret: creds.AccessKeySecret,
                    stsToken: creds.SecurityToken,
                    bucket: bucket
                });
                return func(client);
            });
        };
     
        //上传文件
        var uploadFile = function (client) {
            if (upfiles.length < 1)
                return;
            upfile = upfiles[0];
            var file = upfile.file;
            //key可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
            var key = upfile.name;
            var objkey = key + "_" + uid + ".json";
            return client.multipartUpload(key, file, {
                progress: function (p, cpt, res) {
                    console.log("p:", p);
                    console.log("cpt:", cpt);
                    if (cpt != undefined) {
                        var content = JSON.stringify(cpt);
                        client.put(objkey, new Buffer(content));
                    }
     
                    console.log(Math.floor(p * 100) + '%');
                    var bar = document.getElementById('progress-bar_' + upfile.num);
                    bar.style.width = Math.floor(p * 100) + '%';
                    bar.innerHTML = Math.floor(p * 100) + '%';
     
                }
            }).then(function (res) {
                console.log('upload success: ', res);
                upfiles.shift();
                client.delete(objkey);
                applyTokenDo(uploadFile);
            }).catch(function(err) {
                console.log(err);
                error(err);
            });
        };
     
        //断点续传文件
        var reUploadFile = function (client) {
            if (upfiles.length < 1)
                return;
            upfile = upfiles[0];
            var file = upfile.file;
            //key可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
            var key = upfile.name;
            var objkey = key + "_" + uid + ".json";
            return client.get(objkey).then(function (res) {
                var data = JSON.parse(res.content);
                data.file = file;
                return client.multipartUpload(key, file, {
                    checkpoint: data,
                    progress: function (p, cpt, res) {
                        console.log("p:", p);
                        console.log("cpt:", cpt);
                        if (cpt != undefined) {
                            var content = JSON.stringify(cpt);
                            client.put(objkey, new Buffer(content));
                        }
                        var bar = document.getElementById('progress-bar_' + upfile.num);
                        bar.style.width = Math.floor(p * 100) + '%';
                        bar.innerHTML = Math.floor(p * 100) + '%';
                    }
                }).then(function (ret) {
                    console.log('upload success:', ret);
                    upfiles.shift();
                    client.delete(objkey);
                    applyTokenDo(uploadFile);
                }).catch(function(err) {
                    console.log(err);
                    error(err);
                });
            });
        };
     
        function error(err){
            switch (err.status) {
                case 0:
                    if (err.name == "cancel") { //手动点击暂停上传
                        return;
                    }
                    break;
                case -1: //请求错误,自动重新上传
                    // 重新上传;
                    return;
                case 203: //回调失败
                    // 前端自己给后台回调;
                    return;
                case 400:
                    switch (err.code) {
                        case 'FilePartInterity': //文件Part已改变
                        case 'FilePartNotExist': //文件Part不存在
                        case 'FilePartState': //文件Part过时
                        case 'InvalidPart': //无效的Part
                        case 'InvalidPartOrder': //无效的part顺序
                        case 'InvalidArgument': //参数格式错误
                            // 清空断点;
                            // 重新上传;
                            return;
                        case 'InvalidBucketName': //无效的Bucket名字
                        case 'InvalidDigest': //无效的摘要
                        case 'InvalidEncryptionAlgorithmError': //指定的熵编码加密算法错误
                        case 'InvalidObjectName': //无效的Object名字
                        case 'InvalidPolicyDocument': //无效的Policy文档
                        case 'InvalidTargetBucketForLogging': //Logging操作中有无效的目标bucket
                        case 'MalformedXML': //XML格式非法
                        case 'RequestIsNotMultiPartContent': //Post请求content-type非法
                            // 重新授权;
                            // 继续上传;
                            return;
                        case 'RequestTimeout'://请求超时
                            // 重新上传;
                            return;
                    }
                    break;
                case 403: //授权无效,重新授权
                case 411: //缺少参数
                case 404: //Bucket/Object/Multipart Upload ID 不存在
                    // 重新授权;
                    // 继续上传;
                    return;
                case 500: //OSS内部发生错误
                    // 重新上传;
                    return;
                default:
                    break;
            }
        }
        //文件上传队列
        var upfiles = [];
     
        $(function () {
            //初始化文件上传队列
            $("#file").change(function (e) {
                var ufiles = $(this).prop('files');
                var htm = "";
                for (var i = 0; i < ufiles.length; i++) {
                    htm += "<dl><dt>" + ufiles[i].name + "</dt><dd><div class=\"progress\"><div id=\"progress-bar_" + i + "\" class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"min-width: 2em;\">0%</div></div></dd></dl>";
                    upfiles.push({
                        num: i,
                        name: ufiles[i].name,
                        file: ufiles[i]
                    })
                }
                console.log('upfiles:', upfiles);
                $("#up_wrap").html(htm);
            });
            //上传
            $("#file-button").click(function () {
                applyTokenDo(uploadFile);
            });
            //续传
            $("#Continue-button").click(function () {
                applyTokenDo(reUploadFile);
            })
        })
    </script>
    

    分片上传完成之后会返回带uploadId的文件夹链接,上传给后端就行了。
    注意几点,
    1:角色的权限,RAM 设置的时候要给put, delete 请求权限,不然js会报错。
    2:跨域访问:角色设置的时候要有 Access-Control-Allow-Origin header头,允许跨域访问。

    综合所述

    功能没那么难,问题出在要么阿里云给的给的文档不好使,要么sdk 难用。本人也是综合了好几个百度出来的文档才搞出来的,分享出来希望以后大家少走弯路吧。

    展开全文
  • 之前在潭州教育教学网站上看了一个视频关于java大文件分片与合并自己在练习的时候遇到一些坑,调试了好长时间代码如下:首先配置一个专门放参数的类SplitFileParampublic class SplitFileParam { public static ...

    之前在潭州教育教学网站上看了一个视频关于java大文件的分片与合并

    自己在练习的时候遇到一些坑,调试了好长时间

    代码如下:

    首先配置一个专门放参数的类

    SplitFileParam

    public class SplitFileParam {
        public static String file="C:\\Users\\pc\\Desktop\\photo/1.jpg"; //文件的路径
        public static String outfile="C:\\Users\\pc\\Desktop\\photo/out.jpg"; //文件的路径
        public static int count=10;   //将文件切割成多少份
    }
    

    SplitFile

    package SplitFileUtil;
    
    import jdk.nashorn.internal.runtime.logging.Logger;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    
    
    public class SplitFile {
    
        public static void main(String[] args) {
            getSplitFile();
            merge(SplitFileParam.outfile,SplitFileParam.file,10);
        }
    
        /**
         * 文件分割方法
         */
        public static void getSplitFile() {
            String file = SplitFileParam.file; //文件的路径
    
            int count = SplitFileParam.count; //文件分割的份数
            RandomAccessFile raf = null;
            try {
                //获取目标文件 预分配文件所占的空间 在磁盘中创建一个指定大小的文件   r 是只读
                raf = new RandomAccessFile(new File(file), "r");
                long length = raf.length();//文件的总长度
                long maxSize = length / count;//文件切片后的长度
                long offSet = 0L;//初始化偏移量
                for (int i = 0; i < count - 1; i++) { //最后一片单独处理
                    long begin = offSet;
                    long end = (i + 1) * maxSize;
    //                offSet = writeFile(file, begin, end, i);
                    offSet = getWrite(file, i, begin, end);
                }
                if (length - offSet > 0) {
                    getWrite(file, count-1, offSet, length);
                }
    
            } catch (FileNotFoundException e) {
                System.out.println("没有找到文件");
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        /**
         * 指定文件每一份的边界,写入不同文件中
         * @param file 源文件
         * @param index 源文件的顺序标识
         * @param begin 开始指针的位置
         * @param end 结束指针的位置
         * @return long
         */
        public static long getWrite(String file,int index,long begin,long end){
            String a=file.split(".jpg")[0];
            long endPointer = 0L;
            try {
                //申明文件切割后的文件磁盘
                RandomAccessFile in = new RandomAccessFile(new File(file), "r");
                //定义一个可读,可写的文件并且后缀名为.tmp的二进制文件
                RandomAccessFile out = new RandomAccessFile(new File(a + "_" + index + ".tmp"), "rw");
    
                //申明具体每一文件的字节数组
                byte[] b = new byte[1024];
                int n = 0;
                //从指定位置读取文件字节流
                in.seek(begin);
                //判断文件流读取的边界
                while(in.getFilePointer() <= end && (n = in.read(b)) != -1){
                    //从指定每一份文件的范围,写入不同的文件
                    out.write(b, 0, n);
                }
    
                //定义当前读取文件的指针
                endPointer = in.getFilePointer();
    
                //关闭输入流
                in.close();
                //关闭输出流
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return endPointer;
        }
        /**
         * 文件合并
         * @param file 指定合并文件
         * @param tempFile 分割前的文件名
         * @param tempCount 文件个数
         */
        public static void merge(String file,String tempFile,int tempCount) {
            String a=tempFile.split(".jpg")[0];
            RandomAccessFile raf = null;
            try {
                //申明随机读取文件RandomAccessFile
                raf = new RandomAccessFile(new File(file), "rw");
                //开始合并文件,对应切片的二进制文件
                for (int i = 0; i < tempCount; i++) {
                    //读取切片文件
                    RandomAccessFile reader = new RandomAccessFile(new File(a + "_" + i + ".tmp"), "r");
                    byte[] b = new byte[1024];
                    int n = 0;
                    //先读后写
                    while ((n = reader.read(b)) != -1) {//读
                        raf.write(b, 0, n);//写
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    

    上面是运行后完整的代码。

    下面就说说我遇到坑,也怪我不细心


    在分割文件的方法中,将两个条件弄反了,导致丢失一般的数据

    上面是正确的截图

    将两个条件反过来之后

    结果是:


    原因就是:

                while(in.getFilePointer() <= end && (n = in.read(b)) != -1){
                while( (n = in.read(b)) != -1&&in.getFilePointer() <= end){

    上面是数据的指针是从零开始的

    而下面的数据指针是从in读过之后的位置开始的,就是上面与下面的数据指针位置相差一个之前定义的byte数组长度个长度,这就是我测试是,数据丢失的原因

    展开全文
  • 阿里云OSS-大文件-分片上传

    千次阅读 2018-02-11 21:49:45
    粗糙测试:postman同时(相差2秒内)send请求访问阿里云OSS的普通上传和分片上传212MB的eclipse-jee-indigo-SR2-64.zip;得到21:7:13========21:9:7(分片)---&gt;耗时2分钟得到21:7:11================21:15:...
  • MongoDB分片测试

    千次阅读 2016-03-23 15:31:51
    ReplicaSet+Sharding部署:http://blog.csdn.net/lichangzai/article/details/50927588MongoDB分片测试1. 连接到mongos可查看系统相关信息configsvr> show dbs configsvr> use config configsvr> show collections...
  • **核心思路:**前端对文件进行分片,并且发送文件的唯一标识(文件名、类型、大小或者其他属性进行md5摘要计算可得)、分片索引(第几个分片)、分片总数、文件名称(方便合并后的文件名称)记住这4个参数;...
  • Python实现大文件分片上传(WebUploader)

    千次阅读 2018-09-18 15:39:44
    想借着这篇文章简要谈谈WebUploader大文件上传与Python结合的实现。 WebUploader是百度团队对大文件上传的前端实现,而后端需要根据不同的语言自己实现。这里我采用Python语言的Flask框架搭建后端,配合使用...
  • 项目搭建IDE为STS,spring boot + redis 实现功能,前后端代码实现细节。导入即可启动,运行时浏览器打开localhost:9090/index.html测试页面。 PS:重要!开发环境注意设置好存储路径,...注意修改配置文件中redis地址
  • - 支持 HTTP/HTTPS 大文件分片上传 - 支持 类EventBus的task状态变更通知,支持三种线程的订阅模式 - 支持 任务分组,分用户设计简单的下载或上传:下载: ““ java mHandler = new DefaultHttpDownloadHandler...
  • 分片上传及断点续传原理深入分析及示例Demo

    万次阅读 多人点赞 2019-02-23 14:47:39
    分片上传、断点续传,这两个名词对于做过或者熟悉文件上传的朋友来说应该不会陌生。之所有有这边文章,还是因为自己在网上浏览了一些文章后发现没有找到一篇能瞬间明白原理和实现的,因此决定自己写一篇文章,方便有...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    测试开发笔记 第一章 测试基础 7 什么是软件测试: 7 ★软件测试的目的、意义:(怎么做好软件测试) 7 3.软件生命周期: 7 第二章 测试过程 8 1.测试模型 8 H模型: 8 V模型 9 2.内部测试 10 3外部测试: 10 验收...
  • 分片

    千次阅读 2017-11-26 21:19:36
    MyCat分片-海量数据存储解决方案1 什么是分片简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。 数据的切分(Sharding)...
  • 支持 HTTP/HTTPS 大文件分片上传 支持 类EventBus的task状态变更通知,支持三种线程的订阅模式 支持 任务分组,分用户 支持 自定义任务处理器,以及传输处理器 设计 简单的下载或上传: 下载: mHandler = new ...
  • 示例是纯净的,后端程序仅引用了需要的jar包; 后端程序启动时,请确保配置文件里的信息是跟你minio服务信息一致的; 前端程序启动时,请阅读一下“composeFile”...前后端程序都启动完成,直接上传一个文件即可测试
  • 使用webuploader组件实现大文件分片上传,断点续传 Xiangdong_She 关注 0.2 2017.08.20 13:48* 字数 1028 阅读 4363评论 3喜欢 12赞赏 1 无组件断点续传.gif 1. 组件简介 webuploader:是一个以HTML5为主, ...
  • 5个目标文件,演示Address EJB的实现 ,创建一个EJB测试客户端,得到名字上下文,查询jndi名,通过强制转型得到Home接口,getInitialContext()函数返回一个经过初始化的上下文,用client的getHome()函数调用Home接口...
  • 最近抽时间搭建了一下mongodb简单的分片,整个过程还算是蛮顺利,只不过在用户验证这一块遇到了一些问题,好在最后终于搞定。一、服务器搭建过程:1、安装四个mongodb:一个作为config、一个作为mongos、另外两个...
  • Vue中实现视频的分片上传

    千次阅读 2019-11-19 10:51:05
    console.log("文件分片上传成功") } else { // 当总数大于等于分片个数的时候 if (chunkInfo.currentChunk >= chunkInfo.chunkCount - 1) { console.log("文件开始------合并成功") //调用合并接口,传参为...
  • 该程序是为自己项目写的一个测试性能的小程序。 主要通过对服务器下载文件的并发响应进行统计。 使用多线程对服务器提供的链接地址下载文件, 能统计出并发情况下的下载速度,请不要开辟开多线程,以免导致服务器受...
  • SpringBoot WebUploader大文件分片上传

    千次阅读 热门讨论 2019-04-18 21:10:37
    后端技术:SpringBoot2.x 页面:freemarker webuploader:http://fex.baidu.com/webuploader/getting-started.html 1.application.properties server.port=9999 ...#上传文件存放目录 xdja.upload...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 91,538
精华内容 36,615
关键字:

文件分片测试