springboot實(shí)現(xiàn)圖片上傳與下載功能
寫一下后端spring項(xiàng)目經(jīng)常要做的功能,實(shí)現(xiàn)圖片上傳和下載,這里也把前端代碼附上了??赡芩闶莻€(gè)簡(jiǎn)單版的,我這里圖片上傳都存在當(dāng)前項(xiàng)目的根目錄resource下了。
這里包含了,上傳文件、下載文件(下載文件流、獲取base64),service中還有個(gè)文件流轉(zhuǎn)base64的工具方法。
下面是后端代碼
Controller
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 { @Autowired TestService 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}") @CrossOrigin public 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
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 */ @Override public ActionResult upload(MultipartFile file) throws IOException { if(file.isEmpty()) { return ActionResult.fail("文件不能為空"); } // 上傳的文件名稱 String originalFilename = file.getOriginalFilename(); // 文件后綴 String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); // 使用uuid當(dāng)前文件名存儲(chǔ),防止名稱相同覆蓋 String uuid = UUID.randomUUID().toString(); String fileName = uuid + suffix; // 文件保存的路徑(我這里存在當(dāng)前項(xiàng)目下,所以獲取當(dāng)前項(xiàng)目的絕對(duì)路徑,然后拼接上圖片存放的文件夾的名稱) String savePath = ResourceUtils.getURL("resource").getPath(); // 判斷是否存在resource目錄, 沒有則創(chuàng)建 File dir = new File(savePath); if(dir != null && !dir.exists()) { dir.mkdir(); } savePath = savePath + File.separator + fileName; // 文件上傳 file.transferTo(new File(savePath)); // 將文件存儲(chǔ)的名稱uuid和原文件名稱返回給前端 Map<String, Object> map = new HashMap<>(); map.put("id", uuid); map.put("name", originalFilename); // 將下載圖片的接口(返回文件流)路徑返回給前端,前端直接將服務(wù)器地址拼上該鏈接即可回顯圖片 map.put("url", "/file/download/" + fileName); return ActionResult.success("上傳成功", map); } /** * 文件下載邏輯(文件流) * @param name * @return * @throws FileNotFoundException */ @Override public 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); } // 創(chuàng)建輸入流 InputStream inputStream = new FileInputStream(file); // 設(shè)置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 */ @Override public 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); } /** * 將文件流轉(zhuǎn)為base64編碼(工具方法, service中接口沒有該方法) * @param response * @return * @throws IOException */ public String streamToBase64(ResponseEntity<InputStreamResource> response) throws IOException { // 獲取InputStreamResource對(duì)象 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轉(zhuǎn)換為字節(jié)數(shù)組 byte[] fileBytes = byteArrayOutputStream.toByteArray(); // 使用Base64編碼器進(jìn)行編碼 String base64Encoded = Base64.getEncoder().encodeToString(fileBytes); base64Encoded = "data:image/png;base64," + base64Encoded; return base64Encoded; } } }
代碼里面也都寫注釋了,大家一看應(yīng)該就明白了。
簡(jiǎn)單說一下思路把,存圖片的時(shí)候,我是以u(píng)uid當(dāng)作圖片的名稱存儲(chǔ)的,這樣即便是前端是兩個(gè)文件,但是名稱一樣,上傳之后也不會(huì)覆蓋掉原來的圖片。
然后圖片上傳完之后,我把回顯圖片的路徑返回給前端了,前端可以使用服務(wù)器地址(當(dāng)然開發(fā)環(huán)境下會(huì)有跨域問題,一般會(huì)直接用前綴)拼上返回的這個(gè)路徑即可回顯圖片。
返回的這個(gè)url其實(shí)就是后端編寫好的一個(gè)接口,返回的是個(gè)文件流,前端直接將完整的請(qǐng)求的后端的路徑放在img標(biāo)簽的src上,其實(shí)就相當(dāng)于發(fā)送了請(qǐng)求,所以這里注意這種方式回顯要求后端將改接口放在白名單中(即該接口不需要token校驗(yàn)),否則前端就不能像正常路徑一樣直接放在img的src上回顯,就要像普通的接口一樣調(diào)用接口,然后通過URL.createObjectURL(new Blob(res))的方式轉(zhuǎn)為路徑再復(fù)制給src。
然后我上面封裝了了個(gè)ActionResult的返回給前端的包裝類,也在這貼一下吧:
ActionResult
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; } }
接下來再貼一下前端代碼:
<template> <div class="wft-test"> <el-upload class="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> <!-- 測(cè)試下載圖片 獲取文件流 --> <el-button @click="testDownload('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">測(cè)試下載圖片流</el-button> <!-- 測(cè)試下載圖片 獲取base64 --> <el-button @click="testDownloadBase64('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">測(cè)試下載圖片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(""); // 上傳成功回調(diào) function handleAvatarSuccess(res: any) { if(res.code == 200) { imageUrl.value = res.data.url // 正常成功回顯 } } /** * 測(cè)試下載圖片(獲取文件流) * @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) }) } /** * 測(cè)試下載圖片(獲取文件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>
以上就是springboot實(shí)現(xiàn)圖片上傳與下載功能的詳細(xì)內(nèi)容,更多關(guān)于springboot圖片上傳下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
并行Stream與Spring事務(wù)相遇會(huì)發(fā)生什么?
這篇文章主要介紹了并行Stream與Spring事務(wù)相遇會(huì)發(fā)生什么?文章主要解決實(shí)戰(zhàn)中的Bug及解決方案和技術(shù)延伸,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05springboot實(shí)現(xiàn)對(duì)注解的切面案例
這篇文章主要介紹了springboot實(shí)現(xiàn)對(duì)注解的切面過程,首先定義一個(gè)注解、再編寫對(duì)注解的切面只是記錄的執(zhí)行時(shí)間和打印方法,可以實(shí)現(xiàn)其他邏輯,需要的朋友可以參考一下2022-01-01java實(shí)現(xiàn)大文件導(dǎo)出的實(shí)現(xiàn)與優(yōu)化
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)大文件導(dǎo)出的實(shí)現(xiàn)與優(yōu)化的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們深入了解java有一定的幫助,感興趣的小伙伴可以了解下2023-11-11Java計(jì)算兩個(gè)漢字相似度的實(shí)現(xiàn)方法
有時(shí)候我們希望計(jì)算兩個(gè)漢字的相似度,比如文本的 OCR 等場(chǎng)景,用于識(shí)別糾正,本文給大家詳細(xì)介紹了Java計(jì)算兩個(gè)漢字相似度的實(shí)現(xiàn)方法,文中有詳細(xì)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2023-11-11Spring Core動(dòng)態(tài)代理的實(shí)現(xiàn)代碼
通過JDK的Proxy方式或者CGLIB方式生成代理對(duì)象的時(shí)候,相關(guān)的攔截器已經(jīng)配置到代理對(duì)象中去了,接下來通過本文給大家介紹Spring Core動(dòng)態(tài)代理的相關(guān)知識(shí),需要的朋友可以參考下2021-10-10Java中JDom解析XML_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
JDOM是一種解析XML的Java工具包。DOM適合于當(dāng)今流行的各種語言,包括Java,JavaScripte,VB,VBScript,Perl,C,C++等。下面通過本文給大家介紹Java中JDom解析XML的方法,感興趣的朋友一起學(xué)習(xí)吧2017-07-07SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗
Spring Boot是一個(gè)用于構(gòu)建獨(dú)立的、基于生產(chǎn)級(jí)別的Spring應(yīng)用程序的框架,下面這篇文章主要給大家介紹了關(guān)于SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗的相關(guān)資料,需要的朋友可以參考下2024-08-08