大文件分塊上傳技術分享

来源:http://www.cnblogs.com/web10000/archive/2017/07/08/7137536.html
-Advertisement-
Play Games

重點講解了多文件上傳,大文件上傳,分塊上傳,斷點續傳,文件秒傳,上傳失敗自動修複再上傳等功能,上傳3、5個G,那都不是事。特別是大文件秒傳功能,不僅節省了上傳時間,還節省了網路帶寬和伺服器空間。 ...


 我們平時做業務系統的時候,一般都會使用表單提交數據,提交數據的時候常常又伴隨著附件的上傳,如果附件小,用傳統的辦法就能解決,如果附件比較大,比如板給你安排一個上傳視頻的任務,視頻文件一般都比較大,動不動就是幾個G,用傳統的辦法就顯得非常有限。為了幫助大家解決大文件上傳的問題,我們特別編寫了這篇文章,重點講解了多文件上傳,大文件上傳,分塊上傳,斷點續傳,文件秒傳,上傳失敗自動修複再上傳等功能,上傳3、5個G,那都不是事。特別是大文件秒傳功能,不僅節省了上傳時間,還節省了網路帶寬和伺服器空間,採用md5加密標識文件唯一性,避免了同一文件的重覆上傳,上傳過程帶進度條,即時顯示上傳數據量和上傳百分比。求人不如求己,搞定這些技術,讓老闆加薪,再也不用低三下四的求人,大文件上傳無壓力,讓學習更高效,讓工作更輕鬆,媽媽再也不用擔心你的學習!

 

直接上代碼:

 

前臺前端index.html:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8" />
		<title>夜鷹教程網大文件分塊上傳</title>
		<style type="text/css">
			* {
				font-family: "微軟雅黑";
				margin: 0;
				padding: 0;
			}
			
			.container {
				padding-top: 10px;
				padding-left: 10px;
			}
			
			.container input {
				width: 120px;
				height: 30px;
				background-color: blue;
				color: white;
				border: 0;
				line-height: 30px;
				border-radius: 5px;
				margin-right: 5px;
				outline: none;
				cursor: pointer;
			}
			
			#filelist {
				width: 800px;
				border: solid 1px #eee;
				border-collapse: collapse;
				margin: 10px;
			}
			
			#filelist td {
				border-bottom: solid 1px #eee;
				height: 30px;
				font-size: 12px;
				/*line-height:30px ;*/
				padding: 0 3px;
			}
			
			.filename {
				width: 200px;
				text-align: center;
			}
			
			.filestatus {
				width: 100px;
				text-align: center;
			}
			
			.fileprogress {
				text-align: center;
			}
			
			.domprogress {
				width: 320px;
			}
			
			.domsize {
				display: block;
			}
			
			#tdmsg {
				text-align: center;
			}
			
			#fileselect {
				display: none;
			}

		    span.domtime {
		        display:block;
            }
		</style>

	</head>

	<body>
		<div class="container">
			<input type="file" name="fileselect" id="fileselect" value="" multiple/>
			<input type="button" id="btnselect" value="選擇上傳的文件" />
			<input type="button" id="btnupload" value="開始上傳" />
		</div>

		<table cellspacing="0" cellpadding="0" id="filelist">
			<tr>
				<td class="filename">文件名</td>
				<td class="fileprogress">進度</td>
				<td class="filestatus">狀態</td>
			</tr>
			<!--<tr><td>人民的名義.avi </td><td><progress value="10" max="100" class="domprogress"></progress><span class="dompercent">10%</span><span class="domsize">0/1.86GB</span></td><td class="filestatus"><span class="domstatus">排隊中</span></td></tr>-->
			<tr id="trmsg">
				<td colspan="3" id="tdmsg">請選擇要上傳的文件! 技術支持QQ:1416759661</td>
			</tr>

		</table>
		<script src="js/jquery-1.11.0.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/spark-md5.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			$("#btnselect").click(function() {
				$("#fileselect").click();
			});

			$("#fileselect").change(function() {
				var files = this.files;
				if(files.length > 0) {
					$("#trmsg").remove();
					$(files).each(function(index, item) {
						console.log(index, item);
						var filesize = 0;
						if((item.size / 1024 / 1024 / 1024) >= 1) {
							filesize = (item.size / 1024 / 1024 / 1024).toFixed(2) + "GB"; // b=>kb=>mb=>gb
						} else if((item.size / 1024 / 1024 / 1024) < 1 && (item.size / 1024 / 1024) >= 1) {
							filesize = (item.size / 1024 / 1024).toFixed(2) + "MB";
						} else if((item.size / 1024 / 1024) < 1 && (item.size / 1024) >= 1) {
							filesize = (item.size / 1024).toFixed(2) + "KB";
						} else {
							filesize = item.size + "B";
						}

						var htmlstr = '<tr><td>' + item.name + '</td><td><progress value="0" max="100" class="domprogress"></progress><span class="dompercent"> 0/'+filesize+'</span><span class="domtime">總共耗時:0 秒</span></td><td class="filestatus"><span class="domstatus">排隊中</span></td></tr>';
						$("#filelist").append(htmlstr);

					});

				}

			});

			$("#btnupload").click(function() {

			    var files = $("#fileselect")[0].files;
			    $(files).each(function (index, item) {
			        yyupload(files[index], $("span.domstatus").eq(index), $("span.dompercent").eq(index), $(".domprogress").eq(index), $("span.domtime").eq(index));
			    });                
			});

			//文件上傳
			function yyupload(file, dommsg, dompercentmb, domprogress, domtime, fn) {
			    var startTime = new Date();
				//獲取文件的md5字元串,用於標識文件的唯一性。
				calculate(file);
                //獲取文件的加密字元串
				function calculate(file) {
					var fileReader = new FileReader();
					var chunkSize = 1024 * 1024 * 5; //每次讀取5MB 
					var chunksCount = Math.ceil(file.size / chunkSize); //回大於參數x的最小整數 8=》8  8.4=》9  8.5=》9 -8.5=》-8
					var currentChunk = 0; //當前塊的索引
					var spark = new SparkMD5();
					fileReader.onload = function(e) {
						console.log((currentChunk + 1) + "/" + chunksCount)
						dommsg.text("正在檢查文件: " + (currentChunk + 1) + "/" + chunksCount);
						spark.appendBinary(e.target.result); // 添加二進位字元串
						currentChunk++;
						if(currentChunk < chunksCount) {
							loadNext();
						} else {
							var md5value = spark.end();
							console.log("文件加密結束,密鑰為:" + md5value);
							checkfile(md5value, file); //檢查伺服器是否存在該文件,存在就從斷點繼續上傳
						}
					};

					function loadNext() {
						var start = currentChunk * chunkSize; //計算讀取開始位置
						var end = start + chunkSize >= file.size ? file.size : start + chunkSize; //計算讀取結束位置
						fileReader.readAsBinaryString(file.slice(start, end)); //讀取為二進位字元串
					};
					loadNext();
				}

				var repeatcount = 0;
				//檢查文件是否已經存在
				function checkfile(md5value, file) {
					var fd = new FormData();
					fd.append('rquesttype', "chekcfile");
					fd.append('filename', file.name);
					fd.append('md5value', md5value);
					var xhr = new XMLHttpRequest();
					xhr.open('post', 'FileUpoload.ashx', true);
					xhr.onreadystatechange = function(res) {
						if(xhr.readyState == 4 && xhr.status == 200) {
							var jsonobj = JSON.parse(xhr.responseText); //可以將json字元串轉換成json對象  //JSON.stringify(jsonobj); //可以將json對象轉換成json對符串 
							console.log("繼續上傳的位置:" + jsonobj.startindex);
							switch(jsonobj.flag) {
								case "0":
									doUpload(md5value, file, 0);
									break;
								case "1":
									doUpload(md5value, file, parseInt(jsonobj.startindex));
									break;
							    case "2":
							        secondUpload(file);
									break;
							}
							repeatcount = 0;
						} else if(xhr.status == 500) {
							setTimeout(function() {
								if(repeatcount < 3) {
									checkfile(md5value, file);
								}
								repeatcount++;
							}, 3000);
						}
					}
					//開始發送
					xhr.send(fd);
				}

                //實現秒傳功能
				function secondUpload(file)
				{
				    var timerange = (new Date().getTime() - startTime.getTime()) / 1000;
				    domtime.text("耗時" + timerange + "秒");
				    //顯示結果進度
				    var percent =100;
				    dommsg.text(percent.toFixed(2) + "%");
				    domprogress.val(percent);
				    var total = file.size;
				    if (total > 1024 * 1024 * 1024) {
				        dompercentmb.text((total / 1024 / 1024 / 1024).toFixed(2) + "GB/" + (total / 1024 / 1024 / 1024).toFixed(2) + "GB");
				    } else if (total > 1024 * 1024) {
				        dompercentmb.text((total / 1024 / 1024).toFixed(2) + "MB/" + (total / 1024 / 1024).toFixed(2) + "MB");
				    } else if (total > 1024 && total < 1024 * 1024) {
				        dompercentmb.text((total / 1024).toFixed(2) + "KB/" + (total / 1024).toFixed(2) + "KB");
				    } else {
				        dompercentmb.text((total).toFixed(2) + "B/" + (total).toFixed(2) + "B");
				    }

				}



			    //上傳文件
				function doUpload(md5value,file,startindex) {
				    var reader = new FileReader();//新建一個讀文件的對象
				    var step = 1024 * 200; //每次讀取文件大小  200KB
				    var cuLoaded = startindex; //當前已經讀取總數
				    var total = file.size;//文件的總大小
				    //讀取一段成功
				    reader.onload = function (e) {
				        //處理讀取的結果
				        var result = reader.result; //本次讀取的數據
				        var loaded = e.loaded; //本次讀取的數據長度
				        uploadFile(result, cuLoaded, function () { //將分段數據上傳到伺服器
				            cuLoaded += loaded; //如果沒有讀完,繼續
				            var timerange = (new Date().getTime() - startTime.getTime()) / 1000;
				            if (total > 1024 * 1024 * 1024) {
				                dompercentmb.text((cuLoaded / 1024 / 1024 / 1024).toFixed(2) + "GB/" + (total / 1024 / 1024 / 1024).toFixed(2) + "GB");
				            } else if (total > 1024 * 1024) {
				                dompercentmb.text((cuLoaded / 1024 / 1024).toFixed(2) + "MB/" + (total / 1024 / 1024).toFixed(2) + "MB");
				            } else if (total > 1024 && total < 1024 * 1024) {
				                dompercentmb.text((cuLoaded / 1024).toFixed(2) + "KB/" + (total / 1024).toFixed(2) + "KB");
				            } else {
				                dompercentmb.text((cuLoaded).toFixed(2) + "B/" + (total).toFixed(2) + "B");
				            }

				            domtime.text("耗時" + timerange + "秒");
				            if (cuLoaded < total) {
				                readBlob(cuLoaded);
				            } else {
				                console.log('總共用時:' + timerange);
				                cuLoaded = total;
				                sendfinish(); //告知伺服器上傳完畢   
				                domtime.text("上傳完成,總共耗時" + timerange + "秒");
				            }
				            //顯示結果進度
				            var percent = (cuLoaded/total) * 100;
				            dommsg.text(percent.toFixed(2) + "%");
				            domprogress.val(percent);
				        });
				    }
				    var k = 0;
				    function sendfinish() {
				        var fd = new FormData();
				        fd.append('rquesttype', "finishupload");
				        fd.append('filename', file.name);
				        fd.append('md5value', md5value);
				        fd.append('totalsize', file.size);
				        var xhr = new XMLHttpRequest();
				        xhr.open('post', 'FileUpoload.ashx', true);
				        xhr.onreadystatechange = function () {
				            if (xhr.readyState == 4 && xhr.status == 200) {
				                if (fn) {
				                    fn(); //如果上傳成功,繼續上傳下一個文件
				                }
				                k = 0;
				            } else if (xhr.status == 500) {
				                setTimeout(function () {
				                    if (k < 3) {
				                        sendfinish();
				                        //上傳完畢的前端處理
				                    }
				                    k++
				                }, 3000);
				            }
				        }
				        //開始發送
				        xhr.send(fd);
				    }
				    var m = 0;
				    //關鍵代碼上傳到伺服器
				    function uploadFile(result, startIndex, onSuccess) {
				        var blob = new Blob([result]);
				        //提交到伺服器
				        var fd = new FormData();
				        fd.append('file', blob);
				        fd.append('rquesttype',"uploadblob");				        
				        fd.append('filename', file.name);
				        fd.append('md5value', md5value);
				        fd.append('loaded', startIndex);
				        var xhr = new XMLHttpRequest();
				        xhr.open('post', 'FileUpoload.ashx', true);
				        xhr.onreadystatechange = function () {
				            if (xhr.readyState == 4 && xhr.status == 200) {
				                m = 0;
				                if (onSuccess)
				                    onSuccess();
				            } else if (xhr.status == 500) {
				                setTimeout(function () {
				                    if (m < 3) {
				                        containue();
				                        m++;
				                    }
				                }, 1000);
				            }
				        }
				        //開始發送
				        xhr.send(fd);
				    }

				    //指定開始位置,分塊讀取文件
				    function readBlob(start) {
				        //指定開始位置和結束位置讀取文件
				        var blob = file.slice(start, start + step); //讀取開始位置和結束位置的文件
				        reader.readAsArrayBuffer(blob); //讀取切割好的文件塊
				    }
				    //繼續
				    function containue() {
				        readBlob(cuLoaded);
				    }
				    readBlob(cuLoaded);
				}

				
			}
		</script>
	</body>

</html>

需要到網上下載一個spark-md5.js文件,下載地址:

https://raw.githubusercontent.com/satazor/SparkMD5/master/spark-md5.js

後端處理請求的文件FileUpoload.ashx代碼:

<%@ WebHandler Language="C#" Class="FileUpoload" %>

using System;
using System.Web;
using System.IO;
using Microsoft.VisualBasic.Devices;

public class FileUpoload : IHttpHandler {
    private string basefilename = HttpContext.Current.Server.MapPath("/FileTemp/");
    private long totalCount = 0;
    public void ProcessRequest (HttpContext context) {
        HttpRequest req=context.Request;
        string rquesttype=req.Form["rquesttype"];
        switch (rquesttype)
        {
            case "chekcfile": chekcfile(req); break;
            case "uploadblob": uploadblob(req); break;
            case "finishupload": finishupload(req); break;
        }
        
    }

    /// <summary>
    /// 結束文件上傳,修改上傳後的名稱
    /// </summary>
    /// <param name="req"></param>
    public void finishupload(HttpRequest req)
    {
        //接收前端傳遞過來的參數
        var md5value = req.Form["md5value"];//文件md5加密的字元串
        var filename = req.Form["filename"];//文件的名稱
        var totalsize = req.Form["totalsize"];//文件的總大小
        
        string fullname = basefilename + md5value + ".part";//上傳的時候的名稱
        string okname = basefilename + md5value + ".ok";//上傳完畢之後再該目錄下創建一個.ok尾碼的文件,這個文件的大小為0,沒有內容,不占空間,主要用於前端檢查這個文件是否已經存在了
        var oldname = basefilename + filename;
        Computer MyComputer = new Computer();
        try
        {
            File.Create(okname);          
            FileInfo fi = new FileInfo(oldname);
            if (fi.Exists)
            {
                fi.Delete();
            }
            MyComputer.FileSystem.RenameFile(fullname, filename);
        }
        catch (Exception ex)
        {

        }
        finally
        {
            var str = string.Format("{{\"data\":\"ok\"}}");
            HttpContext.Current.Response.Write(str);
        }         
    }

    
    /// <summary>
    /// 處理文件分塊上傳的數據
    /// </summary>
    /// <param name="req"></param>
    public void uploadblob(HttpRequest req)
    {
        if (req.Files.Count <= 0)
        {
            HttpContext.Current.Response.Write("獲取伺服器上傳文件失敗");
            return;
        }
        HttpPostedFile _file = req.Files[0];
        //獲取參數
        string filename = req.Form["filename"];
        string md5value = req.Form["md5value"];
        var tempfilename = md5value + ".part";
        //如果是int 類型當文件大的時候會出問題 最大也就是 1.9999999990686774G
        long loaded = Convert.ToInt64(req.Form["loaded"]);
        totalCount += loaded;
        string newname = basefilename + tempfilename;
        Stream stream = _file.InputStream;
        if (stream.Length <= 0)
            throw new Exception("接收的數據不能為空");
        byte[] dataOne = new byte[stream.Length];
        stream.Read(dataOne, 0, dataOne.Length);
        FileStream fs;
        try
        {
            fs = new FileStream(newname, FileMode.Append, FileAccess.Write, FileShare.Read, 1024);
            fs.Write(dataOne, 0, dataOne.Length);
            fs.Close();
        }
        catch (Exception ex)
        {

        }
        finally
        {
            stream.Close();
            //檢查文件已經上傳的大小是否等於文件的總大小
        }
        HttpContext.Current.Response.Write("分段數據保存成功");
    }

    /// <summary>
    /// 檢查文件是否存在
    /// </summary>
    /// <param name="req"></param>
    public void chekcfile(HttpRequest req)
    {
        var md5value = req.Form["md5value"];//得到前端傳遞過來的文件的mdf字元串
        var path_ok = basefilename + md5value + ".ok";
        var path_part = basefilename + md5value + ".part";
        int flag = 0;
        string json = string.Empty;
        if (File.Exists(path_ok))//傳完了
        {
            flag = 2;
            json = string.Format("{{\"flag\":\"{0}\"}}", flag);
        }
        else if (File.Exists(path_part))//傳了一部分
        {
            flag = 1;
            var startindex = new FileInfo(path_part).Length.ToString();
            json = string.Format("{{\"flag\":\"{0}\",\"startindex\":\"{1}\"}}", flag, startindex);
        }
        else//新文件
        {
            flag = 0;
            json = string.Format("{{\"flag\":\"{0}\",\"startindex\":\"0\"}}", flag);
        }
        HttpContext.Current.Response.Write(json);
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

}
如果看不懂以上代碼,我們還有配套的視頻教程,如果需要視頻教程可以咨詢QQ:1416759661

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這是官方截圖,mysql5.7安裝後,會有一個預設密碼,保存在mysql.log裡面,找的他,並更改 官方文檔地址 https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html ...
  • 所有的Unix Like系統都會內建vi文書編輯器,其他的文書編輯器不一定存在,vim是vi的升級版,具有程式編輯的能力,可以主動的以字體顏色辨別語法的正確性,方便程式設計。vim 裡面加入了很多額外的功能,例如支持正規表示法的搜尋結構、多文件編輯、區塊複製等。 Vim有三種模式:命令模式、編輯模式 ...
  • 很好的解決辦法,問題已解決 利用rlwrap工具解決方法 1、安裝rlwrap和readline庫 CentOS下可以用EPEL的yum源直接安裝,步驟如下: (1)RHEL/CentOS/SL Linux 6.x 下安裝 EPEL6 yum源: 32位系統選擇: # rpm -ivh http:/ ...
  • 1、下載centos操作系統,提供百度雲盤鏈接:http://pan.baidu.com/s/1pLHOR03 2、打開上篇在VMware中新建好的空白虛擬機,將centos安裝在此空白虛擬機上,步驟如下圖: 3、開啟此虛擬機,出現如下圖,選擇,進行下一步,若沒有出現下圖或開機在命令行一直不動,則需 ...
  • Linux伺服器要保證高可用性,就要對其進行有效的監控,實時瞭解到伺服器的運行狀況,各項性能指標是否正常,以防患以未然,進行運維日誌的記錄,圖形化的監控,出現問題的消息報警機制,都是保證Linux伺服器能正常對外提供服務的先決條件。 ...
  • 本人從事Linux驅動開發。現在要實現兩端之間wifi長距離(1km左右)傳輸視頻數據(全向天線)的功能。目前用的平臺是Atheros AR9342。我在網上查到一些資料是關於禁用802.11的CSMA功能轉為使用TDMA傳輸,從而實現遠距離傳輸。我按照裡面方法修改mac層,其中有一個禁用發送時等待 ...
  • 系統:CentOS Linux release 7.2.1511 (Core) zabbix:3.2.4 一、yum -y install httpd mysql mysql-server mysql-devel php php-mysql php-common php-mbstring php-g ...
  • [user@localhost ~]$ curl -h Usage: curl [options...] Options: (H) means HTTP/HTTPS only, (F) means FTP only --anyauth Pick "any" authentication method... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...