java对大文件分片上传

news2025/1/18 21:01:30
  • 这里记录一下,Java对大文件的切分,和后端接口分片上传的实现逻辑
    正常,前后端分离的项目其实是前端去切分文件,后端接口接收到切分后的分片文件去合并,这里都用java来记录一下。
  • 特别说明:我这里用的是zip包的上传,里面是音频文件,如果你的文件是单个文件,切分和合并文件逻辑都是一样的,只是不用后续的解压。
  • 因为是测试代码,所以部分代码规范不严谨

1.文件的切分

public static void chunkFile(){
        //每片的大小,这里是10M
        int TENMB = 10485760;
        //要切分的大文件和切分后的文件放到目录
       String PATH = "D:\\test\\fenpian\\";
        try {
           File file = new File(PATH, "55.zip");
            RandomAccessFile accessFile = new RandomAccessFile(file, "r");
            // 文件的大小
            long size = FileUtil.size(file);
            int chunkSize = (int) Math.ceil((double) size / TENMB);
            for (int i = 0; i < chunkSize; i++) {
                // 文件操作的指针位置
                long filePointer = accessFile.getFilePointer();
                byte[] bytes;
                if (i == chunkSize - 1) {
                    int len = (int) (size - filePointer);
                    bytes = new byte[len];
                    accessFile.read(bytes, 0, bytes.length);
                } else {
                    bytes = new byte[TENMB];
                    accessFile.read(bytes, 0, bytes.length);
                }
                FileUtil.writeBytes(bytes, new File(PATH, String.valueOf(i) + ".zip"));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

55.zip切分成了10个小的zip包

2.Spring boot分片上传接口

controller层

	@Resource
    private FileUploadService fileUploadService;

    @RequestMapping(value = "/upload")
    public String upload(MultipartFileParam fileParam) throws IOException {
        try{
            return fileUploadService.fileUpload(fileParam);
        }catch (Exception e){
            e.printStackTrace();
            return "error";
        }
    }

service层

public interface FileUploadService {
    String fileUpload(MultipartFileParam fileParam) throws IOException;
 }

service实现层

@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {
	//合并后的文件的父目录
	private String FILE_UPLOAD_DIR = "D:\\test\\fenpian";
	//分片文件大小
	private Integer CHUNK_SIZE = 10485760;

	/**
     * 分片上传
     * @param fileParam
     * @return
     * @throws IOException
     */
    private String chunkUpload(MultipartFileParam fileParam) throws IOException {
        // 是否为最后一片
        boolean lastFlag = false;

        int currentChunk = fileParam.getChunk();
        int totalChunk = fileParam.getTotalChunk();
        long totalSize = fileParam.getTotalSize();
        String fileName = fileParam.getName();
        String fileMd5 = fileParam.getMd5();
        MultipartFile multipartFile = fileParam.getFile();

        String parentDir = FILE_UPLOAD_DIR + File.separator + fileMd5 + File.separator;
        String tempFileName = fileName + "_tmp";

        // 写入到临时文件
        File tmpFile = tmpFile(parentDir, tempFileName, multipartFile, currentChunk, totalSize, fileMd5);
        // 检测是否为最后一个分片(这里吧每个分片数据放到了一张表里,后续可以改为用redis记录)
        FileChunkRecordExample example = new FileChunkRecordExample();
        example.createCriteria().andMd5EqualTo(fileMd5);
        long count = fileChunkRecordMapper.countByExample(example);

        if (count == totalChunk) {
            lastFlag = true;
        }

        if (lastFlag) {
            // 检查md5是否一致
            log.info("是否最后一个分片:{}","是");
            if (!checkFileMd5(tmpFile, fileMd5)) {
                cleanUp(tmpFile, fileMd5);
                throw new RuntimeException("文件md5检测不符合要求, 请检查!");
            }
     
            System.out.println("开始重命名....");
            File newFile = renameFile(tmpFile, fileName);

            //解析文件数据 -解压缩unzip
            System.out.println("开始解压缩....");
            File zipFile = ZipUtil.unzip(newFile);
            //得到压缩包内所有文件

            System.out.println("遍历zipFile.....");
            File[] files = zipFile.listFiles();
            System.out.println("打印fileName.....");
            //解析文件,处理业务数据
            for (File file : files) {
                System.out.println(file.getName());
            }
            log.info("所有文件上传完成, 时间是:{}, 文件名称是:{}", DateUtil.now(), fileName);
			//所有数据都处理完成后,删除文件和数据库记录
            cleanUp(new File(parentDir + fileName),fileMd5);
        }else{
            log.info("是否最后一个分片:{}","否");
        }


        return "success";
    }

	private File tmpFile(String parentDir, String tempFileName, MultipartFile file,
                         int currentChunk, long totalSize, String fileMd5) throws IOException {
        log.info("开始上传文件, 时间是:{}, 文件名称是:{}", DateUtil.now(), tempFileName);
        long position = (currentChunk - 1) * CHUNK_SIZE;
        File tmpDir = new File(parentDir);
        File tmpFile = new File(parentDir, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
        if (tempRaf.length() == 0) {
            tempRaf.setLength(totalSize);
        }

        // 写入该分片数据
        FileChannel fc = tempRaf.getChannel();
        MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, position, file.getSize());
        map.put(file.getBytes());
        clean(map);
        fc.close();
        tempRaf.close();

        // 记录已经完成的分片
        FileChunkRecord fileChunkRecord = new FileChunkRecord();
        fileChunkRecord.setMd5(fileMd5);
        fileChunkRecord.setUploadStatus(1);
        fileChunkRecord.setChunk(currentChunk);
        fileChunkRecordMapper.insert(fileChunkRecord);
        log.info("分片文件上传完成, 时间是:{}, 文件名称是:{}", DateUtil.now(), tempFileName);
        return tmpFile;
    }

	private void cleanUp(File file, String md5) {
        if (file.exists()) {
            file.delete();
        }
        // 删除上传记录
        FileChunkRecordExample example = new FileChunkRecordExample();
        example.createCriteria().andMd5EqualTo(md5);
        fileChunkRecordMapper.deleteByExample(example);
    }


	/**
     * 最后一片接受完后执行
     * @param toBeRenamed
     * @param toFileNewName
     * @return
     */
    private File renameFile(File toBeRenamed, String toFileNewName) {
        // 检查要重命名的文件是否存在,是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            log.info("File does not exist: " + toBeRenamed.getName());
            throw new RuntimeException("File does not exist");
        }
        String parentPath = toBeRenamed.getParent();
        File newFile = new File(parentPath + File.separatorChar + toFileNewName);
        // 如果存在, 先删除
        if (newFile.exists()) {
            newFile.delete();
        }
        toBeRenamed.renameTo(newFile);
        return newFile;
    }

	private static void clean(MappedByteBuffer map) {
        try {
            Method getCleanerMethod = map.getClass().getMethod("cleaner");
            Cleaner.create(map, null);
            getCleanerMethod.setAccessible(true);
            Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(map);
            cleaner.clean();
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

/**
     * 文件md5值检查,最后一片文件合并后执行,
     * @param file    所有分片文件合并后的文件(正常情况下md5应该和前端传过来的大文件的md5一致)
     * @param fileMd5 大文件的md5值
     * @return
     * @throws IOException
     */
    private boolean checkFileMd5(File file, String fileMd5) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        String checkMd5 = DigestUtils.md5DigestAsHex(fis).toUpperCase();
        fis.close();
        if (checkMd5.equals(fileMd5.toUpperCase())) {
            return true;
        }
        return false;
    }



	/**
     * 不分片
     * @param fileParam
     * @return
     */
    private String singleUpload(MultipartFileParam fileParam) {
        MultipartFile file = fileParam.getFile();
        File baseFile = new File(FILE_UPLOAD_DIR);
        if (!baseFile.exists()) {
            baseFile.mkdirs();
        }
        try {
            file.transferTo(new File(baseFile, fileParam.getName()));
            Date now = new Date();
            FileRecord fileRecord = new FileRecord();
            String filePath = FILE_UPLOAD_DIR + File.separator + fileParam.getName();
            long size = FileUtil.size(new File(filePath));
            String sizeStr = size / (1024 * 1024) + "Mb";
            fileRecord.setFileName(fileParam.getName()).setFilePath(filePath).setUploadStatus(1)
                    .setFileMd5(fileParam.getMd5()).setCreateTime(now).setUpdateTime(now).setFileSize(sizeStr);
            //fileRecordMapper.insert(fileRecord);
        } catch (IOException e) {
            log.error("单独上传文件错误, 问题是:{}, 时间是:{}", e.getMessage(), DateUtil.now());
        }
        return "success";
    }

}

后端入参实体类

package com.server.controller.bigFileUpload;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.web.multipart.MultipartFile;

/**
 * @description:
 * @date: created in 2021/10/6
 * @modified:
 */
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class MultipartFileParam {
    /**
     * 是否分片
     */
    private boolean chunkFlag;

    /**
     * 当前为第几块分片
     */
    private int chunk;

    /**
     * 总分片数量
     */
    private int totalChunk;

    /**
     * 文件总大小, 单位是byte
     */
    private long totalSize;

    /**
     * 文件名
     */
    private String name;

    /**
     * 文件
     */
   
    private MultipartFile file;

    /**
     * md5值
     */
    private String md5;

}

合并后的文件放到了文件mdf的文件夹内
在这里插入图片描述

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

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

相关文章

Codeforces Round 888 (Div. 3)ABC

Codeforces Round 888 (Div. 3) 目录 A. Escalator Conversations题目大意思路代码 B. Parity Sort题目大意思路代码 C. Tiles Comeback题目大意思路代码 A. Escalator Conversations 题目大意 判断有多少个人能够通过站在楼梯上的操作和VLAD一样高或者是VLAD通过站在楼梯上的…

6.1 MyBatis基础

1.MyBatis概述 MyBatis本是apache的一个开源项目iBatis&#xff0c;2010年这个项目由apache software foundation迁移到了google code&#xff0c;并且改名为MyBatis&#xff0c;2013年11月MyBatis又被迁移到Github。 MyBatis是一个支持普通SQL查询、存储过程以及高级映射的持…

欧拉算法与埃氏筛法比较

#include<iostream> using namespace std; bool data[100000005]; // zhishu用于存储质数的数组 &#xff0c;cnt下标 int zhishu[100000000],cnt0;int main() {data[1] 1;// 1表示素数 int n;cin >> n;// 循环遍历for(int i2;i<n;i){if(data[i] 0){// 表明是…

嵌入式学习之C语言指针部分复习

今天主要把C语言的指针部分再次认真的复习了一下&#xff0c;对于指针的整体框架有了更加深刻的理解&#xff0c;特别要重点区分函数指针&#xff0c;指针函数&#xff0c;数组指针&#xff0c;指针数组部分&#xff0c;对于这部分的应用回非常的重要&#xff0c;而且C语言指针…

海龟绘图——长方体(包含建立和销毁的过程)

运行结果&#xff1a; 代码&#xff1a; import turtle turtle.setup(1024,1024,200,200)# 移动画笔 turtle.penup() turtle.goto(-200,200) turtle.pendown() # 绘制第一条实线 turtle.setheading(0) turtle.forward(300) # 绘制第二条实线 turtle.setheading(18045) turtle.…

Python语法基础--条件选择

学习目标 使用比较运算符编写布尔表达式。使用random.randint(a,b)或者random.random()函数来生成随机数。编写布尔表达式(AdditionQuiz)。使用单向if语句实现选择控制。使用单向if语句编程。使用双向if-else语句实现选择控制。使用嵌套if和多向if-elif-else语句实现选择控制。…

winform使用IrisSkin4.dll换肤后排除控件

设置控件的tag属性为9999 参考&#xff1a; https://www.cnblogs.com/mq0036/p/6654219.html

YOLOV5改进:加入RCS-OSA模块,提升检测速度

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:RCS-OSA模块更加轻量化,有效提升检…

一场大火烧毁了印度的芯片梦 | 百能云芯

谈起印度的半导体发展史&#xff0c;鲜为人知的是&#xff0c;该国曾有可能成为全球半导体制造业的重要中心。然而&#xff0c;一个意外的事件彻底改变了历史进程&#xff0c;让印度错失了超越台积电的机会。 01半导体制造潜力 在高科技行业&#xff0c;也许很多人都不看好印度…

UE5.2程序发布及运行问题记录

发布后的程序默认是以全屏模式启动运行的&#xff0c;通过添加以下命令行参数&#xff0c;可实现程序的窗口模式运行&#xff1a; -ResX1280 -ResY720 -WINDOWED 发布后的程序&#xff0c;启动时&#xff0c;提示显卡驱动警告&#xff08;如图1所示&#xff09;&#xff0c;但是…

Java-Sec-Code靶场

文章目录 前言1.靶场搭建靶场地址、环境Window环境修改点 靶场通关和源码分析命令注入RCE反序列化fastjson反序列化目录穿越文件上传Spel表达式sql注入poi-ooxml组件XXE 总结 前言 一直都是一个Java盲&#xff0c;但是如今Java却占据了开发的半壁江山&#xff0c;平时遇见的多…

情感分析工具: TextBlob 与 VADER 的对比

一、说明 在本文我们将看到&#xff0c;在情感分析方面&#xff0c;我们更喜欢哪个库。这取决于很多情况。例如。数据采集。在我们进行分析之前&#xff0c;让我们先看看这两个库是关于什么的。 二、亮相工具库 2.1. 工具库TextBlob介绍&#xff1a; 图像。TextBlob: Simplif…

对自定义表格数据设计自定义查询/汇总

目录 1 前言 2 生成数据 3 设计一个汇总 4 试一下效果 5 导出为excel文件的源代码 6 后记 1 前言 对自定义表格中录入或者导入的数据&#xff0c;必须能定义查询和汇总&#xff0c;否则程序基本没什么用。就是说&#xff0c;程序应该具备对任意表格进行方便的查询汇总公式…

VS Code终端系统启动失败的问题

一百度真的是各种各样打不开&#xff0c;有的配置一下就可以了&#xff0c;有的改成管理员权限就可以了&#xff0c;我的是怎么设置都不行&#xff0c;安装了最新版本的不行&#xff0c;又退了几个版本才行了&#xff0c;1.56版本内网行&#xff0c;1.55版本能行&#xff0c;比…

企业权限管理(十五)-方法级别权限控制

方法级别权限控制 jsr-250 3.Secured注解使用 开启表达式的使用 页面控制 显示xxx在线 <div class"pull-left info"><p><security:authentication property"principal.username"></security:authentication></p><a h…

Blazor:子组件deep:: 样式修改无效

文章目录 资料无效代码有效代码 资料 Blazor Css隔离 简单来说&#xff0c;就是外层要加个div&#xff0c;不加div就无法识别。 无效代码 <Layout ShowFooter"true" class"Layout" SideWidth"100px"><Header><div class"t…

C语言刷题训练DAY.3

1.ASCII码 解题思路&#xff1a; 这里直接以字符的形式打印就行&#xff1a; 解题代码&#xff1a; #include<stdio.h> int main() {printf("%c%c%c%c%c%c%c%c%c%c%c%c", 73, 32, 99, 97, 110, 32, 100, 111, 32, 105, 116, 33);return 0; } 2.出生日期输…

09 - 连续的多个commit整理成1个

查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;GIT常用场景- 目录 文章目录 将连续的多个commit整理成1个 将连续的多个commit整理成1个 将anranxiaohunzhang和xianglongshibazhang合并起来&#xff08;将anranxiaohunzhang合并到降龙十八掌上&#xff0c;生成新…

概要设计完整版word文档,《某信息管理平台总体设计方案》,505页20万字供参考,附下载

上一篇介绍了软件生命周期全过程&#xff0c;软件工程全周期全过程20项文档模板&#xff0c;附下载。从《合同》到《需求规格说明书》到软件设计、开发、实施、验收、维护等全过程相关文档模板。有朋友反馈附件内容没有补全&#xff0c;本次及后续会用实际案例补全附件内容&…