Net Core 3.1大文件分片上傳

在很多實際的開發場景中,我們都需要上傳文件,比如圖片,文檔,pdf等等。而除了圖片相對比較小以外(現在很多智能手機拍出的圖片,都是幾M的大小),其它文檔相對來說都比較大。

而我們在上傳文件的時候,一般服務器都是對上傳文件有大小限制的,就算我們把服務器的上傳文件大小改到1G,我們在上傳的時候,也難免出現接口超時,界面卡死等現象。

所以這裏我用net core 3.0 + vue-antd-pro的上傳插件,實現了一個大文件分片上傳的功能,比如100M的文件,我們把它分割成50個文件,每個文件2M(分割文件的大小,可以自定義)。這樣我們上傳的時候就不會經常出現超時,界面卡死的問題。
先看看效果圖:

jucheap 大文件分片上傳
jucheap 大文件分片上傳

net core 3.0大文件分片上傳的代碼如下:

using JuCheap.Core.Infrastructure.Extentions;
using JuCheap.Core.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace JuCheap.Core.WebApi.Controllers
{
    /// <summary>
    /// 文件上傳api
    /// </summary>
    [Route("api/file")]
    public class FileUploadController : BaseApiController
    {
        private readonly ILogger<FileUploadController> _logger; 
        private readonly IWebHostEnvironment _webHostEnvironment;
        /// <summary>
        /// 文件存放的根路徑
        /// </summary>
        private readonly string _baseUploadDir;

        /// <summary>
        /// ctor
        /// </summary>
        public FileUploadController(ILogger<FileUploadController> logger,
            IWebHostEnvironment hostEnvironment)
        {
            _logger = logger;
            _webHostEnvironment = hostEnvironment;
            _baseUploadDir = string.Concat(_webHostEnvironment.ContentRootPath, "\\uploads\\");
        }

        /// <summary>
        /// 分片上傳文件
        /// </summary>
        [HttpPost]
        [Route("upload")]
        public async Task<ActionResult<FileResponseDto>> Upload()
        {
            var fileName = Request.Form["name"];
            //當前分塊序號
            var index = Request.Form["chunk"].ToString().ToInt();
            //所有塊數
            var maxChunk = Request.Form["maxChunk"].ToString().ToInt();
            //前端傳來的GUID號
            var guid = Request.Form["guid"];
            //臨時保存分塊的目錄
            var dir = Path.Combine(_baseUploadDir, guid);
            dir.CreateDirectoryIfNotExists();
            //分塊文件名爲索引名,更嚴謹一些可以加上是否存在的判斷,防止多線程時併發衝突
            var filePath = Path.Combine(dir, index.ToString());
            //表單中取得分塊文件
            var file = Request.Form.Files["file"];
            //獲取文件擴展名
            //var extension = file.FileName.Substring(file.FileName.LastIndexOf(".") + 1, (file.FileName.Length - file.FileName.LastIndexOf(".") - 1));
            var filePathWithFileName = string.Concat(filePath, fileName);
            using (var stream = new FileStream(filePathWithFileName, FileMode.Create))
            {
                await file.CopyToAsync(stream);
            }

            //如果是最後一個分塊, 則合併文件
            var fileResponseDto = new FileResponseDto();
            if (index == maxChunk - 1)
            {
                await MergeFileAsync(fileName, guid);
                fileResponseDto.Completed = true;
            }
            return Ok(fileResponseDto);
        }

        /// <summary>
        /// 合併分片的文件
        /// </summary>
        /// <param name="fileName">文件名稱</param>
        /// <param name="guid">文件guid</param>
        private async Task MergeFileAsync(string fileName, string guid)
        {
            //臨時文件夾
            var dir = Path.Combine(_baseUploadDir, guid);
            //獲得下面的所有文件
            var files = Directory.GetFiles(dir);
            var yearMonth = DateTime.Now.ToString("yyyyMM");
            //最終的文件名(demo中保存的是它上傳時候的文件名,實際操作肯定不能這樣)
            var finalDir = Path.Combine(_baseUploadDir, yearMonth, guid);
            finalDir.CreateDirectoryIfNotExists();
            var finalPath = Path.Combine(finalDir, fileName);
            using (var fs = new FileStream(finalPath, FileMode.Create))
            {
                //排一下序,保證從0-N Write
                var fileParts = files.OrderBy(x => x.Length).ThenBy(x => x);
                foreach (var part in fileParts)
                {
                    var bytes = await System.IO.File.ReadAllBytesAsync(part);
                    await fs.WriteAsync(bytes, 0, bytes.Length);
                    bytes = null;
                    //刪除分塊
                    System.IO.File.Delete(part);
                }
                await fs.FlushAsync();
                fs.Close();
                //刪除臨時文件夾和分片文件
                Directory.Delete(dir);
            }
        }
    }
}

前臺頁面js代碼如下:

<template>
  <div>
    <a-card :loading="loading" title="說明">
      <p>1.支持自定義上傳方式</p>
      <p>2.支持大文件上傳, 把一個大文件,分割成若干個小文件,上傳到服務</p>
      <p>3.支持顯示上傳進度</p>
    </a-card>
    <p></p>
    <a-upload-dragger
      name="file"
      :multiple="true"
      action="/api/file/upload"
      @change="handleChange"
      :customRequest="customRequest"
    >
      <p class="ant-upload-drag-icon">
        <a-icon type="inbox" />
      </p>
      <p class="ant-upload-text">點擊或者拖拽文件到此區域,上傳文件</p>
      <p class="ant-upload-hint">
        支持單個文件或批量上傳文件。嚴禁上傳公司機密資料
      </p>
    </a-upload-dragger>
  </div>
</template>
<script>
import defaultSettings from '@/config/defaultSettings'
import ProcessHelper from '@/utils/helper/ProcessHelper'
const rootUrl = () => {
  if (ProcessHelper.isProduction() || ProcessHelper.isPreview()) {
    return defaultSettings.publishRootUrl
  } else {
    return defaultSettings.localRootUrl
  }
}
export default {
  data() {
    return {
      //已上傳的文件
      fileList:[],
      loading: true
    }
  },
  mounted(){
    this.loading = false
  },
  methods:{
    handleChange(info) {
      let me = this
      const status = info.file.status
      if (status !== 'uploading') {//removed
        console.log(info.file, info.fileList);
      }
      if (status === 'removed') {
        me.fileList.forEach(function(item, index){
          if(item.uid === info.file.uid){
            me.fileList.splice(index, 1)
          }
        })
      }
      if (status === 'done') {
        
      } else if (status === 'error') {
        
      }
    },
    customRequest(option){
      console.log(option)
      let url = rootUrl() + option.action
      let createGuid = function() {
        function S4() {
            return (((1+Math.random())*0x10000)|0).toString(16).substring(1)
        }
        return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())
      }
      this.fileUpload(url, option, 0, createGuid())
    },
    //上傳文件
    fileUpload(uploadUrl, option, chunk, guid) {
      let file = option.file
      //每次上傳文件的大小
      let chunkSize = 1024 * 1024
      //最大上傳大小  默認100M
      let maxSize = 1024 * 1024 * 100
      let maxChunk = Math.ceil((file.size / chunkSize))
      let formData = new FormData()
      //將文件進行分段
      let fileSize = file.size
      if (fileSize > maxSize) {
        this.$message.error("文件大小不能超過" + (maxSize / 1024 / 1024) + "M")
        return
      }

      //當前上傳進度
      let currentPercent = parseInt((chunk / maxChunk) * 100)
      option.onProgress({ percent: currentPercent })
      formData.append('file', file.slice(chunk * chunkSize, (chunk + 1) * chunkSize))
      formData.append('name', file.name)
      formData.append('chunk', chunk)
      formData.append('maxChunk', maxChunk)
      formData.append('guid', guid)
      this.$http.post(uploadUrl, formData).then(resJson => {
        this.confirmLoading = false
        if (resJson.success) {
          if(!resJson.data.completed){
            this.fileUpload(uploadUrl, option, ++chunk, guid)
          }else{
            this.$message.success("文件上傳成功")
            option.onProgress({ percent: 100 })
            option.onSuccess()
          }
        } else {
          this.$message.error(resJson.message)
        }
      })
    }
  }
}
</script>

效果查看地址:http://core.jucheap.com
賬號:jucheap 密碼:qwaszx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章