写在前面的话
本人也是找了好多资料,发现要么就是很老的代码了,要么就是不全适用需求,所以整理记录一下,方便日后查阅
适用需求:
- 不想通过文件的URL直接下载服务器资源,例如:需要对下载增加限制(如:消耗平台流量、下载次数或频率限制)、需要统计下载次数、不想暴露文件下载地址等,这就需要给原有的文件URL包一层WebAPI接口,在API里面我们就可以实现这些限制了。
个人需求
- 平台的部分资源下载需要耗费平台流量,平台流量是需要充值购买的
- 要支持断点续传,客户端可能下载一半出现中断
- 下载过程中,客户端保存失败或者取消下载,不应该扣除用户整个文件的流量大小(如:文件100M,下载1M的时候客户端就出错了,这时只能扣除1M流量)
一、.NET Core WebAPI封装文件下载(取消下载服务端不会知道)
如果只是需要满足API下载文件和断点续传,这个就够用了.....
/// <summary>
/// 文件下载
/// </summary>
/// <param name="FilePath">文件相对路径</param>
/// <returns></returns>
[HttpGet]
public IActionResult DownloadFile([FromQuery] string FilePath)
{
try
{
string filePath = GlobalConfig.Com.WebPath.UserResourcePath + FilePath; //文件物理路径
if (!System.IO.File.Exists(filePath))
{
LogHelper.Info($"客户端下载文件失败:文件不存在,文件绝对路径:{filePath}", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum());
return new EmptyResult();
}
string fileName = Path.GetFileName(filePath);
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, true); //异步读取文件
return File(fileStream, "application/octet-stream", fileName, true); //为true时,支持断点续传
}
catch (Exception ex)
{
LogHelper.Error("客户端下载文件出现异常:", ex, LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum());
return new EmptyResult();
}
}
二、.NET Core 写Response.Body下载(取消下载服务端知道)
用于监听客户端是否保存文件失败或者取消下载了,缺点是:需要自己写断点续传逻辑......
/// <summary>
/// 文件中转下载
/// </summary>
/// <param name="FilePath">文件相对路径</param>
/// <returns></returns>
[HttpGet]
public void DownloadFile1([FromQuery] string FilePath)
{
try
{
string filePath = GlobalConfig.Com.WebPath.UserResourcePath + FilePath; //文件物理路径
if (!System.IO.File.Exists(filePath))
{
LogHelper.Info($"客户端下载文件失败:文件不存在,文件绝对路径:{filePath}", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum());
return;
}
string fileName = Path.GetFileName(filePath);
long beginPosition = 0;
var fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, true);
//断点续传
string rangeStr = Request.Headers["range"]; //range 参数格式:byte=1024-,这个是http协议的格式,也可以自定义格式
if (!string.IsNullOrEmpty(rangeStr)) //断点续传
{
string byteStr = rangeStr.Split("=")?[1];
if (!string.IsNullOrEmpty(byteStr))
{
var byteArr = byteStr.Split("-");
if (byteArr != null && byteArr.Length > 1)
{
beginPosition = Convert.ToInt64(byteArr[0]);
}
}
}
HttpContext.Response.ContentType = "application/octet-stream";
HttpContext.Response.Headers.Append("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName));
HttpContext.Response.Headers.Append("Charset", "utf-8");
HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
int bufferSize = 1024; //每次读取1MB到服务器内存
using (HttpContext.Response.Body)
{
long contentLength = fs.Length;
HttpContext.Response.ContentLength = contentLength;
byte[] buffer;
long hasRead = 0;
while (hasRead < contentLength)
{
if (HttpContext.RequestAborted.IsCancellationRequested)
{
//取消下载会进来,这里可以做一些操作。。。。。
break;
}
fs.Seek(hasRead, SeekOrigin.Begin);
buffer = new byte[bufferSize];
//从下载文件中读取bufferSize(1024字节)大小的内容到服务器内存中
int currentRead = fs.Read(buffer, 0, bufferSize);
HttpContext.Response.Body.WriteAsync(buffer, 0, currentRead);
HttpContext.Response.Body.Flush();
hasRead += currentRead;
}
if (hasRead == contentLength) //下载完成
{
//下载完成之后要做的事。。。。
return;
}
}
}
catch (Exception ex)
{
LogHelper.Error("客户端下载文件出现异常:", ex, LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum());
return;
}
}