1. 程式人生 > >web專案檔案上傳下載演變總結

web專案檔案上傳下載演變總結

  背景:檔案上傳下載對於在網際網路開發中是一個比較常見的功能,本文將分別對上傳和下載進行闡述,只關心核心功能,其他邊緣功能不在敘述,每個功能採用演變的思想,給出幾種方案,當然每種方案都能完成需求,希望大家根據專案的需求以及上傳檔案大小挑選出適合自己的方案。

1 檔案上傳

    以下所有上傳方案只針對單檔案上傳做優化,通過簡單的封裝和多執行緒的改寫,可以支援多執行緒上傳。另外在上傳過程中邊緣功能,例如檔案大小限制,格式判斷,檔案摘要,進度條等不在此次方案中贅述。

 

 1> 檔案上傳精簡版

   本方案是所有開發者首先想到的方案,也是不願意折騰的方案,適合小檔案上傳,具體流程為:

   1 前端傳送檔案流  2 後端順序接受 3 操作檔案流儲存為檔案

 

前端表單

<form action="http://localhost:8081/upload" method="post" enctype="multipart/form-data" accept-charset="utf-8">
     <input type="file" name="file" value="選擇檔案"/>
     <input id="submit_form" type="submit"  value="提交"/>
</form>

備註:accept-chaset主要是為了解決中文檔名稱亂碼問題。

 

後端程式碼如下:

@Override
	public ServiceResult upload(HttpServletRequest req) {
		// TODO Auto-generated method stub
		// TODO Auto-generated method stub
		ServiceResult result = null;
		try {
			/*
			 * CommonsMultipartResolver resolver = new
			 * CommonsMultipartResolver(req.getSession().getServletContext());
			 * resolver.setDefaultEncoding("utf-8");
			 */
			MultipartHttpServletRequest params = ((MultipartHttpServletRequest) req);
			List<MultipartFile> files = params.getFiles("file"); // 多檔案的上傳
			if (files != null && files.size() > 0) {
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
				long time = System.currentTimeMillis();
				String prefix = "logs" + File.separator;
				String fullPath = uploadPath + prefix;
				File file = new File(fullPath);
				if (!file.exists()) {
					file.mkdirs();
				}
				// 某一個上傳錯誤,全部中斷
				List<String> fileNames = new ArrayList<String>();
				for (MultipartFile multipartFile : files) {
					if (multipartFile != null) {
						String fileName = multipartFile.getOriginalFilename();
						multipartFile.transferTo(new File(fullPath + fileName));
						fileNames.add(fileName);
					}
				}
				result = new ServiceResult<>();
				result.setData(fileNames);
			} else {
				result = new ServiceResult(HttpResultEnum.CLIENT_UNABLE_OBTAIN);
			}

		} catch (Exception e) {
			e.printStackTrace();
			result = new ServiceResult(HttpResultEnum.FAIL);
		}

		return result;

	}

備註:使用springmvc 上傳,支援多檔案上傳。
 

 2> 分塊上傳

       當檔案比較大,需要嚴格控制上傳的時間時,那麼我們就需要充分利用網路資源,壓榨伺服器效能。回想上一個方案中,上傳檔案作為一個整體被髮送到服務端,服務端也按照順序一個位元組一個位元組的讀取,按照目前的伺服器配置大多都是雙cpu,多核心,那麼我們可以同時讓其處理一個檔案的多個部分,從而縮短上傳時間。

  分塊上傳需要前後端配合完成,對於分塊上傳前端的程式設計思路為:

     1 確定分塊的大小   2 非同步的傳送資料塊(根據情況,不要併發太多,3-5個足以)  

    前端js分段程式碼

<script>
        function Upload() {
            var files = document.getElementById('myFile').files;
            var file = files[0];
            var totalSize = file.size;//檔案大小
            var blockSize = 1024 * 1024 * 2;//塊大小
            var count = Math.ceil(totalSize / blockSize);//總塊數

            for(var i =0;i<count;i++){
            
            //建立FormData物件
            var formData = new FormData();
            formData.append('fileName', file.name);//檔名
            formData.append('total', blockCount);//總塊數
            formData.append('index', index);//當前上傳的塊下標
            var start = index * blockSize;
            var end = Math.min(totalSize, start + blockSize);
            var block = file.slice(start, end);
            formData.set('data', block);
            $.ajax({
                url: 'localhost/upload',
                type: 'post',
                data: formData,
                processData: false,
                contentType: false,
                success: function (res) {
                   
                }
            });
         }
        }
    </script>

備註:前端計算好每塊的md5值,用md5+index作為檔名,為斷點續傳做準備。

後端處理的步驟為:

 1  處理每塊上傳的資料  2 把每塊上傳的資訊儲存到資料庫  3  每次上傳完成後,檢視當前所有分塊是否上傳完成,當上傳完成後進行合併。

 程式碼如下:

        MultipartHttpServletRequest params = ((MultipartHttpServletRequest) req);
            //獲取所有前端資訊
			List<MultipartFile> files = params.getFiles("file"); // 多檔案的上傳
			String oleFileName = params.getParameter("oleFileName"); //檔名稱md5
			String fileName = params.getParameter("fileName"); //檔名稱md5
			String total = params.getParameter("total");   //總共有幾塊
			String index = params.getParameter("index");   //當前是第幾塊
            //儲存塊檔案
		    multipartFile.transferTo(new File(fullPath + fileName));
            //把塊相關資訊插入資料庫
			saveInfo(FileChunkForm);	
            //判斷是否已經上傳完成,當所有的都上傳完成進行合併
			if(checkFinish(oleFileName)==Integer.parseInt(total)){
				mergeFile(oleFileName);
			}

 

 3> 斷點續傳

  此方案是一個優化的策略,可以和前兩個方案結合,按照字面意思理解斷點續傳,最重要的是要斷點在哪,所以就需要服務端做記錄,儲存上次客戶端上傳之前先獲取該檔案在服務端的狀態。 web前端可以選擇外掛類似WebUploader等完成斷點續傳,也可以採用分塊的思想自行編寫程式碼。

private void upload(long[] startPos, long[] endPos, File tmpfile) {
	        RandomAccessFile rantmpfile = null;
	        try {
	            if (tmpfile.exists()) {
	                rantmpfile = new RandomAccessFile(tmpfile, "rw");
	                for (int i = 0; i < threadNum; i++) {
	                    rantmpfile.seek(8 * i + 8);
	                    startPos[i] = rantmpfile.readLong();

	                    rantmpfile.seek(8 * (i + 1000) + 16);
	                    endPos[i] = rantmpfile.readLong();
	                }
	            } 
	        } catch (FileNotFoundException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        } finally {
	            try {
	                if (rantmpfile != null) {
	                    rantmpfile.close();
	                }
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	    }

備註:方案1中改造後端主要通過RandomAccessFile 完成斷點續傳,方案2中因為已經拆分為塊,續傳只需要控制到塊級別就行就行(已經完成的塊不在上傳,沒有完成的塊重頭開始上傳)。

 

2 檔案下載

     未完待續