1. 客户每次上传前先获取一下当前文件已经被服务器接受了多少
2. 上传时设定偏移量
服务端代码如下:
/// <summary> /// 断点续传,获取已上传文件大小 /// </summary> /// <returns></returns> [HttpPost] public long GetFileLength() { long result = 0; FileStream fStream = null; BinaryReader bReader = null; try { var fileName = ""; IEnumerable<string> hasFileNameList = new List<string>(); bool hasFileName = Request.Headers.TryGetValues("FileName", out hasFileNameList); if (hasFileName && hasFileNameList.Any()) { fileName = hasFileNameList.First(); } //设置文件存放路径 string dir = HttpContext.Current.Server.MapPath(@"~UploadFiles"); fileName= dir+"\"+fileName; if (File.Exists(fileName)) { FileInfo file = new FileInfo(fileName); result = file.Length; } } catch (Exception ex) { logger.Error(ex, ex.Message); } return result; } /// <summary> /// 断点续传 服务端保存文件代码 /// <param>Offset 偏移量</param> /// </summary> /// <returns></returns> [HttpPost] public Bussiness<bool> UploadFile() { #region Log Test FileStream fStreamLog = null; BinaryReader bReaderLog = null; try { fStreamLog = new FileStream(SavePath + "\" + $"{DateTime.Now:HHmmss}.File.tmp", FileMode.OpenOrCreate, FileAccess.ReadWrite); int upLoadLengthTest = (int)HttpContext.Current.Request.InputStream.Length; bReaderLog = new BinaryReader(HttpContext.Current.Request.InputStream); byte[] dataTest = new byte[upLoadLengthTest]; bReaderLog.Read(dataTest, 0, upLoadLengthTest); //将上传文件流读到byte[]中 fStreamLog.Write(dataTest, 0, upLoadLengthTest); //将byte[]写到文件中, } catch (Exception exr) { logger.Error(exr, exr.Message); } finally { if (fStreamLog != null) { //释放流 fStreamLog.Close(); } if (bReaderLog != null) { bReaderLog.Close(); } } #endregion var uploadFileName = HttpContext.Current.Request.Form["UploadFileName"]; Bussiness<bool> result = new Bussiness<bool>(); FileStream fStream = null; BinaryReader bReader = null; try { long offset = 0; //客户端定义,从X开始往后写,该值由服务端GetFileLength方法提供 IEnumerable<string> hasOffsetList = new List<string>(); bool hasOffset = Request.Headers.TryGetValues("Offset", out hasOffsetList); if (hasOffset && hasOffsetList.Any()) { long.TryParse(hasOffsetList.First(), out offset); } var offsetStr = HttpContext.Current.Request.Form["Offset"]; if (!string.IsNullOrEmpty(offsetStr)) { long.TryParse(offsetStr, out offset); } if (string.IsNullOrEmpty(uploadFileName)) { result.BussinessCode = -1; result.BussinessMsg = "uploadFileName 上传文件名不能为空"; result.BussinessData = false; return result; } //设置文件存放路径 //string dir = HttpContext.Current.Server.MapPath(SavePath); string dir = SavePath; //如果不存在文件夹,就创建文件夹 if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); //////var fileName = dir + "\" + file.FileName; ///TODO var fileName = dir + "\" + (string.IsNullOrEmpty(uploadFileName) ? $"{DateTime.Now.ToString("HHmmss")}.txt" : uploadFileName); fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); //偏移指针 fStream.Seek(offset, SeekOrigin.Begin); if (HttpContext.Current.Request.Files.Count > 0) { #region 文件形式 HttpPostedFile file = HttpContext.Current.Request.Files["UploadFile"]; int upLoadLength = Convert.ToInt32(file.InputStream.Length); //从客户端的请求中获取文件流 bReader = new BinaryReader(file.InputStream); byte[] data = new byte[upLoadLength]; bReader.Read(data, 0, upLoadLength); //将上传文件流读到byte[]中 fStream.Write(data, 0, upLoadLength);//将byte[]写到文件中, logger.Info($"上传文件 {uploadFileName} Offset => {offset} File.ContentLength => {upLoadLength} => {fileName}"); #endregion } else { #region Base64 //如果没传文件是以byte[] var byteFile = HttpContext.Current.Request.Form["UploadFile"]; byte[] byteArray = Convert.FromBase64String(byteFile); int upLoadLength = Convert.ToInt32(byteArray.Length); fStream.Write(byteArray, 0, upLoadLength);//将byte[]写到文件中, logger.Info($"上传文件 {uploadFileName} Offset => {offset} File.ContentLength => {upLoadLength} => {fileName}"); #endregion } result.BussinessData = true; result.BussinessMsg = $"上传成功!"; result.BussinessCode = 0; } catch (Exception ex) { logger.Error(ex, ex.Message); result.BussinessCode = -1; result.BussinessMsg = ex.Message; result.BussinessData = false; #region 出异常后,看看客户端上传的啥 FileStream fstreamErr = null; BinaryReader readerErr = null; try { fstreamErr = new FileStream(SavePath + "\" + $"{uploadFileName}_{DateTime.Now:HHmmss}.err", FileMode.OpenOrCreate, FileAccess.ReadWrite); int upLoadLength = (int)HttpContext.Current.Request.InputStream.Length; readerErr = new BinaryReader(HttpContext.Current.Request.InputStream); byte[] data = new byte[upLoadLength]; readerErr.Read(data, 0, upLoadLength); //将上传文件流读到byte[]中 fstreamErr.Write(data, 0, upLoadLength); //将byte[]写到文件中, } catch (Exception exr) { logger.Error(exr, exr.Message); } finally { if (fstreamErr != null) { //释放流 fstreamErr.Close(); } if (readerErr != null) { readerErr.Close(); } } #endregion return result; } finally { if (fStream != null) { //释放流 fStream.Close(); } if (bReader != null) { bReader.Close(); } } return result; }
上传前,源和目标文件对比
用 Postman 测试
先获取已上传文件的大小,供下面设置偏移量使用
上传后的源和目标文件对比
java 客户端代码
package com.vipsoft.demo.Controller; import org.springframework.core.io.FileSystemResource; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.RandomAccessFile; import java.util.Base64; @RestController @RequestMapping("/demo") public class DemoController { @GetMapping(value = "/test") public String login() { return "这是demo下面的,用来写测试代码,测试通过后,移到正式项目中!"; } @GetMapping(value = "/uploadFile") public String uploadFile(HttpServletRequest request) throws Exception { String url = "http://localhost:44999/api/Upload/UploadFile"; RestTemplate restTemplate = new RestTemplate(); String filePath = "D:\Projects\TEST.DAT"; File file = new File(filePath); FileSystemResource resource = new FileSystemResource(file); MultiValueMap<String, Object> param = new LinkedMultiValueMap<>(); param.add("Offset", "6"); param.add("UploadFileName", "ABC.txt"); param.add("UploadFile", resource); String rev = restTemplate.postForObject(url, param, String.class); System.out.println(rev); return rev; } @GetMapping(value = "/uploadByte") public String uploadByte(HttpServletRequest request) throws Exception { String url = "http://localhost:44999/api/Upload/UploadFile"; RestTemplate restTemplate = new RestTemplate(); String filePath = "D:\Projects\TEST.DAT"; File file = new File(filePath); //每次上传 10KB int step = 10 * 1024; for (long i = 0; i <= file.length(); i = i + step) { //偏移量 long offset = i; byte[] bytes = new byte[step]; //如果超过了就取最后的。 if (i + step > file.length()) { bytes = new byte[(int) (file.length() - i)]; } RandomAccessFile raf = new RandomAccessFile(file, "r"); raf.seek(offset); int readSize = raf.read(bytes); MultiValueMap<String, Object> param = new LinkedMultiValueMap<>(); param.add("Offset", i); param.add("UploadFileName", "ABC.txt"); //转成 Base64 param.add("UploadFile", Base64.getEncoder().encodeToString(bytes)); String rev = restTemplate.postForObject(url, param, String.class); //如果接口返回值出错,break System.out.println(rev); } return ""; } }
C# 客户端伪代码
/// <summary> /// 客户端上传伪代码 /// </summary> /// <param name="fileName">待上传文件全路径</param> /// <param name="byteCount">上传时的流量控制,文件较大时,用于切割文件上传</param> /// <param name="msg">错误信息</param> /// <returns>成功或者失败</returns> public static bool UpLoadFile(string fileName, int byteCount, out string msg) { msg = ""; bool result = true; long cruuent = GetServerFileLength(fileName); //客户端需要读取服务器已上传了多少内容,然后从偏移量开始往后读取发送 FileStream fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); BinaryReader bReader = new BinaryReader(fStream); long length = fStream.Length; fileName = fileName.Substring(fileName.LastIndexOf('\') + 1); #region 开始上传文件 try { #region 续传处理 byte[] data; if (cruuent > 0) { fStream.Seek(cruuent, SeekOrigin.Current); } #endregion #region 分割文件上传 for (; cruuent <= length; cruuent = cruuent + byteCount) { try { if (cruuent + byteCount > length) { data = new byte[Convert.ToInt64((length - cruuent))]; bReader.Read(data, 0, Convert.ToInt32((length - cruuent))); } else { data = new byte[byteCount]; bReader.Read(data, 0, byteCount); } Hashtable parms = new Hashtable(); parms.Add("Offset", cruuent.ToString()); var falg= PostData("",param); if (falg == false) { break; } } catch (Exception ex) { msg = ex.ToString(); result = false; break; } #endregion } } catch (Exception ex) { throw ex; } finally { bReader.Close(); fStream.Close(); } GC.Collect(); #endregion return result; }