在Web应用开发中,文件上传和下载是非常常见的功能,例如用户头像上传、附件管理、图片展示等。本文将通过一个完整的示例,介绍如何使用Spring Boot 构建后端文件上传下载接口,并结合Vue 3 + Element Plus前端组件实现文件的上传与展示。代码简洁清晰,可直接应用于实际项目。
技术栈
-
后端:Spring Boot 2.x,文件操作使用java.nio.file
-
前端:Vue 3 + Element Plus(el-upload、el-image)
-
构建工具:Maven(后端),Vite(前端)
功能概述
-
上传文件:通过POST请求将文件保存到服务器指定目录,并返回该文件的下载URL。
-
下载文件:通过GET请求根据文件名返回文件流(支持浏览器直接下载或预览)。
-
前端集成:使用Element Plus的上传组件上传头像,上传成功后展示图片。
后端实现
1. 创建Spring Boot项目
在pom.xml中添加必要依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 编写文件上传下载Controller
创建FileController,处理/api/files/upload(上传)和/api/files/download/{fileName}(下载)请求。
java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/files")
public class FileController {
// 文件存储目录,实际项目中应配置在application.yml中
private final String uploadDir = "uploads/";
public FileController() {
// 确保上传目录存在
Path path = Paths.get(uploadDir);
if (!Files.exists(path)) {
try {
Files.createDirectories(path);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 文件上传
* @param file 上传的文件(参数名必须与前端一致)
* @return 文件的下载URL
*/
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String fileName = file.getOriginalFilename();
Path path = Paths.get(uploadDir + fileName);
Files.write(path, file.getBytes());
// 构建可访问的下载URL
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/files/download/")
.path(fileName)
.toUriString();
return ResponseEntity.ok(fileDownloadUri);
} catch (IOException e) {
return ResponseEntity.badRequest().body("文件上传失败: " + e.getMessage());
}
}
/**
* 文件下载
* @param fileName 文件名
* @param response HttpServletResponse
*/
@GetMapping("/download/{fileName}")
public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {
Path path = Paths.get(uploadDir + fileName);
if (!Files.exists(path)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
byte[] bytes = Files.readAllBytes(path);
ServletOutputStream os = response.getOutputStream();
os.write(bytes);
os.flush();
os.close();
}
}
关键点说明:
-
上传接口使用
MultipartFile接收文件,保存到本地uploads/目录(若目录不存在则自动创建)。 -
返回的URL是通过
ServletUriComponentsBuilder动态生成的绝对路径,确保前端可以直接访问下载接口。 -
下载接口设置
Content-Type为application/octet-stream,强制浏览器以附件形式下载;若希望图片直接显示,可改为对应的MIME类型(如image/jpeg),下文会讨论优化方案。
前端实现(Vue 3 + Element Plus)
1. 引入Element Plus
在Vue项目中安装Element Plus并全局引入(或按需引入):
bash
npm install element-plus
在main.js中:
javascript
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
2. 表单中使用el-upload上传头像
假设有一个用户表单,包含头像上传项:
XML
<template>
<el-form :model="form" label-width="80px">
<el-form-item label="头像">
<el-upload
action="http://localhost:8080/api/files/upload"
:on-success="handleFileSuccess"
list-type="picture"
>
<el-button type="primary">上传头像</el-button>
</el-upload>
</el-form-item>
<!-- 其他表单项... -->
</el-form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
avatar: '' // 用于存储头像URL
})
const handleFileSuccess = (response) => {
// response 是上传成功后后端返回的字符串(即下载URL)
form.avatar = response
console.log('头像URL:', response)
}
</script>
说明:
-
action指向后台上传接口地址,需与后端实际地址一致(注意端口)。 -
on-success回调在文件上传成功后触发,response参数即为后端返回的字符串(图片下载URL),将其保存到表单字段中。
3. 在表格中展示头像
若需要在表格中展示已上传的头像,可以使用el-image组件,src绑定为保存的URL:
XML
<el-table :data="tableData">
<el-table-column label="头像" width="120">
<template #default="scope">
<el-image
v-if="scope.row.img"
:src="scope.row.img"
:preview-src-list="[scope.row.img]"
:preview-teleported="true"
style="width: 40px; height: 40px; border-radius: 50%; display: block"
/>
</template>
</el-table-column>
<!-- 其他列 -->
</el-table>