首页 > 其他分享 >springboot+vue+elementui大文件分片上传

springboot+vue+elementui大文件分片上传

时间:2024-09-29 14:34:48浏览次数:10  
标签:vue springboot shardIndex elementui param let key 分片 public

工具类方法:

 /**
     * 大文件分片上传
     * @param fileName 文件名
     * @param file       文件
     * @param fileKey    文件key
     * @param shardIndex 当前分片下标
     * @param shardTotal 分片总量
     */
    public static void bigUpload(String fileName,MultipartFile file, String fileKey, Long shardIndex, Long shardTotal) throws Exception {
        String fileDir = getDefaultBaseDir() +"/"+ DateUtils.datePath() + "/" + fileKey;
        File dir=new File(fileDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File dest = new File(fileDir+"/" + fileKey + "." + shardIndex);
        // 分片文件保存到文件目录
        file.transferTo(dest);
        if (shardIndex == shardTotal) {
            merge(fileName, shardTotal, fileKey);
        }
    }

    /**
     * 分片大文件上传,文件合并
     *
     * @param fileName   文件名比如123.mp4
     * @param shardTotal 分片总量
     * @param fileKey    文件key
     * @throws Exception
     */
    private static void merge(String fileName, Long shardTotal, String fileKey) throws Exception {
        String mergeFilePath = getDefaultBaseDir()+"/" + DateUtils.datePath() + "/" + fileKey + "/" + fileName;
        File newFile = new File(mergeFilePath);
        if (newFile.exists()) {
            newFile.delete();
        }
        FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入
        FileInputStream fileInputStream = null;//分片文件
        byte[] byt = new byte[10 * 1024 * 1024];
        int len;
        try {
            for (int i = 0; i < shardTotal; i++) {
                // 读取第i个分片
                String shardFilePath = getDefaultBaseDir() +"/"+ DateUtils.datePath() + "/" + fileKey + "/" + fileKey + "." + (i + 1);
                fileInputStream = new FileInputStream(shardFilePath);
                while ((len = fileInputStream.read(byt)) != -1) {
                    outputStream.write(byt, 0, len);//一直追加到合并的新文件中
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                outputStream.close();
                System.gc();
            } catch (Exception e) {
            }
        }
    }

controller需要实现两个接口:上传文件和分片文件状态检查。

  @GetMapping("/check")
    public AjaxResult check(@RequestParam String key) {
        PanoramicFileTb fileTb = panoramicFileTbService.selectLatestIndex(key);
        log.info("检查分片:{}", key);
        return AjaxResult.success(fileTb);

    }
/**
     * 大文件上传
     *
     * @param file
     * @param filePojo
     * @return
     * @throws Exception
     */
    @PreAuthorize("@ss.hasPermi('system:BusinessFile:add')")
    @Log(title = "文件记录", businessType = BusinessType.INSERT)
    @PostMapping("/big-upload")
    public AjaxResult bigUpload(@RequestParam(value = "file") MultipartFile file,
                                FilePojoVo filePojo) throws Exception {
        FileUploadUtils.bigUpload(filePojo.getFileName(),file, filePojo.getKey(), filePojo.getShardIndex(), filePojo.getShardTotal());
        log.info("文件分片 {} 保存完成", filePojo.getShardIndex());
        PanoramicFileTb fileTb = PanoramicFileTb.builder()
                .fKey(filePojo.getKey())
                .fIndex(filePojo.getShardIndex())
                .fTotal(filePojo.getShardTotal())
                .fName(filePojo.getFileName())
                .build();
        if (panoramicFileTbService.isNotExist(filePojo.getKey())) {
            panoramicFileTbService.saveFile(fileTb);
        } else {
            panoramicFileTbService.UpdateFile(fileTb);
        }
        return AjaxResult.success();
    }
public class FilePojoVo {

    private String key;
    private String fileName;
    private Long shardIndex;
    private Long shardSize;
    private Long shardTotal;
    private Long size;
    private String suffix;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public Long getShardIndex() {
        return shardIndex;
    }

    public void setShardIndex(Long shardIndex) {
        this.shardIndex = shardIndex;
    }

    public Long getShardSize() {
        return shardSize;
    }

    public void setShardSize(Long shardSize) {
        this.shardSize = shardSize;
    }

    public Long getShardTotal() {
        return shardTotal;
    }

    public void setShardTotal(Long shardTotal) {
        this.shardTotal = shardTotal;
    }

    public Long getSize() {
        return size;
    }

    public void setSize(Long size) {
        this.size = size;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}
FilePojoVo
@Builder
public class PanoramicFileTb extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** $column.columnComment */
    private Integer id;

    /** 文件唯一标识 */
    @Excel(name = "文件唯一标识")
    private String fKey;

    /** 第几个分片 */
    @Excel(name = "第几个分片")
    private Long fIndex;

    /** 共有几个分片 */
    @Excel(name = "共有几个分片")
    private Long fTotal;

    /** 文件名称,后面可以返回出去 */
    @Excel(name = "文件名称,后面可以返回出去")
    private String fName;

    public void setId(Integer id) 
    {
        this.id = id;
    }

    public Integer getId() 
    {
        return id;
    }
    public void setfKey(String fKey) 
    {
        this.fKey = fKey;
    }

    public String getfKey() 
    {
        return fKey;
    }
    public void setfIndex(Long fIndex) 
    {
        this.fIndex = fIndex;
    }

    public Long getfIndex() 
    {
        return fIndex;
    }
    public void setfTotal(Long fTotal) 
    {
        this.fTotal = fTotal;
    }

    public Long getfTotal() 
    {
        return fTotal;
    }
    public void setfName(String fName) 
    {
        this.fName = fName;
    }

    public String getfName() 
    {
        return fName;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("id", getId())
            .append("fKey", getfKey())
            .append("fIndex", getfIndex())
            .append("fTotal", getfTotal())
            .append("fName", getfName())
            .toString();
    }
}
PanoramicFileTb

上面两个实体类,FilePojoVo是必须的,需要和页面做数据交互,PanoramicFileTb是非必须的,可以选择把FilePojoVo存储到数据库、内存、redis等都可以,只要能验证到对应文件的md5值是否已存在。我这里存到数据库是因为可以做急速上传,已上传的文件md5值可能会一样,加上其他验证方式,这样已上传过的文件再上传其实就不需要再传了。

下面附上对应的service方法,其中mapper方法无非就是用key去查数据或更新数据。就不提供出来了:

@Override
    public void saveFile(PanoramicFileTb fileTb) {
        panoramicFileTbMapper.insertPanoramicFileTb(fileTb);
    }

    @Override
    public void UpdateFile(PanoramicFileTb fileTb) {
        panoramicFileTbMapper.UpdateFile(fileTb);
    }

    @Override
    public boolean isNotExist(String key){
        Integer id = panoramicFileTbMapper.isExist(key);
        if (ObjectUtils.isEmpty(id)) {
            return true;
        }
        return false;
    }

    @Override
    public PanoramicFileTb selectLatestIndex(String key) {
        PanoramicFileTb fileTb = panoramicFileTbMapper.selectLatestIndex(key);
        if (ObjectUtils.isEmpty(fileTb)) {
            fileTb = PanoramicFileTb.builder().fKey(key).fIndex(-1L).fName("").build();
        }
        return fileTb;
    }

以上就是后台相关代码,可以根据自己的需求扩展功能。

下面是前端代码,需要npm install --save js-md5安装,引用import md5 from 'js-md5';

<template>
    <div class="file-upload">
        <h1>大文件分片上传、极速秒传</h1>
        <div class="file-upload-el">

            <el-upload
                    class="upload-demo"
                    drag
                    ref="upload"
                    :limit=1
                    :action="actionUrl"
                    :on-exceed="handleExceed"
                    :http-request="handUpLoad"
                    :auto-upload="false"
            >
                <i class="el-icon-upload"></i>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
            </el-upload>
            <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
        </div>
        <div>
            <!-- autoplay-->
            <el-card class="v-box-card">
                <video :src="videoUrl"
                       controls
                       autoplay
                       class="video"
                       width="100%">

                </video>
            </el-card>
        </div>
    </div>
</template>

<script>
    export default {
        name: "FileUpload",
        data() {

            return {
                actionUrl: 'http://localhost:8098/upload',//上传的后台地址
                shardSize: 10 * 1024 * 1024,
                videoUrl: ''

            };
        },
        methods: {

            handleExceed(files, fileList) {
                this.$message.warning(`当前限制选择 1个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
            },
            submitUpload() {
                this.$refs.upload.submit();
            },
            async check(key) {
                var res = await this.$http.get('/check', {
                    params: {'key': key}
                })
                let resData = res.data;
                return resData.data;
            },
            async recursionUpload(param, file) {
                //FormData私有类对象,访问不到,可以通过get判断值是否传进去
                let _this = this;
                let key = param.key;
                let shardIndex = param.shardIndex;
                let shardTotal = param.shardTotal;
                let shardSize = param.shardSize;
                let size = param.size;
                let fileName = param.fileName;
                let suffix = param.suffix;

                let fileShard = _this.getFileShard(shardIndex, shardSize, file);

                //param.append("file", fileShard);//文件切分后的分片
                //param.file = fileShard;
                let totalParam = new FormData();
                totalParam.append('file', fileShard);
                totalParam.append("key", key);
                totalParam.append("shardIndex", shardIndex);
                totalParam.append("shardSize", shardSize);
                totalParam.append("shardTotal", shardTotal);
                totalParam.append("size", size);
                totalParam.append("fileName", fileName);
                totalParam.append("suffix", suffix);
                let config = {
                    //添加请求头
                    headers: {"Content-Type": "multipart/form-data"}
                };
                console.log(param);
                var res = await this.$http.post('/upload', totalParam, config)

                var resData = res.data;
                if (resData.status) {
                    if (shardIndex < shardTotal) {
                        this.$notify({
                            title: '成功',
                            message: '分片' + shardIndex + '上传完成。。。。。。',
                            type: 'success'
                        });
                    } else {
                        this.videoUrl = resData.data;//把地址赋值给视频标签
                        this.$notify({
                            title: '全部成功',
                            message: '文件上传完成。。。。。。',
                            type: 'success'
                        });
                    }

                    if (shardIndex < shardTotal) {
                        console.log('下一份片开始。。。。。。');
                        // 上传下一个分片
                        param.shardIndex = param.shardIndex + 1;
                        _this.recursionUpload(param, file);
                    }
                }


            },

            async handUpLoad(req) {
                let _this = this;
                var file = req.file;
                /*  console.log('handUpLoad', req)
                  console.log(file);*/
                //let param = new FormData();
                //通过append向form对象添加数据

                //文件名称和格式,方便后台合并的时候知道要合成什么格式
                let fileName = file.name;
                let suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
                //这里判断文件格式,有其他格式的自行判断
                if (suffix != 'mp4') {
                    this.$message.error('文件格式错了哦。。');
                    return;
                }

                // 文件分片
                // let shardSize = 10 * 1024 * 1024;    //以10MB为一个分片
                // let shardSize = 50 * 1024;    //以50KB为一个分片
                let shardSize = _this.shardSize;
                let shardIndex = 1;        //分片索引,1表示第1个分片
                let size = file.size;
                let shardTotal = Math.ceil(size / shardSize); //总片数
                // 生成文件标识,标识多次上传的是不是同一个文件
                let key = this.$md5(file.name + file.size + file.type);
                let param = {
                    key: key,
                    shardIndex: shardIndex,
                    shardSize: shardSize,
                    shardTotal: shardTotal,
                    size: size,
                    fileName: fileName,
                    suffix: suffix
                }
                /*param.append("uid", key);
                param.append("shardIndex", shardIndex);
                param.append("shardSize", shardSize);
                param.append("shardTotal", shardTotal);
                param.append("size", size);
                param.append("fileName", fileName);
                param.append("suffix", suffix);

*/

                let checkIndexData = await _this.check(key);//得到文件分片索引
                let checkIndex = checkIndexData.findex;

                //console.log(checkIndexData)
                if (checkIndex == -1) {
                    this.recursionUpload(param, file);
                } else if (checkIndex < shardTotal) {
                    param.shardIndex = param.shardIndex + 1;
                    this.recursionUpload(param, file);
                } else {
                    this.videoUrl = checkIndexData.fname;//把地址赋值给视频标签
                    this.$message({
                        message: '极速秒传成功。。。。。',
                        type: 'success'
                    });
                }


                //console.log('结果:', res)
            },

            getFileShard(shardIndex, shardSize, file) {
                let _this = this;
                let start = (shardIndex - 1) * shardSize;    //当前分片起始位置
                let end = Math.min(file.size, start + shardSize); //当前分片结束位置
                let fileShard = file.slice(start, end); //从文件中截取当前的分片数据
                return fileShard;
            },


        }
    }

</script>

<style scoped lang="less">
    .file-upload {
        .file-upload-el {

        }

    }
    .v-box-card{
        width: 50%;
    }
</style>
前端代码

源码参考地址:bigfileupload: springboot+vue大文件分片上传 (gitee.com)

标签:vue,springboot,shardIndex,elementui,param,let,key,分片,public
From: https://www.cnblogs.com/rolayblog/p/18439702

相关文章

  • SpringBoot+Docker +Nginx 部署前后端项目Hf
    部署SpringBoot项目(通关版)一、概述使用java-jar命令直接部署项目的JAR包和使用Docker制作镜像进行部署是两种常见的部署方式。以下是对这两种方式的概述和简要的优劣势分析:1.1、使用java-jar命令直接部署项目的JAR包概述:通过java-jar直接部署项目的JAR包是最简单直......
  • 基于SPRINTBOOT+VUE文献资料检索系统
    文未可获取一份本项目的java源码和数据库参考。1选题背景   随着世界一体化和经济全球化席卷世界,越来越多的高校认识到,利用信息技术的发展来改变对文档、文献的运作方法和管理模式,提高高校的管理效益和生产效益,从而提高高校经济效益,增强高校竞争力是高校发展的趋势。......
  • 海滨体育馆管理系统:SpringBoot实现技巧与案例
    2系统关键技术2.1JAVA技术Java是一种非常常用的编程语言,在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中,Java的身影无处不在,并且拥有旺盛的生命力。Java的跨平台能力十分强大,只需一次编译,任何地方都可以运行。除此之外,它还拥有简单的语法和实用的类库,......
  • 2024最新高分源码基于SpringBoot+Vue+uniapp的办事大厅政务预约系统(源码+lw+部署文档
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 2024最新高分源码基于SpringBoot+Vue+uniapp的网络办公系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 2024最新高分源码基于SpringBoot+Vue+uniapp的贫困认定管理平台(源码+lw+部署文档+讲
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • Vue脚手架搭建及vue项目创建
    文章目录Vue脚手架搭建及vue项目创建一、引言二、环境搭建1、安装Node.js和npm2、安装VueCLI2.1、切换npm镜像源2.2、全局安装VueCLI三、创建Vue项目1、创建项目目录2、使用VueCLI创建项目3、安装项目依赖4、运行项目四、项目结构与运行流程1、项目目录......
  • Vue 常用的指令用法
    文章目录Vue常用的指令用法一、引言二、指令详解1、v-model2、v-bind3、v-for4、v-if/v-else-if/v-else5、v-show6、v-on7、v-text和v-html三、指令使用技巧四、总结Vue常用的指令用法一、引言Vue.js是一个构建用户界面的渐进式框架,它通过一系列指令来实......
  • 海滨体育馆管理系统:SpringBoot技术的应用
    1引言1.1课题背景当今时代是飞速发展的信息时代。在各行各业中离不开信息处理,这正是计算机被广泛应用于信息管理系统的环境。计算机的最大好处在于利用它能够进行信息管理。使用计算机进行信息控制,不仅提高了工作效率,而且大大的提高了其安全性。尤其对于复杂的信息管理,计......
  • SpringBoot实战:海滨体育馆管理系统开发指南
    1引言1.1课题背景当今时代是飞速发展的信息时代。在各行各业中离不开信息处理,这正是计算机被广泛应用于信息管理系统的环境。计算机的最大好处在于利用它能够进行信息管理。使用计算机进行信息控制,不仅提高了工作效率,而且大大的提高了其安全性。尤其对于复杂的信息管理,计......