SpringBoot+Vue前后端文件传输问题总结

news2024/11/16 9:35:02

在这里插入图片描述

SpringBoot+Vue前后端文件传输问题总结

    • 一、文件上传功能
      • 前端:文件上传
        • 1.File
        • 2.FormData(常用)
        • 3.Blob
        • 4.ArrayBuffer
        • 5.Base64
      • 后端:文件接收
        • 1.MultipartFile
    • 二、文件下载功能
      • 后端:文件传输
      • 前端:文件接收
        • 1.设置响应类型为'blob'
        • 2.文件解析及下载
    • 三、开发中遇到的问题
      • 前端代码:
      • 后端代码:

解决前后端文件传输的问题有以下几种解决方案:

1.文件上传时,前端以二进制流文件发送到后端,后端通过多种方式(MultipartFile/byte[]/File)进行接受,处理后进行存储;文件下载时,后端通常返回前端二进制流(byte[])的形式,并将文件附带信息(fileName、contentType)放在response header中一并传输到前端供其解析与下载。

2.微服务项目中,通常搭建网盘模块提供文件上传下载功能,供文件传输业务使用。

一、文件上传功能

前端:文件上传

前端文件上传主要有以下五种方式

  • File
  • FormData
  • Blob
  • ArrayBuffer
  • Base64
1.File

文件上传 enctype 要用 multipart/form-data,而不是 application/x-www-form-urlencoded

<form action="http://localhost:8080/files" enctype="multipart/form-data" method="POST">
  <input name="file" type="file" id="file">
  <input type="submit" value="提交">
</form>
2.FormData(常用)

采用这种方式进行文件上传,主要是掌握文件上传的请求头和请求内容。

<template>
  <div>
      <el-form ref="form" :model="form" >
        <el-form-item v-show="!form.isURL" label="文件" prop="file">
          <el-upload
            ref="upload"
            :limit="1"
            accept="*"
            action="#"
            class="el-input"
            drag>
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">拖拽文件或者单击以选择要上传的文件</div>
          </el-upload>
        </el-form-item>
        <el-form-item label="说明" prop="description">
          <el-input v-model="form.description" autosize type="textarea"></el-input>
        </el-form-item>
        <el-button type="primary" @click="uploadFile()">上 传</el-button>
      </el-form>
  </div>
</template>
<script>
import axios from 'axios'
import {downLoadFile} from "@/utils/downloadFile";
export default {
  data() {
    return {
      form: {
        description: '',
        file: {},
      }
    }
  },
  methods: {
    uploadFile() {
      let formData = new FormData();
      formData.append('file', this.form.file.raw);
      formData.append('description', this.form.description);
      axios.post('http://localhost:8080/files', formData,{ headers: { 'Content-Type': 'multipart/form-data' }}).then(res => {
        console.log(res.data);
      })
    }
  }
}
</script>

image-20230812141805107

3.Blob

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

1.直接使用 blob 上传

const json = { hello: "world" };
const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });

const form = new FormData();
form.append('file', blob, 'test.json');
axios.post('http://localhost:8080/files', form);

2.使用 File 对象,再进行一次包装

const json = { hello: "world" };
const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });

const file = new File([blob], 'test.json');
form.append('file', file);
axios.post('http://localhost:8080/files', form)
4.ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区、是最贴近文件流的方式。在浏览器中,ArrayBuffer每个字节以十进制的方式存在。

const bufferArrary = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,1,3,0,0,0,37,219,86,202,0,0,0,6,80,76,84,69,0,0,255,128,128,128,76,108,191,213,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,196,1,149,43,14,27,0,0,0,10,73,68,65,84,8,153,99,96,0,0,0,2,0,1,244,113,100,166,0,0,0,0,73,69,78,68,174,66,96,130];
const array = Uint8Array.from(bufferArrary);
const blob = new Blob([array], {type: 'image/png'});
const form = new FormData();
form.append('file', blob, 'test.png');
axios.post('http://localhost:8080/files', form)

这里需要注意的是 new Blob([typedArray.buffer], {type: 'xxx'}),第一个参数是由一个数组包裹。里面是 typedArray 类型的 buffer。

5.Base64
const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEUAAP+AgIBMbL/VAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==';
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const array = Uint8Array.from(byteNumbers);
const blob = new Blob([array], {type: 'image/png'});
const form = new FormData();
form.append('file', blob, 'test.png');
axios.post('http://localhost:8080/files', form);

后端:文件接收

1.MultipartFile

MultipartFile是SpringMVC提供简化上传操作的工具类。在不使用框架之前,都是使用原生的HttpServletRequest来接收上传的数据,文件是以二进制流传递到后端的,然后需要我们自己转换为File类,MultipartFile主要是用表单的形式进行文件上传,在接收到文件时,可以获取文件的相关属性,比如文件名、文件大小、文件类型等等。

  • 需要注意,@RequestParam MultipartFile file,因此前端传来的需要有形参file,即上文formData.append('file', this.form.file.raw);
@PostMapping("/upLoadFile")
public void upLoadFile(@RequestBody MultipartFile file) {
    // 获取文件的完整名称,文件名+后缀名
    System.out.println(file.getOriginalFilename());
    // 文件传参的参数名称
    System.out.println(file.getName());
    // 文件大小,单位:字节
    System.out.println(file.getSize());
    // 获取文件类型,并非文件后缀名
    System.out.println(file.getContentType());
    try {
        // MultipartFile 转 File
        File resultFile = FileUtil.multipartFile2File(file);
        System.out.println(resultFile.getName());

    } catch (IOException e) {
        log.info("文件转换异常");
    }

}

FileUtil工具类

public class FileUtil {
    /**
     * file转byte
     */
    public static byte[] file2byte(File file){
        byte[] buffer = null;
        try{
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int n;
            while ((n = fis.read(b)) != -1)
            {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }
        catch (IOException e){
            e.printStackTrace();
        }
        return buffer;
    }

    /**
     * byte 转file
     */
    public static File byte2file(byte[] buf, String filePath, String fileName){
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try{
            File dir = new File(filePath);
            if (!dir.exists() && dir.isDirectory()){
                dir.mkdirs();
            }
            file = new File(filePath + File.separator + fileName);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(buf);
        }catch (Exception e){
            e.printStackTrace();
        }
        finally{
            if (bos != null){
                try{
                    bos.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
            if (fos != null){
                try{
                    fos.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
        return file;
    }
/**
* multipartFile转File
**/
public static File multipartFile2file(MultipartFile multipartFile){
        File file = null;
        if (multipartFile != null){
            try {
                file=File.createTempFile("tmp", null);
                multipartFile.transferTo(file);
                System.gc();
                file.deleteOnExit();
            }catch (Exception e){
                e.printStackTrace();
                log.warn("multipartFile转File发生异常:"+e);
            }
        }
        return file;
    }
}

二、文件下载功能

后端:文件传输

@GetMapping("/download")
public ResponseEntity<Resource> download( @RequestParam("fileId") String fileId) {
    if (StringUtils.isNotBlank(fileId)) {
        File file = new File("test.jpg");
        String fileName = file.getName();
        String contentType = file.getContentType();
        FileSystemResource fileSource = new FileSystemResource(file)
        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
            .header("filename", fileName)
            // 配置使前端可以获取的header中的
            .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "filename")
            .contentLength(resource.contentLength())
            .contentType(parseMediaType(contentType))
            .body(fileSource);
    }
    return (ResponseEntity<Resource>) ResponseEntity.badRequest();
}

image-20230812145320428

如果后端返回的是如下图一样的,那么就是传输的文件流

前端:文件接收

1.设置响应类型为’blob’

Blob:按文本或二进制的格式进行读取,在axios请求中设置response: 'blob'

假设这是一个返回文件流的请求:

axios.get('http://localhost:8080/download', { 
    response: 'blob'
})

如果是post请求还需要在请求头里携带Content-Type: ‘multipart/form-data’

axios.post('http://localhost:8080/download', { 
    response: 'blob',
    headers: {
        'Content-Type': 'multipart/form-data'
    }
})
2.文件解析及下载
axios.get(`http://localhost:8080/download?fileId=${fileId}`, { responseType: 'blob', observe: 'response' })
    .then(response => {
    const headers = response.headers;
    console.log(response.headers)
    const filename = headers['x-filename'];
    const contentType = headers['content-type'];
    const linkElement = document.createElement('a');
    try {
        const blob = new Blob([response.data], { type: contentType });
        const url = URL.createObjectURL(blob);
        linkElement.setAttribute('href', url);
        linkElement.setAttribute('download', filename);
        const clickEvent = new MouseEvent('click',{
            view: window,
            bubbles: true,
            cancelable: false
        });
        linkElement.dispatchEvent(clickEvent);
        return null;
    } catch (e) {
        throw e;
    }
})
    .catch(error => {
    console.error('下载文件时出错:', error);
});

三、开发中遇到的问题

1.后端无法使用统一的结果返回类(统一结果返回类会被序列化为JSON),故需要使用可以携带二进制流文件(byte[])的返回类,即ResponseEntity<Resource>,通常需要将文件配置(文件名、文件类型)保存在http response headers头中,将二进制流文件放在ResponseEntity的body中。

2.前端发送请求时,要注意http请求的config配置(headers与responseType与observe),另外可以将文件解析下载的操作封装成一个js工具。

3.前后端交互时,axios请求放在response header里的文件名时,会出问题,跨前后端分离发送http请求时,默认reponse header中只能取到以下5个默认值,要想取得其他的字段需要在后端设置Access-Control-Expose-Headers 配置前端想要获取的header。

  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

前端代码:

downloadPackage(row) {
    this.$api.downloadOtaPackage(row.id.id)
        .then(res => {
        downLoadFile(res)
    })
        .catch(error => {
        console.error('下载文件时出错:', error);
    });
},

downLoadFile.js

export function downLoadFile (res) {
  // 获取响应头中的filename contentType
  const headers = res.headers;
  const filename = headers['x-filename'];
  const contentType = headers['content-type'];
  // 创建一个a链接标签
  const linkElement = document.createElement('a');
  try {
    // 将返回的文件流转换成一个blob文件对象
    const blob = new Blob([res.data], { type: contentType });
    // 生成一个文件对象的url地址
    const url = URL.createObjectURL(blob);
    // 将文件对象的url地址赋值给a标签的href属性
    linkElement.setAttribute('href', url);
    // 为a标签添加download属性并指定文件的名称
    linkElement.setAttribute('download', filename);
    // 调用a标签的点击函数
    const clickEvent = new MouseEvent('click',
      {
        view: window,
        bubbles: true,
        cancelable: false
      }
    );
    linkElement.dispatchEvent(clickEvent);
    // 释放URL对象
    URL.revokeObjectURL(url);
    // 将页面的a标签删除
    document.body.removeChild(linkElement);
  } catch (e) {
    throw e;
  }
}

后端代码:

@GetMapping("/download")
public ResponseEntity<Resource> downloadOtaPackage(
    @ApiParam(value = "OTA包Id", required = true) @RequestParam("otaPackageId") String otaPackageId
) {
    if (StringUtils.isNotBlank(otaPackageId)) {
        ResponseEntity<Resource> responseEntity = iOtaClient.downloadOtaPackage(otaPackageId);
        ByteArrayResource resource = (ByteArrayResource) responseEntity.getBody();
        String fileName = responseEntity.getHeaders().get("x-filename").get(0);
        String contentType = responseEntity.getHeaders().getContentType().toString();
        // return FileResult.success(resource, fileName, contentType);
        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
            .header("x-filename", fileName)
            .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "x-filename")
            .contentLength(resource.contentLength())
            .contentType(parseMediaType(contentType))
            .body(resource);
    }
    return (ResponseEntity<Resource>) ResponseEntity.badRequest();
}

参考文章:

  • https://zhuanlan.zhihu.com/p/120834588
  • https://www.cnblogs.com/liuxianbin/p/13035809.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1083578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动捕设备在动画影视制作中的应用

随着科技的发展&#xff0c;动画影视作品中的CG角色越来越逼真、生动形象&#xff0c;而这些CG角色大多背后是通过真人穿戴动捕设备&#xff0c;从而捕捉真人演员的肢体动作而创建的&#xff0c;如《遮天》作为国内首部全流程虚幻引擎5动画&#xff0c;结合动捕设备实现真人化动…

三相空气开关

一、三相空开的作用 三相空气开关对任意一相出现过载或短路&#xff0c;均起到保护作用。 二、三相空气开关原理图&#xff1a; 1、短路时&#xff0c;电磁脱钩器工作 2、过载时&#xff0c;发热元件引起双金属片弯曲&#xff0c;使脱钩器工作 3、测试按钮闭合时&#xff0c…

<图像处理> Fast角点检测

Fast角点检测 基本原理是使用圆周长为N个像素的圆来判定其圆心像素P是否为角点&#xff0c;如下图所示为圆周长为16个像素的圆&#xff08;半径为3&#xff09;&#xff1b;OpenCV还提供圆周长为12和8个像素的圆来检测角点。 相对中心像素的位置信息 //圆周长为16 static c…

uni-app : 生成三位随机数、自定义全局变量、自定义全局函数、传参、多参数返回值

核心代码 function generateRandomNumber() {const min 100;const max 999;// 生成 min 到 max 之间的随机整数// Math.random() 函数返回一个大于等于 0 且小于 1 的随机浮点数。通过将其乘以 (max - min 1)&#xff0c;我们得到一个大于等于 0 且小于等于 (max - min 1…

【ARM CoreLink 系列 7 -- TZC-400控制器简介】

文章目录 概述TZC-400 使用示例TZC-400 interfacesFPID & NSAIDRegionregion 检查规则 FeaturesRegister summaryTZC-400和TZPC和TZASC区别 转自&#xff1a;https://www.cnblogs.com/lvzh/p/16582717.html 概述 TZC-400对发送到内存或外设的事务执行安全检查。TZC-400使…

华为认证 | HCIP-Datacom,这门认证正式发布新版本!

华为认证数通高级工程师HCIP-Datacom-Campus Network Planning and Deployment V1.5&#xff08;中文版&#xff09;自2023年9月28日起&#xff0c;正式在中国区发布。 01 发布概述 基于“平台生态”战略&#xff0c;围绕“云-管-端”协同的新ICT技术架构&#xff0c;华为公司…

Visual Studio 2022 cmake编译 PP-OCRv4

1 环境准备 下载PaddleOCR PaddleOCR C 部署代码位于 PaddleOCR\deploy\cpp_infer目录下 paddle_inference paddle_inference opencv 这里使用已经安装好的opencv4.5.5下载dirent-master.zip 下载dirent-master.zip, 解压并复制dirent.h文件到PaddleOCR\deploy\cpp_infer目录下…

Spark(林子雨慕课课程)

文章目录 10. Spark10.1 Spark简介10.1.1 Spark简介10.1.2 Spark和Hadoop的对比 10.2 Spark生态系统10.3 Spark运行架构10.3.1 基本概念和架构设计 10.3.2 Spark运行基本流程10.3.3 RDD概念10.3.4 RDD特性10.3.5 RDD的依赖关系和运行过程 10.4 Spark SQL10.5 Spark的部署和应用…

video_topic

使用qt5,ffmpeg6.0,opencv&#xff0c;os2来实现。qt并非必要&#xff0c;只是用惯了。 步骤是&#xff1a; 1.读取rtsp码流&#xff0c;转换成mat图像 2.发送ros::mat图像 项目结构如下&#xff1a; videoplayer.h #ifndef VIDEOPLAYER_H #define VIDEOPLAYER_H#include …

【node】nodemailer配置163、qq等邮件服务指南

上一章 【node】发送邮件及附件简要使用说明 邮箱配置 参数配置参考如下&#xff1a; let transporter nodemailer.createTransport({host: smtp.qq.com,port: 465,secure: true,auth: {user: **********,pass: your-password} });邮箱服务提供商的要求&#xff0c;配置SM…

应用商店优化的好处有哪些?

应用程序优化优势包括应用在商店的可见性和曝光度&#xff0c;高质量和被相关用户的更好发现&#xff0c;增加的应用下载量&#xff0c;降低用户获取成本和持续增长&#xff0c;增加应用收入和转化率以及全球受众范围。 1、提高知名度并在应用商店中脱颖而出。 如果用户找不到…

麒麟操作系统提示“默认密钥环已上锁”的解决办法

在国产麒麟操作系统上,有的时候不知道为啥,打开vscode或者其他应用软件时,总是提示“密钥环已上锁”,该怎么处理呢? 需要点击“开始”,在搜索框中输入“password” 点击打开“密码和密钥”,看到如下图。 然后点击左上角的箭头,回退,打开如下图:

HTTP协议是什么

HTTP (全称为 “超文本传输协议”) 是一种应用非常广泛的 应用层协议&#xff0c;是一种网络通信协议。 超文本&#xff1a;所谓 “超文本” 的含义, 就是传输的内容不仅仅是文本(比如 html, css 这个就是文本), 还可以是一些其他的资源, 比如图片, 视频, 音频等二进制的数据。…

软件工程第六周

软件体系结构概述 体系结构&#xff1a;一种思想&#xff0c;而框架就是思想的实现&#xff0c;设计模式就是根据某一特殊问题实现的框架。 体系结构&#xff1a;体系结构是软件系统的高级结构。它定义了系统的主要组成部分&#xff0c;以及这些部分之间的关系和交互方式。 框…

压缩包格式可以转换吗?如何转换?

不知道大家会不会遇到需要转换压缩包格式的问题&#xff1f;如果需要转换压缩包格式&#xff0c;除了将文件解压出来之后&#xff0c;重新压缩以外&#xff0c;还有其他方法&#xff0c;今天将方法分享个大家&#xff1a; 工具&#xff1a;WinRAR 打开WinRAR&#xff0c;选中…

《Unity Shader 入门精要》笔记07

透明效果 为什么渲染顺序很重要Unity Shader的渲染顺序透明度测试透明度混合开启深度写入的半透明效果ShaderLab 的混合命令混合等式和参数混合操作常见的混合类型 双面渲染的透明效果透明度测试的双面渲染透明度混合的双面渲染 Unity中通常使用两种方法来实现透明效果&#xf…

华为云云耀云服务器L实例评测|使用redis事务和lua脚本

文章目录 云服务器的类型云服务优点redis一&#xff0c;关系型数据库&#xff08;sqlserver&#xff0c;mysql&#xff0c;oracle&#xff09;的事务隔离机制说明&#xff1a;redis事务机制 lualua脚本好处&#xff1a;一&#xff0c;怎么在redis中使用lua脚本二&#xff0c;脚…

vue3前端开发系列 - 项目框架搭建篇

文章目录 1. 项目初始化1.1 项目目录结构1.2 相关组件列表1.3 创建项目 2. 重置样式表3.设置路径别名4. 设置环境变量5.状态存储(Pinia)5.1 安装插件5.2 配置5.3 用户信息案例5.3.1 状态存储设置5.3.2 使用用户信息 6. 路由设置(Router)6.1 安装路由插件6.2 配置路由 7. 安装el…

光耦合器继电器与传统继电器:哪种最适合您的项目?

在电子和电气工程领域&#xff0c;继电器的选择可以显着影响项目的性能和安全性。两种常见类型的继电器是光耦合器继电器和传统机电继电器。每个都有其优点和缺点&#xff0c;因此选择过程对于项目的成功结果至关重要。 光耦合器继电器&#xff1a;基础知识 光耦合器继电器&…