我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。
首先我们需要了解的是上传文件三要素:
1.表单提交方式:post (get方式提交有大小限制,post没有)
2.表单的enctype属性:必须设置为multipart/form-data.
3.表单必须有文件上传项:file,且文件项需要给定name值
上传文件夹需要增加一个属性webkitdirectory,像这样:
不过webkitdirectory属性有个问题,只能支持高版本的chrome,不能支持低版本的IE,如ie6,ie7,ie8,不能做到全浏览器适配,运行环境比较单一。
js中可以判断文件夹中文件数量及文件夹大小是否符合要求,不符合要求不能向后台提交:
前台HTML模板
this.GetHtmlFiles = function()
{
var acx = "";
acx += '
\
acx += '
//文件夹模板
acx += '
\
acx += '
//上传列表
acx += '
选择多个文件\
选择文件夹\
粘贴文件和目录\
安装控件\
\
清除已完成文件\
return acx;
};
选择文件,选择文件夹,粘贴文件和文件夹的逻辑
this.open_files = function (json)
{
for (var i &#61; 0, l &#61; json.files.length; i { this.addFileLoc(json.files[i]); } setTimeout(function () { _this.PostFirst(); },500); }; this.open_folders &#61; function (json) { for (var i &#61; 0, l &#61; json.folders.length; i this.addFolderLoc(json.folders[i]); } setTimeout(function () { _this.PostFirst(); }, 500); }; this.paste_files &#61; function (json) { for (var i &#61; 0, l &#61; json.files.length; i { this.addFileLoc(json.files[i]); } }; 后台在接收文件夹时不同之处在需要用MultipartHttpServletRequest boolean isMultipart &#61; ServletFileUpload.isMultipartContent(request); FileItemFactory factory &#61; new DiskFileItemFactory(); ServletFileUpload upload &#61; new ServletFileUpload(factory); List files &#61; null; try { files &#61; upload.parseRequest(request); } catch (FileUploadException e) {// 解析文件数据错误 out.println("read file data error:" &#43; e.toString()); return; } FileItem rangeFile &#61; null; // 得到所有上传的文件 Iterator fileItr &#61; files.iterator(); // 循环处理所有文件 while (fileItr.hasNext()) { // 得到当前文件 rangeFile &#61; (FileItem) fileItr.next(); if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr")) { pathSvr &#61; rangeFile.getString(); pathSvr &#61; PathTool.url_decode(pathSvr); } } server端的包和类 文件块处页面&#xff0c;验证代码部分 boolean verify &#61; false; String msg &#61; ""; String md5Svr &#61; ""; long blockSizeSvr &#61; rangeFile.getSize(); if(!StringUtils.isBlank(blockMd5)) { md5Svr &#61; Md5Tool.fileToMD5(rangeFile.getInputStream()); } verify &#61; Integer.parseInt(blockSize) &#61;&#61; blockSizeSvr; if(!verify) { msg &#61; "block size error sizeSvr:" &#43; blockSizeSvr &#43; "sizeLoc:" &#43; blockSize; } if(verify && !StringUtils.isBlank(blockMd5)) { verify &#61; md5Svr.equals(blockMd5); if(!verify) msg &#61; "block md5 error"; } if(verify) { //保存文件块数据 FileBlockWriter res &#61; new FileBlockWriter(); //仅第一块创建 if( Integer.parseInt(blockIndex)&#61;&#61;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 &#61; new JSONObject(); o.put("msg", "ok"); o.put("md5", md5Svr); o.put("offset", blockOffset);//基于文件的块偏移位置 msg &#61; o.toString(); } rangeFile.delete(); out.write(msg); 生成文件名称的逻辑 public String genFile(int uid, String md5,String nameLoc) throws IOException { SimpleDateFormat fmtDD &#61; new SimpleDateFormat("dd"); SimpleDateFormat fmtMM &#61; new SimpleDateFormat("MM"); SimpleDateFormat fmtYY &#61; new SimpleDateFormat("yyyy"); Date date &#61; new Date(); String strDD &#61; fmtDD.format(date); String strMM &#61; fmtMM.format(date); String strYY &#61; fmtYY.format(date); String path &#61; this.getRoot() &#43; "/"; path &#61; path.concat(strYY); path &#61; path.concat("/"); path &#61; path.concat(strMM); path &#61; path.concat("/"); path &#61; path.concat(strDD); path &#61; path.concat("/"); path &#61; path.concat(md5); path &#61; path.concat("."); path &#61; path.concat(PathTool.getExtention(nameLoc)); File fl &#61; new File(path); return fl.getCanonicalPath();// } 以下是service层做的处理&#xff1a; 整体模块划分如下&#xff1a; 其中数据类实体逻辑处理如下 public class FileInf { public FileInf(){} public String id&#61;""; public String pid&#61;""; public String pidRoot&#61;""; /** * 表示当前项是否是一个文件夹项。 */ public boolean fdTask&#61;false; // /// 是否是文件夹中的子文件 /// public boolean fdChild&#61;false; /** * 用户ID。与第三方系统整合使用。 */ public int uid&#61;0; /** * 文件在本地电脑中的名称 */ public String nameLoc&#61;""; /** * 文件在服务器中的名称。 */ public String nameSvr&#61;""; /** * 文件在本地电脑中的完整路径。示例&#xff1a;D:\Soft\QQ2012.exe */ public String pathLoc&#61;""; /** * 文件在服务器中的完整路径。示例&#xff1a;F:\\ftp\\uer\\md5.exe */ public String pathSvr&#61;""; /** * 文件在服务器中的相对路径。示例&#xff1a;/www/web/upload/md5.exe */ public String pathRel&#61;""; /** * 文件MD5 */ public String md5&#61;""; /** * 数字化的文件长度。以字节为单位&#xff0c;示例&#xff1a;120125 */ public long lenLoc&#61;0; /** * 格式化的文件尺寸。示例&#xff1a;10.03MB */ public String sizeLoc&#61;""; /** * 文件续传位置。 */ public long offset&#61;0; /** * 已上传大小。以字节为单位 */ public long lenSvr&#61;0; /** * 已上传百分比。示例&#xff1a;10% */ public String perSvr&#61;"0%"; public boolean complete&#61;false; public Date PostedTime &#61; new Date(); public boolean deleted&#61;false; /** * 是否已经扫描完毕&#xff0c;提供给大型文件夹使用&#xff0c;大型文件夹上传完毕后开始扫描。 */ public boolean scaned&#61;false; } 后台数据库中的逻辑基本上都用到了上面的实体类 文件数据表操作类如下 加载所有未完成的文件列表 public String GetAllUnComplete(int f_uid) { StringBuilder sb &#61; 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&#61;? and f_deleted&#61;0 and f_fdChild&#61;0 and f_complete&#61;0 and f_scan&#61;0");//fix(2015-03-18):只加载未完成列表 ArrayList DbHelper db &#61; new DbHelper(); PreparedStatement cmd &#61; db.GetCommand(sb.toString()); try { cmd.setInt(1, f_uid); ResultSet r &#61; db.ExecuteDataSet(cmd); while(r.next()) { FileInf f &#61; new FileInf(); f.uid &#61; f_uid; f.id &#61; r.getString(1); f.fdTask &#61; r.getBoolean(2); f.nameLoc &#61; r.getString(3); f.pathLoc &#61; r.getString(4); f.md5 &#61; r.getString(5); f.lenLoc &#61; r.getLong(6); f.sizeLoc &#61; r.getString(7); f.offset &#61; r.getLong(8); f.lenSvr &#61; r.getLong(9); f.perSvr &#61; r.getString(10); f.complete &#61; r.getBoolean(11); f.pathSvr &#61; 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 &#61; new Gson(); return g.toJson( files);//bug:arrFiles为空时&#xff0c;此行代码有异常 } 实现后的整体效果如下 文件夹上传完后的效果 服务器保存的文件夹数据&#xff0c;而且层级结构与本地客户端是一致的。这在OA系统中&#xff0c;或者网盘系统中使用时是非常有用的 后端代码逻辑大部分是相同的&#xff0c;目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库&#xff0c;可以参考我写的这篇文章&#xff1a;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/ 欢迎入群一起讨论“374992201”