1. 程式人生 > 其它 >java+上傳大視訊檔案斷點續傳

java+上傳大視訊檔案斷點續傳

這裡只寫後端的程式碼,基本的思想就是,前端將檔案分片,然後每次訪問上傳介面的時候,向後端傳入引數:當前為第幾塊檔案,和分片總數

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

上傳檔案實體類:

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

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;

}

首先是檔案資料接收邏輯,負責接收控制元件上傳的檔案塊資料,然後寫到伺服器的檔案中。控制元件已經提供了塊的索引,大小,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

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

}

}

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

%>

檔案初始化部分

<%

out.clear();

WebBase web =newWebBase(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=newFileInf();

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 =newPathBuilderUuid();

fileSvr.pathSvr = pb.genFile(fileSvr.uid,fileSvr);

fileSvr.pathSvr = fileSvr.pathSvr.replace("\\","/");

DBConfig cfg =newDBConfig();

DBFile db = cfg.db();

FileInf fileExist =newFileInf();

booleanexist = 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 =newFileBlockWriter();

fr.CreateFile(fileSvr.pathSvr,fileSvr.lenLoc);

}

Gson gson =newGson();

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超大檔案上傳與下載/

歡迎入群一起討論:374992201