vue+Springboot实现简单文件上传到本地

实现效果

点击上传文件按钮后,选择需要上传的文件,如果是图片的话,上传成功后可以直接在下面预览。

前端页面

<template>
  <div class="file-upload">
    <el-upload
      :headers="getUploadConfig(token).headers"
      action="http://localhost:1011/api/user/file/upload"
      :on-success="handleSuccess"
      list-type="picture"
      v-model:file-list="fileList"
    >
      <el-button size="small" type="primary"> 上传文件 </el-button>
    </el-upload>
    <div class="demo-image__preview">
      <el-image
        style="width: 100px; height: 100px; border-radius: 50%"
        :src="url"
        :preview-src-list="[url]"
        fit="cover"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useTokenStore } from "@/stores/token";

const url = ref("");
const fileList = ref([]);
const tokenStore = useTokenStore();

// 这里暂时保留从localStorage获取token的方式,但这是不安全的,仅用于示例
const token = ref("");
try {
  const storedToken = JSON.parse(localStorage.getItem("token"));
  if (storedToken && storedToken.token) {
    token.value = storedToken.token;
  }
} catch (error) {
  console.error("Error parsing token from localStorage:", error);
}

const getUploadConfig = () => {
  const token = tokenStore.token;
  return {
    headers: {
      Authorization: token,
    },
  };
};

const handleSuccess = (response, file, fileList) => {
  fileList.value = fileList;
  console.log("上传成功,响应内容:", response);
  console.log("上传的文件:", file);
  console.log("更新后的文件列表:", fileList);
  console.log("当前文件列表:", fileList.value);
  url.value = response.data;
  // 处理非成功情况
  console.log("当前文件列表:", url.value);
};
</script>

<style scoped>
.file-upload {
  max-width: 400px;
  margin: auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
}
.demo-image__error .image-slot {
  font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
  font-size: 30px;
}
.demo-image__error .el-image {
  width: 100%;
  height: 200px;
}
</style>

主要代码详细解释: 

<el-upload
    :headers="getUploadConfig(token).headers"
    action="http://localhost:1011/api/user/file/upload"
    :on-success="handleSuccess"
    list-type="picture"
    v-model:file-list="fileList"
>
    <el-button size="small" type="primary">上传文件</el-button>
</el-upload>
  • el-upload组件:这是基于 Element Plus 库的文件上传组件。
    • :headers:绑定了一个通过getUploadConfig(token)函数获取的请求头对象,其中包含了Authorization信息(可能用于后端的身份验证)。
    • action:指定了文件上传的目标 URL,即后端处理文件上传的接口地址,需要根据自己项目情况填写。
    • :on-success:绑定了handleSuccess函数,当文件上传成功时会触发该函数。
    • list-type="picture":设置文件列表的类型为图片形式,这可能会影响文件在界面上的显示样式(比如显示图片缩略图)。
    • v-model:file-list="fileList":使用v-model双向绑定文件列表数据,fileList是一个在script部分定义的响应式数据,用于存储已选择的文件信息。
    • 内部的el-button是触发文件选择和上传的按钮。
<div class="demo-image__preview">
    <el-image
        style="width: 100px; height: 100px; border-radius: 50%"
        :src="url"
        :preview-src-list="[url]"
        fit="cover"
    />
</div>
  • 这部分用于显示上传成功后的文件预览(这里假设是图片)。el-image组件用于显示图片,它的src属性绑定了url(在script部分定义的响应式数据),preview-src-list也使用了url,这样当用户点击图片时可以进行预览。style属性设置了图片的尺寸和样式(圆形,宽高为 100px),fit="cover"表示图片填充方式。
const token = ref("");
try {
    const storedToken = JSON.parse(localStorage.getItem("token"));
    if (storedToken && storedToken.token) {
        token.value = storedToken.token;
    }
} catch (error) {
    console.error("Error parsing token from localStorage:", error);
}

这段代码从localStorage中获取token信息。首先从localStorage中获取token字符串,然后解析为对象并检查是否存在token属性,如果有则将其赋值给token响应式数据。 

const getUploadConfig = () => {
    const token = tokenStore.token;
    return {
        headers: {
            Authorization: token,
        },
    };
};
  • 这个函数用于获取文件上传的请求头配置。它从tokenStore中获取token,并返回一个包含Authorization头的对象,用于在文件上传请求中传递身份验证信息给后端。(这里是用于权限鉴定的,将token信息以Authorization头的形式传给后端,然后没有token可以省略这个代码)
const handleSuccess = (response, file, fileList) => {
    fileList.value = fileList;
    url.value = response.data;
   
};

 handleSuccess函数:这是文件上传成功的回调函数。

  • 首先将传入的fileList赋值给fileList.value,以更新本地存储的文件列表数据。
  • response.data赋值给url.value,这意味着如果后端在成功响应中返回了文件的访问地址(比如图片的 URL),则将其存储在url中,用于在界面上显示文件预览。

 后端代码

在 Spring Boot 项目中创建一个控制器(Controller)来处理文件上传和下载请求,例如FileController.java。 

 

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    // 上传文件的方法,与之前代码相同
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        if (!file.isEmpty()) {
            try {
                // 设置文件保存的路径,这里假设保存到项目的一个upload目录下
                String filePath = "upload/" + file.getOriginalFilename();
                File dest = new File(filePath);
                file.transferTo(dest);
                return "文件上传成功";
            } catch (IOException e) {
                e.printStackTrace();
                return "文件上传失败:" + e.getMessage();
            }
        } else {
            return "请选择要上传的文件";
        }
    }

    // 下载文件的方法
    @GetMapping("/download/{filename}")
    public ResponseEntity<FileSystemResource> downloadFile(@PathVariable("filename") String filename) {
        // 根据文件名构建文件对象,假设文件保存在项目的upload目录下
        File file = new File("upload/" + filename);

        if (file.exists()) {
            // 创建一个FileSystemResource对象,用于包装要下载的文件
            FileSystemResource resource = new FileSystemResource(file);

            // 设置响应头,包括文件名和文件类型等信息
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Disposition", "attachment; filename=" + filename);
            headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
            headers.add("Pragma", "no-cache");
            headers.add("Expires", "0");

            // 根据文件类型设置Content-Type
            MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
            if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
                mediaType = MediaType.IMAGE_JPEG;
            } else if (filename.endsWith(".png")) {
                mediaType = MediaType.IMAGE_PNG;
            } else if (filename.endsWith(".pdf")) {
                mediaType = MediaType.APPLICATION_PDF;
            }

            headers.setContentType(mediaType);

            // 返回一个ResponseEntity对象,包含文件资源、响应头和状态码200,表示下载成功
            return ResponseEntity.ok()
                   .headers(headers)
                   .body(resource);
        } else {
            // 如果文件不存在,返回一个状态码为404的ResponseEntity对象,表示文件未找到
            return ResponseEntity.notFound().build();
        }
    }
}

在上述代码中:

  1. downloadFile方法接受一个@PathVariable注解的参数filename,用于指定要下载的文件名。
  2. 根据文件名构建了对应的文件对象,并检查文件是否存在。
  3. 如果文件存在,创建了FileSystemResource对象来包装要下载的文件,并设置了一系列响应头信息,包括Content-Disposition用于指定下载时的文件名(确保客户端能以正确的文件名保存下载的文件),以及Cache-ControlPragmaExpires等用于控制缓存的信息。同时,根据文件的后缀名设置了合适的Content-Type
  4. 最后,返回一个ResponseEntity对象,其中包含了文件资源、设置好的响应头和状态码200,表示下载成功。如果文件不存在,则返回一个状态码为404ResponseEntity对象,表示文件未找到。

这样,客户端就可以通过访问/download/{filename}这个接口来下载指定的文件了。例如,如果上传了一个名为test.jpg的文件,客户端可以通过访问/download/test.jpg来下载该文件。

 

 

 

 

 

 

 

 

上一篇:前端上传大文件,后端报错Content-Type ‘application/octet-stream‘ is not supported【解决】


下一篇:Spring Boot集成SQL Server快速入门Demo