文章目录
上传文件(图片)有两种方法,一种是获取文件后转成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 即可访问: