java大文件分片上传

news2024/10/5 17:24:59

1.效果图

2.前端html

<!DOCTYPE html>
<html>
<head></head>
<body>
<form>
    <input type="file" id="fileInput" multiple>
    <button type="button" onclick="upload()" >大文件分片上传</button>
</form>
<script>
    function upload() {
        var fileInput = document.getElementById('fileInput');
        var fileName = document.getElementById("fileInput").files[0].name;
        var files = fileInput.files;
        var chunkSize = 1024 * 1024 * 10; // 每个块的大小为10MB
        var totalChunks = Math.ceil(files[0].size / chunkSize); // 文件总块数
        var currentChunk = 0; // 当前块数
        console.log("当前文件:"+fileName+",大小:"+files[0].size+",分片大小:"+chunkSize+",分片数:"+totalChunks);

        // 分片上传文件
        function uploadChunk() {
            var xhr = new XMLHttpRequest();
            var formData = new FormData();

            // 将当前块数和总块数添加到formData中
            formData.append('current_slice_index', currentChunk);
            formData.append('file_name', fileName);
            formData.append('user_name', "15910761260");

            // 计算当前块在文件中的偏移量和长度
            var start = currentChunk * chunkSize;
            var end = Math.min(files[0].size, start + chunkSize);
            var chunk = files[0].slice(start, end);

            // 添加当前块到formData中
            formData.append('file', chunk);

            // 发送分片到后端
            xhr.open('POST', 'http://192.x.x.x:8060/file/fileInfo/uploadSlice');
            xhr.send(formData);

            xhr.onload = function(data) {
                console.log('上传第'+ currentChunk +'个分片结果:'+xhr.responseText);
                // 需要判断反馈结果,如果成功,才继续,否则再次上传失败部分
                // 更新当前块数
                currentChunk++;

                // 如果还有未上传的块,则继续上传
                if (currentChunk < totalChunks) {
                    uploadChunk();
                } else {
                    // 所有块都上传完毕,进行文件合并
                    console.log("开始合并");
                    mergeChunks(fileName);
                }
            }
        }

        // 合并所有分片
        function mergeChunks() {
            var xhr = new XMLHttpRequest();
            var formData = new FormData();
            formData.append('user_name', "15910761260");
            formData.append('total_slice_num', totalChunks);
            formData.append('file_name', fileName);
            formData.append('file_owner', 'MediaResource');
            formData.append('order_code', 'MediaResource');
            formData.append('folder', 'MediaResource');

            xhr.open("POST", "http://192.x.x.x:8060/file/fileInfo/mergeSlice");
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        console.log("文件合并完成:", xhr.responseText);
                    } else {
                        console.error(xhr.responseText);
                    }
                }
            };
            xhr.send(formData);
        }

        // 开始上传
        uploadChunk();
    }
</script>
</body>
</html>

2.java代码

        上传分片代码

    @ResponseBody
    @PostMapping(value = "/uploadSlice")
    @Operation(summary = "上传文件分片", description = "返回是否上传成功")
    public MisResult<String> UploadSlice(UploadFileSliceInput uploadFileSliceInput,
                                         @RequestParam(value = "file") MultipartFile multipartFile) throws Exception {
        return fileInfoService.UploadSlice(uploadFileSliceInput, multipartFile.getBytes());
    }

    @Override
    public MisResult<String> UploadSlice(UploadFileSliceInput uploadFileSliceInput, byte[] content) {
        MisResult<String> result = new MisResult<>();

        try {
            //参数校验
            ValidateUtil.ValidateThrowException(uploadFileSliceInput);
            if (content == null || content.length == 0) {
                result.Error(MisResultCode.PARAM_ERROR);
                return result;
            }

            //判断分片是否已上传
            String nameMd5 = DigestUtils.md5Hex(uploadFileSliceInput.getFile_name());
            String redisKey = nameMd5 + "_" + uploadFileSliceInput.getUser_name();
            String chunkStatus = (String) RedisUtil.HashGet(redisKey, uploadFileSliceInput.getCurrent_slice_index() + "");
            if (StringUtils.hasLength(chunkStatus) && MisStatus.Finished.equals(chunkStatus)) {
                result.Success("文件分片上传成功!");
                return result;
            }

            //保存文件分片到临时文件夹
            String absolutePath = FileConfig.Path + File.separator + "temp" + File.separator + "slice_" + nameMd5
                    + "_" + uploadFileSliceInput.getCurrent_slice_index();
            File saveFile = new File(absolutePath);
            FileCopyUtils.copy(content, saveFile);

            //记录上传状态
            long timeout = RedisUtil.GetExpire(redisKey);
            RedisUtil.HashSet(redisKey, uploadFileSliceInput.getCurrent_slice_index() + "", MisStatus.Finished);
            result.Success("文件分片上传成功!");
            if (timeout < 0) {
                RedisUtil.SetExpire(redisKey, 60 * 60);//1个小时超时删除
            }
        } catch (Exception e) {
            result.Error(MisResultCode.UNKNOWN_ERROR, "分片上传异常," + e.getMessage());
            log.error("分片上传异常", e);
        }
        return result;
    }

        上传参数

package mis.dto.file.fileinfo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * description 分片上传文件dto
 */
@Schema(description = "分片上传文件dto")
@Data
@Accessors(chain = true)
public class UploadFileSliceInput {

    @NotNull
    @Schema(description = "上传手机号")
    private String user_name;

    @NotNull
    @Schema(description = "文件名")
    private String file_name;

    @NotNull
    @Schema(description = "当前分片序号")
    private int current_slice_index;

}

          合并分片代码

    @ResponseBody
    @PostMapping(value = "/mergeSlice")
    @Operation(summary = "合并文件分片", description = "返回是否上传成功")
    public MisResult<FileInfoOutput> MergeSlice(UploadFileSliceMerge uploadFileSliceMerge) throws Exception {
        return fileInfoService.MergeSlice(uploadFileSliceMerge);
    }

    @Override
    public MisResult<FileInfoOutput> MergeSlice(UploadFileSliceMerge uploadFileSliceMerge) throws Exception {
        MisResult<FileInfoOutput> result = new MisResult<>();

        //参数校验,非空项及上传分片数量是否正确
        ValidateUtil.ValidateThrowException(uploadFileSliceMerge);
        String nameMd5 = DigestUtils.md5Hex(uploadFileSliceMerge.getFile_name());
        String redisKey = nameMd5 + "_" + uploadFileSliceMerge.getUser_name();
        Map<Object, Object> sliceMap = RedisUtil.HashGetMap(redisKey);
        if (sliceMap.size() != uploadFileSliceMerge.getTotal_slice_num()) {
            result.Error(MisResultCode.MIS_ERR_NOTEXITS, "文件分片上传不完整," + uploadFileSliceMerge.getUser_name());
            log.error(result.getMessage());
            return result;
        }

        //根据分片文件序号排列
        File tmpDir = new File(FileConfig.Path + File.separator + "temp");
        File[] sliceFiles = tmpDir.listFiles((dir, name) -> name.contains("slice") &&
                name.contains(nameMd5));
        if (sliceFiles == null) {
            result.Error(MisResultCode.MIS_ERR_NOTEXITS, "文件分片不存在!");
            return result;
        }
        Arrays.sort(sliceFiles, (o1, o2) -> {
            String o1Index = o1.getName().replace("_", "")
                    .replace("slice", "")
                    .replace(nameMd5, "");
            String o2Index = o2.getName().replace("_", "")
                    .replace("slice", "")
                    .replace(nameMd5, "");
            return Integer.parseInt(o1Index) - Integer.parseInt(o2Index);
        });

        //设置最终存储路径
        String fileOwner = StringUtils.hasLength(uploadFileSliceMerge.getFile_owner()) ?
                uploadFileSliceMerge.getFile_owner() : "unknown";//文件拥有者
        String newFileName = UuidGenerator.generate() + "." + FileUtil.GetSuffix(uploadFileSliceMerge.getFile_name());//新文件名
        String storageDir = FileConfig.Path + File.separator;
        if (StringUtils.hasLength(uploadFileSliceMerge.getFolder())) {
            storageDir += uploadFileSliceMerge.getFolder();//存储目录
        } else {
            storageDir += DateUnitl.ToString(null, "yyyyMM") + File.separator + fileOwner;//存储目录
        }
        if (!new File(storageDir).exists()) {
            new File(storageDir).mkdirs();
        }
        String absolutePath = storageDir + File.separator + newFileName;//存储绝对路径
        String filePath = absolutePath.replace(FileConfig.Path, "");//存储相对路径

        //合并分片文件
        FileChannel outChannel = null;
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(absolutePath);
            outChannel = outputStream.getChannel();
            for (int i = 0; i < sliceFiles.length; i++) {
                FileInputStream inputStream = new FileInputStream(sliceFiles[i]);
                try (FileChannel inChannel = inputStream.getChannel()) {
                    inChannel.transferTo(0, inChannel.size(), outChannel);
                    FileUtil.Close(inChannel);
                }
                FileUtil.Close(inputStream);
            }
        } catch (Exception e) {
            log.error("合并分片异常", e);
            result.Error(MisResultCode.ACTION_ERROR, "合并分片异常," + uploadFileSliceMerge.getFile_name());
            return result;
        } finally {
            FileUtil.Close(outChannel);
            FileUtil.Close(outputStream);
        }

        //保存最终文件信息
        File bigFile = new File(absolutePath);
        String uuid = UuidGenerator.generate();
        String downloadUrl = FileConfig.Access + "?fileInfoId=" + uuid;
        FileInfo newFileInfo = new FileInfo(uuid)
                .setFile_owner(fileOwner)
                .setOrder_code(uploadFileSliceMerge.getOrder_code())
                .setFile_name(uploadFileSliceMerge.getFile_name())
                .setFile_path(filePath)
                .setFile_absolute_path(absolutePath)
                .setFile_size(bigFile.length())
                .setFile_type(FileUtil.JudgeFileTypeByName(uploadFileSliceMerge.getFile_name()))
                .setFile_status(MisStatus.Enable)
                .setDownload_path(downloadUrl)
                .setNote(MisServerConfig.App);
        var savedFile = this.fileInfoRepository.save(newFileInfo);

        //清理分片和缓存
        for (File sliceFile : sliceFiles) {
            sliceFile.delete();
        }
        RedisUtil.Delete(nameMd5 + "_" + uploadFileSliceMerge.getUser_name());

        //返回文件对象
        var modelMapper = new ModelMapper();
        var fileInfoOutput = modelMapper.map(savedFile, FileInfoOutput.class);
        fileInfoOutput.setPreview_path("/file/fileInfo/preview?fileInfoId=" + uuid);
        result.Success(fileInfoOutput);
        return result;
    }

        合并分片参数

package mis.dto.file.fileinfo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * description 合并分片参数dto
 */
@Schema(description = "分片上传文件dto")
@Data
@Accessors(chain = true)
public class UploadFileSliceMerge {

    //合并参数
    @NotNull
    @Schema(description = "上传手机号")
    private String user_name;

    @NotNull
    @Schema(description = "总分片数")
    private double total_slice_num;

    //文件参数
    @NotNull
    @Schema(description = "文件名")
    private String file_name;//文件名

    @Schema(description = "文件持有人")
    private String file_owner;//文件持有人

    @Schema(description = "单据编码")
    private String order_code;//单据编码

    @Schema(description = "文件夹,如果文件夹不为空,则使用传入的文件夹保存文件")
    private String folder;//文件夹,如果文件夹不为空,则使用传入的文件夹保存文件
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1426878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通俗易懂理解通道注意力机制(CAM)与空间注意力机制(SAM)

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 一、参考资料 通道注意力&#xff0c;空间注意力&#xff0c;像素注意力 通道注意力机制和空间注意力机制 视觉 注意力机制——通道注意力、空间注意力、自注意力…

上位机图像处理和嵌入式模块部署(二进制图像的读写)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前我们说过&#xff0c;对于图像处理而言&#xff0c;势必会涉及到文件的读写。但是不同格式文件的读写&#xff0c;这本身又是体力活&#xff0…

大数据学习之Redis、从零基础到入门(三)

目录 三、redis10大数据类型 1.哪十个&#xff1f; 1.1 redis字符串&#xff08;String&#xff09; 1.2 redis列表&#xff08;List&#xff09; 1.3 redis哈希表&#xff08;Hash&#xff09; 1.4 redis集合&#xff08;Set&#xff09; 1.5 redis有序集合&#xff08…

提升CKA考试胜算:一文带你全面了解RBAC权限控制!

RBAC概述 RBAC引入了四个新的顶级资源对象。Role、ClusterRole、RoleBinding、 ClusterRoleBinding。同其他 API 资源对象一样&#xff0c;用户可以使用 kubectl 或者 API 调用等 方式操作这些资源对象。kubernetes集群相关所有的交互都通过apiserver来完成&#xff0c;对于这…

【CSS】移动端适配

移动端适配怎么做&#xff1f; 适配的目的是在屏幕大小不同的终端设备拥有统一的界面&#xff0c;让拥有更大屏幕的终端展示更多的内容。 meta viewport (视口) 移动端初始视口的大小默认是980px&#xff0c;因为世界上绝大多数PC网页的版心宽度为980px &#xff0c;如果网页…

Kudu数据库详解

文章目录 1、概要2、 Kudu产品特点&#xff1a;3 、Kudu架构4、 基础概念5、 服务端口6、 启停命令7 、kudu与impala结合8、 使用限制9、 使用kudu-client操作kudu 1、概要 Apache Kudu 是由 Cloudera开源的列式存储系统&#xff0c;可以同时提供低延迟的随机读写和高效的数据…

运行程序时出现“无效类”的解决方法

最近做开发时&#xff0c;遇到了一个奇怪的问题&#xff0c;打开的烧录软件突然出现了“无效类”的字样&#xff0c;以前打开却时正常的&#xff0c;真的是莫名其妙。 然后找了很久的解决办法&#xff0c;终于在某一天运行一个系统软件出现了同样的问题&#xff0c;有提示到WMI…

JS 异常处理

1、抛出异常 throw 1.throw抛出异常信息&#xff0c;程序也会终止执行 2.throw后面跟的是错误提示信息 3.Error对象配合 throw使用&#xff0c;能够设置更详细的错误信息 示例 function fn(x, y) {if (!x || !y) {throw new Error(没有参数传进来) }return x y } fn()打印结果…

0201-2-进程的描述与控制

第二章进程的描述与控制 前驱图和程序执行 程序并发执行 程序的并发执行 程序并发执行时的特征 间断性失去封闭性不可再现性 进程的描述 进程的定义 进程是程序的一次执行进程是一个程序及其数据在处理机上顺序执行时所发生的活动进程是具有独立功能的程序在一个数据集合…

业务流程自动化平台在制造业应用案例,助力业务自动化、智能化

捷昌驱动成立于2000年&#xff0c;并于2018年9月在上海证券交易所上市&#xff0c;是一家专注于线性驱动产品研发、生产及销售的科技集团。 公司整合全球资源&#xff0c;为智慧办公、医疗康护、智能家居、工业自动化等关联产业提供驱动及智能控制解决方案&#xff0c;以科技驱…

华为手机滚屏翻译:双指长按屏幕,快速翻译屏幕上的一屏或多屏内容,自动检测英日法韩西俄等10种语言

翻译作为沟通工具&#xff0c;能够帮助跨越各种语言的障碍&#xff0c;让人们能够与来自不同文化背景的人有效交流。翻译也是文化传播的重要途径&#xff0c;它允许不同语言和文化的故事、思想、知识和艺术形式被广泛传播&#xff0c;增进了人们对异域文化的理解和尊重。 在手机…

「C/C++」常见注释格式

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明

使用 PyTorch 构建 NLP 聊天机器人

一、说明 聊天机器人提供自动对话&#xff0c;可以帮助用户完成任务或寻求信息。随着深度学习的最新进展&#xff0c;聊天机器人正变得越来越具有对话性和实用性。这个全面的教程将利用 PyTorch 和 Python 从头开始构建聊天机器人&#xff0c;涵盖模型架构、数据准备、训练循环…

妤带皇冠必承其重:刘芊妤从模特到演员的蜕变

刘芊妤自2015年至2017年参加了十几场国内外的知名模特大赛&#xff0c;选美大赛&#xff0c;多次荣获冠军&#xff0c;十佳以及单项奖&#xff0c;2017年赴台湾和埃及&#xff0c;赴土耳其参加国际赛事&#xff0c;荣获奖项, 为国争光&#xff0c;她聪明睿智&#xff0c;乘风破…

基于Java SSM框架实现智能快递分拣系统项目【项目源码】计算机毕业设计

基于java的SSM框架实现智能快递分拣系统演示 JAVA简介 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterprise JavaBeans&#xff09;的全面支持&#xff0c;java servlet API&#xff0c;JSP&#xff08;java serv…

CV论文--2024.2.2

1、Motion Guidance: Diffusion-Based Image Editing with Differentiable Motion Estimators 中文标题&#xff1a;运动引导&#xff1a;利用可微分运动估计器进行基于扩散的图像编辑 简介&#xff1a;当根据文本描述生成图像时&#xff0c;扩散模型能够产生引人注目的图像&…

Kafka常见生产问题详解

目录 生产环境常见问题分析 消息零丢失方案 1、生产者发消息到Broker不丢失 2、Broker端保存消息不丢失 3、消费者端防止异步处理丢失消息 消息积压如何处理 如何保证消息顺序 ​问题一、如何保证Producer发到Partition上的消息是有序的 问题二&#xff1a;Partition中…

IDEA 配置和缓存目录 设置

IDEA系列产品&#xff0c;一般会在用户目录创建 配置 和 缓存 目录&#xff1a; %APPDATA%\JetBrains%LOCALAPPDATA%\JetBrains 一般会展示为&#xff1a; C:\Users\<username>\AppData\Roaming\JetBrainsC:\Users\<username>\AppData\Local\JetBrains 一般占用…

为啥监管层要打击量化交易?

&#xff08;1&#xff09;李鬼量化交易&#xff1a;程序化交易 我先讲讲李鬼。它本来不属于量化交易&#xff0c;但是人们说它是量化交易&#xff0c;好吧&#xff0c;三人成虎众口铄金&#xff0c;既然大家说鹿就是马&#xff0c;那鹿就是马&#xff0c;至于鹿是不是马&#…

中国文化之光:微博数据的探索与可视化分析

大家好&#xff0c;我是八块腹肌的小胖 下面我们针对主题“中国文化”相关的微博数据进行爬取 使用LDA、情感分析、情感演化、词云等可视化操作进行相关的展示 1、导包 第一步我们开始导包工作 下面这段代码&#xff0c;首先&#xff0c;pandas被请来了&#xff0c;因为它是…