前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大文件上传切片上传 vue java

大文件上传切片上传 vue java

作者头像
solate
发布2020-06-18 17:51:56
6.5K1
发布2020-06-18 17:51:56
举报
文章被收录于专栏:solate 杂货铺solate 杂货铺

大文件上传

前端实现

使用vue+elementui进行前端开发, 实现在dialog中 带进度条的上传大文件页面

<el-form :model="ruleForm" ref="ruleForm" :label-width="formLabelWidth" :rules="theRules" >
    <el-form-item prop="jar" :label-width="formLabelWidth">
        <label slot="lable" style="font-weight: lighter">上传文件</label>
        <el-upload
            ref="upload"
            action=""
            :http-request="handleFile"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :before-remove="beforeRemove"
            :on-change="handleChange"
            :multiple="false"
            :limit="1"
            :file-list="fileList"
            accept=".tar">
            <el-button slot="trigger" size="small" type="primary" :disabled="fileButtonDisabled">选择应用包</el-button>
            <el-button style="margin-left: 10px" size="small" type="success" @click="uploadFile" :disabled="fileButtonDisabled">上传</el-button>
        </el-upload>
    </el-form-item>

    <el-form-item>
        <label slot="lable" style="font-weight: lighter"></label>
        <el-progress :text-inside="true" :stroke-width="15" :percentage="filePercentage"></el-progress>
    </el-form-item>
</el-form>

<div slot="footer" class="dialog-footer">
    <el-button @click="handleClose('ruleForm')">取 消</el-button>
    <el-button type="primary" @click="submitForm('ruleForm')">确 定</el-button>
</div>

这里使用axios有一个坑需要注意一下,必须是这个指定的header,且必须用Promise包起来。

    data: function () {
        return {
            ruileForms: {
                file: '',
                jar: ''
            }
            theRules: {
                //jar:[{required:true, message:"请上传tar包", trigger:'blur'}]
            }
            fileList:[],
            filePercentage: 0, // 文件上传进度条
            fileLocation: '', // 文件在后台方式的位置
            fileCancelUpload: false, // 取消文件分片上传
            fileButtonDisabled: false, //文件上传按钮禁用
        }
    }
    methods: {
        submitForm(formName) {
            this.$refs[formName].validate((valid) => {
              if (valid) {
                  if (this.fileLocation == "") {
                      this.$message({message:"请上传文件", type:'fail'})
                  }

                  let formData = new FormData()
                  formData.append("fileLocation", this.fileLocation)

                  // 设置header头
                  let config = {
                      headers: {
                          'Content-Type':'multipart/form-data',
                      }
                  }

                  Axios.post('/api/fileUpload', formData, config)
                    .then((response) => {
                        if (response.data.result == true) {
                            this.$message({message:"成功", type:'success'})
                            this.resetForm('ruleForm')

                        }
                    })
                    .catch((err) => {
                        console.log(err)
                    })

              }

            })
        },

        handleFile() {
          // 空方法
        },
        handleChange(file, fileList) {
            // 文件改变时
            this.fileList = fileList
        },
        handleRemove(file, fileList) {
            this.fileCancelUpload = true
            this.filePercentage = 0 //进度条置空
            this.fileList = []
            this.fileButtonDisabled = false // 上传可点击
        },
        beforeRemove(file, fileList) {
            return this.$confirm('确定移除 ${file.name} ?');
        },

        //上传文件
        uploadFile() {
            let file = this.fileList[0] ? this.fileList[0].raw : ""
            if (file == "") {//判断文件是否存在
                 this.$message({message:"未选择文件", type:'fail'})
                return;
            }

             if (file.size > 50 * 1024 * 1024) {
                 //判断文件大小
                 this.$message({message:"文件不能大于50M", type:'fail'})
                return;
            }

            //判断文件类型
            if (file.type.indexOf("tar") == "-1") { //application/x-tar
                //判断文件大小
                this.$message({message:"文件必须为tar包", type:'fail'})
                return;
            }

            if (file.name.length > 30) {
                //判断文件大小
                this.$message({message:"文件名大于30个字符", type:'fail'})
                return;
            }

            this.fileButtonDisabled = true
            // 唯一标识
            var uiqueIdentifier = this.Id + '-' + parseInt(new Data().getTime() / 1000)
            console.log(uiqueIdentifier)
            this.uploadBySplit(file, uiqueIdentifier, 0)

        },
        //分片上传
        uploadBySplit(file, identifier, i) {

            //如果取消上传直接初始化为最初状态
            if (this.fileCancelUpload) {
                this.fileCancelUpload = false
                this.filePercentage = 0
                this.fileList = []
                this.fileButtonDisabled = false
            }

            var chunkSize = 1024 * 1024 * 1; //分片大小1M
            var size = file.size; //总大小
            var totalChunks = Math.ceil(size/chunkSize);

            //分片停止条件
            if (i == totalChunks) {
                this.$message({message:"上传成功", type:'success'})
                return;
            }

            //计算每一篇的起始位置和结束位置
            var start = i * chunkSize;
            var  end = Math.min(size, start + chunkSize);
            var fileData= file.slice(start, end)

            //文件分块上传
            var reader = new FileReader();
            reader.readAsBinaryString(fileData);
            reader.onload = function(e) {
                let formData = new FormData();
                formData.append('chunkNumber', i+1); //当前第几片,从0开始,文件下表从1计算。
                formData.append('chunkSize', chunkSize); //当前分片大小
                formData.append('currentChunkSize', fileData.size); //当前块大小
                formData.append('totalSize', size) //总的大小
                formData.append('identifier', identifier) //唯一标识
                formData.append('filename', file.name) //文件名
                formData.append('type', file.type) //文件类型
                // formData.append('relativePath', "/") //相对路径,暂时没用
                formData.append('totalChunks', totalChunks) //总片数
                formData.append('file', fileData) //总片数

                // 必须用这个Promise包起来axios,不然有问题
                return new Promise((resolve, reject) => {

                    // 必须用这个类型的头,并且要包括boundary
                    let config = {
                        headers: {
                            'Content-Type':'multipart/form-data; charset=utf-8; boundary="another cool boundary";',
                        }
                    }

                    Axios.post('/api/upload', formData, config)
                        .then((response) => {
                            if (response.data.result == true) {
                                if (response.data.data != "") {
                                    this.fileLocation=response.data.data//合并后文件放置的位置
                                }

                                //上传进度
                                var process =Math.random(end/ size*1000);
                                this.fileLocation = process
                                i++;
                                this.uploadBySplit(file, identifier, i);
                                resolve(response.data)
                            }else {
                                this.$message({message:"分片上传失败", type:'fail'})
                                reject(err)
                            }
                        })
                        .catch((err) => {
                            console.log(err)
                        })

                })
            }
        }



    }

后端实现 java版

    @ResponseBody
    @RequestMapping(value="/upload", method = RequestMethod.Post)
    public JsonResult upload(HttpServletRequest request, Chunk chunk) {
        try {
            
            boolen isMutipart = ServletFileUpload.isMutipartContent(request);
            if(isMutipart) {
                MultipartFile file = chunk.getFile();
                
                if(file == null) {
                    return JsonResult.failure("文件为空");
                }
                
                File outFile = new File(generatePath(chunk));
                InputStream inputStream = file.getInputStream();
                FileUtils.copyInputStreamToFile(inputStream, outFile);
                
                //判断所有分片是否全部上传完成,完成就合并
                File dir = new File(generateFileDir(chunk));
                File[] files = dir.listFiles();
                if (files.length == chunk.getTotalChunks()) {
                    String filePath = mergeFile(chunk); //合并文件
                    return JsonResult.success(filePath);
                }
                
            }
            
            
            return JsonResult.success();
        } catch (Exception e) {
            log.error(e)
            return JsonResult.failure("系统错误");
        }
    }
    
    // 获取上传文件路径
    public String generatePath(Chunk chunk) {
        StringBuilder sb = new StringBuilder();
        sb.append(uploadDir).append("/").append(chunk.getIdentifier());
        if (!Files.isWritable(Paths.get(sb.toString()))) {
            log.info("path not exist, create path:" , sb.toString());
        }
        try {
            Files.createDiretories(Paths.get(sb.toString()));
        } catch (IOExeception e){
            log.error(e)
        }
        
        return sb.append("/").append(chunk.getFilename()).append("-").append(chunk.getChunkNumber().toString());
    }
    
    //获取切片路径
    public String getnerateFileDir(Chunk chunk) {
         StringBuilder sb = new StringBuilder();
         sb.append(uploadDir).append("/")/append(chunk.getIdentifier());
         return sb.toString();
    }
    
    //合并文件
    public String mergeFile(Chunk chunk) {
        String filename = chunk.getFilename(); //文件名
        String folder = generateFileDir(chunk); //文件路径
        String file = folder + File.separator + filename; //生成文件名
        merge(file, folder, filename); //合并
        return chunk.getIdentifier() + + File.separator + filename; //返回相对路径
    }
    
    //合并
    public static void merge(String targetFile, String folder) {
        try {
            Files.createFile(Paths.get(targetFile));
            Files.list(Paths.get(folder))
                    .filter(path -> path.getFileName().toString().contains("-"))
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int i1 = p1.lastIndexOf("-");
                        int i2 = p2.lastIndexOf("-");
                        return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式写入文件
                            Files.write(Paths.get(targetFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
                            //合并后删除该块
                            Files.delete(path);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

chunk 块结构

@Data
@Entity
public class Chunk implements Serializable {
    @Id
    @GeneratedValue
    private Long id;
    /**
     * 当前文件块,从1开始
     */
    @Column(nullable = false)
    private Integer chunkNumber;
    /**
     * 分块大小
     */
    @Column(nullable = false)
    private Long chunkSize;
    /**
     * 当前分块大小
     */
    @Column(nullable = false)
    private Long currentChunkSize;
    /**
     * 总大小
     */
    @Column(nullable = false)
    private Long totalSize;
    /**
     * 文件标识
     */
    @Column(nullable = false)
    private String identifier;
    /**
     * 文件名
     */
    @Column(nullable = false)
    private String filename;
    /**
     * 相对路径
     */
    @Column(nullable = false)
    private String relativePath;
    /**
     * 总块数
     */
    @Column(nullable = false)
    private Integer totalChunks;
    /**
     * 文件类型
     */
    @Column
    private String type;
    @Transient
    private MultipartFile file;
}

参考

HTML5结合springboot带进度条大文件分段上传

javascript之大文件分段上传、断点续传(一)

SpringBoot+Vue.js前后端分离实现大文件分块上传

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 大文件上传
  • 前端实现
  • 后端实现 java版
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档