java大文件分片上传

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;//文件夹,如果文件夹不为空,则使用传入的文件夹保存文件
    
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS17使用safari调试wkwebview

isInspectable配置 之前开发wkwebview的页面的时候一直使用safari调试&#xff0c;毕竟jssdk交互还是要用这个比较方便&#xff0c;虽说用一个脚本插件没问题。不过还是不太方便。 但是这个功能突然到了iOS17之后发现不能用了&#xff0c;还以为又是苹果搞得bug&#xff0c;每…

盲盒小程序开发,实现“双收益”

盲盒在我国是一个发展潜力较高的市场&#xff0c;盲盒具有的刺激和收藏价值&#xff0c;深受消费者的喜爱&#xff0c;盲盒的“隐藏款”机制&#xff0c;能够为消费者带来惊喜感。盲盒一般与影视动漫IP合作&#xff0c;推出盲盒商品&#xff0c;这也是深受年轻人追捧的一大特点…

ManticoreSearch-(安装配置,集群搭建)-学习总结

ManticoreSearch-(安装配置)-学习总结 基础概念安装搭建集群搭建(基于K8S) 原文地址 https://blog.csdn.net/liuyij3430448/article/details/135955025 基础概念 Manticore Search是一个专门为搜索设计的多存储数据库&#xff0c;具有强大的全文搜索功能&#xff0c;适用于…

【原神游戏开发日志5】同地图多客户端玩家同步

版权声明&#xff1a; ● 本文为“优梦创客”原创文章&#xff0c;您可以自由转载&#xff0c;但必须加入完整的版权声明 ● 文章内容不得删减、修改、演绎 ● 本文视频版本&#xff1a;见文末 ● 相关学习资源&#xff1a;见文末 前言 ● 今天给大家分享选角色、进游戏 打包…

员工持股方案

目的&#xff1a;为激发员工积极性&#xff0c;留住好员工&#xff0c;给员工提供创业机会&#xff0c;使员工共同关注企业发展&#xff0c;建立促进企业发展的激励机制。股权设置与持股比例&#xff1a; 公司以总资产 万元&#xff0c;折合股权 份&#xff0c;每股 …

【algorithm】一个简单的PID工程 base 用于手生时候快速复习 用于设计模式 cpp语法八股 快速复习校验

写在前面 最近项目一直用matlab&#xff0c;防止手生整一个回忆工具使用的简单的pid demo&#xff0c;走一边流程&#xff0c;包括配工程debug看结果&#xff0c;复用之前记录的配置见我的bloghttps://blog.csdn.net/weixin_46479223/article/details/135082867?csdn_share_t…

【算法】登山(线性DP,最长上升)

题目 五一到了&#xff0c;ACM队组织大家去登山观光&#xff0c;队员们发现山上一共有N个景点&#xff0c;并且决定按照顺序来浏览这些景点&#xff0c;即每次所浏览景点的编号都要大于前一个浏览景点的编号。 同时队员们还有另一个登山习惯&#xff0c;就是不连续浏览海拔相…

小程序样例4:个人中心+我的书单

基本功能&#xff1a; 1、展示个人基本信息&#xff1a;头像、昵称 、读书时间统计 2、邮件列表&#xff0c;点击加入计划跳转到书架 3、今日任务 学习进度 4、邮件滑动到最末尾或者最开始&#xff0c;会有弹框提示&#xff1a; 5、图书搜索框 代码分析&#xff1a; 1、邮件…

和朋友随时随地玩——幻兽帕鲁服务器极简部署流程

什么是游戏服务器&#xff1f;通俗来说就是一个公共的电脑&#xff0c;玩家可以在任意时刻进入服务器游玩&#xff0c;不需要等待某个玩家创建房间&#xff0c;即可任意在一个世界一起游戏 本文将为您提供极简部署幻兽帕鲁服务器的指引&#xff0c;「仅需轻点三次鼠标&#xff…

get out of black background

文章目录 基础 Sequence settings (after selected a Sequence) 看见 ( 让Pr表示透明 ) Effects-> Color Key, drag into your Sequence >.如果看不到 Effects 面板, 可以在 Window 菜单中打开 在Effect Controls 你可以调整 Color Key 的效果了先吸取黑色 还可以使用ma…

力扣136、只出现一次的数字(简单)

1 题目描述 图1 题目描述 2 题目解读 在非空整数数组nums中&#xff0c;有一个元素只出现了一次&#xff0c;其余元素均出现两次。要求找出那个只出现一次的元素。 3 解法一&#xff1a;位运算 位运算&#xff0c;是一种非常简便的方法。 3.1 解题思路 异或运算&#xff0c;有…

力扣hot100 数组中的第K个最大元素 堆 三路划分

Problem: 215. 数组中的第K个最大元素 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( log ⁡ n ) O(\log{n}) O(logn) Code class Solution {public int findKthLargest(int[] nums, int k…

Kafka运维相关知识

目录 一、基本概念 二、技术特性 三、设计思想 四、运维建议 一、基本概念 Apache kafka 是一个分布式的基于push-subscribe的消息系统&#xff0c;它具备快速、可扩展、可持久化的特点。它的最大的特性就是可以实时的处理大量数据以满足各种需求场景&#xff1a;比如基于h…

Facebook的社交影响力:用户行为解析与趋势

在当今数字时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为全球最大的社交平台之一&#xff0c;其社交影响力愈发显著。本文将深入分析Facebook的社交影响力&#xff0c;解析用户行为&#xff0c;同时探讨当前和未来的社交趋势。 社…

强大的虚拟机Parallels Desktop 19 mac中文激活

Parallels Desktop是一款功能全面、易于使用的虚拟机软件&#xff0c;它为用户提供了在Mac电脑上同时运行多个操作系统的便利。 软件下载&#xff1a;Parallels Desktop 19 mac中文激活版下载 Parallels Desktop 19 mac具有快速启动和关闭虚拟机的能力&#xff0c;让用户能够迅…

记录 arm 开发板上 nginx 配置 http 服务注意事项

1. 自定义项目&#xff0c;需要在 conf.d 目录中增加一个 .conf 配置文件&#xff1a; server {listen 9200; # 端口号server_name localhost; # 服务名称location / {root /home/imx6q/media; # 项目根目录&#xff08;需要修改 n…

C++ 类与对象(中)

本节目标 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认…

【JVM】类加载流程

目录 1.加载 2.链接 &#xff08;1&#xff09;校验 &#xff08;2&#xff09;准备 &#xff08;3&#xff09;解析 3.初始化 4.使用 5.卸载 1.加载 加载阶段&#xff0c;简言之&#xff0c;查找并加载类的二进制数据&#xff0c;生成 Class 的实例 在加载类时&#x…

QT学习日记 | QT的环境搭建

目录 前言 一、QT概述 二、QT的环境搭建 1、QT SDK安装 2、环境变量的配置 前言 本系列为小编新开的一个系列&#xff0c;主要记录小编学习QT的过程&#xff0c;作为笔记仅供各位参考&#xff1b; 一、QT概述 Qt是一个跨平台C图形应用界面框架&#xff1b;简单来说&#x…

RK3568平台 热插拔机制

一.热插拔的基本概念 热插拔是指在设备运行的情况下&#xff0c;能够安全地插入或拔出硬件设备&#xff0c;而无需关闭或重启系统。这意味着你可以在计算机或其他电子设备上插入或拔出硬件组件&#xff08;比如USB设备&#xff0c;扩展卡&#xff0c;硬件驱动器等&#xff09;…