怎么用springboot操作阿里云OSS實(shí)現(xiàn)文件上傳,下載,刪除(附源碼)
Springboot 去操作阿里云OSS文件存儲(chǔ)。
1.需求
(沒踩過下面的坑的小伙伴可以直接跳過這一章節(jié))
問題簡述
首先,我在之前自己做一些開源小項(xiàng)目案例中遇到一些文件上傳下載的問題,比如在本機(jī)文件上傳和下載都可以正常使用,通過將文件上傳到Springboot項(xiàng)目的根目錄下,按日期分文件夾,文件訪問也很方便,可以直接返回文件相對路徑地址,并直接可以訪問。
問題
然而,這種方式存在弊端,因?yàn)楫?dāng)項(xiàng)目打包(jar包)部署阿里云學(xué)生機(jī)后,出現(xiàn)類似io.NotFoundException...(No Such Directory)的問題,,而如果打war包部署到tomcat則沒問題,可以正常使用,經(jīng)過排查很久,找出問題所在:
因?yàn)閖ar打包封裝后是不能改變其內(nèi)部目錄結(jié)構(gòu)的,也就是說,按日期分類的文件上傳文件夾,如果當(dāng)需要?jiǎng)?chuàng)建新日期的文件夾的時(shí)候,是無法在jar包中新增文件夾的,這時(shí)候就會(huì)出現(xiàn)IO異常問題。而對于放在tomcat中的war包,當(dāng)tomcat運(yùn)行的時(shí)候會(huì)自動(dòng)解壓war包,其在服務(wù)器上是存在真實(shí)路徑的。
解決方案
方案一:我在網(wǎng)上找了一種方法,是通過打完jar包部署后,給springboot項(xiàng)目static下的文件上傳文件夾單獨(dú)分離出來(相當(dāng)于是以相對路徑換絕對路徑),訪問的時(shí)候直接相當(dāng)通過服務(wù)器上和jar包同級目錄下新建一個(gè)文件上傳文件夾。
方案二:直接將文件上傳到服務(wù)器指定路徑下的文件上傳位置,這種方式也相當(dāng)于直接使用絕對路徑。
方案三:在服務(wù)器上使用FastDFS和Nginx搭建分布式文件存儲(chǔ),這種方式比較復(fù)雜,而且學(xué)生及本來內(nèi)存和帶寬就小,在自己電腦的虛擬機(jī)可以試試這種方案,還是挺好用的,學(xué)生服務(wù)器就算了。
方案四:就是直接將文件上傳到阿里云OSS文件存儲(chǔ)系統(tǒng)上
2. 阿里云OSS購買和配置
這個(gè)比較簡單,給大家推薦一篇博文自己了解下阿里云oss購買和配置,也可以參考阿里云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ì)提示有問題 --> <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>
我們使用自己添加的application-aliyun-oss.properties配置文件,去配置OSS相關(guān)信息,之所以不在application.yml 中配置,看個(gè)人習(xí)慣了,因?yàn)樽远x的配置屬性還是提出來配比較好,沒必要所有的都配到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)// 開啟鏈?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// 開啟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-文件上傳下載測試") .version("1.0") .contact(new Contact("CSP", "https://blog.csdn.net/weixin_43591980", "")) .build(); } }
定義一個(gè)關(guān)于執(zhí)行狀態(tài)結(jié)果的枚舉類
service層
在service使用ossClient操作阿里云OSS,進(jìn)行上傳、下載、刪除、查看所有文件等操作,同時(shí)可以將圖片的url進(jìn)行入庫操作:
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 * 也就是說,獲取圖片鏈接后,圖片是下載鏈接,而并非在線瀏覽鏈接, * 因此,這里在上傳的時(shí)候要解決ContentType的問題,將其改為image/jpg */ ObjectMetadata meta = new ObjectMetadata(); meta.setContentType("image/jpg"); //文件上傳至阿里云OSS ossClient.putObject(bucketName, uploadImgeUrl, inputStream, meta); /** * 注意:在實(shí)際項(xiàng)目中,文件上傳成功后,數(shù)據(jù)庫中存儲(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(); // 日期目錄 // 注意,這里雖然寫成這種固定獲取日期目錄的形式,邏輯上確實(shí)存在問題,但是實(shí)際上,filePath的日期目錄應(yīng)該是從數(shù)據(jù)庫查詢的 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) { } 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(); // 日期目錄 // 注意,這里雖然寫成這種固定獲取日期目錄的形式,邏輯上確實(shí)存在問題,但是實(shí)際上,filePath的日期目錄應(yīng)該是從數(shù)據(jù)庫查詢的 String filePath = new DateTime().toString("yyyy/MM/dd"); try { /** * 注意:在實(shí)際項(xiàng)目中,不需要?jiǎng)h除OSS文件服務(wù)器中的文件, * 只需要?jiǎng)h除數(shù)據(jù)庫存儲(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(); e.printStackTrace(); return StatusCode.ERROR.getMsg(); } } }
controller層
controller提供測試接口
/** * @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)目測試API接口
本機(jī)訪問:http://localhost:8083/swagger-ui.html