java对大文件分片上传

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

1.文件的切分

public static void chunkFile(){//每片的大小,这里是10Mint 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层

	@Resourceprivate 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);//解析文件数据 -解压缩unzipSystem.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.mzph.cn/news/38906.shtml

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

相关文章

ReactDOM模块react-dom/client没有默认导出报错解决办法

import ReactDOM 模块“"E:/Dpandata/Shbank/rt-pro/node_modules/.pnpm/registry.npmmirror.comtypesreact-dom18.2.7/node_modules/types/react-dom/client"”没有默认导出。 解决办法 只需要在tsconfig.json里面添加配置 "esModuleInterop": true 即…

【C++】queue容器

1.queue容器基本概念 2.queue常用接口 #include <iostream> using namespace std;//队列queue #include<queue>//创建Person类 class Person { public:Person(string name, int age){this->m_Name name;this->m_Age age;}string m_Name; //姓名int m_Age; …

优维低代码实践:自定义模板

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

禾赛科技Q2营收交付双新高,国产激光雷达从量变到质变

随着2022年激光雷达元年、2023年城市智能辅助驾驶&#xff08;NOA&#xff09;元年相继到来&#xff0c;激光雷达产业迎来爆发期。 今年以来&#xff0c;自动驾驶公司、汽车制造商以及移动出行公司等各路人马积极推动城市级别的智能辅助驾驶全面落地&#xff0c;北京、上海、深…

通过css设置filter 属性,使整个页面呈现灰度效果,让整个网页变灰

通过css设置filter 属性设置页面整体置灰 效果图: 通过设置 filter 属性为 grayscale(100%)&#xff0c;页面中的所有元素都会被应用灰色滤镜效果&#xff0c;使整个页面呈现灰度效果。 <style type"text/css"> html { filter: grayscale(100%); -webkit-f…

TB/TM-商品详情原数据(APP)

一、接口参数说明&#xff1a; item_get_app-获得TB/TMapp商品详情原数据&#xff0c;点击更多API调试&#xff0c;请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_get_app 名称类型必须描述keyString是调用key&…

考研 408 | 【计算机网络】 应用层

导图 网络应用模型 客户/服务器&#xff08;c/s&#xff09;模型 P2P模型 DNS 域名 域名服务器 域名解析过程 文件传输协议FTP FTP服务器和用户端 FTP工作原理 电子邮件 电子邮件的信息格式 组成结构 邮件服务器的功能&#xff1a; 1.发送&接收邮件 2.给发件人报告邮…

《游戏编程模式》学习笔记(四) 观察者模式 Observer Pattern

定义 观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 这是定义&#xff0c;看不懂就看不懂吧&#xff0c;我接下来举个例子慢慢说 为什么我们需要观察者模式 我们看一个很简…

如何在iPhone手机上修改手机定位和模拟导航?

如何在iPhone手机上修改手机定位和模拟导航&#xff1f; English Location Simulator&#xff08;定位模拟工具&#xff09; 是一款功能强大的 macOS 应用&#xff0c;专为 iPhone 用户设计&#xff0c;旨在修改手机定位并提供逼真的模拟导航体验。无论是为了保护隐私、测试位…

Python中的字符串与字符编码

Hello&#xff0c;这里是Token_w的博客&#xff0c;欢迎您的到来 今天文章讲解的是Python中的字符串与字符编码&#xff0c;其中有基础的理论知识讲解&#xff0c;也有实战中的应用讲解&#xff0c;希望对你有所帮助 整理不易&#xff0c;如对你有所帮助&#xff0c;希望能得到…

PDM/PLM系统建设

仅供学习使用&#xff0c;会随时更新 工程机械跨生命周期数据管理系统 来源&#xff1a;清华大学 浅论企业PDM/PLM系统建设成功经验 来源&#xff1a;e-works 作者&#xff1a;陈凡 https://articles.e-works.net.cn/pdm/article149572.htm 随着“中国制造2025”强基工程战略的…

张俊林:由ChatGPT反思大语言模型(LLM)的技术精要

转自&#xff1a;https://mp.weixin.qq.com/s/eMrv15yOO0oYQ-o-wiuSyw 导读&#xff1a;ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成这样&#xff1b;惊醒是顿悟到我们对LLM的认知及发展理念&a…

Elisp之获取PC电池状态(二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

ArcGIS Pro 基础安装与配置介绍

ArcGIS Pro ArcGIS Pro作为ESRI面向新时代的GIS产品&#xff0c;它在原有的ArcGIS平台上继承了传统桌面软件&#xff08;ArcMap&#xff09;的强大的数据管理、制图、空间分析等能力&#xff0c;还具有其独有的特色功能&#xff0c;例如二三维融合、大数据、矢量切片制作及发布…

Unity 鼠标控制 UI 放大、缩小、拖拽

文章目录 1. 代码2. 测试场景 1. 代码 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class UIDragZoom : MonoBehaviour, IDragHandler, IScrollHandler {private Vector2 originalSize;private Vector2 originalPosition;private RectTr…

css3 瀑布流布局遇见截断下一列展示后半截现象

css3 瀑布流布局遇见截断下一列展示后半截现象 注&#xff1a;css3实现瀑布流布局简直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 场景-在uniapp项目中 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px&#xff0c;column-count:2,2列展…

在阿里云服务器上安装Microsoft SharePoint 2016流程

本教程阿里云百科分享如何在阿里云ECS上搭建Microsoft SharePoint 2016。Microsoft SharePoint是Microsoft SharePoint Portal Server的简称。SharePoint Portal Server是一个门户站点&#xff0c;使得企业能够开发出智能的门户站点。 目录 背景信息 步骤一&#xff1a;添加…

无涯教程-Perl - setgrent函数

描述 此功能将枚举设置(或重置)到组条目集的开头。该函数应在第一次调用getgrent之前调用。 语法 以下是此函数的简单语法- setgrent返回值 此函数不返回任何值。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl -wwhile( ($name,$passwd,$gid,$members)getgrent…

ide internal errors【bug】

ide internal errors【bug】 前言版权ide internal errors错误产生相关资源解决1解决2 设置虚拟内存最后 前言 2023-8-15 12:36:59 以下内容源自《【bug】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是h…

代码随想录算法训练营第58天|动态规划part15|392.判断子序列、115.不同的子序列

代码随想录算法训练营第58天&#xff5c;动态规划part15&#xff5c;392.判断子序列、115.不同的子序列 392.判断子序列 392.判断子序列 思路&#xff1a; &#xff08;这道题也可以用双指针的思路来实现&#xff0c;时间复杂度也是O(n)&#xff09; 这道题应该算是编辑距…