使用HTML5 FormData对象实现大文件分块上传(断点上传)功能
FormData是HTML5新增的一个对象,通过FormData对象可以组装一组用 XMLHttpRequest
发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit()
方法传输的数据格式相同。具体用法参考 FormData对象的使用。
实现逻辑:客户端首先请求接口,获取一个唯一的UploadID,然后每次按照固定大小读取文件块,同时计算需要上传的总块数total,将UploadID、total、当前上传文件块的下标index、文件名称以及文件块上传到服务端,服务端根据以上参数把文件块保存到临时目录,同时判断文件块是否已经全部上传完,如果已全部上传,则需要进行合并操作,最后会返回合并后的文件信息。我的例子中,把获取UploadID的步骤跟上传放在了一起,上传接口中会判断如果UploadID为空并且index为1,则会生成一个UploadID并返回,后续每次上传都需要带上UploadID参数。
一下前端代码
前端代码
1 @{ 2 Layout = null; 3 } 4 5 DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>断点上传title> 11 <script src="@Url.Content("~/Scripts/jquery-3.1.1.min.js")">script> 12 head> 13 <body> 14 <input id="myFile" type="file"> 15 <button onclick="Start()">上传button> 16 <button onclick="Pause()">暂停button> 17 <button onclick="Continue()">继续button> 18 <label>当前进度:<span id="progress">span>label> 19 <script> 20 var uploadId = ''; 21 var index = 1; 22 var pause = false; //暂停 23 24 function Start() { 25 index = 1; 26 uploadId = ''; 27 Upload(); 28 } 29 30 function Upload() { 31 var files = document.getElementById('myFile').files; 32 if (files.length < 1) { 33 alert('请选择文件~'); 34 return; 35 } 36 var file = files[0]; 37 var totalSize = file.size;//文件大小 38 var blockSize = 1024 * 1024 * 2;//块大小 39 var blockCount = Math.ceil(totalSize / blockSize);//总块数 40 41 //创建FormData对象 42 var formData = new FormData(); 43 formData.append('fileName', file.name);//文件名 44 formData.append('total', blockCount);//总块数 45 formData.append('index', index);//当前上传的块下标 46 formData.append('uploadId', uploadId);//上传编号 47 formData.append('data', null); 48 49 UploadPost(file, formData, totalSize, blockCount, blockSize); 50 } 51 52 function UploadPost(file, formData, totalSize, blockCount, blockSize) { 53 if (pause) { 54 return; //暂停 55 } 56 try { 57 var start = index * blockSize; 58 var end = Math.min(totalSize, start + blockSize); 59 var block = file.slice(start, end); 60 formData.set('data', block); 61 formData.set('index', index); 62 formData.set('uploadId', uploadId); 63 64 $.ajax({ 65 url: '', 66 type: 'post', 67 data: formData, 68 processData: false, 69 contentType: false, 70 success: function (res) { 71 block = null; 72 if (res.Code === 1) { 73 if (index === 1) 74 uploadId = res.UploadID; 75 76 $('#progress').text((index / blockCount * 100).toFixed(2) + '%'); 77 if (index < blockCount) { 78 index++; 79 UploadPost(file, formData, totalSize, blockCount, blockSize); 80 } 81 } 82 } 83 }); 84 } catch (e) { 85 alert(e); 86 } 87 } 88 ///暂停 89 function Pause() { 90 pause = true; 91 } 92 //继续 93 function Continue() { 94 pause = false; 95 Upload(); 96 } 97 script> 98 body> 99 html>前端代码
服务端代码
1 using System; 2 using System.IO; 3 using System.Linq; 4 using System.Web; 5 6 namespace UploadTest 7 { 8 public class UploadHelper 9 { 10 11 private UploadHelper() 12 { 13 14 } 15 16 public UploadHelper(string fileRootPath) 17 { 18 if (string.IsNullOrWhiteSpace(fileRootPath)) 19 throw new ArgumentNullException("fileRootPath", "fileRootPath is null"); 20 21 FileRootPath = fileRootPath; 22 BlockRootPath = fileRootPath + "/blocktmp/"; 23 } 24 ///UploadHelper25 /// 块文件存储根路径 26 /// 27 private string BlockRootPath { get; set; } 28 29 /// 30 /// 文件存储根路径 31 /// 32 public string FileRootPath { get; set; } 33 34 /// 35 /// 分块上传 36 /// 37 public UploadResult Upload(string uploadId, int blockCount, int currIndex, string fileName, HttpPostedFileBase file) 38 { 39 try 40 { 41 if (file == null) 42 return new UploadResult { Msg = "请选择文件~" }; 43 if (blockCount < 1) 44 return new UploadResult { Msg = "块数量不能小于1~" }; 45 if (currIndex < 0) 46 return new UploadResult { Msg = "块数量小于0~" }; 47 if (string.IsNullOrWhiteSpace(uploadId) && currIndex > 1) 48 return new UploadResult { Msg = "上传编号为空~" }; 49 50 var result = new UploadResult { Code = 1, Msg = "上传成功~" }; 51 52 //首次上传需创建上传编号 53 if (string.IsNullOrWhiteSpace(uploadId) || uploadId.Equals("undefind")) 54 uploadId = GenerateUploadId(); 55 56 result.UploadID = uploadId; 57 58 #region ==块处理== 59 60 //块文件名称 61 var blockName = $"{uploadId}_{currIndex}.block"; 62 //块文件目录路径 63 var blockPath = Path.Combine(BlockRootPath, uploadId); 64 //块文件目录对象 65 DirectoryInfo blockDirectoryInfo = Directory.Exists(blockPath) ? new DirectoryInfo(blockPath) : Directory.CreateDirectory(blockPath); 66 //块文件完整路径 67 var blockFullPath = Path.Combine(blockPath, blockName); 68 if (File.Exists(blockFullPath)) 69 { 70 //块已上传,不做失败处理 71 return new UploadResult { Code = 1, Msg = "该文件块已上传~" }; 72 } 73 74 file.SaveAs(blockFullPath); 75 76 #endregion 77 78 #region ==块合并处理== 79 80 //判断块文件是否已将上传完,上传完合并文件 81 if (blockDirectoryInfo.GetFiles().Count().Equals(blockCount)) 82 { 83 var timestamp = DateTime.Now.ToString("yyyMMdd"); 84 fileName = uploadId + "." + GetExtension(fileName); 85 var filePath = Path.Combine(FileRootPath, timestamp); 86 if (!Directory.Exists(filePath)) 87 { 88 Directory.CreateDirectory(filePath); 89 } 90 //完整文件存储路径 91 var fileFullPath = Path.Combine(filePath, fileName); 92 using (var fs = new FileStream(fileFullPath, FileMode.Create)) 93 { 94 for (var i = 1; i <= blockCount; i++) 95 { 96 var path = Path.Combine(blockPath, $"{uploadId}_{i}.block"); 97 var bytes = File.ReadAllBytes(path); 98 fs.Write(bytes, 0, bytes.Length); 99 } 100 Directory.Delete(blockPath, true); 101 102 result.FileInfo = new UploadFileInfo 103 { 104 FileName = fileName, 105 FilePath = Path.Combine(timestamp, fileName) 106 }; 107 } 108 } 109 110 return result; 111 #endregion 112 } 113 catch (Exception ex) 114 { 115 return new UploadResult { Msg = ex.Message }; 116 } 117 } 118 119 /// 120 /// 生成上传唯一编号 121 /// 122 /// 123 public string GenerateUploadId() 124 { 125 var guid = Guid.NewGuid().ToString(); 126 return guid.Replace("-", ""); 127 } 128 129 /// 130 /// 获取文件扩展名 131 /// 132 /// 133 /// 134 public string GetExtension(string fileName) 135 { 136 if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOf(".") < 0) 137 { 138 return string.Empty; 139 } 140 var arr = fileName.Split('.'); 141 return arr[arr.Length - 1]; 142 } 143 } 144 /// 145 /// 文件上传结果 146 /// 147 public class UploadResult 148 { 149 /// 150 /// 状态码 0失败 1成功 151 /// 152 public int Code { get; set; } 153 /// 154 /// 消息 155 /// 156 public string Msg { get; set; } 157 /// 158 /// 上传编号,唯一 159 /// 160 public string UploadID { get; set; } 161 /// 162 /// 文件保存信息 163 /// 164 public UploadFileInfo FileInfo { get; set; } 165 166 } 167 public class UploadFileInfo 168 { 169 /// 170 /// 文件保存名称 171 /// 172 public string FileName { get; set; } 173 /// 174 /// 文件保存路径 175 /// 176 public string FilePath { get; set; } 177 /// 178 /// 文件MD5值 179 /// 180 public string MD5 { get; set; } 181 } 182 }
Controller代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace UploadTest.Controllers { [RoutePrefix("upload")] public class UploadController : Controller { [Route] [HttpGet] public ActionResult Index() { return View(); } [Route] [HttpPost] public ActionResult Upload(string uploadId,int total,int index,string fileName) { var helper = new UploadHelper("D:\\Upload"); var result = helper.Upload(uploadId, total, index, fileName, Request.Files[0]); return Json(result); } } }