1. 簡介:
本篇文章基於實際專案的開發,將介紹專案中關於大檔案分片上傳、檔案驗證、斷點續傳、手動重試上傳等需求的使用場景及實現;
2. 專案需求
- 在一個音視訊的新增中,既要有音視訊的簡介(如音視訊內容文字介紹、自定義主題名稱等一些基本的資訊),又要有音視訊所需要的多個檔案(就像電視劇,一部電視劇有多集一樣)。在資料庫中具體表現為一對多的關係,即一個視訊對應多個檔案。
下文就以電視劇為例
- 如果一個電視劇中,既有上百兆的,也有幾十兆的視訊,但是如果在不穩定的一個網路環境中,c傳輸大檔案時,客戶想先把小的視訊上傳了,之後再來繼續傳未傳完的大檔案聲譽部分,這就需要斷點續傳的功能;
- 電視劇中至少有一集(至少有一個檔案),無檔案的電視劇基本資訊無效;
3. 需求分析
確定電視劇基本資訊(自定義名稱,內容簡介、演員簡介、播出時間等)及檔案的上傳方式
- 基本資訊和音視訊檔案分開上傳(因為在原有的資料庫表設計中,檔案表是關聯於基本資訊,所以必須要有音視訊主鍵,才能在資料庫新增對應的檔案資訊),取得基本資訊主鍵之後再去上傳檔案;
檔案斷點續傳中,如何分片;檔案接收方式;伺服器端如何判斷是哪個檔案的分片;如何拼接各個分片;上傳過程中發生意外情況(如斷網,關閉瀏覽器),如何處理?
- 分片方式: 在客戶端進行分片;
- 伺服器端接收方式:使用MultipartFile接收檔案
- 伺服器端確定是哪個檔案的分片: 在客戶端按照一定規則(UUID或其他方式)生成唯一名稱,在伺服器端直接找到與該名稱相同的檔案片段;
- 拼接檔案分片: 使用NIO的方式,將分片追加到已有分片的後面;
- 上傳中發生意外:
A. 斷網: 該情況類似於暫停上傳,上傳到檔案處於暫停狀態,網路恢復,即可點選繼續上傳按鈕,繼續上傳;
B. 關閉瀏覽器: 在關閉時,給使用者提示框,詢問是否繼續儲存,若不儲存,則根據視訊基本資訊表的主鍵的刪除髒資料;
C. 第一個檔案在上傳時候,被使用者取消或者斷網,則伺服器端未修改基本資訊為有效,並且也未標記該檔案為有效記錄,可以理解為髒資料,但不需要清理這些資料(在查詢的時候,不能查出這些無效記錄,可以在更新視訊基本資訊記錄的時候,查詢這些髒資料,並清理磁碟上及資料表中的記錄);
4. 實現
根據需求,已經確認了先上傳基本資訊,後上傳檔案,基本資訊的提交(標題、簡介、封面)比較簡單,只需要在前端提交資料表所需欄位,然後後臺返回插入的主鍵即可,所以基本資訊的提交及返回不過多說明,僅僅通過前端頁面截圖及後端部分程式碼進行簡要說明,實現部分主要講解分析檔案上傳部分的程式碼;
說明:本篇文章主要涉及到兩張表的操作,兩張表的資料結構如下:
- 4.1.1 前端提交基本資訊頁面
提交頁面使用模板+原生html+css實現,每個人的頁面、所需引數各不相同,所以在前端程式碼中,沒有多大的參考價值,所以直接使用截圖,來表現我需要做的工作、傳的引數。
- 4.1.2 前端儲存基本資訊程式碼
可以根據自己的業務使用FormData封裝傳遞的引數
//點選儲存按鈕後儲存資料 function save_microClass() { //獲取form的基本資訊 var classTitle = $("#classTitle").val(), classDes = $("#classDes").val(), coverFile = document.getElementById("coverFileName").files[0], //構造一個新表單,FormData是HTML5新增的,因為基本資訊中存在封面圖片,所以需要使用表單提交資料 var form = new FormData(); form.append("title", classTitle); form.append("desc", classDes); form.append("coverFile", coverFile); //Ajax提交 $.ajax({ url: "micro/save", type: "POST", data: form, async: true, //非同步 processData: false, //很重要,告訴jquery不要對form進行處理 contentType: false, //很重要,指定為false才能形成正確的Content-Type success: function(data){ //在新增音視訊基本資訊之後,伺服器端返回新增音視訊的主鍵,以便之後上傳檔案時,與檔案進行關聯 if(data.length>0){ $("#microClassId").val(data); submitFile();//提交檔案 } } }); } }複製程式碼
- 4.1.3 基本資訊伺服器端接收方法
伺服器端需要與前端協同作戰,接收引數需要與前端傳遞過來的引數對應上,以免報出400的錯誤。
/** * @Description: 新增基本資訊時儲存form表單 * @param title * 音視訊名稱 * @param coverFile * 封面物件 * @param desc * 音視訊描述欄位 */ @RequestMapping(value = "/save", method = RequestMethod.POST) @ResponseBody public Integer create(String title,String desc, MultipartFile coverFile) { try { //儲存基本資訊邏輯,自己實現,然後返回基本資訊插入後的主鍵 //request,即HttpServletRequest物件,在專案啟動時候就被注入,如下: /** * @Autowired * private HttpServletRequest request; **/ Integer videoId = videoService.save(title,desc, coverFile, request); return videoId; } catch (Exception e) { e.printStackTrace(); } return null; }複製程式碼
- 4.2.1 前端上傳檔案示例
- 4.2.2 前端實現
在前端實現中,採用純JavaScript+html+css來實現按鈕的刪除、新增、檔案的新增、上傳時暫停、繼續、以及檔案的順序控制;
<div class="row pb40 pt40 mlr-10 border-top-1">
<div class="col-xs-12 col-lg-12">
<div class="col-xs-12 col-lg-12">
<span id="mediaLable">新增視訊內容</span><small style="color: #ff5704;margin-left:10px;" id="mediaLimit">視訊格式僅支援MP4及AVI,檔案大小需小於500M</small>
</div>
<div id="mediaFiles" class="col-xs-12 col-lg-12 mt20 video-list-wrap">
</div>
<div class="col-xs-10 col-xs-offset-1 col-lg-10 col-lg-offset-1 mt20 mb10">
<a href="javascript:;" id="addMediaButton" onclick="addFileInput()" class="mybtn btn-add-user2">新增視訊</a>
</div>
</div>
</div>複製程式碼
以上程式碼,就是檔案上傳部分的html程式碼,無需關心css樣式,但需要注意每個元素的id/name/事件函式;以下就是JavaScript實現檔案上傳邏輯的程式碼;
<script type="text/javascript">
//刪除一個上傳檔案文字框
function delFileInput(that) {
var delFileDivId = $(that).parent().attr("id");
//如果是修改頁面的刪除則將要刪除的附件ID儲存在隱藏框
if (delFileDivId.indexOf("modify") == 0) {
var deleteFileIds = $("#deleteFileIds").val();
if (deleteFileIds.length > 0) {
deleteFileIds = deleteFileIds + "," + delFileDivId.split("_").pop();
$("#deleteFileIds").val(deleteFileIds);
} else {
$("#deleteFileIds").val(delFileDivId.split("_").pop());
}
}
var uploadFileDivs = $("#mediaFiles").children();
episodeCount = uploadFileDivs.length-1;
//刪除後修改集數
for(var i=1;i<uploadFileDivs.length;i++){
if($("#mediaFiles").children()[i].id==$(that).parent().parent().attr("id")){
$(that).parent().parent().remove();
for(var j=i;j<uploadFileDivs.length;j++){
$($("#mediaFiles").children()[j]).find(".account").text("第"+(j+1)+"集");
}
break;
}
}
}
//上傳附件文字框計數器
var inputCount = 0;
var episodeCount = 0;
//點選按鈕後新增一個上傳檔案文字框
function addFileInput() {
//把新增的每一行都放到一個div中,刪除時刪除這個父div節點即可
var rootFileDiv = document.createElement("div");
rootFileDiv.setAttribute("id", "rootFileDiv" + inputCount);
if(inputCount==0){
rootFileDiv.setAttribute("class", "col-xs-12 col-lg-12 video-list");
}else{
rootFileDiv.setAttribute("class", "col-xs-12 col-lg-12 full video-list mt20");
}
$("#mediaFiles").append(rootFileDiv);
//刪除圖示
var delDiv = document.createElement("div");
delDiv.setAttribute("id", "delFileDiv" + inputCount);
delDiv.setAttribute("class", "delete");
if(inputCount>0){
var delDivText = "<span>×</span>";
delDiv.innerHTML = delDivText;
}
//集數
var countDiv = document.createElement("div");
countDiv.setAttribute("id", "account");
countDiv.setAttribute("class", "account");
var countDivText = "第"+(episodeCount+1)+"集";
countDiv.innerHTML = countDivText;
//檔名稱顯示文字框
var mediaFileNameDiv = document.createElement("div");
mediaFileNameDiv.setAttribute("class", "name");
var mediaFileNameDivInput = "<input type='text' id=mediaFile" + inputCount + "_Name" + " class='form-control' />";
mediaFileNameDiv.innerHTML = mediaFileNameDivInput;
//檔案上傳文字框
var mediaFileDiv = document.createElement("div");
mediaFileDiv.setAttribute("class", "filebtn");
var mediaType = $("#type").val();
var mediaFileInput;
if(mediaType==0){
mediaFileInput = "<a><span>選擇檔案 ></span><input type='file' accept='.mp4,.avi' name='myfiles' id=mediaFile"+inputCount+"_ class='' onchange='handleFile(this)'/></a>";
}else{
mediaFileInput = "<a><span>選擇檔案 ></span><input type='file' accept='.mp3' name='myfiles' id=mediaFile"+inputCount+"_ class='' onchange='handleFile(this)'/></a>";
}
mediaFileDiv.innerHTML = mediaFileInput;
//上傳進度條
var progressbarDiv = document.createElement("div");
progressbarDiv.setAttribute("id", "progressbar"+inputCount);
progressbarDiv.setAttribute("class", "progress hide");
var progressbarDivText = '<div class="progress-bar progress-bar-green" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div><div class="percent">0%</div><div class="size">40M/200M</div>';
progressbarDiv.innerHTML = progressbarDivText;
//暫停上傳或者繼續上傳按鈕
var uploadStatusButtonDiv = document.createElement("div");
uploadStatusButtonDiv.setAttribute("id", "uploadStatusButton"+inputCount);
uploadStatusButtonDiv.setAttribute("class", "result");
uploadStatusButtonDivButton = '<span class="success hide"><i class="fa fa-check-circle"></i>上傳完成</span><span class="stop-continue hide" id="uploadStatusButton" onclick="changeUploadStatus(this)">暫停上傳</span>';
uploadStatusButtonDiv.innerHTML = uploadStatusButtonDivButton;
$("#rootFileDiv" + inputCount).append(delDiv);
$("#rootFileDiv" + inputCount).append(countDiv);
$("#rootFileDiv" + inputCount).append(mediaFileNameDiv);
$("#rootFileDiv" + inputCount).append(mediaFileDiv);
$("#rootFileDiv" + inputCount).append(progressbarDiv);
$("#rootFileDiv" + inputCount).append(uploadStatusButtonDiv);
inputCount++;
episodeCount++;
return rootFileDiv;
}
//mediaFileArray存放分片資料資訊
var mediaFileArray=new Array();
//獲取上傳分片資料基本資訊,獲取完畢後呼叫上傳方法提交分片
function submitFile(){
var fileInputs = $("input[name='myfiles']");
for(var i=0;i<fileInputs.length;i++){
if(fileInputs[i].files[0]!=null){
var newName = guid();
var name = fileInputs[i].files[0].name, //檔名
size = fileInputs[i].files[0].size, //總大小
type = fileInputs[i].files[0].type, //檔案型別
shardSize = 10 * 1024 * 1024, // shardSize = 10 * 1024 * 1024, 以10MB為一個分片
shardCount = Math.ceil(size / shardSize); //總片數
var shardArray = new Array();
shardArray[0]=name;
shardArray[1]=size;
shardArray[2]=type;
shardArray[3]=shardSize;
shardArray[4]=shardCount;
shardArray[5]=0;//當前上傳狀態,0-等待上傳,1-正在上傳,2是上傳完成
shardArray[6]=0;//已傳輸最後分片編號
shardArray[7]=newName;//伺服器儲存名稱
mediaFileArray[i]=shardArray;//將每個file的分片資訊放入全域性陣列
}else{
//當進入修改頁面file為空時
var newName = guid();
var name = "", //檔名
size = 0, //總大小
type = "", //檔案型別
shardSize = 10 * 1024 * 1024, // shardSize = 10 * 1024 * 1024, 以10MB為一個分片
shardCount = 0; //總片數
var shardArray = new Array();
shardArray[0]=name;
shardArray[1]=size;
shardArray[2]=type;
shardArray[3]=shardSize;
shardArray[4]=shardCount;
shardArray[5]=0;//當前上傳狀態,0-等待上傳,1-正在上傳,2是上傳完成
shardArray[6]=0;//已傳輸最後分片編號
shardArray[7]=newName;//伺服器儲存名稱
mediaFileArray[i]=shardArray;//將每個file的分片資訊放入全域性陣列
}
}
postFile(0,0);//第一個引數是第幾個file元素,最後一個引數是從第幾個分片開始上傳
}
//附件完成上傳計數器
var endCount=0;
//控制是否繼續往服務端傳送分片資料,0-繼續提交,1-停止提交
var flag=0;
//檔案斷點續傳
function postFile(fileNum,shardNum){
if(flag==0){
//計算每一片的起始與結束位置
var file = $("input[name='myfiles']")[fileNum].files[0];
var oldName;
if($("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).val().length>0){
oldName = $("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).val()+"#";
}else{
oldName = mediaFileArray[fileNum][0]+"#";
}
//修改時需要oldname拼接fileId
var fileNameId = $("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).attr("id");
if(fileNameId.indexOf("modify")==0&&fileNameId.length>0&&fileNameId.split("_").length>0){
oldName = oldName + fileNameId.split("_")[1];
}
var start = shardNum * mediaFileArray[fileNum][3],
end = Math.min(mediaFileArray[fileNum][1], start + mediaFileArray[fileNum][3]);
/* //修改檔案為空,但所有檔案都已傳輸完則跳轉回view
if(file==null&&endCount>=mediaFileArray.length){
window.location.href="micro/index?type="+$("#type").val();
return;
} */
//當檔案不為空,當前檔案所有分片都傳輸完時控制button顯示並跳轉回view
if(shardNum >= mediaFileArray[fileNum][4]&&file!=null){
$("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).addClass("hide");
$("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).addClass("hide");
$("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
//如果本檔案傳完,開始傳輸下一個檔案
if(fileNum<mediaFileArray.length-1&&mediaFileArray[fileNum+1][6]==0){
postFile(fileNum+1,0);
}
endCount++;
//如果所有的檔案都傳輸完則跳轉回view
if(endCount>=mediaFileArray.length){
var location = (window.location+'').split('/');
var basePath = location[0]+'//'+location[2]+'/'+location[3];
window.location.href=basePath+"/micro/index?type="+$("#type").val();
}
return;
}
//服務端URL
var requestUrl = "micro/uploadSlice";
//構造一個表單,FormData是HTML5新增的
var form = new FormData();
if(file!=null){
form.append("data", file.slice(start,end)); //slice方法用於切出檔案的一部分
}
//form.append("lastModified", file.lastModified); //檔案最後修改時間不支援IE
form.append("name", mediaFileArray[fileNum][7]); //檔案儲存名稱
form.append("fileType", mediaFileArray[fileNum][2]); //檔案型別
form.append("total", mediaFileArray[fileNum][4]); //總片數
form.append("index", shardNum + 1); //當前是第幾片
form.append("oldName", oldName); //使用者自定義檔名
form.append("seq", fileNum); //檔案所處的順序
form.append("microClassId", $("#microClassId").val()); //microClassId
//Ajax提交
$.ajax({
url: requestUrl,
type: "POST",
data: form,
async: true, //非同步
processData: false, //很重要,告訴jquery不要對form進行處理
contentType: false, //很重要,指定為false才能形成正確的Content-Type
success: function(data){
//顯示暫停按鈕
if(start==0&&file!=null){
$("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).removeClass("hide");
}
mediaFileArray[fileNum][6]=shardNum+1;
if(data!="0"){
shardNum = data++;
console.log("當前分片數:",shardNum);
var num = Math.ceil(shardNum*mediaFileArray[fileNum][3]*100 / mediaFileArray[fileNum][1]); //百分比進度
//改變進度條進度
$("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).removeClass("hide");
$("#mediaFiles").find(".video-list").eq(fileNum).find(".progress-bar").eq(0).attr("style","width: "+num+"%;");
$("#mediaFiles").find(".video-list").eq(fileNum).find(".percent").eq(0).text(num+"%");
$("#mediaFiles").find(".video-list").eq(fileNum).find(".size").eq(0).text(shardNum*10+"/"+Math.ceil(mediaFileArray[fileNum][1]/(1024 * 1024))+"M");
//通過button狀態來判斷是否繼續上傳
var text = $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).text();
if(text == "暫停上傳") postFile(fileNum,shardNum);
}else{
//當修改檔案為空時的處理
$("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
endCount=endCount+1;
if(endCount<mediaFileArray.length){
postFile(fileNum+1,0);
}else{
$("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).addClass("hide");
$("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).addClass("hide");
$("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
var location = (window.location+'').split('/');
var basePath = location[0]+'//'+location[2]+'/'+location[3];
window.location.href=basePath+"/micro/index?type="+$("#type").val();
}
}
},
error:function(xhr,errorText,errorType){
$("#wrong_msg").text("網路錯誤,上傳失敗,請檢查網路後繼續上傳!");
$("#tips1").modal("show");
mediaFileArray[fileNum][6]=shardNum;
$("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).text("繼續上傳");
}
});
}else{
return;
}
}
//控制上傳狀態
function changeUploadStatus(that){
if($(that).text()=="暫停上傳"){
var fileNum = $(that).parents(".video-list").eq(0).index();
flag=1;
$(that).text("繼續上傳");
mediaFileArray[fileNum][5]=0;
if(fileNum<mediaFileArray.length-1 && mediaFileArray[fileNum+1][6]==0){
$(that).parents("#mediaFiles").find(".video-list").eq(fileNum+1).find(".stop-continue").eq(0).text("暫停上傳");
flag=0;
postFile(fileNum+1,mediaFileArray[fileNum+1][6]);
}
return;
}else{
var fileNum = $(that).parents(".video-list").eq(0).index();
var shardNum = mediaFileArray[fileNum][6];
for(var i=0; i<mediaFileArray.length; i++){
if(mediaFileArray[i][5]==1){
mediaFileArray[i][5]==0;
flag=1;
$(that).parents("#mediaFiles").find(".video-list").eq(i).find(".stop-continue").eq(0).text("繼續上傳");
}
}
flag=0;
mediaFileArray[fileNum][5]=1;
$(that).text("暫停上傳");
postFile(fileNum,shardNum);
return;
}
}
//用於生成uuid
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
function guid() {
return (S4()+S4()+S4()+S4()+S4()+S4()+S4()+S4());
}
</script>複製程式碼
以上程式碼為前端控制檔案上傳所需的程式碼,之後介紹後臺部分的實現
- 4.3.3 後臺實現
後臺實現,主要是接收檔案的分片,利用前端傳入唯一的新檔名稱,使用NIO的方式,將分片進行合併。
Controller實現
/**
* @Description: 上傳檔案分片
*
* @param data
* 分片檔案
* @param fileType
* 檔案型別 video/avi audio/mp3
* @param name
* 檔名稱(newName),由Client生成,即NewName 如
* MICRO_CLASS_1502179979829.mp4
* @param total
* 分片總數
* @param index
* 當前分片數
* @param microClassId
* 檔案關聯的MicroClass主鍵,通過先儲存基本資訊取得並返回(DataFile中的OwerID)
* @param oldName
* 由使用者自己輸入的節目名稱,需要與檔案對應起來,如 實現兩個100年奮鬥目標
* @param seq
* 檔案的順序,如第一集對應1,第二集對應2....
*
* @return index 當前合併到檔案的分片數
*
* @throws Exception
*/
@RequestMapping("/uploadSlice")
@ResponseBody
public Integer uploadSlice(MultipartFile data, String fileType, String name, Integer total, Integer index,
Integer microClassId, String oldName, int seq) throws Exception {
int countFile = 0;// 記錄一次儲存中上傳的檔案數目
/**
* oldName:使用輸入框中的字串與fileId拼接,形如D1#D2 如:
* 實現兩個100年奮鬥目標#36,其中“實現兩個100年奮鬥目標”在資料庫中存為OldName,36表示資料庫中fileData主鍵
*
* D1: 可以為空,表示使用者將Client獲取到的檔名稱刪除,並且未輸入任何字串,可以為空; D2:
* 可以為空,表示新增的檔案,不為空,則表示在修改頁面,傳回的fileID;
**/
String[] str=oldName.split("#");
String oldNameFiled=null;
String fileDataId=null;
System.out.println(str);
switch (str.length) {
case 0:
break;
case 1:
oldNameFiled = oldName.split("#")[0];
break;
case 2:
oldNameFiled = oldName.split("#")[0];
fileDataId = oldName.split("#")[1];
break;
default:
break;
}
if (total == 0) {
// 表示在修改頁面,使用者只可能修改了oldName,但是未修改檔案
if (fileDataId != null && !fileDataId.equals("")) {
if (oldNameFiled != null && !oldNameFiled.equals("")) {
dataFileService.updateOldNameById(fileDataId, oldNameFiled);//
}
}
} else {
if (index <= total) {//說明是有分片
String dirType = fileType.split("/")[0];// 檔案型別,用於建立不同的目錄,如(video/audio)
String fileExt = "." + fileType.split("/")[1];// 副檔名,如.mp3/.mp4/.avi
System.out.println(data.getSize() + "----" + name + "-----" + total + "----" + index);
// 追加分片到已有的分片上,返回儲存檔案的路徑,如/fileDate/video/2017/08/09
String savePath = FileUtil.randomWrite(request, data.getBytes(), name, dirType, fileExt);
if (index == 1 && savePath != null) {// 說明是新的檔案的第一個分片,在資料庫中建立相應的記錄,並且狀態為無效,等到全部上傳完畢之後在修改為有效
dataFile = new DataFile();
dataFile.setOldName(oldNameFiled);
dataFile.setFileUrl(savePath);
dataFile.setNewName(name + fileExt);
dataFile.setOwerId(microClassId);
dataFile.setSeq(seq);
dataFile.setStatus(1);
dataFileService.saveDataFile(dataFile);
}
if (index == total) {// 說明已經成功上傳一個檔案
// 根據檔名稱和OwerId來更新檔案記錄,把記錄的狀態修改為0(有效)
dataFileService.updateByNewNameAndOwerId(name+fileExt, microClassId);
countFile++;
if (countFile == 1) {// 說明已經上傳成功一個檔案,則吧MicroClass的狀態改為0(有效);
microClassService.updateMicroClass(microClassId);// 根據microClassId來修改status
}
LOGGER.info("已上傳 " + countFile + " 個檔案");
}
return index++;
} else {
return 0;
}
}
return 0;
}複製程式碼
FileUtil中randomWrite方法實現
/**
* @Description: 分片檔案追加
* @param request
* @param sliceFile 分片檔案
* @param name 檔名稱
* @param dirType 資料夾型別 如video/audio
* @param fileExt 副檔名 如.mp4/.avi ./mp3
* @return
*/
public static String randomWrite(HttpServletRequest request, byte[] sliceFile, String name, String dirType,String fileExt) {
try {
/** 以讀寫的方式建立一個RandomAccessFile物件 **/
//獲取相對路徑/home/gzxiaoi/apache-tomcat-8.0.45/webapps
String realPath=getRealPath(request);
//拼接檔案儲存路徑 /fileDate/video/2017/08/09 如果沒有該資料夾,則建立
String savePath=getSavePath(realPath,dirType);
String realName = name;
String saveFile =realPath+ savePath + realName+fileExt;
RandomAccessFile raf = new RandomAccessFile(saveFile, "rw");
// 將記錄指標移動到檔案最後
raf.seek(raf.length());
raf.write(sliceFile);
return savePath;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @Description: 取得tomcat中的webapps目錄 如: /home/software/apache-tomcat-8.0.45/webapps
* @param request
* @return
*/
public static String getRealPath(HttpServletRequest request) {
String realPath = request.getSession().getServletContext().getRealPath(File.separator);
realPath = realPath.substring(0, realPath.length() - 1);
int aString = realPath.lastIndexOf(File.separator);
realPath = realPath.substring(0, aString);
return realPath;
}
/**
* @Description: 獲取檔案儲存的路徑,如果沒有該目錄,則建立
* @param realPath 相對路徑 ,如 /home/software/apache-tomcat-8.0.45/webapps
* @param fileType 檔案型別 如: images/video/audio用於拼接檔案儲存路徑,區分音視訊
* @return
*/
public static String getSavePath(String realPath, String fileType) {
SimpleDateFormat year = new SimpleDateFormat("yyyy");
SimpleDateFormat m = new SimpleDateFormat("MM");
SimpleDateFormat d = new SimpleDateFormat("dd");
Date date = new Date();
String sp=File.separator + "fileDate" + File.separator +fileType + File.separator + year.format(date) + File.separator
+ m.format(date) + File.separator + d.format(date) + File.separator;
String savePath = realPath+ sp;
File folder = new File(savePath);
if (!folder.exists()) {
folder.mkdirs();
}
return sp;
}複製程式碼
5. 總結
本篇文章主要從實際專案出發,介紹了檔案上傳中所常見的一些情況,以及具體的實現。在斷點續傳中,需要注意的關鍵點:
- 瀏覽器端(前端)需要獲取檔案的大小,去計算總分片數,並且需要校驗檔案的合法性,在上傳過程中,需要及時的獲取到當前傳輸檔案的當前分片數,以更新下一次需要傳輸檔案的範圍大小;
- 伺服器端,需要根據前端傳入的引數,去確定分片所屬的原始檔,並追加;(或者根據一定規則,建立臨時目錄,將屬於同一檔案的分片,放在同一目錄,在將所有分片全部上傳完畢之後<即當前分片數=總分片數>,在合併所有的分片);