领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

简单易懂的大文件上传教程:Vue3与.NET 6完美结合

nixiaole 2024-11-27 18:38:54 知识剖析 28 ℃

在现代Web开发中,前后端分离是一种常见的架构模式。它将前端和后端的开发分离,使得两者可以独立进行开发、测试和部署。Vue3和.NET 6是目前流行的前端和后端开发框架,它们提供了丰富的功能和工具,使得前后端分离开发变得更加便捷和高效。

本文将介绍如何在Vue3和.NET 6中实现大文件上传功能。前端部分使用Vue3来封装文件分片上传函数,后端部分使用.NET 6来处理文件分片并保存文件。

首先,让我们看一下前端的上传函数代码:

app.config.globalProperties.uploadFile = async function uploadFile(file) {
  const CHUNK_SIZE = 2 * 1024 * 1024;
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  const fileReader = new FileReader();
  let currentChunk = 0;
  let res = "";
  
  // 显示上传进度
  const toast = Toast.loading({
    duration: 0,
    forbidClick: true,
    message: '0%',
  });
  
  function readAndUploadChunk() {
    return new Promise((resolve, reject) => {
      const start = currentChunk * CHUNK_SIZE;
      const end = Math.min(start + CHUNK_SIZE, file.size);
      const chunk = file.slice(start, end);
      fileReader.readAsArrayBuffer(chunk);
      
      fileReader.onload = async (event) => {
        try {
          const chunkData = event.target.result;
          let res1 = await myAxios.post("/BaoZi/BigFileUpload", chunkData, {
            headers: {
              "Content-Type": "application/octet-stream",
              "File-Name": encodeURIComponent(file.name),
              "Total-Chunks": totalChunks,
              "Current-Chunk": currentChunk,
            },
            transformRequest: null,
          });
          
          if (res1.data !== "") {
            res = res1.data;
          }
          
          currentChunk++;
          
          if (currentChunk < totalChunks) {
            toast.message = `${(start * 100 / file.size).toFixed(2)}%`;
            resolve(readAndUploadChunk());
          } else {
            toast.message = "上传成功";
            toast.clear();
            resolve(res);
          }
        } catch (error) {
          reject({ status: "fail", message: error.message });
        }
      };
    });
  }
  
  return readAndUploadChunk();
}

上述代码中,我们使用FileReader读取文件分片,并通过axios发送分片请求到后端的/BaoZi/BigFileUpload接口。每上传完一个分片,会递归调用readAndUploadChunk函数继续上传下一个分片,直到全部分片上传完成。

接下来,让我们看一下后端的.NET 6代码:

[Route("/BaoZi/BigFileUpload")]
public async Task<IActionResult> UploadFile()
{
    var encodedFileName = Request.Headers["File-Name"];
    var fileName = HttpUtility.UrlDecode(encodedFileName);
    var totalChunks = Convert.ToInt32(Request.Headers["Total-Chunks"]);
    var currentChunk= Convert.ToInt32(Request.Headers["Current-Chunk"]);
    var extension = Path.GetExtension(fileName).ToLower();

    if (!AllowedExtensions.Contains(extension))
    {
        return BadRequest("不允许上传该文件类型");
    }

    var tempPath = Path.Combine(_hostingEnvironment.ContentRootPath, TempDirectory);
    var targetPath = Path.Combine(_hostingEnvironment.ContentRootPath, TargetDirectory, DateTime.Now.ToString("yyyyMMdd"));
    var tempFilePath = Path.Combine(tempPath, fileName);

    if (!Directory.Exists(tempPath))
    {
        Directory.CreateDirectory(tempPath);
    }

    using (var fileStream = new FileStream(tempFilePath, FileMode.Append))
    {
        await Request.Body.CopyToAsync(fileStream);
    }

    if (currentChunk == totalChunks - 1)
    {
        if (!Directory.Exists(targetPath))
        {
            Directory.CreateDirectory(targetPath);
        }

        var targetFileName = Guid.NewGuid().ToString() + Path.GetExtension(fileName);
        var targetFilePath = Path.Combine(targetPath, targetFileName);
        CombineFileChunks(tempPath, fileName, targetFilePath);

        return Ok(targetFilePath.Substring(targetFilePath.IndexOf("\\static")).Replace("\\", "/"));
    }

    return Ok();
}

private void CombineFileChunks(string tempPath, string fileName, string targetFilePath)
{
    using (var targetStream = new FileStream(targetFilePath, FileMode.Append))
    {
        var files = Directory.GetFiles(tempPath, #34;{fileName}.*");
        Array.Sort(files);

        foreach (var filePath in files)
        {
            using (var sourceStream = new FileStream(filePath, FileMode.Open))
            {
                sourceStream.CopyTo(targetStream);
            }

            System.IO.File.Delete(filePath);
        }
    }
}

[Route("/BaoZi/GetFile")]
public async Task<IActionResult> GetFile(string FilePath)
{
    string contentType = "application/octet-stream";
    string fileName = Path.GetFileName(FilePath);
    FilePath = Path.Combine(_hostingEnvironment.ContentRootPath, FilePath);
    return PhysicalFile(FilePath, contentType, fileName);
}

在上述后端代码中,我们定义了UploadFile接口来处理文件分片上传的请求。首先,我们从请求头中获取文件名、分片信息等参数。然后,我们检查文件后缀名是否允许上传,如果不允许,则返回错误响应。

接下来,我们将分片保存到临时路径,并根据当前分片是否为最后一个分片,进行不同的处理。如果是最后一个分片,我们将临时路径中的所有分片文件合并为最终的文件,并将其保存到目标路径。最后,我们返回保存的文件路径。

另外,我们还定义了GetFile接口来获取文件。通过该接口可以根据文件路径获取对应的文件并返回给前端。

最后,前端可以通过调用getFileUrl函数来获取文件的URL,然后在页面中使用该URL来展示图片、视频或者提供文件下载链接。

补充说明:

在上述代码中,我们补充了一些关于后端代码和前端代码的说明。另外,我们还提到了一些相关的工具和组件。

  1. 后端代码已经打包到NuGet上,包名是BaoZi.Tools.BigFileUpload。这意味着你可以通过引入该包来使用封装好的大文件上传功能,方便快捷地在你的项目中集成。
  2. 前端使用了Vant组件库中的Toast组件来展示上传进度信息。你可以根据自己的需求选择其他组件库或自定义样式来替换Toast组件。
  3. 在前端代码中,我们定义了一个名为myAxios的axios实例,通过配置拦截器实现了请求和响应的处理。这个实例可以根据你的需求进行定制和配置。其中,请求拦截器用于在请求头中添加token信息,响应拦截器用于处理响应数据和对特定的状态码进行处理。
let myAxios =  axios.create(AxiosConfig);

//配置拦截器
myAxios.interceptors.request.use(function (config) {
    let token = window.localStorage.getItem("accessToken")
    if (token) {
        config.headers.accessToken = token;    //将token放到请求头发送给服务器
        //这里经常搭配token使用,将token值配置到tokenkey中,将tokenkey放在请求头中
        // config.headers['accessToken'] = Token;
    }
    return config;
}, function (error) {
    // Do something with request error
    return Promise.reject(error);
});


// 添加响应拦截器
myAxios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
 const {accessToken} =response.data;
 if(accessToken && accessToken==="NoLogin"){
    //设置当前的登录信息为NoLogin
    localStorage.setItem("accessToken","NoLogin");
    router.push('/Login/Index')
 }
    return response;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么


    return Promise.reject(error);
});

下面是使用方法:

上传调用

//这里获取的path是后端保存的路径
let path = await this.uploadfile(file);

获取文件

需要再定义一个函数getFileUrl,如果后端有登录校验,则需要加上token如:

app.config.globalProperties.getFileUrl = function getFileUrl(path){
    return '/'+AxiosConfig.baseURL+'/BaoZi/GetFile?FilePath='+path+'&token='+ encodeURIComponent(window.localStorage.getItem('accessToken')) 
  }

代码示例

图片
<img style="width: 100px;height: 100px;"
:src="getFileUrl('/static/upload/files/20230512/64c6b565-e401-4e31-b593-fcaccc9bde16.jpg')">

视频
<video controls>
      <source :src="getFileUrl('/static/upload/files/20230514/cab8b3e4-c44f-4337-9844-08e92d656efc.mp4')" type="video/mp4">
</video>

文件下载
<a :href="getFileUrl('/static/upload/files/20230512/32ccdf96-f8c7-442f-88e4-71c17a4cdd2e.rar')">
下载附件
</a>
最近发表
标签列表