天天看點

如何用HTML5實作分片上傳GB級大檔案

這裡隻寫後端的代碼,基本的思想就是,前端将檔案分片,然後每次通路上傳接口的時候,向後端傳入參數:目前為第幾塊檔案,和分片總數

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

上傳檔案實體類:

看得出來,實體類中已經有很多我們需要的功能了,還有實用的屬性。如MD5秒傳的資訊。

public class FileInf {

     public FileInf(){}

     public String id="";

     public String pid="";

    public String pidRoot="";   

     public boolean fdTask=false;        

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

     public boolean fdChild=false;

     public int uid=0;

     public String nameLoc="";

     public String nameSvr="";

     public String pathLoc="";  

     public String pathSvr="";

     public String pathRel="";

     public String md5="";

     public long lenLoc=0;

     public String sizeLoc="";

     public long offset=0;

     public long lenSvr=0;

     public String perSvr="0%";

     public boolean complete=false;

     public Date PostedTime = new Date();

     public boolean deleted=false;

     public boolean scaned=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);

第七步:釋放緩沖區

第八步:檢查檔案是否全部完成上傳

如何用HTML5實作分片上傳GB級大檔案

檔案夾掃描類

如何用HTML5實作分片上傳GB級大檔案

存儲路徑生成類

如何用HTML5實作分片上傳GB級大檔案

好了,到此就全部結束了,如果有疑問或批評,歡迎評論和私信,我們一起成長一起學習。

最後放一張實作的效果圖

如何用HTML5實作分片上傳GB級大檔案

後端代碼邏輯大部分是相同的,目前能夠支援MySQL,Oracle,SQL。在使用前需要配置一下資料庫,可以參考我寫的這篇文章:http://blog.ncmem.com/wordpress/2019/08/07/java超大檔案上傳與下載下傳/

歡迎入群一起讨論:374992201

繼續閱讀