使用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         /// 
 25         /// 块文件存储根路径
 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 }
UploadHelper

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