大文件上传

tech2023-06-23  106

前端

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 1.引入文件 --> <link rel="stylesheet" type="text/css" href="/css/webuploader.css"> <script type="text/javascript" src="/js/jquery-2.1.4.min.js"></script> <script type="text/javascript" src="/js/webuploader.js"></script> </head> <body> <!-- 2.创建页面元素 --> <div id="uploader"> <!-- 创建用于拖拽的区域: 把文件拖到这里,它就会自动上传 --> <#--<div id="dndArea">--> <#--<p style="font-size: 8px">把文件拖到这里,它就会自动上传</p>--> <#--</div>--> <!-- 用于显示文件列表 --> <div id="fileListDiv" class="container"> <table id="fileList" class="table table-striped" border="1" width="800px"> <tr class="filelist-head"> <th class="file-name">文件名称</th> <th class="file-size">大小</th> <th width="20%" class="file-pro">进度</th> <th class="file-status">状态</th> <th width="20%" class="file-manage">操作</th> </tr> </table> </div> <!-- 用于选择文件: 手动选择文件 filePicker这个控件ID是固定的--> <div id="filePicker">文件上传</div> </div> <!-- 3.添加js代码 --> <script type="text/javascript"> // 监听分块上传的时间点,断点续传 var fileMd5=[]; //文件名,用于最后合并时生成相同名字的文件,最好不用中文文件名 var fileName=[]; var count=0;//当前正在上传的文件在数组中的下标,一次上传多个文件时使用 var filesArr=new Array(); //注册事件函数 WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send":"beforeSend", "after-send-file":"afterSendFile" },{ //beforeSendFile先于beforeSend事件执行 beforeSendFile:function(file) { fileName.push(file.name); // fileName=file.name; console.info("上传文件:" + fileName); // 创建一个deffered,用于通知是否完成操作 var deferred = WebUploader.Deferred(); // 计算文件的唯一标识MD5值,用于断点续传和妙传 (new WebUploader.Uploader()).md5File(file, 0, 5*1024*1024) .progress(function(percentage){ // $("#"+file.id).find("span.state").text("正在获取文件信息..."); }) .then(function(val) { // fileMd5 = val; fileMd5.push(val); //$("#" + file.id).find("span.state").text("成功获取文件信息"); //放行 deferred.resolve(); }); // 通知完成操作 return deferred.promise(); }, beforeSend:function(block) { var deferred = WebUploader.Deferred(); // 支持断点续传,发送到后台判断是否已经上传过 $.ajax({ type:"POST", url:"/check",//后台处理分块接口 async: false, data:{ // 文件唯一表示 fileMd5:fileMd5[0], // 当前分块下标(插件会自动生成,至于生成多少块,插件决定) chunk:block.chunk, // 当前分块大小 chunkSize:block.end-block.start }, dataType:"json", success:function(response) { if(response.ifExist) { // 分块存在,跳过该分块 deferred.reject(); } else { // 分块不存在或不完整,重新发送 deferred.resolve(); } } } ); // 发送文件md5字符串到后台 this.owner.options.formData.fileMd5 = fileMd5; return deferred.promise(); }, afterSendFile:function(file) { // 当所有分块都发送完毕之后,通知后台合并分块 var name =fileName.pop(); var md5=fileMd5.pop(); async: false, $.ajax({ type:"POST", url:"/union",//调用后台合并分块的接口 data:{ fileMd5:md5, fileName:name, //原文件名要传到后台,作为合并后文件的文件名 //fileSize:file.get fileSize:file.size }, success:function(response){ } }); } } ); // 上传基本配置--具体上传器,上传每个块 var uploader = WebUploader.create({ swf:"/js/Uploader.swf", server:"/bigupload",//后台的上传接口,用于接收每个块 pick:"#filePicker", auto:true, // dnd:"#dndArea", //拖拽区域 disableGlobalDnd:true, paste:"#uploader", // 分块上传设置 // 是否分块 chunked:true, // 每块文件大小(默认5M) chunkSize:5*1024*1024, // 开启几个并行线程(默认3个) threads:1, // fileNumLimit // 在上传当前文件时,准备好下一个文件 prepareNextFile:false, duplicate: false //是否支持重复上传 // chunkRetry : 2, //如果某个分片由于网络问题出错,允许自动重传次数 }); // 生成缩略图和上传进度 uploader.on("fileQueued", function(file) { //fileQueued事件 // 把文件信息追加到fileList的div中 var $list = $("#fileList"); $list.append('<tr id="'+ file.id +'" class="file-item">'+ '<td class="file-name">'+ file.name +'</td>'+ '<td width="20%" class="file-size">'+ (file.size/1024/1024).toFixed(1)+'M' +'</td>' + '<td width="20%" class="file-pro">0%</td>'+ '<td class="file-status">等待上传</td>'+ '<td width="20%" class="file-manage"><a class="stop-btn" href="javascript:;">暂停 </a>' + '<a class="restart-btn" href="javascript:;">开始 </a>' + '<a class="remove-this" href="javascript:;">取消</a></td>'+ '</tr>'); //暂停上传的文件 $list.on('click','.stop-btn',function(){ uploader.stop(true); }); //删除上传的文件(取消操作,这并不会重置进度,下次重传还是会从之前的进度开始) $list.on('click','.remove-this',function(){ if ($(this).parents(".file-item").attr('id') == file.id) { uploader.removeFile(file); $(this).parents(".file-item").remove(); } }); //暂停后继续开始 $list.on('click','.restart-btn',function(){ // uploader.start();//用这个是错误的 // uploader.startUpload(file);//也是错的 //至于哪个函数才是正确的,看下源码就知道 //正确的是:upload uploader.upload(file); }); } ); //重复添加文件 var timer1; uploader.onError = function(code){ clearTimeout(timer1); timer1 = setTimeout(function(){ alert('该文件已存在'); },250); }; // 监控上传进度 // percentage:代表上传文件的百分比 // uploader.on("uploadProgress", function(file, percentage) { // //更新进度的具体逻辑,可由自己实现 // //最简单的就是展示数字,例如:87% // $("#" + file.id).find("span.percentage").text(Math.round(percentage * 100) + "%"); // }); //形象一点,可以展示滚动条,进度条 // 文件上传过程中创建进度条实时显示 uploader.on( 'uploadProgress', function( file, percentage ) { $("td.file-pro").text(""); var $li = $( '#'+file.id ).find('.file-pro'), $percent = $li.find('.file-progress .progress-bar'); // 避免重复创建 if ( !$percent.length ) { $percent = $('<div class="progress progress-striped active">' + '<div class="progress-bar" role="progressbar" style="width: 0%">' + '<p class="per" style="line-height: 0px;">0%</p>' + '</div>' + '</div>' ).appendTo( $li ).find('.progress-bar'); } $li.siblings('.file-status').text('上传中'); //将百分比赋值到文本控件 $li.find('.per').text((percentage * 100).toFixed(2) + '%'); $percent.css('width', percentage * 100 + '%'); }); //其他事件 // 文件上传成功 uploader.on( 'uploadSuccess', function( file ) { $( '#'+file.id ).find('.file-status').text('已上传'); }); // 文件上传失败,显示上传出错 uploader.on( 'uploadError', function( file ) { $( '#'+file.id ).find('.file-status').text('上传出错'); }); </script> </body> </html>

后端代码

package com.lopo.controller; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.nio.channels.FileChannel; import java.util.*; /** * 合并文件 */ @RestController public class BigfileController { //文件后台统一存放位置 private static final String serverPath = "C:\\upload"; /** * 文件合并 * * @param request * @param response * @throws IOException */ // String fileMd5=null; @PostMapping("/union") public void BigFileUnion(HttpServletRequest request, HttpServletResponse response) throws IOException { // 获得需要合并的目录 String fileMd5 = request.getParameter("fileMd5"); //文件的原始文件名 String fileName = request.getParameter("fileName"); Integer fileSize = Integer.valueOf(request.getParameter("fileSize")); System.out.println("fileSize = " + fileSize); // 读取目录所有文件 File f = new File(serverPath + File.separator + fileMd5); File[] fileArray = f.listFiles(); // File[] fileArray = f.listFiles(new FileFilter() { // // 排除目录,只要文件 // public boolean accept(File pathname) { // if (pathname.isDirectory()) { // return false; // } // return true; // } // }); // 转成集合,便于排序 List<File> fileList = new ArrayList<>(Arrays.asList(fileArray)); //System.out.println("fileList = " + fileList); // 从小到大排序 Collections.sort(fileList, new Comparator<File>() { public int compare(File o1, File o2) { if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) { return -1; } return 1; } }); //输出文件名 File outputFile = new File(serverPath + File.separator + File.separator + fileName); System.out.println("outputFile = " + outputFile); File parentFile = outputFile.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } // 创建文件 outputFile.createNewFile(); // 输出流 FileOutputStream fileOutputStream = new FileOutputStream(outputFile); FileChannel outChannel = fileOutputStream.getChannel(); // 合并,核心就是FileChannel,将多个文件合并为一个文件 FileChannel inChannel; for (File file : fileList) { inChannel = new FileInputStream(file).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); inChannel.close(); // 删除分片 file.delete(); } // 关闭流 fileOutputStream.close(); outChannel.close(); // 清除文件夹 File tempFile = new File(serverPath + File.separator + fileMd5); if (tempFile.isDirectory() && tempFile.exists()) { tempFile.delete(); } } @ResponseBody @PostMapping("/check") public String BigFileCheck(HttpServletRequest request, HttpServletResponse response) throws IOException { // 校验文件是否已经上传并返回结果给前端,就一个作用:校验块是否存在,假如不存在,前端会再次用上传器传到后台 // 文件唯一表示 String fileMd5 = request.getParameter("fileMd5"); // 当前分块下标 String chunk = request.getParameter("chunk"); // 当前分块大小 String chunkSize = request.getParameter("chunkSize"); // 直接根据块的索引号找到分块文件 File checkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk); // 检查文件是否存在,且大小一致(必须满足这两个条件才认为块是已传成功) // response.setContentType("text/html;charset=utf-8"); if (checkFile.exists() && checkFile.length() == Integer.parseInt((chunkSize)) && fileMd5.length() == 32) { // response.getWriter().write("{\"ifExist\":1}"); return "{\"ifExist\":1}"; } else { //假如文件没存在,说明没有上传成功,返回0 // response.getWriter().write("{\"ifExist\":0}"); return "{\"ifExist\":0}"; } } /** * 文件上传 * @param request * @param response * @throws IOException */ @PostMapping("/bigupload") public void BigFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException { // response.getWriter().append("Served at: ").append(request.getContextPath()); System.out.println("进入FileUploadServlet后台..."); // 1.创建DiskFileItemFactory对象,配置缓存用 DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); // 2. 创建 ServletFileUpload对象 ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); // 3. 设置文件名称编码 servletFileUpload.setHeaderEncoding("utf-8"); // 4. 开始解析文件 // 文件md5获取的字符串 String fileMd5 = null; // 文件的索引 String chunk = null; try { List<FileItem> items = servletFileUpload.parseRequest(request); for (FileItem fileItem : items) { if (fileItem.isFormField()) { //普通数据,例如字符串 String fieldName = fileItem.getFieldName(); if ("info".equals(fieldName)) { String info = fileItem.getString("utf-8"); System.out.println("info:" + info); } if ("fileMd5".equals(fieldName)) { fileMd5 = fileItem.getString("utf-8"); System.out.println("fileMd5:" + fileMd5); } if ("chunk".equals(fieldName)) { chunk = fileItem.getString("utf-8"); System.out.println("chunk:" + chunk); } } else { if (StringUtils.isEmpty(fileMd5)) { fileMd5 = "test";//假如md5没有,就用test作为目录名 } if (StringUtils.isEmpty(chunk)) { chunk = fileItem.getName();//filename } // 如果文件夹没有创建文件夹 File file = new File(serverPath + File.separator + fileMd5); if (!file.exists()) { file.mkdirs(); } //这时保存的每个块,块先存好,后续会调合并接口,将所有块合成一个大文件 File chunkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk); FileUtils.copyInputStreamToFile(fileItem.getInputStream(), chunkFile); } } } catch (Exception e) { e.printStackTrace(); } } }
最新回复(0)