• .NET Core WebAPI文件下载+断点续传+取消下载


    写在前面的话

    本人也是找了好多资料,发现要么就是很老的代码了,要么就是不全适用需求,所以整理记录一下,方便日后查阅

    适用需求:

    • 不想通过文件的URL直接下载服务器资源,例如:需要对下载增加限制(如:消耗平台流量、下载次数或频率限制)、需要统计下载次数、不想暴露文件下载地址等,这就需要给原有的文件URL包一层WebAPI接口,在API里面我们就可以实现这些限制了。

    个人需求

    1. 平台的部分资源下载需要耗费平台流量,平台流量是需要充值购买的
    2. 要支持断点续传,客户端可能下载一半出现中断
    3. 下载过程中,客户端保存失败或者取消下载,不应该扣除用户整个文件的流量大小(如:文件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;
            }
        }
  • 相关阅读:
    Vue 入门. 如何在HTML代码里面快速使用Vue
    CSS3 神器总结
    《JavaScript高级程序设计(第3版)》阅读总结记录第二章之在HTML中使用JavaScript
    《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介
    简单轮播的实现
    怎么作好一个领导者?什么是团队?怎么当好一个组员?
    谈BFC和haslayout
    JavaScript 数组
    ubuntu配置android开发环境和编译源码遇到的一些问题
    C语言文件操作函数大全
  • 原文地址:https://www.cnblogs.com/wangxiaorang/p/16378527.html
Copyright © 2020-2023  润新知