在很多实际的开发场景中,我们都需要上传文件,比如图片,文档,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

Logo

前往低代码交流专区

更多推荐