上山打老虎 发表于 2021-7-11 09:16:38

前端 WebUploader 分块上传

  
前端 WebUploader 分块上传

  这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数
  下面直接贴代码吧,一些难懂的我大部分都加上注释了:
  上传文件实体类:
  看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。
  public class FileInf {
  public FileInf(){}
  public String id="";
  public String pid="";
  public String pidRoot="";   
  /**  * 表示当前项是否是一个文件夹项。    */
  public boolean fdTask=false;        
  //   /// 是否是文件夹中的子文件  /// </summary>
  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超大文件上传与下载/

  
文档来源:51CTO技术博客https://blog.51cto.com/u_14023400/3019744
页: [1]
查看完整版本: 前端 WebUploader 分块上传