springboot实现图片上传、下载功能

news/2024/12/26 17:25:44/

 

写一下后端spring项目经常要做的功能,实现图片上传和下载,这里也把前端代码附上了。可能算是个简单版的,我这里图片上传都存在当前项目的根目录resource下了。

这里包含了,上传文件、下载文件(下载文件流、获取base64),service中还有个文件流转base64的工具方法。

下面是后端代码

Controller

java">package com.wft.controller;import com.wft.model.ActionResult;
import com.wft.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.*;@RestController
@RequestMapping("/file")
public class TestController {@AutowiredTestService testService;/*** 文件上传* @param file* @return* @throws IOException*/@PostMapping("/upload")public ActionResult uploadTest(@RequestParam("file") MultipartFile file) throws IOException {return testService.upload(file);}/*** 文件下载* @param name* @return* @throws FileNotFoundException*/@GetMapping("/download/{name}")@CrossOriginpublic ResponseEntity<InputStreamResource> downloadTest(@PathVariable("name") String name) throws FileNotFoundException {return testService.download(name);}/*** 获取文件的base64编码* @param name* @return* @throws IOException*/@GetMapping("/getBase64/{name}")public ActionResult getBase64(@PathVariable("name") String name) throws IOException {return testService.getBase64(name);}}

Service接口就不贴了哈 

ServiceImpl 

java">package com.wft.service.impl;import com.wft.model.ActionResult;
import com.wft.service.TestService;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;import java.io.*;
import java.nio.file.Files;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@Service
public class TestServiceImpl implements TestService {/*** 文件上传逻辑* @param file* @return* @throws IOException*/@Overridepublic ActionResult upload(MultipartFile file) throws IOException {if(file.isEmpty()) {return ActionResult.fail("文件不能为空");}// 上传的文件名称String originalFilename = file.getOriginalFilename();// 文件后缀String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));// 使用uuid当前文件名存储,防止名称相同覆盖String uuid = UUID.randomUUID().toString();String fileName = uuid + suffix;// 文件保存的路径(我这里存在当前项目下,所以获取当前项目的绝对路径,然后拼接上图片存放的文件夹的名称)String savePath = ResourceUtils.getURL("resource").getPath();// 判断是否存在resource目录, 没有则创建File dir = new File(savePath);if(dir != null && !dir.exists()) {dir.mkdir();}savePath = savePath + File.separator + fileName;// 文件上传file.transferTo(new File(savePath));// 将文件存储的名称uuid和原文件名称返回给前端Map<String, Object> map = new HashMap<>();map.put("id", uuid);map.put("name", originalFilename);// 将下载图片的接口(返回文件流)路径返回给前端,前端直接将服务器地址拼上该链接即可回显图片map.put("url", "/file/download/" + fileName);return ActionResult.success("上传成功", map);}/*** 文件下载逻辑(文件流)* @param name* @return* @throws FileNotFoundException*/@Overridepublic ResponseEntity<InputStreamResource> download(String name) throws FileNotFoundException {String path = ResourceUtils.getURL("resource").getPath() + File.separator + name;File file = new File(path);if(!file.exists()) {return new ResponseEntity<>(HttpStatus.NOT_FOUND);}// 创建输入流InputStream inputStream = new FileInputStream(file);// 设置HTTP头部信息HttpHeaders headers = new HttpHeaders();System.out.println(file.getName() + "---->>>文件名");headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());// 返回文件流ResponseEntity<InputStreamResource> body = ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_OCTET_STREAM).body(new InputStreamResource(inputStream));return body;}/*** 获取文件的base64编码* @param name* @return* @throws IOException*/@Overridepublic ActionResult getBase64(String name) throws IOException {String path = ResourceUtils.getURL("resource").getPath() + File.separator + name;File file = new File(path);if(!file.exists()) {return ActionResult.fail("文件不存在");}byte[] fileContent = Files.readAllBytes(file.toPath());String base64 = Base64.getEncoder().encodeToString(fileContent);base64 = "data:image/png;base64," + base64;return ActionResult.success((Object) base64);}/*** 将文件流转为base64编码(工具方法, service中接口没有该方法)* @param response* @return* @throws IOException*/public String streamToBase64(ResponseEntity<InputStreamResource> response) throws IOException {// 获取InputStreamResource对象InputStreamResource resource = response.getBody();if (resource == null) {throw new IllegalArgumentException("Response body is null");}try (InputStream inputStream = resource.getInputStream();ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, bytesRead);}// 将InputStream转换为字节数组byte[] fileBytes = byteArrayOutputStream.toByteArray();// 使用Base64编码器进行编码String base64Encoded = Base64.getEncoder().encodeToString(fileBytes);base64Encoded = "data:image/png;base64," + base64Encoded;return base64Encoded;}}
}

 

代码里面也都写注释了,大家一看应该就明白了。

简单说一下思路把,存图片的时候,我是以uuid当作图片的名称存储的,这样即便是前端是两个文件,但是名称一样,上传之后也不会覆盖掉原来的图片。

然后图片上传完之后,我把回显图片的路径返回给前端了,前端可以使用服务器地址(当然开发环境下会有跨域问题,一般会直接用前缀)拼上返回的这个路径即可回显图片。

返回的这个url其实就是后端编写好的一个接口,返回的是个文件流,前端直接将完整的请求的后端的路径放在img标签的src上,其实就相当于发送了请求,所以这里注意这种方式回显要求后端将改接口放在白名单中(即该接口不需要token校验),否则前端就不能像正常路径一样直接放在img的src上回显,就要像普通的接口一样调用接口,然后通过URL.createObjectURL(new Blob(res))的方式转为路径再复制给src。

然后我上面封装了了个ActionResult的返回给前端的包装类,也在这贴一下吧:

ActionResult 

java">package com.wft.model;import lombok.Data;@Data
public class ActionResult<T> {private Integer code;private String msg;private T data;public static ActionResult success() {ActionResult jsonData = new ActionResult();jsonData.setCode(200);jsonData.setMsg("success");return jsonData;}public static ActionResult success(String msg) {ActionResult jsonData = new ActionResult();jsonData.setCode(200);jsonData.setMsg(msg);return jsonData;}public static ActionResult success(Object object) {ActionResult jsonData = new ActionResult();jsonData.setData(object);jsonData.setCode(200);jsonData.setMsg("success");return jsonData;}public static ActionResult success(String msg, Object object) {ActionResult jsonData = new ActionResult();jsonData.setData(object);jsonData.setCode(200);jsonData.setMsg(msg);return jsonData;}public static ActionResult fail(Integer code, String message) {ActionResult jsonData = new ActionResult();jsonData.setCode(code);jsonData.setMsg(message);return jsonData;}public static ActionResult fail(String msg, String data) {ActionResult jsonData = new ActionResult();jsonData.setMsg(msg);jsonData.setData(data);return jsonData;}public static ActionResult fail(String msg) {ActionResult jsonData = new ActionResult();jsonData.setMsg(msg);jsonData.setCode(400);return jsonData;}
}

接下来再贴一下前端代码:

java"><template><div class="wft-test"><el-uploadclass="avatar-uploader":action="baseURL + '/file/upload'":show-file-list="false":on-success="handleAvatarSuccess"><img v-if="imageUrl" :src="baseURL + imageUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload><!-- 测试下载图片 获取文件流 --><el-button @click="testDownload('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">测试下载图片流</el-button><!-- 测试下载图片 获取base64 --><el-button @click="testDownloadBase64('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">测试下载图片base64</el-button><img style="width: 200px;height: 200px;" v-if="imgBase64" :src="imgBase64" alt=""></div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
import request from '@/utils/request';const imageUrl = ref("");
const imgBase64 = ref("");// 上传成功回调
function handleAvatarSuccess(res: any) {if(res.code == 200) {imageUrl.value = res.data.url  // 正常成功回显}
}/*** 测试下载图片(获取文件流)* @param fileName 文件名*/
function testDownload(fileName: string) {request({url: `/file/download/${fileName}`,method: 'get',responseType: 'blob'}).then((res: any) => {const link = document.createElement('a')link.href = URL.createObjectURL(new Blob([res]))link.download = 'test.png'document.body.appendChild(link)link.click()document.body.removeChild(link)})
}/*** 测试下载图片(获取文件base64编码)* @param fileName */
function testDownloadBase64(fileName: string) {request({url: `/file/getBase64/${fileName}`,method: 'get'}).then((res: any) => {if(res.code == 200) {imgBase64.value = res.data}})
}</script>
<style scoped>
.wft-test {width: 100%;height: 100%;
}.avatar-uploader .avatar {width: 178px;height: 178px;display: block;
}
</style><style>
.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;text-align: center;
}
</style>

http://www.ppmy.cn/news/1558315.html

相关文章

谈谈JSON

中文名&#xff1a;JS键值对数据 英文名&#xff1a;JavaScript Object Notation JSON是一种常用的数据格式&#xff0c;其简洁和清晰的层次结构有效地提升了网络传输效率&#xff0c;很多编程语言都支持JSON格式的数据交换。 JSON 相比于 XML 来说&#xff0c;更小、…

LabVIEW物联网开发实战:专栏总述

本专栏以LabVIEW为开发平台&#xff0c;讲解物联网通信组网原理与开发方法&#xff0c;覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例&#xff0c;展示如何利用LabVIEW和常用模块实现物联网系统的快速开发与原型设计&#xff0c;助你从基础到实战&#xff0…

Android笔试面试题AI答之Android基础(2)

文章目录 1.启动应用后&#xff0c;改变系统语言&#xff0c;应用的语言会改变么&#xff1f;一、应用支持多语言切换的情况二、应用不支持多语言切换的情况三、Android系统的特殊处理四、实践中的注意事项 2.请介绍下adb、ddms、aapt 的作用adb&#xff08;Android Debug Brid…

作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化

文|作业帮大数据团队&#xff08;阮文俊、孙建业&#xff09; 背 景 基于 Apache DolphinScheduler &#xff08;以下简称DolphinScheduler&#xff09;搭建的 UDA 任务调度平台有效支撑了公司的业务数据开发需求&#xff0c;处理着日均百万级别的任务量。 整个 UDA 的架构如…

使用Turtle库实现,鼠标左键绘制路径,用鼠标右键结束绘制,小海龟并沿路径移动

使用Turtle库实现&#xff0c;鼠标左键绘制路径&#xff0c;用鼠标右键结束绘制&#xff0c;小海龟并沿路径移动 Turtle库是Python标准库的一部分&#xff0c;它提供了一种基于命令的图形绘制方式。Turtle模块通过一个“海龟”&#xff08;Turtle&#xff09;对象在屏幕上移动…

解决集群Elasticsearch 未授权访问漏洞

1、ES集群配置 首先至少是三个节点 2、生成证书&#xff08;后面要用&#xff09; cd /home/elasticsearch-7.4.2/bin ./elasticsearch-certutil cert 回车&#xff0c;空密码&#xff08;可以输入密码&#xff09;&#xff0c;回车 3、将elastic-certificates.p12 复制到三…

什么是微服务、微服务如何实现Eureka,网关是什么,nacos是什么

目录 1、了解微服务 1、微服务的由来 2、为什么需要微服务 3、微服务与单体架构区别 4、微服务本质 5、什么样的项目适合微服务 6、微服务开发框架 2、微服务实现Eureka 1、创建普通springboot项目 2、创建子模块 3、使用Eureka注册中心 4、创建消费者 3.网关 1、…

排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)

排序:经常在算法题中作为一个前置操作,为了之后的贪心or else做个铺垫,虽然我们经常都只是调用个sort,但是了解一些排序算法可以扩充下知识库 排序的分类: 从存储设备角度&#xff1a; ✓ 内排序&#xff1a;在排序过程中所有数据元素都在内存中&#xff1b; ✓ 外排序&a…