elementUI大文件分片上传

前端实现

使用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;
}
  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
文件分片上传通常需要以下步骤: 1. 切片:将大文件切成多个小文件,每个小文件大小一般在几十 KB 到几 MB 之间。 2. 上传:将每个小文件上传到服务器,可以使用 AJAX 或 WebSocket 等技术实现。 3. 合并:将所有上传的小文件合并成一个完整的大文件。 在 Vue 中,可以使用 element-ui 中的 Upload 组件实现文件上传功能。具体步骤如下: 1. 设置上传地址和上传方式: ```html <el-upload action="/upload" :http-request="uploadFile" :on-exceed="handleExceed" :limit="3" :file-list="fileList"> </el-upload> ``` 其中,`action` 表示上传地址,`http-request` 表示上传方法,`on-exceed` 表示超出文件数量限制时的回调函数,`limit` 表示最多上传文件数量,`file-list` 表示已上传文件列表。 2. 实现上传方法: ```javascript async uploadFile(file) { // 切片文件 const chunks = await this.splitFile(file) // 上传文件 const response = await this.uploadChunks(chunks) // 合并文件 await this.mergeFile(response.data) }, ``` 其中,`splitFile` 方法用于将文件切片,`uploadChunks` 方法用于上传切片文件,`mergeFile` 方法用于合并文件。 3. 实现切片方法: ```javascript async splitFile(file) { const chunkSize = 2 * 1024 * 1024 // 每个切片文件大小为 2MB const chunks = [] let start = 0 let index = 0 while (start < file.size) { const end = Math.min(start + chunkSize, file.size) const chunk = file.slice(start, end) chunks.push({ file: chunk, index: index, name: file.name, size: chunk.size, total: chunks.length, }) start += chunkSize index++ } return chunks }, ``` 该方法将文件切成多个大小相等的切片文件,并返回一个包含切片信息的数组。 4. 实现上传切片文件方法: ```javascript async uploadChunks(chunks) { const responses = [] for (const chunk of chunks) { const formData = new FormData() formData.append('file', chunk.file) formData.append('name', chunk.name) formData.append('index', chunk.index) formData.append('size', chunk.size) formData.append('total', chunk.total) const response = await axios.post('/upload', formData) responses.push(response) } return responses[0] }, ``` 该方法使用 axios 发送 POST 请求,将切片文件和切片信息一起发送到服务器。 5. 实现合并文件方法: ```javascript async mergeFile(data) { const formData = new FormData() formData.append('name', data.name) formData.append('size', data.size) formData.append('total', data.total) const response = await axios.post('/merge', formData) console.log(response.data) }, ``` 该方法将所有切片文件的信息发送到服务器,服务器根据切片信息将所有切片文件合并成一个完整的文件。 以上就是使用 Vue 和 element-ui 实现大文件分片上传的基本步骤。需要注意的是,由于切片和上传都是异步操作,因此需要使用 async/await 或 Promise 等方式来处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值