• 课外知识----断点续传


    1 文件不分块

    客户端会记录当前服务器的已经上传文件的大小,等到下载网络连接成功,给服务器发生我已经上传文件的大小,并继续发生字节

    服务器接收已经上传文件的大小,用这个大小来将移动文件的cursor,进行append

    2 文件分块

    客户端首先将需要上传的文件分块,网络恢复正常,已经上传的文件分块不在上传,上传失败的文件分块重新上传

    服务器将接受到的文件分块进行合并

    Web客户端实现断点上传功能

    WebUploader

    官网:fexteam.gz01.bdysite.com/webuploader/

     http://fex.baidu.com/webuploader/

    web

    <template>
      <div><br/>
        操作步骤:<br/>
        1、点击“选择文件”,选择要上传的文件<br/>
        2、点击“开始上传”,开始上传文件<br/>
        3、如需重新上传请重复上边的步骤。<br/><br/>
    
        <div id="uploader" class="wu-example">
          <div class="btns" style="float:left;padding-right: 20px">
            <div id="picker">选择文件</div>
          </div>
          <div id="ctlBtn" class="webuploader-pick" @click="upload()">开始上传</div>
    
        </div>
        <!--用来存放文件信息-->
        <div id="thelist" class="uploader-list">
          <div v-if="uploadFile.id" :id='uploadFile.id'><span>{{uploadFile.name}}</span> <span class='percentage'>{{percentage}}%</span>
          </div>
        </div>
      </div>
    </template>
    <script>
        import $ from '../../../../static/plugins/jquery/dist/jquery.js'
        import webuploader from '../../../../static/plugins/webuploader/dist/webuploader.js'
        import '../../../../static/css/webuploader/webuploader.css'
    
        export default {
            data() {
                return {
                    uploader: {},
                    uploadFile: {},
                    percentage: 0,
                    fileMd5: ''
                }
            },
            methods: {
                //开始上传
                upload() {
                    if (this.uploadFile && this.uploadFile.id) {
                        this.uploader.upload(this.uploadFile.id);
                    } else {
                        alert("请选择文件");
                    }
                }
            },
            mounted() {
    //      var fileMd5;
    //      var uploadFile;
                WebUploader.Uploader.register({
                        "before-send-file": "beforeSendFile",
                        "before-send": "beforeSend",
                        "after-send-file": "afterSendFile"
                    }, {
                        beforeSendFile: function (file) {
                            // 创建一个deffered,用于通知是否完成操作
                            var deferred = WebUploader.Deferred();
                            // 计算文件的唯一标识,用于断点续传
                            (new WebUploader.Uploader()).md5File(file, 0, 100 * 1024 * 1024)
                                .then(function (val) {
    
                                    this.fileMd5 = val;
                                    this.uploadFile = file;
    //                alert(this.fileMd5 )
                                    //向服务端请求注册上传文件
                                    $.ajax(
                                        {
                                            type: "POST",
                                            url: "/api/media/upload/register",
                                            data: {
                                                // 文件唯一表示
                                                fileMd5: this.fileMd5,
                                                fileName: file.name,
                                                fileSize: file.size,
                                                mimetype: file.type,
                                                fileExt: file.ext
                                            },
                                            dataType: "json",
                                            success: function (response) {
                                                if (response.success) {
                                                    //alert('上传文件注册成功开始上传');
                                                    deferred.resolve();
                                                } else {
                                                    alert(response.message);
                                                    deferred.reject();
                                                }
                                            }
                                        }
                                    );
                                }.bind(this));
    
                            return deferred.promise();
                        }.bind(this),
                        beforeSend: function (block) {
                            var deferred = WebUploader.Deferred();
                            // 每次上传分块前校验分块,如果已存在分块则不再上传,达到断点续传的目的
                            $.ajax(
                                {
                                    type: "POST",
                                    url: "/api/media/upload/checkchunk",
                                    data: {
                                        // 文件唯一表示
                                        fileMd5: this.fileMd5,
                                        // 当前分块下标
                                        chunk: block.chunk,
                                        // 当前分块大小
                                        chunkSize: block.end - block.start
                                    },
                                    dataType: "json",
                                    success: function (response) {
                                        if (response.fileExist) {
                                            // 分块存在,跳过该分块
                                            deferred.reject();
                                        } else {
                                            // 分块不存在或不完整,重新发送
                                            deferred.resolve();
                                        }
                                    }
                                }
                            );
                            //构建fileMd5参数,上传分块时带上fileMd5
                            this.uploader.options.formData.fileMd5 = this.fileMd5;
                            this.uploader.options.formData.chunk = block.chunk;
                            return deferred.promise();
                        }.bind(this),
                        afterSendFile: function (file) {
                            // 合并分块
                            $.ajax(
                                {
                                    type: "POST",
                                    url: "/api/media/upload/mergechunks",
                                    data: {
                                        fileMd5: this.fileMd5,
                                        fileName: file.name,
                                        fileSize: file.size,
                                        mimetype: file.type,
                                        fileExt: file.ext
                                    },
                                    success: function (response) {
                                        //在这里解析合并成功结果
                                        if (response && response.success) {
                                            alert("上传成功")
                                        } else {
                                            alert("上传失败")
                                        }
                                    }
                                }
                            );
                        }.bind(this)
                    }
                );
                // 创建uploader对象,配置参数
                this.uploader = WebUploader.create(
                    {
                        swf: "/static/plugins/webuploader/dist/Uploader.swf",//上传文件的flash文件,浏览器不支持h5时启动flash
                        server: "/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题
                        fileVal: "file",//文件上传域的name
                        pick: "#picker",//指定选择文件的按钮容器
                        auto: false,//手动触发上传
                        disableGlobalDnd: true,//禁掉整个页面的拖拽功能
                        chunked: true,// 是否分块上传
                        chunkSize: 1 * 1024 * 1024, // 分块大小(默认5M)
                        threads: 3, // 开启多个线程(默认3个)
                        prepareNextFile: true// 允许在文件传输时提前把下一个文件准备好
                    }
                );
    
                // 将文件添加到队列
                this.uploader.on("fileQueued", function (file) {
                        this.uploadFile = file;
                        this.percentage = 0;
    
                    }.bind(this)
                );
                //选择文件后触发
                this.uploader.on("beforeFileQueued", function (file) {
    //     this.uploader.removeFile(file)
                    //重置uploader
                    this.uploader.reset()
                    this.percentage = 0;
                }.bind(this));
    
                // 监控上传进度
                // percentage:代表上传文件的百分比
                this.uploader.on("uploadProgress", function (file, percentage) {
                    this.percentage = Math.ceil(percentage * 100);
                }.bind(this));
                //上传失败触发
                this.uploader.on("uploadError", function (file, reason) {
                    console.log(reason)
                    alert("上传文件失败");
                });
                //上传成功触发
                this.uploader.on("uploadSuccess", function (file, response) {
                    console.log(response)
    //        alert("上传文件成功!");
                });
                //每个分块上传请求后触发
                this.uploader.on('uploadAccept', function (file, response) {
                    if (!(response && response.success)) {//分块上传失败,返回false
                        return false;
                    }
                });
            }
        }
    </script>
    <style scoped>
    
    
    </style>

    server

    package com.xuecheng.manage_media.service;
    
    import com.alibaba.fastjson.JSON;
    import com.xuecheng.framework.domain.media.MediaFile;
    import com.xuecheng.framework.domain.media.response.CheckChunkResult;
    import com.xuecheng.framework.domain.media.response.MediaCode;
    import com.xuecheng.framework.exception.ExceptionCast;
    import com.xuecheng.framework.model.response.CommonCode;
    import com.xuecheng.framework.model.response.ResponseResult;
    import com.xuecheng.manage_media.config.RabbitMQConfig;
    import com.xuecheng.manage_media.dao.MediaFileRepository;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.io.IOUtils;
    import org.springframework.amqp.AmqpException;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.*;
    import java.util.*;
    
    /**
     * @author Administrator
     * @version 1.0
     **/
    @Service
    public class MediaUploadService {
        @Autowired
        MediaFileRepository mediaFileRepository;
    
        @Value("${xc-service-manage-media.upload-location}")
        String upload_location;
        @Value("${xc-service-manage-media.mq.routingkey-media-video}")
        String routingkey_media_video;
    
        @Autowired
        RabbitTemplate rabbitTemplate;
    
        //得到文件所属目录路径
        private String getFileFolderPath(String fileMd5){
            return  upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/";
        }
        //得到文件的路径
        private String getFilePath(String fileMd5,String fileExt){
            return upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + fileMd5 + "." +fileExt;
        }
    
        //得到块文件所属目录路径
        private String getChunkFileFolderPath(String fileMd5){
            return  upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/chunk/";
        }
        /**
         * 文件上传前的注册,检查文件是否存在
         * 根据文件md5得到文件路径
         * 规则:
         * 一级目录:md5的第一个字符
         * 二级目录:md5的第二个字符
         * 三级目录:md5
         * 文件名:md5+文件扩展名
         * @param fileMd5 文件md5值
         * @param fileExt 文件扩展名
         * @return 文件路径
         */
        public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
    
            //1  检查文件在磁盘上是否存在
            //文件所属目录的路径
            String fileFolderPath = this.getFileFolderPath(fileMd5);
            //文件的路径
            String filePath =this.getFilePath(fileMd5,fileExt);
            File file = new File(filePath);
            //文件是否存在
            boolean exists = file.exists();
    
            //2 检查文件信息在mongodb中是否存在
            Optional<MediaFile > optional = mediaFileRepository.findById(fileMd5);
            if(exists && optional.isPresent()){
                //文件存在
                ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
            }
            //文件不存在时作一些准备工作,检查文件所在目录是否存在,如果不存在则创建
            File fileFolder = new File(fileFolderPath);
            if(!fileFolder.exists()){
                fileFolder.mkdirs();
            }
    
            return new ResponseResult(CommonCode.SUCCESS);
        }
    
        //分块检查(前端会将文件进行分块,每一个块都会进行checkchunk())
        /**
         *
         * @param fileMd5 文件md5
         * @param chunk 块的下标
         * @param chunkSize 块的大小
         * @return
         */
        public CheckChunkResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
            //检查分块文件是否存在
            //得到分块文件的所在目录
            String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
            //块文件
            File chunkFile = new File(chunkFileFolderPath + chunk);
            if(chunkFile.exists()){
                //块文件存在
                return new CheckChunkResult(CommonCode.SUCCESS,true);
            }else{
                //块文件不存在
                return new CheckChunkResult(CommonCode.SUCCESS,false);
            }
        }
        //上传分块
        public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
            //检查分块目录,如果不存在则要自动创建
            //得到分块目录
            String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
            //得到分块文件路径
            String chunkFilePath = chunkFileFolderPath + chunk;
    
            File chunkFileFolder = new File(chunkFileFolderPath);
            //如果不存在则要自动创建
            if(!chunkFileFolder.exists()){
                chunkFileFolder.mkdirs();
            }
            //得到上传文件的输入流
            InputStream inputStream = null;
            FileOutputStream outputStream  =null;
            try {
                inputStream = file.getInputStream();
                outputStream = new FileOutputStream(new File(chunkFilePath));
                IOUtils.copy(inputStream,outputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return new ResponseResult(CommonCode.SUCCESS);
    
        }
    
        //合并文件
        public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
    
            //1、合并所有分块
            //得到分块文件的属目录
            String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
            File chunkFileFolder = new File(chunkFileFolderPath);
            //分块文件列表
            File[] files = chunkFileFolder.listFiles();
            List<File> fileList = Arrays.asList(files);
    
            //创建一个合并文件
            String filePath = this.getFilePath(fileMd5, fileExt);
            File mergeFile = new File(filePath);
    
            //执行合并
            mergeFile = this.mergeFile(fileList, mergeFile);
            if(mergeFile == null){
                //合并文件失败
                ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
            }
    
            //2、校验文件的md5值是否和前端传入的md5一到
            boolean checkFileMd5 = this.checkFileMd5(mergeFile, fileMd5);
            if(!checkFileMd5){
                //校验文件失败
                ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);
            }
            //3、将文件的信息写入mongodb
            MediaFile mediaFile = new MediaFile();
            mediaFile.setFileId(fileMd5);
            mediaFile.setFileOriginalName(fileName);
            mediaFile.setFileName(fileMd5 + "." +fileExt);
            //文件路径保存相对路径
            String filePath1 = fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/";
            mediaFile.setFilePath(filePath1);
            mediaFile.setFileSize(fileSize);
            mediaFile.setUploadTime(new Date());
            mediaFile.setMimeType(mimetype);
            mediaFile.setFileType(fileExt);
            //状态为上传成功
            mediaFile.setFileStatus("301002");
            mediaFileRepository.save(mediaFile);
            //向MQ发送视频处理消息
            sendProcessVideoMsg(mediaFile.getFileId());
            return new ResponseResult(CommonCode.SUCCESS);
        }
    
        /**
         * 发送视频处理消息
         * @param mediaId 文件id
         * @return
         */
        public ResponseResult sendProcessVideoMsg(String mediaId){
    
            //查询数据库mediaFile
            Optional<MediaFile> optional = mediaFileRepository.findById(mediaId);
            if(!optional.isPresent()){
                ExceptionCast.cast(CommonCode.FAIL);
            }
            //构建消息内容
            Map<String,String> map = new HashMap<>();
            map.put("mediaId",mediaId);
            String jsonString = JSON.toJSONString(map);
            //向MQ发送视频处理消息
            try {
                rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,routingkey_media_video,jsonString);
            } catch (AmqpException e) {
                e.printStackTrace();
                return new ResponseResult(CommonCode.FAIL);
            }
    
            return new ResponseResult(CommonCode.SUCCESS);
        }
    
        //校验文件
        private boolean checkFileMd5(File mergeFile,String md5){
    
            try {
                //创建文件输入流
                FileInputStream inputStream = new FileInputStream(mergeFile);
                //得到文件的md5
                String md5Hex = DigestUtils.md5Hex(inputStream);
    
                //和传入的md5比较
                if(md5.equalsIgnoreCase(md5Hex)){
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return false;
    
        }
        //合并文件
        private File mergeFile(List<File> chunkFileList, File mergeFile) {
            try {
                //如果合并文件已存在则删除,否则创建新文件
                if (mergeFile.exists()) {
                    mergeFile.delete();
                } else {
                    //创建一个新文件
                    mergeFile.createNewFile();
                }
                //对块文件进行排序
                Collections.sort(chunkFileList, new Comparator<File>() {
                    @Override
                    public int compare(File o1, File o2) {
                        if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
                            return 1;
                        }
                        return -1;
    
                    }
                });
                //创建一个写对象
                RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
                byte[] b = new byte[1024];
                for(File chunkFile:chunkFileList){
                    RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
                    int len = -1;
                    while ((len = raf_read.read(b))!=-1){
                        raf_write.write(b,0,len);
                    }
                    raf_read.close();
                }
                raf_write.close();
                return mergeFile;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    当文件上传到服务器后,我们需要使用ffmpeg来将视频进行处理,处理完成之后需要写入数据库(视频的id是前端传入的md5,视频的名字,视频的url,等信息)

  • 相关阅读:
    python-面向对象-05_面向对象封装案例 II
    python-面向对象-06_私有属性和私有方法
    python-面向对象-07_继承
    python-面向对象-08_多态
    python-面向对象-09_类属性和类方法
    CentOS 7 安装
    lnmp 一键搭建脚本
    shell 函数与内置变量
    linux无敌权限之chattr
    linux运维配置讲解--sshd-config
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/11991706.html
Copyright © 2020-2023  润新知