评论

收藏

[JavaScript] JS怎么实现web端上传超大文件

开发技术 开发技术 发布于:2021-07-07 16:53 | 阅读数:347 | 评论:0

  
JS怎么实现web端上传超大文件

  我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。
  首先我们需要了解的是上传文件三要素:
  1.表单提交方式:post (get方式提交有大小限制,post没有)
  2.表单的enctype属性:必须设置为multipart/form-data.
  3.表单必须有文件上传项:file,且文件项需要给定name值
  上传文件夹需要增加一个属性webkitdirectory,像这样:
  <input id="fileFolder" name="fileFolder" type="file"  webkitdirectory>
  不过webkitdirectory属性有个问题,只能支持高版本的chrome,不能支持低版本的IE,如ie6,ie7,ie8,不能做到全浏览器适配,运行环境比较单一。
  js中可以判断文件夹中文件数量及文件夹大小是否符合要求,不符合要求不能向后台提交:
  前台HTML模板
  this.GetHtmlFiles = function()
  {
  var acx = "";
  acx += '<div class="file-item" id="tmpFile" name="fileItem">\
  <div class="img-box"><img name="file" src="js/file.png"/></div>\
  <div class="area-l">\
  <div class="file-head">\
  <div name="fileName" class="name">HttpUploader程序开发.pdf</div>\
  <div name="percent" class="percent">(35%)</div>\
  <div name="fileSize" class="size" child="1">1000.23MB</div>\
  </div>\
  <div class="process-border"><div name="process" class="process"></div></div>\
  <div name="msg" class="msg top-space">15.3MB 20KB/S 10:02:00</div>\
  </div>\
  <div class="area-r">\
  <span class="btn-box" name="cancel" title="取消"><img name="stop" src="js/stop.png"/><div>取消</div></span>\
  <span class="btn-box hide" name="post" title="继续"><img name="post" src="js/post.png"/><div>继续</div></span>\
  <span class="btn-box hide" name="stop" title="停止"><img name="stop" src="js/stop.png"/><div>停止</div></span>\
  <span class="btn-box hide" name="del" title="删除"><img name="del" src="js/del.png"/><div>删除</div></span>\
  </div>';
  acx += '</div>';
  //文件夹模板
  acx += '<div class="file-item" name="folderItem">\
  <div class="img-box"><img name="folder" src="js/folder.png"/></div>\
  <div class="area-l">\
  <div class="file-head">\
  <div name="fileName" class="name">HttpUploader程序开发.pdf</div>\
  <div name="percent" class="percent">(35%)</div>\
  <div name="fileSize" class="size" child="1">1000.23MB</div>\
  </div>\
  <div class="process-border top-space"><div name="process" class="process"></div></div>\
  <div name="msg" class="msg top-space">15.3MB 20KB/S 10:02:00</div>\
  </div>\
  <div class="area-r">\
  <span class="btn-box" name="cancel" title="取消"><img name="stop" src="js/stop.png"/><div>取消</div></span>\
  <span class="btn-box hide" name="post" title="继续"><img name="post" src="js/post.png"/><div>继续</div></span>\
  <span class="btn-box hide" name="stop" title="停止"><img name="stop" src="js/stop.png"/><div>停止</div></span>\
  <span class="btn-box hide" name="del" title="删除"><img name="del" src="js/del.png"/><div>删除</div></span>\
  </div>';
  acx += '</div>';
  //上传列表
  acx += '<div class="files-panel" name="post_panel">\
  <div name="post_head" class="toolbar">\
  <span class="btn" name="btnAddFiles">选择多个文件</span>\
  <span class="btn" name="btnAddFolder">选择文件夹</span>\
  <span class="btn" name="btnPasteFile">粘贴文件和目录</span>\
  <span class="btn" name="btnSetup">安装控件</span>\
  </div>\
  <div class="content" name="post_content">\
  <div name="post_body" class="file-post-view"></div>\
  </div>\
  <div class="footer" name="post_footer">\
  <span class="btn-footer" name="btnClear">清除已完成文件</span>\
  </div>\
  </div>';
  return acx;
  };
  选择文件,选择文件夹,粘贴文件和文件夹的逻辑
  this.open_files = function (json)
  {
  for (var i = 0, l = json.files.length; i < l; ++i)
  {
  this.addFileLoc(json.files);
  }
  setTimeout(function () { _this.PostFirst(); },500);
  };
  this.open_folders = function (json)
  {
  for (var i = 0, l = json.folders.length; i < l; ++i) {
  this.addFolderLoc(json.folders);
  }
  setTimeout(function () { _this.PostFirst(); }, 500);
  };
  this.paste_files = function (json)
  {
  for (var i = 0, l = json.files.length; i < l; ++i)
  {
  this.addFileLoc(json.files);
  }
  };
  后台在接收文件夹时不同之处在需要用MultipartHttpServletRequest
  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);
  }
  }
  server端的包和类
  文件块处页面,验证代码部分
  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);
  生成文件名称的逻辑
  public String genFile(int uid, String md5,String nameLoc) throws IOException
  {
  SimpleDateFormat fmtDD = new SimpleDateFormat("dd");
  SimpleDateFormat fmtMM = new SimpleDateFormat("MM");
  SimpleDateFormat fmtYY = new SimpleDateFormat("yyyy");
  Date date = new Date();
  String strDD = fmtDD.format(date);
  String strMM = fmtMM.format(date);
  String strYY = fmtYY.format(date);
  String path = this.getRoot() + "/";
  path = path.concat(strYY);
  path = path.concat("/");
  path = path.concat(strMM);
  path = path.concat("/");
  path = path.concat(strDD);
  path = path.concat("/");
  path = path.concat(md5);
  path = path.concat(".");
  path = path.concat(PathTool.getExtention(nameLoc));
  File fl = new File(path);
  return fl.getCanonicalPath();//
  }
  以下是service层做的处理:
  整体模块划分如下:
  其中数据类实体逻辑处理如下
  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;
  }
  后台数据库中的逻辑基本上都用到了上面的实体类
  文件数据表操作类如下
  加载所有未完成的文件列表
  public String GetAllUnComplete(int f_uid)
  {
  StringBuilder sb = new StringBuilder();
  sb.append("select ");
  sb.append(" f_id");
  sb.append(",f_fdTask");   
  sb.append(",f_nameLoc");
  sb.append(",f_pathLoc");
  sb.append(",f_md5");
  sb.append(",f_lenLoc");
  sb.append(",f_sizeLoc");
  sb.append(",f_pos");
  sb.append(",f_lenSvr");
  sb.append(",f_perSvr");
  sb.append(",f_complete");
  sb.append(",f_pathSvr");//fix(2015-03-16):修复无法续传文件的问题。
  sb.append(" from up6_files ");//change(2015-03-18):联合查询文件夹数据
  sb.append(" where f_uid=? and f_deleted=0 and f_fdChild=0 and f_complete=0 and f_scan=0");//fix(2015-03-18):只加载未完成列表
  ArrayList<FileInf> files = new ArrayList<FileInf>();
  DbHelper db = new DbHelper();
  PreparedStatement cmd = db.GetCommand(sb.toString());
  try {
  cmd.setInt(1, f_uid);
  ResultSet r = db.ExecuteDataSet(cmd);
  while(r.next())
  {
  FileInf f          = new FileInf();
  f.uid              = f_uid;
  f.id               = r.getString(1);
  f.fdTask      = r.getBoolean(2);              
  f.nameLoc          = r.getString(3);
  f.pathLoc          = r.getString(4);
  f.md5              = r.getString(5);
  f.lenLoc      = r.getLong(6);
  f.sizeLoc          = r.getString(7);
  f.offset      = r.getLong(8);
  f.lenSvr      = r.getLong(9);
  f.perSvr      = r.getString(10);
  f.complete         = r.getBoolean(11);
  f.pathSvr     = r.getString(12);//fix(2015-03-19):修复无法续传文件的问题。
  files.add(f);
  }
  r.close();
  cmd.getConnection().close();
  cmd.close();
  } catch (SQLException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
  }
  if(files.size() < 1) return null;
  Gson g = new Gson();
  return g.toJson( files);//bug:arrFiles为空时,此行代码有异常
  }
  实现后的整体效果如下
  文件夹上传完后的效果
  服务器保存的文件夹数据,而且层级结构与本地客户端是一致的。这在OA系统中,或者网盘系统中使用时是非常有用的
  后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/

  
关注下面的标签,发现更多相似文章