vue3+springboot文件下载与上传和静态资源(图片等)访问


上传文件(图片)有两种方法,一种是获取文件后转成base64格式上传,第二种是直接用elementplus组件上传。
base64会比原来的长度长33%。

1,文件上传

第一种:base64方式上传图片(这种方式也适用于上传文件):

前端:

<script setup>
import { Plus } from '@element-plus/icons-vue' // 导入Element Plus图标组件
import { ref } from 'vue'
import { upImageOnce } from '@/api/ImageApi.js' // 导入Vue的ref函数,用于创建响应式数据

const imageUrl = ref() // 创建响应式数据imageUrl,存储用户头像URL
const disabled = ref(false) // 创建响应式数据disabled,根据是否有头像来禁用上传按钮

// 文件变更处理函数
const onChange = (imgFile) => {
  console.log(imgFile)
  // 检查选择的文件是否为图片
  const format = /^image/
  if (format.test(imgFile.raw.type)) {
    // 文件为图片则将图片转为 DataURL base64,并将图片显示在页面上
    convertBase64(imgFile)
    disabled.value = false // 允许上传按钮
  } else {
    alert('请上传图片')
  }
}
// 图片转成 DataURL base64格式 处理函数
const convertBase64 = (file) => {
  const reader = new FileReader()
  reader.readAsDataURL(file.raw) // 将文件转换为DataURL
  reader.onload = () => {
    imageUrl.value = reader.result // 读取完成后,更新imageUrl
  }
}

// 绑定到上传按钮的函数
const upload = async () => {
  // 调api 上传图片,将base64值传入
  const res = await upImageOnce({ file: imageUrl.value })
  if (res) {
    disabled.value = true // 禁用上传按钮
  }
}
</script>
<template>
  <!-- 页面容器,标题为“更换头像” -->
  <el-upload
    class="avatar-uploader"
    :show-file-list="false"
    :auto-upload="false"
    :on-change="onChange"
  >
    <!-- 如果imageUrl存在,显示图片,否则显示上传图标 -->
    <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
    <el-icon v-else class="avatar-uploader-icon">
      <Plus />
    </el-icon>
  </el-upload>

  <div style="margin-top: 30px">
    <el-button
      type="success"
      size="large"
      :disabled="disabled"
      @click="upload"
    >上传图片
    </el-button>
  </div>
</template>

<style scoped>
.avatar-uploader .avatar {
  width: 600px;
  height: auto;
  display: block;
  border: black 1px solid;
}
.el-icon.avatar-uploader-icon {
  font-size: 36px;
  color: #8c939d;
  width: 300px;
  height: 300px;
  text-align: center;
  border: black 1px solid;
}
</style>

后端:

 @PostMapping("/fileTwo")
    public Result uploadFileTwo(@RequestBody Map<String, String> map) {

        try {
            // 1. 获取 Base64 编码的字符串
            String data = map.get("file");
            // 获取文件后缀
            String postfix = "." + data.substring(data.indexOf("/") + 1, data.indexOf(";"));
            // 生成新文件名称
            String newImgName = UUID.randomUUID() + postfix;
            // 2. 移除前缀,Base64 字符串前面有 data:***/**;base64,
            String base64File = data.split(",")[1];

            // 3. 解码 Base64 字符串为字节数组
            byte[] fileBytes = Base64.getDecoder().decode(base64File);

            // 4. 将字节数组保存为文件
            Path path = Paths.get("D:/fileTest/");  // 获取路径
            Path resolve = path.resolve(newImgName); // 拼接目标文件路径
            try (OutputStream outputStream = Files.newOutputStream(resolve.toFile().toPath())) {
                outputStream.write(fileBytes);
            }
            return Result.success("上传成功");
        } catch (Exception e) {
            log.error("文件base64格式,上传错误", e);
            return Result.err();
        }
    }

注意:这种方式会让springboot配置文件限制的上传文件大小失效,因此需要写代码进行文件大小的校验,不建议用此方法上传较大文件

第二种(推荐):普通elementplus上传文件/图片

前端(第一种普通方式):

通过 action=“http://localhost:8080/upload/file” 的方式上传,如果后端没设置允许跨域,则前端要配置跨域代理,然后改成:action=“http://localhost:5173/api/upload/file” 这种代理路径,具体改法参照前端vue配置代理

<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'

const imageUrl = ref('')

const onChange = (uploadFile) => {
  // 判断是否为图片
  const format = /^imag/
  if (!format.test(uploadFile.raw.type)) {
    ElMessage.error('Avatar picture must be JPG format!')
    return false
  }
  // 限制图片大小
  // else if (rawFile.size / 1024 / 1024 > 2) {
  //   ElMessage.error('Avatar picture size can not exceed 2MB!')
  //   return false
  // }
  // 创建一个指向本地文件的临时 URL 用于显示图片在页面
  imageUrl.value = URL.createObjectURL(uploadFile.raw)
  disabled.value = false
  return true
}
// 上传按钮的禁用
const disabled = ref(true)
// 上传组件 ref
const upload = ref(null)
// 上传图片
const onUpload = () => {
  // 调用上传指令
  upload.value.submit()
}

</script>

<template>
  <!-- action 上传链接, on-change 文件变化时触发的方法,:auto-upload="false" 禁用自动上传 -->
  <el-upload
    ref="upload"
    class="avatar-uploader"
    action="http://localhost:8080/upload/file"
    :show-file-list="false"
    :on-change="onChange"
    :auto-upload="false"
  >
    <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
    <el-icon v-else class="avatar-uploader-icon">
      <Plus />
    </el-icon>
  </el-upload>
  <div style="margin-top: 30px">
    <el-button
      type="success"
      size="large"
      :disabled="disabled"
      @click="onUpload"
    >上传图片
    </el-button>
  </div>
</template>

<style scoped>
.avatar-uploader .avatar {
  width: 600px;
  height: auto;
  display: block;
  border: black 1px solid;
}

.el-icon.avatar-uploader-icon {
  font-size: 36px;
  color: #8c939d;
  width: 300px;
  height: 300px;
  text-align: center;
  border: black 1px solid;
}
</style>
前端(第二种方式 axios+FormData):
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import instance from '@/utils/instance'

const imageUrl = ref('')
const imageRaw = ref()

const onChange = (uploadFile) => {
  // 判断是否为图片
  const format = /^imag/
  if (!format.test(uploadFile.raw.type)) {
    ElMessage.error('Avatar picture must be JPG format!')
    return false
  }
  // 限制图片大小
  // else if (rawFile.size / 1024 / 1024 > 2) {
  //   ElMessage.error('Avatar picture size can not exceed 2MB!')
  //   return false
  // }
  // 创建一个指向本地文件的临时 URL 用于显示图片在页面
  imageUrl.value = URL.createObjectURL(uploadFile.raw)
  imageRaw.value = uploadFile.raw
  disabled.value = false
  return true
}
// 上传按钮的禁用
const disabled = ref(true)
// 上传组件 ref
const upload = ref(null)
// 上传图片
const onUpload = async () => {
  // FormData 构建要通过 multipart/form-data 格式提交的表单数据。
  // 这个对象可以容纳键值对,特别适合上传文件
  const formatData = new FormData()
  // 将文件数据添加到 formData中,上传时的名称为 file
  formatData.append('file', imageRaw.value)
  // 上传文件,instance为使用axios自定义的 HTTP 客户端实例,并添加了请求和响应拦截器,用于处理请求前后的逻辑
  const res = await instance.post('/upload/file', formatData)
  console.log(res)
}

</script>

<template>
  <!--  on-change 文件变化时触发的方法,:auto-upload="false" 禁用自动上传 -->
  <el-upload
    ref="upload" 
    class="avatar-uploader"
    :show-file-list="false"
    :on-change="onChange"
    :auto-upload="false"
  >
    <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
    <el-icon v-else class="avatar-uploader-icon">
      <Plus />
    </el-icon>
  </el-upload>
  <div style="margin-top: 30px">
    <el-button
      type="success"
      size="large"
      :disabled="disabled"
      @click="onUpload"
    >上传图片
    </el-button>
  </div>
</template>

<style scoped>
.avatar-uploader .avatar {
  width: 600px;
  height: auto;
  display: block;
  border: black 1px solid;
}

.el-icon.avatar-uploader-icon {
  font-size: 36px;
  color: #8c939d;
  width: 300px;
  height: 300px;
  text-align: center;
  border: black 1px solid;
}
</style>
后端:
   @PostMapping("/file")
    public Result uploadFile(MultipartFile file) {

        if (file == null || file.isEmpty()) return Result.err("文件流为空");

        try {
            // 基本路径
            Path path = Paths.get("D:/fileTest/");

            if (!Files.exists(path)) {
                Files.createDirectories(path);
            }

            //保存文件到指定目录 
            String str = file.getOriginalFilename();

            if (!StringUtils.hasText(str)) return Result.err("文件流为空");
            // 获取文件后缀
            String postfix = str.substring(str.lastIndexOf("."));
            // 生成文件名
            String name = UUID.randomUUID() + postfix;
            // 将 path路径 与 文件名 拼接 成 绝对路径
            Path filePath = path.resolve(name);

            // 将收到的文件传输到给定的目标文件
            file.transferTo(filePath.toFile());

            fileService.insert(File.builder().name(name).originName(str).path(filesPath + name).build());

            return Result.success("成功");
        } catch (IOException e) {
            log.error("文件上传错误", e);
            return Result.err("内部错误");
        }
    }

第二种方法可以在springboot配置文件中限制上传文件的大小:

spring:
  servlet:
    multipart: 
      max-file-size: 10MB      # 最大文件大小
      max-request-size: 50MB   # 最大请求大小

2,文件下载

后端:

我是把文件路径存在了数据库中,请求下载的时候传id,然后查数据库,再获取路径进行其它传输操作
File 是我建的一个实体类,所以调用 jdk中的 File时成了 java.io.File
FileUtils 是来自依赖 commons-io

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
@GetMapping("/file/{id}")
    public void downFile(@PathVariable("id") Long id, HttpServletResponse httpServletResponse) {
        try {
            // 检查传入的 id 是否为空
            if (id == null) throw new RuntimeException("id为空");
            // 向 数据库 查找数据
            List<File> files = fileService.queryRequirement(File.builder().id(id).build());
            // 未找到数据 抛出异常
            if (files == null || files.isEmpty()) throw new RuntimeException("数据未查询到");

            // 找到对应 路径下的 文件 创建成 file对象
            java.io.File file = new java.io.File(files.get(0).getPath());
            // 读取 file 对象 的 数据
            byte[] bytes = FileUtils.readFileToByteArray(file);

            // 清空response,一般不需要清空
//            httpServletResponse.reset();
            // 设置媒体
            httpServletResponse.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            // 设置response的Header
            httpServletResponse.setCharacterEncoding("UTF-8");
            //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
            //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
            // filename表示文件的默认名称,因为网络传输只支持URL编码的相关字符,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
            String filename = files.get(0).getOriginName();
            httpServletResponse.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
            // 告知浏览器文件的大小
            httpServletResponse.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));

            // 将文件 数据写入 响应中
            httpServletResponse.getOutputStream().write(bytes);
            httpServletResponse.getOutputStream().flush();

        } catch (Exception e) {
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            httpServletResponse.setCharacterEncoding("UTF-8");
            Result<Object> result = Result.builder().code(500).message("文件下载失败").build();
            String jsonString = JSON.toJSONString(result);
            httpServletResponse.setStatus(500);
            try {
                httpServletResponse.getWriter().print(jsonString);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

前端:

第一种方式:

先在项目中运行 npm install file-saver
row.id 上面已说明是根据 id 查数据库然后获取文件路径进行数据传输
row.name 文件名称(带后缀)

 axios.request({
    timeout: 1000000,
    method: 'GET',
    responseType: 'blob',  // 设置返回时的类型 二进制 blob 数据
    url: 'http://localhost:8080/down/file/' + row.id
  }).then(res => {
    // 将返回的数据 创建 blob
    const blob = new Blob([res.data], { type: 'application/octet-stream' })
    // 使用 FileSaver.js 下载
    saveAs(blob, row.name)
  })

注意:这样请求需要处理好跨域问题即可

第二种方式:

window.open() : 完全依赖于浏览器的默认行为,不受前端控制
无法控制文件名(文件名由后端的 Content-Disposition 或 URL 决定)
优点:简单快捷,适合不需要额外处理的文件下载。

window.open('http://localhost:8080/down/file/' + row.id)
两种方式主要区别:
  • 行为window.open() 是浏览器默认的文件下载方式,而 axios 提供了更强大的控制能力。
  • 文件名axios 可以自定义文件名,而 window.open() 不行。
  • 错误处理和灵活性axios 能捕获错误、显示进度、控制文件类型等,而 window.open() 基本无法做到。
  • 文件类型处理axios 可以处理任何类型的文件,包括二进制文件、图片、文本等。window.open() 则完全依赖于浏览器的行为。

3,静态资源(图片等)访问

前端访问后端图片资源有两种方式,
第一种是后端将图片转为 base64格式数据 传给前端,前端接收之后转为 DataURL 显示出来。不建议这种方式,访问量大且图片大的话容易出问题。
第二种是后端提供静态资源访问:
写一个配置类实现WebMvcConfigurer重写addResourceHandlers

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pre/**").addResourceLocations("file:D:/fileTest/");
    }
}

配置完成后,例如 D:/fileTest/ 目录下有 123.jpg ,这时通过 后端地址+/pre/123.jpg 即可访问:
在这里插入图片描述

要提升Spring Boot Vue中文件上传的速率,可以采取以下几个步骤: 1. 增加文件上传的并发数:在Spring Boot的配置文件中,可以通过设置`spring.servlet.multipart.max-request-size`和`spring.servlet.multipart.max-file-size`来增加允许上传的文件大小限制。同时,也可以使用Apache Commons FileUpload库中的`FileItemFactory`接口和`DiskFileUpload`类来处理文件上传请求,并通过多线程处理请求以提高并发处理能力。 2. 压缩文件:在前端使用Vue时,可以采用压缩文件的方式来减少文件上传的大小。可以使用Webpack等工具对静态资源进行打包压缩,减小文件体积,从而优化上传速率。同时,在后端使用Gzip或Deflate等压缩算法对响应数据进行压缩,再传输给前端。这样不仅减少了网络传输的时间和带宽占用,还可以提高文件上传速率。 3. 使用异步处理:可以在Spring Boot中使用异步处理技术来处理文件上传请求。通过使用Java的异步处理机制,在接受到上传请求后,将请求放入消息队列中,然后异步处理队列中的请求。这样可以在接受到上传请求后立即返回响应,而不需要等待文件上传完成。既能提高用户体验,又能减少请求等待时间,提高文件上传速率。 4. 使用CDN加速:将上传的文件存储在CDN(内容分发网络)中,可以通过就近访问和负载均衡等技术来提高文件上传的速度。CDN会根据用户的网络条件和地理位置,自动选择最佳的节点提供服务,从而提升文件上传的速率。 总结起来,要提高Spring Boot Vue中文件上传的速率,可以采取增加并发数、压缩文件、使用异步处理和使用CDN加速等方法,从而优化上传过程,提高上传速率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值