參考資料:Java操作阿裡雲OSS操作官方文檔
學會看文檔,并實際運用也是一種習慣和技能
下面就來簡單入門一下,用當下比較熱門的Springboot 去操作阿裡雲OSS檔案存儲。
1.需求
(沒踩過下面的坑的小夥伴可以直接跳過這一章節)
問題簡述
首先,我在之前自己做一些開源小項目案例中遇到一些檔案上傳下載下傳的問題,比如在本機檔案上傳和下載下傳都可以正常使用,通過将檔案上傳到Springboot項目的根目錄下,按日期分檔案夾,檔案通路也很友善,可以直接傳回檔案相對路徑位址,并直接可以通路。
問題
然而,這種方式存在弊端,因為當項目打包(jar包)部署阿裡雲學生機後,出現類似io.NotFoundException...(No Such Directory)的問題,,而如果打war包部署到tomcat則沒問題,可以正常使用,經過排查很久,找出問題所在:
因為jar打包封裝後是不能改變其内部目錄結構的,也就是說,按日期分類的檔案上傳檔案夾,如果當需要建立新日期的檔案夾的時候,是無法在jar包中新增檔案夾的,這時候就會出現IO異常問題。而對于放在tomcat中的war包,當tomcat運作的時候會自動解壓war包,其在伺服器上是存在真實路徑的。
解決方案
方案一:我在網上找了一種方法,是通過打完jar包部署後,給springboot項目static下的檔案上傳檔案夾單獨分離出來(相當于是以相對路徑換絕對路徑),通路的時候直接相當通過伺服器上和jar包同級目錄下建立一個檔案上傳檔案夾。
方案二:直接将檔案上傳到伺服器指定路徑下的檔案上傳位置,這種方式也相當于直接使用絕對路徑。
方案三:在伺服器上使用FastDFS和Nginx搭建分布式檔案存儲,這種方式比較複雜,而且學生及本來記憶體和帶寬就小,在自己電腦的虛拟機可以試試這種方案,還是挺好用的,學生伺服器就算了。
方案四:就是直接将檔案上傳到阿裡雲OSS檔案存儲系統上
2. 阿裡雲OSS購買和配置
這個比較簡單,給大家推薦一篇博文自己了解下阿裡雲oss購買和配置,也可以參考阿裡雲OSS官方文檔。
3. Springboot操作OSS
建立一個spring boot項目,pom檔案需要引入依賴:
pom.xml
<dependencies>
<!--
個人版本踩坑:
不加這個依賴的話,當在配置類中
使用@ConfigurationProperties(prefix = "aliyun")注解時,
我這個版本的spring boot會提示有問題
-->
<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 可不加,個人習慣性引入 -->
<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>
<!-- 熱部署,看個人習慣 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 小辣椒插件,推薦使用,可以節省javaBean的setter/getter,還可以使用鍊式調用 -->
<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相關資訊,之是以不在application.yml 中配置,看個人習慣了,因為自定義的配置屬性還是提出來配比較好,沒必要所有的都配到application.yml(properties)中去。
application-aliyun-oss.properties
# 檔案上傳大小限制
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=1000MB
# 地域節點
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
# 目标檔案夾
aliyun.fileHost=files
config包下的相關配置類
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)// 開啟鍊式調用
public class AliyunOssConfig {
private String endPoint;// 地域節點
private String accessKeyId;
private String accessKeySecret;
private String bucketName;// OSS的Bucket名稱
private String urlPrefix;// Bucket 域名
private String fileHost;// 目标檔案夾
// 将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();
}
}
定義一個關于執行狀态結果的枚舉類
/**
* @Auther: csp1999
* @Date: 2020/10/31/17:03
* @Description: 狀态碼枚舉類
*/
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,進行上傳、下載下傳、删除、檢視所有檔案等操作,同時可以将圖檔的url進行入庫操作:
FileUploadService.java
/**
* @Auther: csp1999
* @Date: 2020/10/31/14:30
* @Description: 檔案上傳Service (為節省文章中的代碼篇幅,不再做接口實作類處理)
*/
@Service("fileUploadService")
public class FileUploadService {
// 允許上傳檔案(圖檔)的格式
private static final String[] IMAGE_TYPE = new String[]{".bmp", ".jpg",
".jpeg", ".gif", ".png"};
@Autowired
private OSS ossClient;// 注入阿裡雲oss檔案伺服器用戶端
@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的地域節點
String endpoint = aliyunOssConfig.getEndPoint();
// 擷取oss的AccessKeySecret
String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
// 擷取oss的AccessKeyId
String accessKeyId = aliyunOssConfig.getAccessKeyId();
// 擷取oss目标檔案夾
String filehost = aliyunOssConfig.getFileHost();
// 傳回圖檔上傳後傳回的url
String returnImgeUrl = "";
// 校驗圖檔格式
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;
// 建構日期路徑, 例如:OSS目标檔案夾/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();
}
/**
* 下面兩行代碼是重點坑:
* 現在阿裡雲OSS 預設圖檔上傳ContentType是image/jpeg
* 也就是說,擷取圖檔連結後,圖檔是下載下傳連結,而并非線上浏覽連結,
* 是以,這裡在上傳的時候要解決ContentType的問題,将其改為image/jpg
*/
ObjectMetadata meta = new ObjectMetadata();
meta.setContentType("image/jpg");
//檔案上傳至阿裡雲OSS
ossClient.putObject(bucketName, uploadImgeUrl, inputStream, meta);
/**
* 注意:在實際項目中,檔案上傳成功後,資料庫中存儲檔案位址
*/
// 擷取檔案上傳後的圖檔傳回位址
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 {
// // 設定響應頭為下載下傳
// response.setContentType("application/x-download");
// // 設定下載下傳的檔案名
// 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目标檔案夾
String filehost = aliyunOssConfig.getFileHost();
// 日期目錄
// 注意,這裡雖然寫成這種固定擷取日期目錄的形式,邏輯上确實存在問題,但是實際上,filePath的日期目錄應該是從資料庫查詢的
String filePath = new DateTime().toString("yyyy/MM/dd");
String fileKey = filehost + "/" + filePath + "/" + fileName;
// ossObject包含檔案所在的存儲空間名稱、檔案名稱、檔案元資訊以及一個輸入流。
OSSObject ossObject = ossClient.getObject(bucketName, fileKey);
try {
// 讀取檔案内容。
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的地域節點
String endpoint = aliyunOssConfig.getEndPoint();
// 擷取oss的AccessKeySecret
String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
// 擷取oss的AccessKeyId
String accessKeyId = aliyunOssConfig.getAccessKeyId();
// 擷取oss目标檔案夾
String filehost = aliyunOssConfig.getFileHost();
// 日期目錄
// 注意,這裡雖然寫成這種固定擷取日期目錄的形式,邏輯上确實存在問題,但是實際上,filePath的日期目錄應該是從資料庫查詢的
String filePath = new DateTime().toString("yyyy/MM/dd");
try {
/**
* 注意:在實際項目中,不需要删除OSS檔案伺服器中的檔案,
* 隻需要删除資料庫存儲的檔案路徑即可!
*/
// 建議在方法中建立OSSClient 而不是使用@Bean注入,不然容易出現Connection pool shut down
OSSClient ossClient = new OSSClient(endpoint,
accessKeyId, accessKeySecret);
// 根據BucketName,filetName删除檔案
// 删除目錄中的檔案,如果是最後一個檔案fileoath目錄會被删除。
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提供測試接口
/**
* @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.運作項目測試API接口
本機通路:http://localhost:8083/swagger-ui.html
上傳成功!
源碼位址:
Gitee代碼倉庫- 如果對大家有幫助,請三連支援一下!
- 有問題歡迎評論區留言,及時幫大家解決!