断点续传(上传)C#版
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;
}
[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>();
if (string.IsNullOrEmpty(uploadFileName))
{
logger.Error("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 = Path.Combine(dir, ecgFileName);
string lockFile = fileName + ".lock";
bool lockAcquired = false;
FileStream fStream = null;
BinaryReader bReader = null;
FileStream lockStream = null;
try
{
// 尝试获取文件锁
int retryCount = 0;
const int maxRetry = 10;
const int retryDelay = 500; // 毫秒
while (retryCount < maxRetry && !lockAcquired)
{
try
{
// 尝试创建锁文件,如果成功则获取到锁
lockStream = new FileStream(lockFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.DeleteOnClose);
{
lockAcquired = true;
logger.Info($"创建锁文件,成功获取到锁: {lockFile}");
var content = $"【UploadFile】,{ecgFileName}, 创建锁文件,成功获取到锁: {lockFile}";
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", content);
}
}
catch (IOException)
{
var content = $"【UploadFile】 Error,{ecgFileName}, 创建锁文件,文件被占用,等待后重试: {lockFile}";
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", content);
// 文件被占用,等待后重试
retryCount++;
if (retryCount < maxRetry)
{
Thread.Sleep(retryDelay);
}
}
catch (UnauthorizedAccessException)
{
var content = $"【UploadFile】 Error,{ecgFileName}, 创建锁文件,没有权限创建锁文件: {lockFile}";
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", content);
// 权限问题,立即失败
logger.Error($"没有权限创建锁文件: {lockFile}");
break;
}
}
if (!lockAcquired)
{
var content = $"【UploadFile】 Error,{ecgFileName}, 无法获取文件锁: {fileName}";
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", content);
logger.Error($"无法获取文件锁: {fileName}");
result.BussinessCode = -2;
result.BussinessMsg = "系统繁忙,请稍后重试";
result.BussinessData = false;
return result;
}
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);
}
//////var fileName = dir + "\\" + file.FileName; ///TODO
var fileName = dir + "\\" + (string.IsNullOrEmpty(uploadFileName) ? $"{DateTime.Now.ToString("HHmmss")}.txt" : uploadFileName);
// 使用FileShare.Read允许其他进程读取但不写入
fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite,FileShare.Read); //关键修改:允许其他进程读取,FileShare.Read
//偏移指针
fStream.Seek(offset, SeekOrigin.Begin);
if (HttpContext.Current.Request.Files.Count > 0)
{
#region 文件形式
HttpPostedFile file = HttpContext.Current.Request.Files["UploadFile"];
if (file == null || file.InputStream == null || file.InputStream.Length == 0)
{
logger.Warn($"EcgFile 大小不能为 0");
result.BussinessCode = -1;
result.BussinessMsg = $"EcgFile 大小不能为 0";
result.BussinessData = false;
return result;
}
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($"上传文件 HttpPostedFile {uploadFileName} Offset => {offset} File.ContentLength => {upLoadLength} => {fileName}");
#endregion
}
else
{
#region Base64
//如果没传文件是以byte[]
var byteFile = HttpContext.Current.Request.Form["UploadFile"];
if (byteFile == null)
{
logger.Warn($"EcgFile 大小不能为 0");
result.BussinessCode = -1;
result.BussinessMsg = $"EcgFile 大小不能为 0";
result.BussinessData = false;
return result;
}
byte[] byteArray = Convert.FromBase64String(byteFile);
int upLoadLength = Convert.ToInt32(byteArray.Length);
fStream.Write(byteArray, 0, upLoadLength);//将byte[]写到文件中,
logger.Info($"上传文件 Base64 {uploadFileName} Offset => {offset} File.ContentLength => {upLoadLength} => {fileName}");
#endregion
}
result.BussinessData = true;
result.BussinessMsg = $"上传成功!";
result.BussinessCode = 0;
var msg = $"上传文件 HttpPostedFile {ecgFileName} Offset => {offset} File.ContentLength => {upLoadLength} => {fileName}";
logger.Info(msg);
var content = $"【UploadFile】,{ecgFileName}, {msg}";
logger.Info(content);
}
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
}
finally
{
if (lockStream != null)
{
lockStream.Close();
}
if (fStream != null)
{
//释放流
fStream.Close();
}
if (bReader != null)
{
bReader.Close();
}
// 释放文件锁
if (lockAcquired && File.Exists(lockFile))
{
try
{
File.Delete(lockFile);
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", $"删除 lockFile {lockFile}");
}
catch (Exception ex)
{
var msg = $"删除锁文件失败: {lockFile}, 错误: {ex.Message}";
logger.Warn(msg);
ThothOpenHelper.SendDevOps(msg);
var content = $"【UploadFile】 Error,{ecgFileName}, {msg}";
WriteLog($"{DateTime.Now.ToString("yyyy-MM-dd")}.CustodyOrder.log", content);
}
}
}
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; }
本文来自博客园,作者:VipSoft 转载请注明原文链接:https://www.cnblogs.com/vipsoft/p/14419197.html
浙公网安备 33010602011771号