当前位置: 首页 > backend >正文

java超大文件上传

前端代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>分片上传前端</title><style>.container { max-width: 800px; margin: 50px auto; padding: 20px; }.drop-zone { border: 2px dashed #ddd; border-radius: 8px; padding: 50px; text-align: center; cursor: pointer; }.progress-bar { height: 30px; background: #f0f0f0; border-radius: 15px; margin: 10px 0; }.progress-fill { height: 100%; background: #4CAF50; border-radius: 10px; transition: width 0.3s ease; }.status { margin-top: 10px; color: #666; }.button-group { margin: 20px 0; }button { padding: 8px 16px; margin-right: 10px; cursor: pointer; }</style>
</head>
<body><div class="container"><h2>大文件分片上传</h2><!-- 拖放区域 --><div id="dropZone" class="drop-zone">拖放文件到此处或<input type="file" id="fileInput" style="display: none"><button onclick="document.getElementById('fileInput').click()">选择文件</button></div><!-- 上传状态 --><div id="uploadStatus" class="status"></div><!-- 进度条 --><div class="progress-bar"><div id="progressFill" class="progress-fill" style="width: 0%"></div></div><!-- 操作按钮 --><div class="button-group"><button id="pauseBtn" onclick="pauseUpload()">暂停</button><button id="resumeBtn" onclick="resumeUpload()" style="display: none">继续</button><button id="cancelBtn" onclick="cancelUpload()">取消</button></div></div><script>// 配置项const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 分片大小const UPLOAD_URL = 'http://localhost:89/api/common/config/uploadShardingFile'; // 后端上传接口let uploadTask = null;let isPaused = false;let isCanceled = false;let uploadedChunks = []; // 已上传的分片索引(从1开始)// 初始化拖放事件document.getElementById('dropZone').addEventListener('dragover', (e) => {e.preventDefault();e.target.style.backgroundColor = '#f0f0f0';});document.getElementById('dropZone').addEventListener('dragleave', (e) => {e.preventDefault();e.target.style.backgroundColor = '';});document.getElementById('dropZone').addEventListener('drop', (e) => {e.preventDefault();e.target.style.backgroundColor = '';const file = e.dataTransfer.files[0];if (file) startUpload(file);});document.getElementById('fileInput').addEventListener('change', (e) => {const file = e.target.files[0];if (file) startUpload(file);});// 开始上传function startUpload(file) {resetState();uploadTask = {file,taskId: generateTaskId(), // 生成唯一任务IDtotalChunks: Math.ceil(file.size / CHUNK_SIZE),currentChunk: 1 // 分片索引从1开始};// 检查本地是否有已上传记录(断点续传)const savedChunks = JSON.parse(localStorage.getItem(`upload_${uploadTask.taskId}`)) || [];uploadedChunks = savedChunks.length ? savedChunks : [];updateStatus();uploadNextChunk();}// 上传下一个分片async function uploadNextChunk() {if (isPaused || isCanceled || uploadTask.currentChunk > uploadTask.totalChunks) return;// 跳过已上传的分片if (uploadedChunks.includes(uploadTask.currentChunk)) {uploadTask.currentChunk++;return uploadNextChunk();}try {const chunk = createChunk(uploadTask.file, uploadTask.currentChunk);const formData = new FormData();formData.append('file', chunk.file);formData.append('taskId', uploadTask.taskId);formData.append('sliceNo', uploadTask.currentChunk);formData.append('fileSlicesNum', uploadTask.totalChunks);formData.append('fileName', uploadTask.file.name);const response = await fetch(UPLOAD_URL, {method: 'POST',body: formData,signal: new AbortController().signal // 支持中断请求});if (!response.ok) throw new Error('上传失败');// 记录已上传分片uploadedChunks.push(uploadTask.currentChunk);localStorage.setItem(`upload_${uploadTask.taskId}`, JSON.stringify(uploadedChunks));uploadTask.currentChunk++;updateProgress();uploadNextChunk();} catch (error) {if (error.name !== 'AbortError') {showStatus('上传失败,请重试', 'red');isCanceled = true;}}}// 创建文件分片function createChunk(file, chunkNumber) {const start = (chunkNumber - 1) * CHUNK_SIZE;const end = Math.min(start + CHUNK_SIZE, file.size);return {index: chunkNumber,file: file.slice(start, end),size: end - start};}// 暂停上传function pauseUpload() {isPaused = true;document.getElementById('pauseBtn').style.display = 'none';document.getElementById('resumeBtn').style.display = 'inline';showStatus('上传已暂停');}// 恢复上传function resumeUpload() {isPaused = false;document.getElementById('pauseBtn').style.display = 'inline';document.getElementById('resumeBtn').style.display = 'none';uploadNextChunk();showStatus('继续上传...');}// 取消上传function cancelUpload() {isCanceled = true;localStorage.removeItem(`upload_${uploadTask?.taskId}`);resetState();showStatus('上传已取消');}// 更新状态显示function updateStatus() {showStatus(`准备上传:${uploadTask.file.name} (${formatSize(uploadTask.file.size)})`, 'green');document.getElementById('progressFill').style.width = '0%';}// 更新进度条function updateProgress() {const progress = (uploadedChunks.length / uploadTask.totalChunks) * 100;document.getElementById('progressFill').style.width = `${progress}%`;showStatus(`已上传 ${uploadedChunks.length}/${uploadTask.totalChunks} 分片`, 'blue');if (progress === 100) {// 上传完成,清理本地记录localStorage.removeItem(`upload_${uploadTask.taskId}`);showStatus('上传完成!', 'green');}}// 重置状态function resetState() {uploadTask = null;isPaused = false;isCanceled = false;uploadedChunks = [];document.getElementById('progressFill').style.width = '0%';document.getElementById('resumeBtn').style.display = 'none';}// 显示状态信息function showStatus(text, color = '#333') {document.getElementById('uploadStatus').innerHTML = text;document.getElementById('uploadStatus').style.color = color;}// 生成唯一任务IDfunction generateTaskId() {return Date.now() + Math.random().toString(36).substr(2, 5);}// 格式化文件大小function formatSize(bytes) {if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`;if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(2)} MB`;return `${(bytes / 1e3).toFixed(2)} KB`;}</script>
</body>
</html>

后端代码

package com.talents.application.controller;import cloud.tianai.captcha.common.response.ApiResponse;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.talents.application.config.CommentConfig;
import com.talents.application.entity.dto.Account;
import com.talents.application.entity.vo.request.CaptchaVo;
import com.talents.application.utils.AESUtils;
import com.talents.application.utils.RedisUtils;
import com.talents.application.utils.UserUtils;
import com.talents.application.utils.file.CaculateMd5Utils;
import com.talents.application.utils.file.TchunkInfo;
import com.talents.application.entity.dto.SystemFile;
import com.talents.application.utils.file.FileInfoUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.*;
import com.talents.application.entity.RestBean;
import com.talents.application.service.SystemFileService;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** <p>* 项目申报表 前端控制器* </p>** @author zhangpu* @since 2024-05-10*/
@Slf4j
@RestController
@RequestMapping("/api/applicant")
public class ApplicantController extends BaseController {@Autowiredprivate SystemFileService systemFileService;@Value("${spring.profiles.active}")private String pofile;@Value("${spring.servlet.multipart.location}")private String path;@Value("${pi.domain}")private String domain;@Value("${pi.domainProfile}")private String domainProfile;@Autowiredprivate RedisUtils redisUtils;@PostMapping("/getUploadFileLastNum")public RestBean getUploadFileLastNum(TchunkInfo chunk){Account account = this.currentUser();String taskIdAndUserName = chunk.getTaskId()+":" + account.getJobNumber() + chunk.getFileName();Long uploadFileTotalSize = redisUtils.getListSize(taskIdAndUserName);return  RestBean.success(uploadFileTotalSize);}@PostMapping("/uploadShardingFileSyncPlus")public RestBean uploadShardingFileSyncPlus(TchunkInfo chunk, MultipartFile file) throws Exception{Account account = this.currentUser();//分片上传到本地chunk.setSliceNo(chunk.getSliceNo());log.info("file originName: {}, chunkNumber: {}", file.getOriginalFilename(), chunk.getSliceNo());String filePath = CommentConfig.getProfile()+account.getId()+"/";try{if (chunk.getFileSlicesNum() == 1){String md5Hex = DigestUtils.md5Hex(file.getInputStream());String taskId = chunk.getTaskId();Path path = Paths.get(FileInfoUtils.generatePathNotNum(filePath, chunk));byte[] bytes = file.getBytes();//文件写入指定路径Files.write(path, bytes);File file1 = new File(FileInfoUtils.generatePathNotNum(filePath, chunk));long length = file1.length();SystemFile systemFile = new SystemFile(null, account.getJobNumber(), chunk.getFileName(), this.domainProfile + account.getId() + "/" + taskId + "/" + chunk.getFileName(), new Date(), new Date(), length, md5Hex, taskId);systemFileService.save(systemFile);//直接保存return RestBean.success(systemFile);}String taskIdTm = chunk.getTaskId();String taskIdAndUserName = chunk.getTaskId() +":"+ account.getJobNumber() + chunk.getFileName();//获取当前上传分片是否存在Long exists = redisUtils.exists(taskIdAndUserName, chunk.getMd5());if (exists != null && exists != -1){if (chunk.getSliceNo().equals(chunk.getFileSlicesNum())){String localFile = filePath + taskIdTm + "/" + chunk.getFileName();String md5Hex = CaculateMd5Utils.calculateMD5(localFile);return RestBean.success(systemFileService.getFileIdByMd5(md5Hex,account.getJobNumber()));}return RestBean.success();}byte[] bytes = file.getBytes();Path path = Paths.get(FileInfoUtils.generatePath(filePath, chunk));//文件写入指定路径Files.write(path, bytes);//将上传完的文件分片放入队列中redisUtils.leftPushList(taskIdAndUserName,chunk.getMd5());//判断如果当前块数等于总块数 合并if (chunk.getSliceNo().equals(chunk.getFileSlicesNum())){String taskId = chunk.getTaskId();//保存到数据库SystemFile systemFile = new SystemFile(null,account.getJobNumber(), chunk.getFileName(), this.domainProfile + account.getId()+"/"+taskId + "/" + chunk.getFileName(), new Date(), new Date(), chunk.getFileTotalSize(), null,taskId);systemFileService.save(systemFile);String id = systemFile.getId();long l = System.currentTimeMillis();//文件地址String localFile = filePath + taskId + "/" + chunk.getFileName();String folder = filePath + taskId;//合并文件FileInfoUtils.merge(localFile, folder, chunk.getFileName());File fileCache = new File(localFile);long length = fileCache.length();String md5Hex = CaculateMd5Utils.calculateMD5(localFile);SystemFile systemFile = new SystemFile(id, account.getJobNumber(), chunk.getFileName(), null, new Date(), new Date(), length, md5Hex,null);systemFileService.updateById(systemFile);//redisUtils.del(taskId);log.info("文件和并与计算MD5时常------------{}毫秒",System.currentTimeMillis()-l);return RestBean.success(systemFile);}} catch (IOException e){e.printStackTrace();return RestBean.failure(400, "上传失败!");}return RestBean.success();}
}

工具类

package com.talents.application.utils.file;import com.talents.application.entity.RestBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.nio.file.*;public class FileInfoUtils {private final static Logger logger = LoggerFactory.getLogger(FileInfoUtils.class);/*** 功能描述: 生成路径** @author zhang pu* @date 13:43 2023/7/31*/public static String generatePath(String uploadFolder, TchunkInfo chunk){StringBuilder sb = new StringBuilder();sb.append(uploadFolder).append("/").append(chunk.getTaskId());//判断uploadFolder/taskId 路径是否存在,不存在则创建if (!Files.isWritable(Paths.get(sb.toString()))){logger.info("path not exist,create path: {}", sb.toString());try{Files.createDirectories(Paths.get(sb.toString()));} catch (IOException e){logger.error(e.getMessage(), e);}}return sb.append("/").append(chunk.getFileName()).append("-").append(chunk.getSliceNo()).toString();}/*** 功能描述: 文件合并** @param file* @param folder* @param filename* @author zhang pu* @date 17:06 2023/8/1*/public static RestBean merge(String file, String folder, String filename){try{//先判断文件是否存在if (fileExists(file)){return RestBean.failure(400, "文件已存在!");} else{//不存在的话,进行合并Files.createFile(Paths.get(file));Files.list(Paths.get(folder)).filter(path -> !path.getFileName().toString().equals(filename)).sorted((o1, o2) -> {String p1 = o1.getFileName().toString();String p2 = o2.getFileName().toString();int i1 = p1.lastIndexOf("-");int i2 = p2.lastIndexOf("-");return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));}).forEach(path -> {try{//以追加的形式写入文件Files.write(Paths.get(file), Files.readAllBytes(path), StandardOpenOption.APPEND);//合并后删除该块Files.delete(path);} catch (IOException e){logger.error(e.getMessage(), e);}});}} catch (IOException e){logger.error(e.getMessage(), e);//合并失败return RestBean.failure(400, "合并失败!");}return RestBean.success();}/*** 根据文件的全路径名判断文件是否存在** @param file* @return*/public static boolean fileExists(String file){boolean fileExists = false;Path path = Paths.get(file);fileExists = Files.exists(path, new LinkOption[]{LinkOption.NOFOLLOW_LINKS});return fileExists;}
}
http://www.xdnf.cn/news/13570.html

相关文章:

  • 【评测】flux-dev文生图模型初体验
  • 股指期货贴水率怎么计算?
  • 知识图谱和图数据库Neo4j
  • AI的发展过程:深度学习中的自然语言处理(NLP);大语言模型(LLM)详解;Transformer 模型结构详解;大模型三要素:T-P-G 原则
  • APP Trace 传参安装流程详解 (开发者视角)
  • Flotherm许可的跨平台兼容性
  • 在RK3588上部署ROS2与ORB-SLAM3实现Gazebo小车自主导航-环境搭建过程
  • 6月11日day51打卡
  • 机器学习与深度学习18-线性代数01
  • Java八股文——Spring「MyBatis篇」
  • RPC启动机制及注解实现
  • 基于OpenCV的图像增强技术:直方图均衡化与自适应直方图均衡化
  • JavaWeb期末速成
  • 力扣 Hot100 动态规划刷题心法:从入门到精通的蜕变之路
  • 论文略读:When Attention Sink Emerges in Language Models: An Empirical View
  • VAS1085Q奇力科技LED驱动芯片车规级线性芯片
  • OpenCV CUDA模块图像变形------ 构建仿射变换的映射表函数buildWarpAffineMaps()
  • Python文件读写操作详解:从基础到实战
  • 【笔记】NVIDIA AI Workbench 中安装 PyTorch
  • Monkey 测试的基本概念及常用命令(Android )
  • 网络安全中对抗性漂移的多智能体强化学习
  • 硬件测试 图吧工具箱分享(附下载链接)
  • 亚马逊商品数据实时获取方案:API 接口开发与安全接入实践
  • 安卓上架华为应用市场、应用宝、iosAppStore上架流程,保姆级记录(1)
  • MySQL 8配置文件详解
  • 数据淘金时代:公开爬取如何避开法律雷区?
  • 杉山将(Sugiyama Masa)《图解机器学习》
  • 重拾前端基础知识:CSS预处理器
  • 计算机视觉与深度学习 | 基于Matlab的低照度图像增强算法原理,公式及实现
  • 第二节:Vben Admin v5 (vben5) Python-Flask 后端开发详解(附源码)