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

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.dtcms.com/a/244564.html

相关文章:

  • 微服务架构下大型商城系统的事务一致性攻坚:Saga、TCC与本地消息表的实战解析
  • Java 8 Stream 流详细教程 - 全面指南
  • uni-app离线打包配置Android打包(使用Android studio打包)
  • 联邦学习中常用的聚合方式
  • 初识 Redis:从入门到应用的全面指南
  • React Context 性能问题及解决方案深度解析
  • 物联网基础概述【一】
  • 开疆智能ModbusTCP转Canopen网关连接汇川AM403PLC与编码器配置案例
  • Vue.js $emit的介绍和简单使用
  • 商品中心—3.商品可采可补可售的技术文档下
  • UE5 学习系列(四)导入模型资产
  • Hydra 工具小白入门教程指导篇
  • RawTherapee:专业RAW图像处理,免费开源
  • CST同轴空气线模型参数定义方法
  • YOLOv1 技术详解:正负样本划分与置信度设计
  • 第二章——线性表之循环链表、静态链表
  • 结合redis实现文件分片秒传断点续传
  • TI dsp Timer 定时器
  • 汽车电子行业的高效研发利器——全星研发项目管理APQP软件系统
  • 开疆智能ModbusTCP转Canopen网关连接AGV地标传感器
  • 楼宇自控整合空调照明安防系统,构建建筑环境智能管理高效体系
  • 第七章——8天Python从入门到精通【itheima】-81~84(函数的多返回值+函数多种传参方式+函数作为参数传递+lambda函数)
  • 大模型技术30讲-4-彩票假设
  • 第六讲——一元函数微分学的应用之中值定理、微分等式与微分不等式
  • 面壁智能MiniCPM4.0技术架构与应用场景
  • OAuth 2.0中/oauth/authorize接口的核心作用解析
  • 大模型的类别对比:LLM、Text Embedding、Rerank、Speech to text,TTS
  • 14.计算机网络End
  • Docker三大核心组件详解:镜像、容器、仓库的协作关系
  • 想考Kubernetes认证?CKA考试内容与报名全解析