C#实现分片上传文件 Code=0表示接口调用成功 上传分片文件 合并分片文件 上传单个文件

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Web;
  5 using System.IO;
  6 using System.Configuration;
  7 
  8 namespace WebApplication1
  9 {
 10     /// <summary>
 11     /// PartialFileSet 的摘要说明
 12     /// </summary>
 13     public class UpLoadPartialFile : IHttpHandler
 14     {
 15         public static DateTime mkdirRecodeTime = DateTime.Now.AddDays(-2);
 16         public static string keyHead = DateTime.Now.Year + "" + DateTime.Now.Month + DateTime.Now.Day;
 17         public static string path = ConfigurationManager.AppSettings["filePath"].ToString();
 18         public static object mkdirLock = new object();
 19    
 20         /// <summary>
 21         /// 根据日期字符串返回路径
 22         /// </summary>
 23         /// <param name="keyHead">日期字符串yyyyMMdd</param>
 24         /// <returns></returns>
 25         private static string getCurrentPathByDate(string keyHead)
 26         {
 27 
 28             return path + "\" + keyHead;
 29         }
 30 
 31         private static string getCurrentFilePathByDate(string keyHead, string guid, int id)
 32         {
 33             return getCurrentPathByDate(keyHead) + "\" + guid + id;
 34         }
 35 
 36         private static string getNewFilePathByDate(string keyHead, string guid)
 37         {
 38             string fileType = HttpContext.Current.Request["fileType"];
 39             if(fileType!=null&& fileType.Length>0)
 40             return getCurrentPathByDate(keyHead) + "\" + guid+"."+fileType;
 41             return getCurrentPathByDate(keyHead) + "\" + guid;
 42         }
 43         /// <summary>
 44         /// 如果目标文件夹不存在 创建文件夹
 45         /// </summary>
 46         /// <param name="keyHead"></param>
 47         private static void mkdirIfNoExit()
 48         {
 49 
 50             if ((DateTime.Now-mkdirRecodeTime ).Days < 1)
 51                 return;
 52           
 53             //创建文件夹目录
 54             if (Directory.Exists(getCurrentPathByDate(keyHead)))
 55                 return;
 56             lock (mkdirLock)
 57             {
 58 
 59                 if (!Directory.Exists(path))
 60                     Directory.CreateDirectory(path);
 61                 if (!Directory.Exists(getCurrentPathByDate(keyHead)))
 62                     Directory.CreateDirectory(getCurrentPathByDate(keyHead));
 63 
 64                 keyHead = DateTime.Now.Year + "" + DateTime.Now.Month + DateTime.Now.Day;
 65                 mkdirRecodeTime = DateTime.Now;
 66                 return;
 67             }
 68 
 69         }
 70 
 71         private void uploadFile(HttpContext context)
 72         {
 73             try
 74             {
 75                 //检查目录
 76                 mkdirIfNoExit();
 77                 
 78 
 79 
 80                 if (context.Request.Files == null || context.Request.Files.Count == 0 || context.Request.Files[0].InputStream.Length == 0)
 81                 {
 82                     context.Response.Write("{"state":"error","code":-6,"msg":"接口调用出错 上传文件不能为空"}");
 83                     return;
 84                 }
 85                 if (context.Request.Files.Count > 1)
 86                 {
 87                     context.Response.Write("{"state":"error","code":-7,"msg":"接口调用出错 每次只能上传单个文件"}");
 88                     return;
 89                 }
 90                 string guid = Guid.NewGuid().ToString();
 91 
 92                 string keyHead = DateTime.Now.Year + "" + DateTime.Now.Month + DateTime.Now.Day;
 93                 string currentPath = getCurrentPathByDate(keyHead);
 94                 string filePath = getNewFilePathByDate(keyHead, guid);
 95               
 96                 lock (filePath)
 97                 {
 98                     //创建文件
 99                     try
100                     {
101                         context.Request.Files[0].SaveAs(filePath);
102 
103                     }
104                     catch (Exception e)
105                     {
106                         Util.LogHelper.Info(e.Message + e.StackTrace);
107 
108                         context.Response.Write("{"state":"error","code":-2,"msg":"接口调用出错 guid为" + guid + "的文件写入时出现错误 已记录日志"}");
109                         return;
110                     }
111                 }
112                 context.Response.Write("{"state":"success","code":0,"msg":""+keyHead+guid+""}");
113 
114             }
115             catch (Exception e)
116             {
117                 Util.LogHelper.Info(e.Message + e.StackTrace);
118                 context.Response.Write("{"state":"error","code":-1,"msg":"接口调用出错 已记录日志"}");
119             }
120         }
121 
122         private void uploadPartialFile(HttpContext context) {
123             try
124             {
125                 //检查目录
126                 mkdirIfNoExit();
127                 if (context.Request["date"] == null)
128                 {
129                     context.Response.Write("{"state":"error","code":-2,"msg":"接口调用出错 参数date(第一片上传时间)不能为空"}");
130                     return;
131                 }
132                 DateTime uploadData;
133                 if (!DateTime.TryParse(context.Request["date"], out uploadData))
134                 {
135 
136                     context.Response.Write("{"state":"error","code":-2,"msg":"接口调用出错date参数错误 格式 yyyy-MM-dd"}");
137                     return;
138                 }
139                 if (context.Request["guid"] == null || context.Request["guid"].Trim().Length != 32)
140                 {
141                     context.Response.Write("{"state":"error","code":-3,"msg":"接口调用出错 guid(文件唯一标示)不能为空且必须为32位长度"}");
142                     return;
143                 }
144 
145                 if (context.Request["id"] == null || context.Request["id"].Trim().Length == 0)
146                 {
147                     context.Response.Write("{"state":"error","code":-4,"msg":"接口调用出错 id(文件分组id)不能为空"}");
148                     return;
149                 }
150                 int id = -1;
151                 if (!int.TryParse(context.Request["id"], out id))
152                 {
153                     context.Response.Write("{"state":"error","code":-5,"msg":"接口调用出错 id(文件分组id)必须为数字"}");
154                     return;
155                 }
156 
157 
158                 if (context.Request.Files == null || context.Request.Files.Count == 0 || context.Request.Files[0].InputStream.Length == 0)
159                 {
160                     context.Response.Write("{"state":"error","code":-6,"msg":"接口调用出错 上传文件不能为空"}");
161                     return;
162                 }
163                 if (context.Request.Files.Count > 1)
164                 {
165                     context.Response.Write("{"state":"error","code":-7,"msg":"接口调用出错 每次只能上传单个文件"}");
166                     return;
167                 }
168                 string guid = context.Request["guid"].Trim();
169                 string keyHead = uploadData.Year + "" + uploadData.Month + uploadData.Day;
170                 string currentPath = getCurrentPathByDate(keyHead);
171                 string filePath = getCurrentFilePathByDate(keyHead, guid, id);
172                 if (File.Exists(filePath))
173                 {
174                     context.Response.Write("{"state":"error","code":-8,"msg":"接口调用出错 guid为" + guid + "文件id为" + id + "的文件已存在"}");
175                     return;
176                 }
177                 lock (filePath)
178                 {
179                     if (File.Exists(filePath))
180                     {
181                         context.Response.Write("{"state":"error","code":-8,"msg":" guid为" + guid + "文件id为" + id + "的文件已存在"}");
182                         return;
183                     }
184                     //创建文件
185                     try
186                     {
187                         context.Request.Files[0].SaveAs(filePath);
188 
189                     }
190                     catch (Exception e)
191                     {
192                         Util.LogHelper.Info(e.Message + e.StackTrace);
193 
194                         context.Response.Write("{"state":"error","code":-9,"msg":"接口调用出错 guid为" + guid + "文件id为" + id + "的文件写入时出现错误 已记录日志"}");
195                         return;
196                     }
197                 }
198 
199                 context.Response.Write("{"state":"success","code":0,"msg":" guid为" + guid + "文件id为" + id + "的文件写入成功"}");
200 
201             }
202             catch (Exception e)
203             {
204                 Util.LogHelper.Info(e.Message + e.StackTrace);
205                 context.Response.Write("{"state":"error","code":-1,"msg":"接口调用出错 已记录日志"}");
206             }
207         }
208 
209         private void joinfile(HttpContext context)
210         {
211             try
212             {
213                 //检查目录
214                 if (context.Request["date"] == null)
215                 {
216                     context.Response.Write("{"state":"error","code":-2,"msg":"接口调用出错 参数date(第一片上传时间)不能为空"}");
217                     return;
218                 }
219                 if (context.Request["guid"] == null || context.Request["guid"].Trim().Length != 32)
220                 {
221                     context.Response.Write("{"state":"error","code":-3,"msg":"接口调用出错 guid(文件唯一标示)不能为空且必须为32位长度"}");
222                     return;
223                 }
224 
225                 if (context.Request["idArray"] == null || context.Request["idArray"].Trim().Length == 0)
226                 {
227                     context.Response.Write("{"state":"error","code":-4,"msg":"接口调用出错 id(文件分组id列表)不能为空"}");
228                     return;
229                 }
230                 DateTime uploadData;
231                 if (!DateTime.TryParse(context.Request["date"],out uploadData)) {
232 
233                     context.Response.Write("{"state":"error","code":-6,"msg":"接口调用出错date参数错误 格式 yyyy-MM-dd"}");
234                     return;
235                 }
236 
237                 string keyHead = uploadData.Year + "" + uploadData.Month + uploadData.Day;
238                 string guid = context.Request["guid"].Trim();
239 
240                 List<int> IdArray = context.Request["idArray"].StringArrayConvertInt(',').OrderBy(u=>u).ToList();
241                 //开始检查文件是否全部存在
242                 List<string> pathList = new List<string>();
243                 if (IdArray.Count < 1)
244                 {
245                     context.Response.Write("{"state":"error","code":-8,"msg":"接口调用出错文件列表文件少于2个无法进行合并操作 请检查IdArray参数"}");
246                     return;
247                 }
248                 foreach (var item in IdArray)
249                 {
250                     string path = getCurrentFilePathByDate(keyHead,guid,item);
251                     if (!File.Exists(path))
252                     {
253                         context.Response.Write("{"state":"error","code":-7,"msg":"id编号为"+item+"的文件在服务器上不存在 请检查"}");
254                         return;
255                     }
256                     pathList.Add(path);
257 
258                 }
259                 string newGuid = Guid.NewGuid().ToString();
260                 string newFilePath = getNewFilePathByDate(keyHead,newGuid);
261                 using (System.IO.FileStream fileStram = File.Open(newFilePath, FileMode.Create,FileAccess.Write))
262                 {
263 
264                     //开始合并文件  创建一个副本
265                     pathList.ForEach(u =>
266                 {
267                     //读取文件
268                     using (FileStream save = new FileStream(u, FileMode.Open, FileAccess.Read))
269                     {
270                         byte[] bt = new byte[1024];
271                         int count = -1;
272                         while ((count = save.Read(bt, 0, bt.Length)) > 0)
273                         {
274                             fileStram.Write(bt, 0, count);
275                         }
276                     }
277 
278                 });
279                     context.Response.Write("{"state":"success","code":0,"msg":""+ keyHead + newGuid + ""}");
280                     //删除文件列表
281                     delFile(pathList);
282                 }
283 
284             }
285             catch (Exception e)
286             {
287                 Util.LogHelper.Info(e.Message + e.StackTrace);
288                 context.Response.Write("{"state":"error","code":-1,"msg":"接口调用出错 已记录日志"}");
289             }
290         }
291         public bool IsReusable
292         {
293             get
294             {
295                 return false;
296             }
297         }
298         public static void delFile(List<string> pathList) {
299             try
300             {
301 
302                 for (int i = 0; i < pathList.Count; i++)
303                 {
304                     File.Delete(pathList[i].Replace("\","/"));
305                 }
306             }
307             catch (Exception e)
308             {
309                 Util.LogHelper.Info("删除分片文件错误"+e.Message+e.StackTrace);
310             }
311         }
312 
313         /// <summary>
314         /// 创建部分文件集合
315         /// </summary>
316         /// <param name="context"></param>
317         public  void ProcessRequest(HttpContext context)
318         {
319             try
320             {
321 
322           
323             context.Response.ContentType = "application/json";
324 
325             string mode = context.Request["mode"].ToLower();
326                 if (mode == "partialfile")
327                     uploadPartialFile(context);
328                 else if (mode == "joinfile")
329                     joinfile(context);
330                 else if (mode == "uploadfile")
331                     uploadFile(context);
332                 else
333                 context.Response.Write("{"state":"error","code":-10,"msg":"接口调用出错 mode值范围为partialfile(上传分片文件)joinfile(合并分片文件)uploadfile上传单个文件 三种 "}");
334 
335             }
336 
337             catch (Exception e)
338             {
339 
340                 Util.LogHelper.Info("ProcessRequest执行错误" + e.Message + e.StackTrace);
341                 context.Response.Write("{"state":"error","code":-1,"msg":"接口调用出错 已记录日志"}");
342 
343 
344             }
345         }
346 
347 
348 
349 
350 
351 
352     }
353 }

js调用

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/jquery.form/3.51/jquery.form.js"></script>
    <form id="fileUpLoad" action="/UpLoadPartialFile.ashx?mode=partialfile" method="post" name="test">
        <input type="file" name="name" onchange="upload(this)" />上传分片文件
        <input type="hidden" id="guid" name="guid"value="aaaaaaaaaaaaaaaa" />
        <input type="hidden" name="date" value="2017-10-02" />
        <input type="hidden" id="id" name="id" value="0" />

    </form>

    <form id="fileUpLoadFile" action="/UpLoadPartialFile.ashx?mode=uploadfile" method="post" name="test">
        <input type="file" name="name2" onchange="uploadFile(this)"  />上传单个文件

    </form>
    <input type="text" id="fileType" name="fileType" placeholder="合并的文件后缀名称 可选(合并文件时有效)" value="mp4" />
    <button onclick="joinFIle()">合并文件</button>

</body>
</html>

<script>

    function newGuid() {
        var guid = "";
        for (var i = 1; i <= 32; i++) {
            var n = Math.floor(Math.random() * 16.0).toString(16);
            guid += n;
          
        }
        return guid;
    }
    var idArray = '';
    var date = 2017-10-02;
   var  i = 0;
    var guid = newGuid();
    $('#guid').val(guid);
    function joinFIle() {

        $.ajax('/UpLoadPartialFile.ashx?mode=joinfile&date=2017-10-02&guid=' + guid + '&idArray=' + idArray + '&fileType=' + $('#fileType').val()).done(function (rs) {
            alert(rs.msg);
        })

    }
    function upload(obj) {

        if (obj.size == 0)
            return;
        //上传表单
        $('#id').val(++i);
        idArray += i+',';
        $('#fileUpLoad').ajaxSubmit();

    }

    function uploadFile() {
        $('#fileUpLoadFile').ajaxSubmit();
    }
</script>

C#调用

        private static void post1()
        {
            string url = @"http://localhost:1128/uploadPartialFile.ashx?mode=partialfile&date=2017-10-03&id=1&guid=5ed1eddf7e7213ed0db6d5150b488335";//这里就不暴露我们的地址啦
            string modelId = "4f1e2e3d-6231-4b13-96a4-835e5af10394";
            string updateTime = "2016-11-03 14:17:25";
            string encrypt = "f933797503d6e2c36762428a280e0559";

            string filePath = @"D:/test2";
            string fileName = "test2";
            byte[] fileContentByte = new byte[1024]; // 文件内容二进制

            #region 将文件转成二进制

            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            fileContentByte = new byte[fs.Length]; // 二进制文件
            fs.Read(fileContentByte, 0, Convert.ToInt32(fs.Length));
            fs.Close();

            #endregion


            #region 定义请求体中的内容 并转成二进制

            string boundary = "ceshi";
            string Enter = "
";

            string modelIdStr = "--" + boundary + Enter
                    + "Content-Disposition: form-data; name="modelId"" + Enter + Enter
                    + modelId + Enter;

            string fileContentStr = "--" + boundary + Enter
                    + "Content-Type:application/octet-stream" + Enter
                    + "Content-Disposition: form-data; name="fileContent"; filename="" + fileName + """ + Enter + Enter;

            string updateTimeStr = Enter + "--" + boundary + Enter
                    + "Content-Disposition: form-data; name="updateTime"" + Enter + Enter
                    + updateTime;

            string encryptStr = Enter + "--" + boundary + Enter
                    + "Content-Disposition: form-data; name="encrypt"" + Enter + Enter
                    + encrypt + Enter + "--" + boundary + "--";


            var modelIdStrByte = Encoding.UTF8.GetBytes(modelIdStr);//modelId所有字符串二进制

            var fileContentStrByte = Encoding.UTF8.GetBytes(fileContentStr);//fileContent一些名称等信息的二进制(不包含文件本身)

            var updateTimeStrByte = Encoding.UTF8.GetBytes(updateTimeStr);//updateTime所有字符串二进制

            var encryptStrByte = Encoding.UTF8.GetBytes(encryptStr);//encrypt所有字符串二进制


            #endregion


            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "multipart/form-data;boundary=" + boundary;

            Stream myRequestStream = request.GetRequestStream();//定义请求流

            #region 将各个二进制 安顺序写入请求流 modelIdStr -> (fileContentStr + fileContent) -> uodateTimeStr -> encryptStr

            myRequestStream.Write(modelIdStrByte, 0, modelIdStrByte.Length);

            myRequestStream.Write(fileContentStrByte, 0, fileContentStrByte.Length);
            myRequestStream.Write(fileContentByte, 0, fileContentByte.Length);

            myRequestStream.Write(updateTimeStrByte, 0, updateTimeStrByte.Length);

            myRequestStream.Write(encryptStrByte, 0, encryptStrByte.Length);

            #endregion

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();//发送

            Stream myResponseStream = response.GetResponseStream();//获取返回值
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));

            string retString = myStreamReader.ReadToEnd();

            myStreamReader.Close();
            myResponseStream.Close();
        }

其他情况下code为负数 msg中包含具体错误信息

单个分片文件上传不能超过4000kb

上传分片文件

http://localhost:1128/UpLoadPartialFile.ashx?mode=partialfile

method:post

参数

date 分片上传 参数格式yyyy-MM-dd日期 例如:     2017-10-02 所有分片上传都使用一个时间 服务端根据这个参数来区分文件夹

id  (整数) 分片文件编号 如1  服务端合并文件将根据编号由小到大排序合并

guid 客户端生成一个guid 后面所有同一个文件的分片均使用同一个guid

guid为32位长度 即省略-分隔符

浏览器js调用报文如下

调用成功返回json

{"state":"success","code":0,"msg":" guid为db334a03fc01b200c770871374734c7b文件id为1的文件写入成功"}

合并分片文件

http://localhost:1128/UpLoadPartialFile.ashx?mode=joinfile&date=2017-10-02&guid=db334a03fc01b200c770871374734c7b&idArray=1,2,&fileType=mp4

method :post

参数

date 分片上传时间(必选)

guid 上传分片时使用的guid (必选)

idArray 编号列表 拼接逗号分隔 需要两个以上 否则无法合并文件(必选)

filetype 文件后缀名称 可选 如果传递该参数 读取时也要传递 否则无法找到文件

调用成功返回

{"state":"success","code":0,"msg":"2017102cdb64b77-5f7c-4ccf-aa11-1e327dfcd49f"}

Msg中的字符串用于读取文件

上传单个文件

http://localhost:1128/UpLoadPartialFile.ashx?mode=uploadfile

method:post

文件流写入到请求中

调用成功返回

{"state":"success","code":0,"msg":"2017102cdb64b77-5f7c-4ccf-aa11-1e327dfcd49f"}

Msg中的字符串用于读取文件 

安卓参考实现

http://blog.csdn.net/ylbf_dev/article/details/50468984

ios参考实现

http://www.jianshu.com/p/a0e3c77d3164