springboot操作阿里云OSS實(shí)現(xiàn)文件上傳,下載,刪除功能
參考資料:Java操作阿里云OSS操作官方文檔
學(xué)會(huì)看文檔,并實(shí)際運(yùn)用也是一種習(xí)慣和技能
下面就來(lái)簡(jiǎn)單入門(mén)一下,用當(dāng)下比較熱門(mén)的Springboot 去操作阿里云OSS文件存儲(chǔ)。
1.需求
(沒(méi)踩過(guò)下面的坑的小伙伴可以直接跳過(guò)這一章節(jié))
問(wèn)題簡(jiǎn)述
首先,我在之前自己做一些開(kāi)源小項(xiàng)目案例中遇到一些文件上傳下載的問(wèn)題,比如在本機(jī)文件上傳和下載都可以正常使用,通過(guò)將文件上傳到Springboot項(xiàng)目的根目錄下,按日期分文件夾,文件訪問(wèn)也很方便,可以直接返回文件相對(duì)路徑地址,并直接可以訪問(wèn)。
問(wèn)題
然而,這種方式存在弊端,因?yàn)楫?dāng)項(xiàng)目打包(jar包)部署阿里云學(xué)生機(jī)后,出現(xiàn)類似io.NotFoundException...(No Such Directory)
的問(wèn)題,,而如果打war包部署到tomcat則沒(méi)問(wèn)題,可以正常使用,經(jīng)過(guò)排查很久,找出問(wèn)題所在:
因?yàn)?strong>jar打包封裝后是不能改變其內(nèi)部目錄結(jié)構(gòu)的,也就是說(shuō),按日期分類的文件上傳文件夾,如果當(dāng)需要?jiǎng)?chuàng)建新日期的文件夾的時(shí)候,是無(wú)法在jar包中新增文件夾的,這時(shí)候就會(huì)出現(xiàn)IO異常問(wèn)題。而對(duì)于放在tomcat中的war包,當(dāng)tomcat運(yùn)行的時(shí)候會(huì)自動(dòng)解壓war包,其在服務(wù)器上是存在真實(shí)路徑的。
解決方案
- 方案一:我在網(wǎng)上找了一種方法,是通過(guò)打完jar包部署后,給springboot項(xiàng)目static下的文件上傳文件夾單獨(dú)分離出來(lái)(相當(dāng)于是以相對(duì)路徑換絕對(duì)路徑),訪問(wèn)的時(shí)候直接相當(dāng)通過(guò)服務(wù)器上和jar包同級(jí)目錄下新建一個(gè)文件上傳文件夾。
- 方案二:直接將文件上傳到服務(wù)器指定路徑下的文件上傳位置,這種方式也相當(dāng)于直接使用絕對(duì)路徑。
- 方案三:在服務(wù)器上使用FastDFS和Nginx搭建分布式文件存儲(chǔ),這種方式比較復(fù)雜,而且學(xué)生及本來(lái)內(nèi)存和帶寬就小,在自己電腦的虛擬機(jī)可以試試這種方案,還是挺好用的,學(xué)生服務(wù)器就算了。
- 方案四:就是直接將文件上傳到阿里云OSS文件存儲(chǔ)系統(tǒng)上
2. 阿里云OSS購(gòu)買和配置
這個(gè)比較簡(jiǎn)單,給大家推薦一篇博文自己了解下阿里云oss購(gòu)買和配置,也可以參考阿里云OSS官方文檔。
3. Springboot操作OSS
創(chuàng)建一個(gè)spring boot項(xiàng)目,pom文件需要引入依賴:
pom.xml
<dependencies> <!-- 個(gè)人版本踩坑: 不加這個(gè)依賴的話,當(dāng)在配置類中 使用@ConfigurationProperties(prefix = "aliyun")注解時(shí), 我這個(gè)版本的spring boot會(huì)提示有問(wèn)題 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- swagger ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!-- thymeleaf 可不加,個(gè)人習(xí)慣性引入 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 熱部署,看個(gè)人習(xí)慣 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- 小辣椒插件,推薦使用,可以節(jié)省javaBean的setter/getter,還可以使用鏈?zhǔn)秸{(diào)用 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- fastJson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!-- aliyun-oos --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>2.8.3</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.1</version> </dependency> <!-- apache-common-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
包結(jié)構(gòu)很簡(jiǎn)單:
我們使用自己添加的application-aliyun-oss.properties配置文件,去配置OSS相關(guān)信息,之所以不在application.yml 中配置,看個(gè)人習(xí)慣了,因?yàn)樽远x的配置屬性還是提出來(lái)配比較好,沒(méi)必要所有的都配到application.yml(properties)中去。
application-aliyun-oss.properties
# 文件上傳大小限制 spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=1000MB # 地域節(jié)點(diǎn) aliyun.endPoint=oss-cn-beijing.aliyuncs.com # Bucket 域名 aliyun.urlPrefix=http://csp-xxxx.oss-cn-beijing.aliyuncs.com/ # accessKey Id aliyun.accessKeyId=LTAI4XXXXXXXzqD1saGFZ # accessKey Secret aliyun.accessKeySecret=2WjxNXXXXXXXX4f2bREc # 你的Bucket名稱 aliyun.bucketName=csp-xxxx # 目標(biāo)文件夾 aliyun.fileHost=files
config包下的相關(guān)配置類
AliyunOssConfig.java
/** * @Auther: csp1999 * @Date: 2020/10/31/13:33 * @Description: 阿里云 OSS 基本配置 */ // 聲明配置類,放入Spring容器 @Configuration // 指定配置文件位置 @PropertySource(value = {"classpath:application-aliyun-oss.properties"}) // 指定配置文件中自定義屬性前綴 @ConfigurationProperties(prefix = "aliyun") @Data// lombok @Accessors(chain = true)// 開(kāi)啟鏈?zhǔn)秸{(diào)用 public class AliyunOssConfig { private String endPoint;// 地域節(jié)點(diǎn) private String accessKeyId; private String accessKeySecret; private String bucketName;// OSS的Bucket名稱 private String urlPrefix;// Bucket 域名 private String fileHost;// 目標(biāo)文件夾 // 將OSS 客戶端交給Spring容器托管 @Bean public OSS OSSClient() { return new OSSClient(endPoint, accessKeyId, accessKeySecret); } }
Swagger2Config.java
/** * @Auther: csp1999 * @Date: 2020/10/31/16:30 * @Description: Swagger 配置類 */ @Configuration @EnableSwagger2// 開(kāi)啟swagger2 public class Swagger2Config { @Bean public Docket webApiConfig() { return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() .paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo() { return new ApiInfoBuilder() .title("SpringBoot整合OSS-API文檔") .description("阿里云OSS-文件上傳下載測(cè)試") .version("1.0") .contact(new Contact("CSP", "https://blog.csdn.net/weixin_43591980", "")) .build(); } }
定義一個(gè)關(guān)于執(zhí)行狀態(tài)結(jié)果的枚舉類
/** * @Auther: csp1999 * @Date: 2020/10/31/17:03 * @Description: 狀態(tài)碼枚舉類 */ public enum StatusCode { SUCCESS("success",200),ERROR("error",500); private String msg; private Integer code; StatusCode(String msg,Integer code){ this.msg = msg; this.code = code; } StatusCode(Integer code){ this.code = code; } StatusCode(String msg){ this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
service層
在service使用ossClient操作阿里云OSS,進(jìn)行上傳、下載、刪除、查看所有文件等操作,同時(shí)可以將圖片的url進(jìn)行入庫(kù)操作:
FileUploadService.java
/** * @Auther: csp1999 * @Date: 2020/10/31/14:30 * @Description: 文件上傳Service (為節(jié)省文章中的代碼篇幅,不再做接口實(shí)現(xiàn)類處理) */ @Service("fileUploadService") public class FileUploadService { // 允許上傳文件(圖片)的格式 private static final String[] IMAGE_TYPE = new String[]{".bmp", ".jpg", ".jpeg", ".gif", ".png"}; @Autowired private OSS ossClient;// 注入阿里云oss文件服務(wù)器客戶端 @Autowired private AliyunOssConfig aliyunOssConfig;// 注入阿里云OSS基本配置類 /* * 文件上傳 * 注:阿里云OSS文件上傳官方文檔鏈接:https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11186623.6.749.11987a7dRYVSzn * @param: uploadFile * @return: string * @create: 2020/10/31 14:36 * @author: csp1999 */ public String upload(MultipartFile uploadFile) { // 獲取oss的Bucket名稱 String bucketName = aliyunOssConfig.getBucketName(); // 獲取oss的地域節(jié)點(diǎn) String endpoint = aliyunOssConfig.getEndPoint(); // 獲取oss的AccessKeySecret String accessKeySecret = aliyunOssConfig.getAccessKeySecret(); // 獲取oss的AccessKeyId String accessKeyId = aliyunOssConfig.getAccessKeyId(); // 獲取oss目標(biāo)文件夾 String filehost = aliyunOssConfig.getFileHost(); // 返回圖片上傳后返回的url String returnImgeUrl = ""; // 校驗(yàn)圖片格式 boolean isLegal = false; for (String type : IMAGE_TYPE) { if (StringUtils.endsWithIgnoreCase(uploadFile.getOriginalFilename(), type)) { isLegal = true; break; } } if (!isLegal) {// 如果圖片格式不合法 return StatusCode.ERROR.getMsg(); } // 獲取文件原名稱 String originalFilename = uploadFile.getOriginalFilename(); // 獲取文件類型 String fileType = originalFilename.substring(originalFilename.lastIndexOf(".")); // 新文件名稱 String newFileName = UUID.randomUUID().toString() + fileType; // 構(gòu)建日期路徑, 例如:OSS目標(biāo)文件夾/2020/10/31/文件名 String filePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); // 文件上傳的路徑地址 String uploadImgeUrl = filehost + "/" + filePath + "/" + newFileName; // 獲取文件輸入流 InputStream inputStream = null; try { inputStream = uploadFile.getInputStream(); } catch (IOException e) { e.printStackTrace(); } /** * 下面兩行代碼是重點(diǎn)坑: * 現(xiàn)在阿里云OSS 默認(rèn)圖片上傳ContentType是image/jpeg * 也就是說(shuō),獲取圖片鏈接后,圖片是下載鏈接,而并非在線瀏覽鏈接, * 因此,這里在上傳的時(shí)候要解決ContentType的問(wèn)題,將其改為image/jpg */ ObjectMetadata meta = new ObjectMetadata(); meta.setContentType("image/jpg"); //文件上傳至阿里云OSS ossClient.putObject(bucketName, uploadImgeUrl, inputStream, meta); /** * 注意:在實(shí)際項(xiàng)目中,文件上傳成功后,數(shù)據(jù)庫(kù)中存儲(chǔ)文件地址 */ // 獲取文件上傳后的圖片返回地址 returnImgeUrl = "http://" + bucketName + "." + endpoint + "/" + uploadImgeUrl; return returnImgeUrl; } /* * 文件下載 * @param: fileName * @param: outputStream * @return: void * @create: 2020/10/31 16:19 * @author: csp1999 */ public String download(String fileName, HttpServletResponse response) throws UnsupportedEncodingException { // // 設(shè)置響應(yīng)頭為下載 // response.setContentType("application/x-download"); // // 設(shè)置下載的文件名 // response.addHeader("Content-Disposition", "attachment;fileName=" + fileName); // response.setCharacterEncoding("UTF-8"); // 文件名以附件的形式下載 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); // 獲取oss的Bucket名稱 String bucketName = aliyunOssConfig.getBucketName(); // 獲取oss目標(biāo)文件夾 String filehost = aliyunOssConfig.getFileHost(); // 日期目錄 // 注意,這里雖然寫(xiě)成這種固定獲取日期目錄的形式,邏輯上確實(shí)存在問(wèn)題,但是實(shí)際上,filePath的日期目錄應(yīng)該是從數(shù)據(jù)庫(kù)查詢的 String filePath = new DateTime().toString("yyyy/MM/dd"); String fileKey = filehost + "/" + filePath + "/" + fileName; // ossObject包含文件所在的存儲(chǔ)空間名稱、文件名稱、文件元信息以及一個(gè)輸入流。 OSSObject ossObject = ossClient.getObject(bucketName, fileKey); try { // 讀取文件內(nèi)容。 InputStream inputStream = ossObject.getObjectContent(); BufferedInputStream in = new BufferedInputStream(inputStream);// 把輸入流放入緩存流 ServletOutputStream outputStream = response.getOutputStream(); BufferedOutputStream out = new BufferedOutputStream(outputStream);// 把輸出流放入緩存流 byte[] buffer = new byte[1024]; int len = 0; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } if (out != null) { out.flush(); out.close(); } if (in != null) { in.close(); } return StatusCode.SUCCESS.getMsg(); } catch (Exception e) { return StatusCode.ERROR.getMsg(); } } /* * 文件刪除 * @param: objectName * @return: java.lang.String * @create: 2020/10/31 16:50 * @author: csp1999 */ public String delete(String fileName) { // 獲取oss的Bucket名稱 String bucketName = aliyunOssConfig.getBucketName(); // 獲取oss的地域節(jié)點(diǎn) String endpoint = aliyunOssConfig.getEndPoint(); // 獲取oss的AccessKeySecret String accessKeySecret = aliyunOssConfig.getAccessKeySecret(); // 獲取oss的AccessKeyId String accessKeyId = aliyunOssConfig.getAccessKeyId(); // 獲取oss目標(biāo)文件夾 String filehost = aliyunOssConfig.getFileHost(); // 日期目錄 // 注意,這里雖然寫(xiě)成這種固定獲取日期目錄的形式,邏輯上確實(shí)存在問(wèn)題,但是實(shí)際上,filePath的日期目錄應(yīng)該是從數(shù)據(jù)庫(kù)查詢的 String filePath = new DateTime().toString("yyyy/MM/dd"); try { /** * 注意:在實(shí)際項(xiàng)目中,不需要?jiǎng)h除OSS文件服務(wù)器中的文件, * 只需要?jiǎng)h除數(shù)據(jù)庫(kù)存儲(chǔ)的文件路徑即可! */ // 建議在方法中創(chuàng)建OSSClient 而不是使用@Bean注入,不然容易出現(xiàn)Connection pool shut down OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret); // 根據(jù)BucketName,filetName刪除文件 // 刪除目錄中的文件,如果是最后一個(gè)文件fileoath目錄會(huì)被刪除。 String fileKey = filehost + "/" + filePath + "/" + fileName; ossClient.deleteObject(bucketName, fileKey); try { } finally { ossClient.shutdown(); } System.out.println("文件刪除!"); return StatusCode.SUCCESS.getMsg(); } catch (Exception e) { e.printStackTrace(); return StatusCode.ERROR.getMsg(); } } }
controller層
controller提供測(cè)試接口
/** * @Auther: csp1999 * @Date: 2020/10/31/16:40 * @Description: OSS 文件上傳controller */ @Api(description = "阿里云OSS文件上傳、下載、刪除API") @RequestMapping("api/pri/file") @RestController public class OssFileController { @Autowired private FileUploadService fileUploadService; /* * 文件上傳api * @param: file * @return: com.alibaba.fastjson.JSONObject * @create: 2020/10/31 17:35 * @author: csp1999 */ @ApiOperation(value = "文件上傳") @PostMapping("upload") public JSONObject upload(@RequestParam("file") MultipartFile file) { JSONObject jsonObject = new JSONObject(); if (file != null) { String returnFileUrl = fileUploadService.upload(file); if (returnFileUrl.equals("error")) { jsonObject.put("error", "文件上傳失??!"); return jsonObject; } jsonObject.put("success", "文件上傳成功!"); jsonObject.put("returnFileUrl", returnFileUrl); return jsonObject; } else { jsonObject.put("error", "文件上傳失敗!"); return jsonObject; } } /* * 文件下載api * @param: fileName * @param: response * @return: com.alibaba.fastjson.JSONObject * @create: 2020/10/31 17:35 * @author: csp1999 */ @ApiOperation(value = "文件下載") @GetMapping(value = "download/{fileName}") public JSONObject download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws Exception { JSONObject jsonObject = new JSONObject(); String status = fileUploadService.download(fileName, response); if (status.equals("error")) { jsonObject.put("error", "文件下載失??!"); return jsonObject; } else { jsonObject.put("success", "文件下載成功!"); return jsonObject; } } /* * 文件刪除api * @param: fileName * @return: com.alibaba.fastjson.JSONObject * @create: 2020/10/31 17:35 * @author: csp1999 */ @ApiOperation(value = "文件刪除") @GetMapping("/delete/{fileName}") public JSONObject DeleteFile(@PathVariable("fileName") String fileName) { JSONObject jsonObject = new JSONObject(); String status = fileUploadService.delete(fileName); if (status.equals("error")) { jsonObject.put("error", "文件刪除失?。?); return jsonObject; } else { jsonObject.put("success", "文件刪除成功!"); return jsonObject; } } }
4.運(yùn)行項(xiàng)目測(cè)試API接口
本機(jī)訪問(wèn):http://localhost:8083/swagger-ui.html
測(cè)試上傳:
結(jié)果如圖:
如果說(shuō)明上傳成功,我們來(lái)看一下這個(gè)鏈接能不能訪問(wèn)到:
上傳成功!
到此這篇關(guān)于springboot操作阿里云OSS實(shí)現(xiàn)文件上傳,下載,刪除功能的文章就介紹到這了,更多相關(guān)springboot文件上傳下載刪除內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+ruoyi框架文件上傳和下載的實(shí)現(xiàn)
- SpringBoot實(shí)現(xiàn)文件上傳下載實(shí)時(shí)進(jìn)度條功能(附源碼)
- SpringBoot+微信小程序?qū)崿F(xiàn)文件上傳與下載功能詳解
- springboot整合minio實(shí)現(xiàn)文件上傳與下載且支持鏈接永久訪問(wèn)
- springboot+vue實(shí)現(xiàn)文件上傳下載
- 詳解SpringBoot下文件上傳與下載的實(shí)現(xiàn)
- SpringBoot 文件上傳和下載的實(shí)現(xiàn)源碼
- SpringBoot后臺(tái)實(shí)現(xiàn)文件上傳下載
- SpringBoot集成Hadoop實(shí)現(xiàn)文件的上傳和下載功能
相關(guān)文章
Springboot Retry組件@Recover失效問(wèn)題解決方法
在使用springboot的retry模塊時(shí),你是否出現(xiàn)過(guò)@Recover注解失效的問(wèn)題呢?不用擔(dān)心,這篇文章就來(lái)告訴你解決@Recover失效的辦法,需要的小伙伴可以參考一下2021-11-11JAVA JNI函數(shù)的注冊(cè)過(guò)程詳細(xì)介紹
這篇文章主要介紹了JAVA JNI函數(shù)的注冊(cè)過(guò)程詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-11-11springboot?serviceImpl初始化注入對(duì)象實(shí)現(xiàn)方式
這篇文章主要介紹了springboot?serviceImpl初始化注入對(duì)象實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05Java Swing中的JButton、JComboBox、JList和JColorChooser組件使用案例
這篇文章主要介紹了Java Swing中的按鈕(JButton)、組合框(JComboBox)、下拉列表(JList)和顏色選擇器(JColorChooser)組件使用案例,需要的朋友可以參考下2014-10-10幾種常見(jiàn)mybatis分頁(yè)實(shí)現(xiàn)方式
這篇文章主要介紹了幾種常見(jiàn)mybatis分頁(yè)實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11SpringBoot項(xiàng)目中定時(shí)器的實(shí)現(xiàn)示例
在Spring?Boot項(xiàng)目中,你可以使用Spring框架提供的@Scheduled注解來(lái)編寫(xiě)定時(shí)任務(wù),本文就來(lái)介紹一下SpringBoot項(xiàng)目中定時(shí)器的實(shí)現(xiàn),感興趣的可以了解一下2023-11-11