http大文件上传支持断点上传
2021-01-08 18:31
标签:lse 大型 技术 config Servle sof 属性 移位 save 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。 public class FileInf { public FileInf(){} public String id=""; public String pid=""; public String pidRoot=""; /** * 表示当前项是否是一个文件夹项。 */ public boolean fdTask=false; // /// 是否是文件夹中的子文件 /// public boolean fdChild=false; /** * 用户ID。与第三方系统整合使用。 */ public int uid=0; /** * 文件在本地电脑中的名称 */ public String nameLoc=""; /** * 文件在服务器中的名称。 */ public String nameSvr=""; /** * 文件在本地电脑中的完整路径。示例:D:\Soft\QQ2012.exe */ public String pathLoc=""; /** * 文件在服务器中的完整路径。示例:F:\\ftp\\uer\\md5.exe */ public String pathSvr=""; /** * 文件在服务器中的相对路径。示例:/www/web/upload/md5.exe */ public String pathRel=""; /** * 文件MD5 */ public String md5=""; /** * 数字化的文件长度。以字节为单位,示例:120125 */ public long lenLoc=0; /** * 格式化的文件尺寸。示例:10.03MB */ public String sizeLoc=""; /** * 文件续传位置。 */ public long offset=0; /** * 已上传大小。以字节为单位 */ public long lenSvr=0; /** * 已上传百分比。示例:10% */ public String perSvr="0%"; public boolean complete=false; public Date PostedTime = new Date(); public boolean deleted=false; /** * 是否已经扫描完毕,提供给大型文件夹使用,大型文件夹上传完毕后开始扫描。 */ public boolean scaned=false; } 首先是文件数据接收逻辑,负责接收控件上传的文件块数据,然后写到服务器的文件中。控件已经提供了块的索引,大小,MD5和长度信息,我们可以根据需要来灵活进行处理,也可以将文件块的数据保存到分布式存储系统中。
out.clear(); String uid = request.getHeader("uid");// String id = request.getHeader("id"); String lenSvr = request.getHeader("lenSvr"); String lenLoc = request.getHeader("lenLoc"); String blockOffset = request.getHeader("blockOffset"); String blockSize = request.getHeader("blockSize"); String blockIndex = request.getHeader("blockIndex"); String blockMd5 = request.getHeader("blockMd5"); String complete = request.getHeader("complete"); String pathSvr = ""; //参数为空 if( StringUtils.isBlank( uid ) || StringUtils.isBlank( id ) || StringUtils.isBlank( blockOffset )) { XDebug.Output("param is null"); return; } // Check that we have a file upload request boolean isMultipart = ServletFileUpload.isMultipartContent(request); FileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List files = null; try { files = upload.parseRequest(request); } catch (FileUploadException e) {// 解析文件数据错误 out.println("read file data error:" + e.toString()); return; } FileItem rangeFile = null; // 得到所有上传的文件 Iterator fileItr = files.iterator(); // 循环处理所有文件 while (fileItr.hasNext()) { // 得到当前文件 rangeFile = (FileItem) fileItr.next(); if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr")) { pathSvr = rangeFile.getString(); pathSvr = PathTool.url_decode(pathSvr); } } boolean verify = false; String msg = ""; String md5Svr = ""; long blockSizeSvr = rangeFile.getSize(); if(!StringUtils.isBlank(blockMd5)) { md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream()); } verify = Integer.parseInt(blockSize) == blockSizeSvr; if(!verify) { msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize; } if(verify && !StringUtils.isBlank(blockMd5)) { verify = md5Svr.equals(blockMd5); if(!verify) msg = "block md5 error"; } if(verify) { //保存文件块数据 FileBlockWriter res = new FileBlockWriter(); //仅第一块创建 if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc)); res.write( Long.parseLong(blockOffset),pathSvr,rangeFile); up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex)); JSONObject o = new JSONObject(); o.put("msg", "ok"); o.put("md5", md5Svr); o.put("offset", blockOffset);//基于文件的块偏移位置 msg = o.toString(); } rangeFile.delete(); out.write(msg); %> 文件初始化部分
out.clear(); WebBase web = new WebBase(pageContext); String id = web.queryString("id"); String md5 = web.queryString("md5"); String uid = web.queryString("uid"); String lenLoc = web.queryString("lenLoc");//数字化的文件大小。12021 String sizeLoc = web.queryString("sizeLoc");//格式化的文件大小。10MB String callback = web.queryString("callback"); String pathLoc = web.queryString("pathLoc"); pathLoc = PathTool.url_decode(pathLoc); //参数为空 if ( StringUtils.isBlank(md5) && StringUtils.isBlank(uid) && StringUtils.isBlank(sizeLoc)) { out.write(callback + "({\"value\":null})"); return; } FileInf fileSvr= new FileInf(); fileSvr.id = id; fileSvr.fdChild = false; fileSvr.uid = Integer.parseInt(uid); fileSvr.nameLoc = PathTool.getName(pathLoc); fileSvr.pathLoc = pathLoc; fileSvr.lenLoc = Long.parseLong(lenLoc); fileSvr.sizeLoc = sizeLoc; fileSvr.deleted = false; fileSvr.md5 = md5; fileSvr.nameSvr = fileSvr.nameLoc; //所有单个文件均以uuid/file方式存储 PathBuilderUuid pb = new PathBuilderUuid(); fileSvr.pathSvr = pb.genFile(fileSvr.uid,fileSvr); fileSvr.pathSvr = fileSvr.pathSvr.replace("\\","/"); DBConfig cfg = new DBConfig(); DBFile db = cfg.db(); FileInf fileExist = new FileInf(); boolean exist = db.exist_file(md5,fileExist); //数据库已存在相同文件,且有上传进度,则直接使用此信息 if(exist && fileExist.lenSvr > 1) { fileSvr.nameSvr = fileExist.nameSvr; fileSvr.pathSvr = fileExist.pathSvr; fileSvr.perSvr = fileExist.perSvr; fileSvr.lenSvr = fileExist.lenSvr; fileSvr.complete = fileExist.complete; db.Add(fileSvr); //触发事件 up6_biz_event.file_create_same(fileSvr); }//此文件不存在 else { db.Add(fileSvr); //触发事件 up6_biz_event.file_create(fileSvr); FileBlockWriter fr = new FileBlockWriter(); fr.CreateFile(fileSvr.pathSvr,fileSvr.lenLoc); } Gson gson = new Gson(); String json = gson.toJson(fileSvr); json = URLEncoder.encode(json,"UTF-8");//编码,防止中文乱码 json = json.replace("+","%20"); json = callback + "({\"value\":\"" + json + "\"})";//返回jsonp格式数据。 out.write(json);%> 第一步:获取RandomAccessFile,随机访问文件类的对象 第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel,这块逻辑可以优化,如果以后有分布式存储需求,可以改为分布式存储,减轻单台服务器的压力。 public class FileBlockWriter { public FileBlockWriter(){} public void CreateFile(String pathSvr,long lenLoc) { try { File ps = new File(pathSvr); PathTool.createDirectory(ps.getParent()); RandomAccessFile raf = new RandomAccessFile(pathSvr, "rw"); raf.setLength(lenLoc);//fix:以原始大小创建文件 raf.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void write(long offset,String pathSvr,FileItem block) { try { InputStream stream = block.getInputStream(); byte[] data = new byte[(int)block.getSize()]; stream.read(data); stream.close(); RandomAccessFile raf = new RandomAccessFile(pathSvr,"rw"); raf.seek(offset); raf.write(data); raf.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 第三步:获取当前是第几个分块,计算文件的最后偏移量 第四步:获取当前文件分块的字节数组,用于获取文件字节长度 第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer 第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b); 第七步:释放缓冲区 第八步:检查文件是否全部完成上传 ? 文件夹扫描类 ? 存储路径生成类 ? 好了,到此就全部结束了,如果有疑问或批评,欢迎评论和私信,我们一起成长一起学习。 最后放一张实现的效果图 ? 后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/07/java超大文件上传与下载/ 欢迎入群一起讨论:374992201 http大文件上传支持断点上传 标签:lse 大型 技术 config Servle sof 属性 移位 save 原文地址:https://www.cnblogs.com/songsu/p/14241141.html