一、背景

项目需要从前端上传文件到第三方文件服务器。后台提取到文件流后需要按照第三方接口post方式将文件上传上去。遇到流在转换传递过程中的坑。 超时问题, 文件上传成功但是打不开问题。

二、项目相关代码

1.后台程序接受前端传递的文件

        /// <summary>
        /// 上传文档
        /// </summary>
        /// <returns>文档Id集合</returns>
        [HttpPost("upload-doc")]
        [SwaggerFileUpload]
        public async Task<DocumentUploadReturnDto> Upload()
        {
            if (Request.Form.Files.Count < 1)
            {
                throw new StatusNotFoundException("Can't read any files.");
            }

            await Task.Run(() => { });
            string rawName = Request.Form.Files[0].FileName;
            var fileDto = _fileUploadService.UploadFile(rawName, Request.Form.Files[0].OpenReadStream());
            return _documentsService.Add(fileDto, CurrentUser.Id, CurrentUser.Name);
        }

2.UploadFile代码如下

        /// <summary>
        /// 文件上传
        /// </summary>
        /// <param name="filename">文件名</param>
        /// <param name="fileStream">文件流</param>
        /// <returns>FileInfoDto</returns>
        public FileInfoDto UploadFile(string filename, Stream fileStream)
        {
            var result = new FileInfoDto();
            try
            {
                string url = _baseUrl + $"Files/Upload?token={_token}";
                var respRaw = HttpUtilHelper.PostFile(url, filename, fileStream.StreamToByte());
                resp = respRaw.PackJsonObject<FileInfoDto>();
                if (resp?.Code == 500)
                {
                    throw new StatusNotFoundException($"附件上传失败,{resp?.Msg}");
                }
            }
            catch (Exception ex)
            {
                ex.ToString();
                throw new StatusNotFoundException("附件上传失败.");
            }

            return result;
        }

3.流转字节

        /// <summary>
        /// 流转byte
        /// </summary>
        /// <param name="stream">流</param>
        /// <returns>byte</returns>
        public static byte[] StreamToByte(this Stream stream)
        {
            List<byte> bytes = new List<byte>();
            if (stream is MemoryStream)
            {
                return (stream as MemoryStream).ToArray();
            }

            int temp = stream.ReadByte();
            while (temp != -1)
            {
                bytes.Add((byte)temp);
                temp = stream.ReadByte();
            }

            return bytes.ToArray();
        }

4.上传

        /// <summary>
        /// 文件上传到别的地方(字节上传文件)
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="fileName">文件名</param>
        /// <param name="filebytes">文件字节</param>
        /// <returns>http响应字符串</returns>
        public static string PostFile(string url, string fileName, byte[] filebytes)
        {
            ...省略...
                // 拿到http请求流,写入请求
                var requestStream = webRequest.GetRequestStream();
                requestStream.Write(startBoundaryBytes, 0, startBoundaryBytes.Length);
                var p1 = Encoding.UTF8.GetBytes(boundaryBlock.ToString());
                requestStream.Write(p1, 0, p1.Length);
                requestStream.Write(filebytes, 0, filebytes.Length);
                requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
                requestStream.Close();
            ...省略...
        }

三、问题原因

Request.Form.Files[0].OpenReadStream() 方法得到的stream直接传入StreamToByte中转字节会导致非常非常的耗时。

四、解决思路

4.1 思路一: 直接将OpenReadStream传到post接口中,不在中间进行流转字节动作

HttpUtilHelper.PostFile重载一个方法直接传流进去,读取流上传,而不是读字节

        /// <summary>
        /// 用流上传文件
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="fileName">文件名</param>
        /// <param name="fileStream">文件流</param>
        /// <returns>http响应字符串</returns>
        public static string PostFile(string url, string fileName, Stream fileStream)
        {
            ...省略...
                byte[] fileByte = new byte[fileStream.Length];
                fileStream.Read(fileByte, 0, fileByte.Length);

                // 拿到http请求流,写入请求
                var requestStream = webRequest.GetRequestStream();
                requestStream.Write(startBoundaryBytes, 0, startBoundaryBytes.Length);
                var p1 = Encoding.UTF8.GetBytes(boundaryBlock.ToString());
                requestStream.Write(p1, 0, p1.Length);
                requestStream.Write(fileByte, 0, fileByte.Length);   // 改动点
                requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
                requestStream.Close();
            ...省略...
        }

修改postFile调用,其他不变

结论:问题得到解决

4.2 思路二:怎么能让流转字节变快

问题是出在OpenReadStream的流上,那么能改成其他流吗?研究发现Request.Form.Files[0]有其他方法取到流,修改如下

            MemoryStream stream = new MemoryStream();
            Request.Form.Files[0].CopyTo(stream);
            var fileDto = _fileUploadService.UploadFile(rawName, stream);

其他不变

但是

如此导致后面第三方接到的流有问题,以至于上传上去的文件打不开

继续排查

第一时间想到是流的问题,调试发现 流的位置不对Position在结尾。修改如下(使用流之前添加即可)

                if (fileStream.Position != 0)
                {
                    fileStream.Position = 0;
                }

结论:问题得到解决

4.3 如何做到统一共用

上面的方法只是解决了这一处接口调用产生的问题, 如果还有其他接口也要上传呢?

修改流转字节方法(原理:在流转字节中将OpenReadStream流转成内存流,然后用内存流转字节方法)

        /// <summary>
        /// 流转byte
        /// </summary>
        /// <param name="stream">流</param>
        /// <returns>byte</returns>
        public static byte[] StreamToByte(this Stream stream)
        {
            List<byte> bytes = new List<byte>();
            if (stream is MemoryStream)
            {
                return (stream as MemoryStream).ToArray();
            }
            else
            {
                try
                {
                    MemoryStream mStream = new MemoryStream();
                    stream.CopyTo(mStream);
                    return mStream.ToArray();
                }
                catch (Exception ex)
                {
                    ex.ToString();
                    int temp = stream.ReadByte();
                    while (temp != -1)
                    {
                        bytes.Add((byte)temp);
                        temp = stream.ReadByte();
                    }

                    return bytes.ToArray();
                }
            }
        }

结论:问题得到解决