使用WebAPI流式傳輸大文件(在IIS上大於2GB)

這裏只寫後端的代碼,基本的思想就是,前端將文件分片,而後每次訪問上傳接口的時候,向後端傳入參數:當前爲第幾塊文件,和分片總數前端

下面直接貼代碼吧,一些難懂的我大部分都加上註釋了:java

上傳文件實體類:web

看得出來,實體類中已經有不少咱們須要的功能了,還有實用的屬性。如MD5秒傳的信息。數據庫

publicclassFileInf {json


 public FileInf(){}後端

 publicStringid="";數組

 publicStringpid="";服務器

 publicStringpidRoot="";   app

 /** * 表示當前項是不是一個文件夾項。 */dom

 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;

}


首先是文件數據接收邏輯,負責接收控件上傳的文件塊數據,而後寫到服務器的文件中。控件已經提供了塊的索引,大小,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超大文件上傳與下載/

相關文章
相關標籤/搜索