我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。
首先我们需要了解的是上传文件三要素:
1.表单提交方式:post (get方式提交有大小限制,post没有)
2.表单的enctype属性:必须设置为multipart/form-data.
3.表单必须有文件上传项:file,且文件项需要给定name值
上传文件夹需要增加一个属性webkitdirectory,像这样:
不过webkitdirectory属性有个问题,只能支持高版本的chrome,不能支持低版本的IE,如ie6,ie7,ie8,不能做到全浏览器适配,运行环境比较单一。
js中可以判断文件夹中文件数量及文件夹大小是否符合要求,不符合要求不能向后台提交:
前台HTML模板
this.GetHtmlFiles =function()
{
varacx ="";
acx +='
acx +='
//文件夹模板
acx +='
acx +='
//上传列表
acx +='
returnacx;
};
选择文件,选择文件夹,粘贴文件和文件夹的逻辑
this.open_files =function(json)
{
for(vari = 0, l = json.files.length; i < l; ++i)
{
this.addFileLoc(json.files[i]);
}
setTimeout(function() { _this.PostFirst(); },500);
};
this.open_folders =function(json)
{
for(vari = 0, l = json.folders.length; i < l; ++i) {
this.addFolderLoc(json.folders[i]);
}
setTimeout(function() { _this.PostFirst(); }, 500);
};
this.paste_files =function(json)
{
for(vari = 0, l = json.files.length; i < l; ++i)
{
this.addFileLoc(json.files[i]);
}
};
后台在接收文件夹时不同之处在需要用MultipartHttpServletRequest
booleanisMultipart = ServletFileUpload.isMultipartContent(request);
FileItemFactory factory =newDiskFileItemFactory();
ServletFileUpload upload =newServletFileUpload(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端的包和类
文件块处页面,验证代码部分
booleanverify =false;
String msg ="";
String md5Svr ="";
longblockSizeSvr = 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 =newFileBlockWriter();
//仅第一块创建
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 =newJSONObject();
o.put("msg","ok");
o.put("md5", md5Svr);
o.put("offset", blockOffset);//基于文件的块偏移位置
msg = o.toString();
}
rangeFile.delete();
out.write(msg);
生成文件名称的逻辑
publicStringgenFile(intuid,Stringmd5,StringnameLoc)throwsIOException
{
SimpleDateFormatfmtDD =newSimpleDateFormat("dd");
SimpleDateFormatfmtMM =newSimpleDateFormat("MM");
SimpleDateFormatfmtYY =newSimpleDateFormat("yyyy");
Datedate =newDate();
StringstrDD = fmtDD.format(date);
StringstrMM = fmtMM.format(date);
StringstrYY = fmtYY.format(date);
Stringpath =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));
Filefl =newFile(path);
returnfl.getCanonicalPath();//
}
以下是service层做的处理:
整体模块划分如下:
其中数据类实体逻辑处理如下
publicclassFileInf{
publicFileInf(){}
publicStringid="";
publicStringpid="";
publicStringpidRoot="";
/***表示当前项是否是一个文件夹项。*/
publicbooleanfdTask=false;
/是否是文件夹中的子文件///
publicbooleanfdChild=false;
/***用户ID。与第三方系统整合使用。*/
publicintuid=0;
/***文件在本地电脑中的名称*/
publicStringnameLoc="";
/***文件在服务器中的名称。*/
publicStringnameSvr="";
/***文件在本地电脑中的完整路径。示例:D:\Soft\QQ2012.exe */
publicStringpathLoc="";
/***文件在服务器中的完整路径。示例:F:\\ftp\\uer\\md5.exe*/
publicStringpathSvr="";
/***文件在服务器中的相对路径。示例:/www/web/upload/md5.exe*/
publicStringpathRel="";
/***文件MD5*/
publicStringmd5="";
/***数字化的文件长度。以字节为单位,示例:120125*/
publiclonglenLoc=0;
/***格式化的文件尺寸。示例:10.03MB*/
publicStringsizeLoc="";
/***文件续传位置。*/
publiclongoffset=0;
/***已上传大小。以字节为单位 */
publiclonglenSvr=0;
/***已上传百分比。示例:10%*/
publicStringperSvr="0%";
publicbooleancomplete=false;
publicDatePostedTime=newDate();
publicbooleandeleted=false;
/***是否已经扫描完毕,提供给大型文件夹使用,大型文件夹上传完毕后开始扫描。*/
publicbooleanscaned=false;
}
后台数据库中的逻辑基本上都用到了上面的实体类
文件数据表操作类如下
加载所有未完成的文件列表
publicStringGetAllUnComplete(intf_uid)
{
StringBuildersb =newStringBuilder();
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 files =newArrayList();
DbHelperdb =newDbHelper();
PreparedStatement cmd = db.GetCommand(sb.toString());
try{
cmd.setInt(1, f_uid);
ResultSet r = db.ExecuteDataSet(cmd);
while(r.next())
{
FileInff =newFileInf();
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(SQLExceptione) {
//TODOAuto-generated catch block
e.printStackTrace();
}
if(files.size() < 1)returnnull;
Gsong =newGson();
returng.toJson( files);//bug:arrFiles为空时,此行代码有异常
}
实现后的整体效果如下
文件夹上传完后的效果
服务器保存的文件夹数据,而且层级结构与本地客户端是一致的。这在OA系统中,或者网盘系统中使用时是非常有用的