• .Net Web API 006 Controller上传大文件


    1、上传大文件的方式

    上传大文件就需要一段一段的上传,主要是先在客户端获取文件的大小,例如想一次传256kb,那就按照256kb分割。分割后又两种上传方式。

    (1)逐个数据段读取,然后调用API上传,把数据追加到文件上。上传完这一段,接着传下一段,直到上传完毕。

    (2)方式与(1)类似,只是可以好几段可以并行上传,上传后,会把每段按照索引命名,在服务器上保存成临时文件。判断都上传完毕后,再调用合并命令,把这些小的碎文件,合并成大的目标文件。

    我一般喜欢用第一种方式,主要是因为简单明了。缺点就是,因为必须等上一段完成后,再传下一段,所以无法并行上传,导致速度会受到影响。

    但我参与的项目基本上都是在局域网运行并且使用人数有限,所以这种传大文件的方式是可以满足系统要求。

    2、上传大文件API实现

    下面的实现用到了定义的AttachedFileEntity和HttpClientEx,这两个类的定义可参考 005 Controller上传小文件

    分三步,开始上传、循环上传二进制段、结束上传。

    开始上传API的目的是为了获取文件在服务器上的存储路径,代码如下。

    复制代码
    /// 
    /// 开始大上传文件
    /// 
    /// 
    [HttpPost]
    [Route("StartUploadBigFile")]
    public IActionResult StartUploadBigFile(AttachedFileEntity pEntity, string pFileEx)
    {
        string myServerFilePath = DateTime.Now.ToString("yyyy_MM_dd") + "\\" + Guid.NewGuid().ToString() + pFileEx;
        pEntity.ServerPath = myServerFilePath;
        return this.Ok(pEntity);
    }
    复制代码

    分段上传文件,接收到后,追加到现有的文件上。

    复制代码
    /// 
    /// 上传大文件
    /// 
    /// 
    /// 
    [HttpPost]
    [Route("UploadBigFile")]
    [DisableRequestSizeLimit]
    public IActionResult UploadBigFile(string pServerPath)
    {
        var myFile = Request.Form.Files[0];
    
        //创建目录
        string myFullServerPath = AppDomain.CurrentDomain.BaseDirectory + "\\Files\\" + pServerPath;
        string myFullFolder = Path.GetDirectoryName(myFullServerPath)!;
        if (Directory.Exists(myFullFolder) == false)
        {
            Directory.CreateDirectory(myFullFolder);
        }
    
        //写入文件
        Stream? myStream = null;
        FileStream? myFileStream = null;
        BinaryWriter? myBinaryWriter = null;
        try
        {
            myStream = myFile.OpenReadStream();
            byte[] myBytes = new byte[myStream.Length];
            myStream.Read(myBytes, 0, myBytes.Length);
            myStream.Seek(0, SeekOrigin.Begin);
    
            myFileStream = new FileStream(myFullServerPath, FileMode.Append);
            myBinaryWriter = new BinaryWriter(myFileStream);
            myBinaryWriter.Write(myBytes);
        }
        catch (Exception ex)
        {
            return this.BadRequest("上传大文件失败," + ex.Message);
        }
        finally
        {
            myBinaryWriter?.Close();
            myFileStream?.Close();
            myStream?.Close();
        }
    
        return this.Ok();
    }
    复制代码

     

    最后,结束上传,把文件的信息记录到数据库中。

    复制代码
    /// 
    /// 结束上传大文件
    /// 
    /// 
    [HttpPost]
    [Route("FinishUploadBigFile")]
    public IActionResult FinishUploadBigFile(AttachedFileEntity pEntity)
    {
        if (string.IsNullOrEmpty(pEntity.GUID))
        {
            pEntity.GUID = Guid.NewGuid().ToString();
        }
        //记录到数据库中
        //代码略
        return this.Ok(pEntity);
    }
    复制代码

    3、客户端调用

    客户端如果用的C#,代码中没有加入进度信息,进度信息可以传入一个ProcessInfo对象,传一段数据后,就更新下进度信息。

    调用的代码入下所示。

    复制代码
    private void Init_BigFileUpLoad_UIs()
    {
        this.UI_BigFile_Button.Click += (x, y) =>
        {
            var myOpenFileDialog = new OpenFileDialog
            {
                Filter = ".*|*.*"
            };
            var myIsOK = myOpenFileDialog.ShowDialog();
            if (myIsOK != true)
            {
                return;
            }
            this.UI_BigFile_TextBox.Text = myOpenFileDialog.FileName;
        };
    
        this.UI_BigFileUpLoad_Button.Click += async (x, y) =>
        {
            var myFilePath = this.UI_BigFile_TextBox.Text.Trim();
            if (myFilePath.Length == 0)
            {
                MessageBox.Show("请选择一个文件。");
                return;
            }
            if (File.Exists(myFilePath) == false)
            {
                MessageBox.Show("文件不存在,请重新选择。");
                return;
            }
    
            //定义AttachedFileEntity
            var myFileEntity = new AttachedFileEntity()
            {
                GUID = Guid.NewGuid().ToString(),
                Name = "用户头像",
                KeyWord = "UserProfilePhoto",
                Description = "",
                EntityGUID = "AAAA"
            };
    
            //打开上传的文件
            var myFileStream = new FileStream(myFilePath, FileMode.Open);
            myFileEntity.FileSize = (int)myFileStream.Length;
            var myFileName = Path.GetFileName(myFilePath);
    
            //每次上传256kb
            int myChunkSize = 1024 * 256;
            int myChunkCount = (int)Math.Ceiling(myFileStream.Length / (double)myChunkSize);
    
            //调用开始上传
            var myHttpClient = new HttpClient();
            var myHttpClientEx = new HttpClientEx(myHttpClient)
            {
                Url = "http://localhost:5000/api/AttachedFile/StartUploadBigFile",
                HttpContent = JsonContent.Create(myFileEntity)
            };
            myHttpClientEx.ParameterDictionary.Add("pFileEx", Path.GetExtension(myFileName));
            await myHttpClientEx.PostAsync();
            if (myHttpClientEx.IsSuccess == false)
            {
                myFileStream.Close();
                MessageBox.Show("上传文件失败," + myHttpClientEx.ResponseContenString);
                return;
            }
            myFileEntity = myHttpClientEx.GetResponseObject();
            if (myFileEntity == null)
            {
                myFileStream.Close();
                MessageBox.Show("上传文件失败,返回的AttachedFileEntity为null。");
                return;
            }
    
            //循环上传文件
            for (int i = 0; i < myChunkCount; i++)
            {
                //组织数据
                int myByteArraySize = myChunkSize;
                if (i == myChunkCount - 1)
                {
                    myByteArraySize = (int)(myFileStream.Length % myChunkSize);
                }
                byte[] myBytes = new byte[myByteArraySize];
                myFileStream.Position = myChunkSize * i;
                myFileStream.Read(myBytes, 0, myBytes.Length);
                var myMemoryStream = new MemoryStream(myBytes);
    
                //请求服务
                myHttpClientEx = new HttpClientEx(myHttpClient)
                {
                    Url = "http://localhost:5000/api/AttachedFile/UploadBigFile",
                    HttpContent = new MultipartFormDataContent
                        {
                            {new StreamContent(myMemoryStream),"pFile",myFileName}
                        }
                };
                myHttpClientEx.ParameterDictionary.Add("pServerPath", myFileEntity!.ServerPath);
                await myHttpClientEx.PostAsync();
    
                //解析结果
                if (myHttpClientEx.IsSuccess == false)
                {
                    myFileStream.Close();
                    MessageBox.Show("上传文件失败," + myHttpClientEx.ResponseContenString);
                    return;
                }
            }
    
            //结束上传
            myHttpClientEx = new HttpClientEx(myHttpClient)
            {
                Url = "http://localhost:5000/api/AttachedFile/FinishUploadBigFile",
                HttpContent = JsonContent.Create(myFileEntity)
            };
            await myHttpClientEx.PostAsync();
            if (myHttpClientEx.IsSuccess == false)
            {
                myFileStream.Close();
                MessageBox.Show("上传文件失败," + myHttpClientEx.ResponseContenString);
                return;
            }
            myFileStream.Close();
            myFileEntity = myHttpClientEx.GetResponseObject();
            var myEntityJosnString = JsonSerializer.Serialize(myFileEntity);
            MessageBox.Show(myEntityJosnString);
        };
    }
    复制代码

    如果客户端是js,代码如下。

    复制代码
    on(myButton, "change", function (e) {
        var myFileReader = new FileReader();
        var myFileName = "";
    
        myFileReader.onloadend = function () {
            var myFileResult = myFileReader.result;
            var myFileLength = myFileResult.byteLength;
    
            var myPerLength = 1024 * 256;
            var myCount = Math.ceil(myFileLength / myPerLength);
    
            var myFileEntity = new Object()
            {
                ServerPath: ""
            };
    
            //调用开始上传StartUploadBigFile,具体代码略。
    
            var myK = 0;
            Upload();
    
            function Upload() {
    
                var myByteArray = myFileResult.slice(myPerLength * myK, myPerLength * (myK + 1));
                var myBlob = new Blob([myByteArray]);
                var myFile = new File([myBlob], myFileName);
                var myFormData = new FormData();
                myFormData.append("file", myFile)
                request.post(myUrl + "?pServerFile=" + myFileEntity.ServerPath +, {
                    data: myFormData
                }).then(function (data) {
                    myFileEntity = json.parse(data);
                    myK++;
                    if (myK < myCount) {
                        Upload();
                    }
                    else {
                        alert("上传大文件结束。");
                        alert(json.stringify(myFileEntity));
                        //结束,post FinishUploadBigFile 
                    }
    
                }, function (err) {
                    alert(err);
                    return;
                });
            }
        }
    
        myFileName = this.files[0].name;
        myFileReader.readAsArrayBuffer(this.files[0]);
    });
    复制代码

    Js代码没有实际测试,只是一个思路。

  • 相关阅读:
    利用rpmbuild 打包可执行文件和链接库生成rpm 包
    【Try to Hack】vulnhub DC2
    3.条件概率与独立性
    【疑难攻关】——文件包含漏洞
    关于根据动态数量的对象的某属性的数组数量呈乘机式增长的数据处理
    把setting.xml放在conf和.m2目录的区别
    AI和人类,谁的钓鱼邮件更胜一筹?
    通过字符设备驱动分步注册方式编写LED驱动,完成设备文件和设备的绑定
    大数据之LibrA数据库常见术语(一)
    基于Python的篮球场共享储物箱管理系统
  • 原文地址:https://www.cnblogs.com/mytudousi/p/17611540.html