1. 程式人生 > 其它 >WebService 大檔案上傳,斷點續傳原始碼

WebService 大檔案上傳,斷點續傳原始碼

我們平時經常做的是上傳檔案,上傳資料夾與上傳檔案類似,但也有一些不同之處,這次做了上傳資料夾就記錄下以備後用。

首先我們需要了解的是上傳檔案三要素:

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()

{

varacx ="";

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>';

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;

/////是否是資料夾中的子檔案/// </summary>

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<FileInf> files =newArrayList<FileInf>();

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系統中,或者網盤系統中使用時是非常有用的

後端程式碼邏輯大部分是相同的,目前能夠支援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/

歡迎入群一起討論“374992201”