天天看點

Springboot內建 阿裡雲OSS上傳及下載下傳使用手冊遇到的問題

文章目錄

  • 使用手冊
    • maven依賴及環境配置
    • 定義配置bean及OSS工具類
    • 定義UploadController和DownloadController
      • 下載下傳接口優化為傳回重定向oss路徑
  • 遇到的問題
    • The bucket you visit is not belong to you
    • 從OSS擷取臨時url是http協定的

    為解決服務端IO壓力,将檔案服務轉移至阿裡雲OSS,先丢一個 阿裡雲OSS官網連結。 上傳及下載下傳流程為:将檔案上傳至OSS,并拿到檔案對應的key。根據擷取key,再從OSS擷取檔案臨時URL(安全性考慮,加上了url有效期)。

    突然想起好久沒寫部落格了,正好上來記錄一二~ 關于代碼中的oss配置,想着寫成一個統一配置bean,這樣就不用在每一個使用的地方都進行設值,但由于本人太懶了等後期再倒騰吧~

使用手冊

Step1:加入maven依賴及配置檔案中配置秘鑰等

Step2:定義配置bean與OSS工具類

Step3:定義UploadController和DownloadController

maven依賴及環境配置

  1. 在pom.xml中加入依賴:
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
           
  1. 在bootstraps.yml(或application.yml等)中配置阿裡雲秘鑰等
alicloud: 
  access-key: xxxxx
  secret-key: xxxx
  oss: 
    endpoint: oss-cn-hangzhou.aliyuncs.com
           

定義配置bean及OSS工具類

  1. 聲明配置bean
/**
 * 阿裡雲OSS配置
 */
@Data
public class AliyunOssItem {
	
	private String accessKey;
	
	private String sceretKey;
	
	private String endpoint;
	
	/**
	 *  存儲空間名稱 
	 */
	private String bucketName;
	
	/**
	 * 上傳目錄
	 */
	private String uploadDir;
	
	/**
	 * 
	 * @param accessKey
	 * @param sceretKey
	 * @param endpoint 
	 * @param bucketName 存儲空間名稱
	 * @param uploadDir 上傳目錄
	 */
	public AliyunOssItem(String accessKey, String sceretKey, String endpoint, String bucketName, String uploadDir) {
		
		this.accessKey = accessKey;
		this.sceretKey = sceretKey;
		this.endpoint = endpoint;
		this.bucketName = bucketName;
		this.uploadDir = uploadDir;
	}
	
	/**
	 * 
	 * @param accessKey
	 * @param sceretKey
	 * @param endpoint 
	 * @param bucketName 存儲空間名稱
	 */
	public AliyunOssItem(String accessKey, String sceretKey, String endpoint, String bucketName) {
		
		this.accessKey = accessKey;
		this.sceretKey = sceretKey;
		this.endpoint = endpoint;
		this.bucketName = bucketName;
	}
}
           
  1. OSS工具類
/**
 * 阿裡雲OSS上傳和下載下傳檔案工具
 */
@Slf4j
@UtilityClass
public class AliyunOssUtil {

	/**
	 * 傳回檔案在OSS存儲的key 
	 * @param file 上傳檔案
	 * @param item OSS配置bean
	 * @param ossClient 
	 * @return
	 */
	public static String upload2OSS(MultipartFile file, AliyunOssItem item) {
		
		OSS ossClient = new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey());
		
		String bucketName = item.getBucketName();
		String fileName = file.getOriginalFilename();// 檔案名
		String uploadDir = item.getUploadDir();// 目錄名
		String uploadPath = "";// 儲存檔案路徑名稱
		InputStream uploadInputStrem = null;
		if (StringUtils.isNotEmpty(uploadDir)) {
			uploadDir = uploadDir.substring(0, uploadDir.length()).replaceAll("\\\\", "/") + "/";
		}
		
		try {
			ensureBucket(item.getBucketName(), ossClient);
			// 擷取上傳檔案字尾名
			String fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
			
			String md5 = MD5.getMessageDigest(file.getBytes());
			uploadPath = String.format("%1$s%2$s%3$s", uploadDir, md5, fileSuffix);
			
			// 建立上傳Object的Metadata。ObjectMetaData是使用者對該object的描述
			ObjectMetadata objectMetadata = new ObjectMetadata();
			objectMetadata.setContentLength(file.getSize());
			objectMetadata.setCacheControl("no-cache");
			objectMetadata.setContentEncoding("utf-8");
			objectMetadata.setContentType(getcontentType(file, fileSuffix));// 擷取檔案類型
			objectMetadata.setContentDisposition("attachment;filename=" + fileName + fileSuffix);
			uploadInputStrem = file.getInputStream(); // 檔案輸入流
			// 上傳檔案
			log.debug("正在上傳檔案到OSS...");
			ossClient.putObject(bucketName, uploadPath, uploadInputStrem, objectMetadata);
			log.debug("上傳檔案完成...");
			return uploadPath;
			
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			try {
				if (uploadInputStrem != null) {
					uploadInputStrem.close();// 關閉檔案流
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			// 關閉OSSClient。
			ossClient.shutdown();
		}
		return null;
	}

	/**
	 * 判斷bucket存儲空間是否已建立 若未建立,先建立一個Bucket
	 */
	private void ensureBucket(String bucketName, OSS ossClient) throws OSSException {
		// 判斷bucket是否存在
		boolean exists = ossClient.doesBucketExist(bucketName);
		if (!exists) {
			// log.error("bucket不存在,建立目前bucket:{}",BUCKETNAME);
			ossClient.createBucket(bucketName);
		}
	}
	
	/**
	 * 根據key,從OSS上擷取圖檔臨時url
	 * @param item OSS配置
	 * @param key 
	 * @param expiration 過期時間(機關s)
	 * @return
	 */
	public String downFromOSS(AliyunOssItem item, String key, int expiration) {
		
		OSS ossClient = new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey());
		
		try {
			if (StringUtils.isNotEmpty(key)) {
				// 設定URL過期時間
				Date expirationDate = new Date(System.currentTimeMillis() + expiration);
				// 生成URL
				URL url = ossClient.generatePresignedUrl(item.getBucketName(), key, expirationDate);
				if (null != url) {
					return url.toString();
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			//關閉
			ossClient.shutdown();
		}
		
		return null;
	}

	/**
	 * 判斷OSS服務檔案上傳時檔案的contentType
	 *
	 * @param file              上傳檔案
	 * @param FilenameExtension 檔案字尾
	 * @return String
	 */
	private String getcontentType(MultipartFile file, String FilenameExtension) {

		if (FilenameExtension.equalsIgnoreCase(".bmp")) {
			return "image/bmp";
		}
		if (FilenameExtension.equalsIgnoreCase(".gif")) {
			return "image/gif";
		}
		if (FilenameExtension.equalsIgnoreCase(".jpeg") || FilenameExtension.equalsIgnoreCase(".jpg")
				|| FilenameExtension.equalsIgnoreCase(".png") || FilenameExtension.equalsIgnoreCase(".jpz")) {
			return "image/jpeg";
		}
		if (FilenameExtension.equalsIgnoreCase(".html") || FilenameExtension.equalsIgnoreCase(".htm")
				|| FilenameExtension.equalsIgnoreCase(".hts")) {
			return "text/html";
		}
		if (FilenameExtension.equalsIgnoreCase(".txt")) {
			return "text/plain";
		}
		if (FilenameExtension.equalsIgnoreCase(".vsd")) {
			return "application/vnd.visio";
		}
		if (FilenameExtension.equalsIgnoreCase(".pptx") || FilenameExtension.equalsIgnoreCase(".ppt")) {
			return "application/vnd.ms-powerpoint";
		}
		if (FilenameExtension.equalsIgnoreCase(".docx") || FilenameExtension.equalsIgnoreCase(".doc")) {
			return "application/msword";
		}
		if (FilenameExtension.equalsIgnoreCase(".xml")) {
			return "text/xml";
		}
		if (FilenameExtension.equalsIgnoreCase(".xls")) {
			return "application/vnd.ms-excel";
		}
		if (FilenameExtension.equalsIgnoreCase(".xlsx")) {
			return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
		}
		if (FilenameExtension.equalsIgnoreCase(".zip")) {
			return "application/zip";
		}
		return file.getContentType();
	
}
           

定義UploadController和DownloadController

  1. 定義UploadController
/**
 * 背景檔案上傳接口
 *
 */
@Slf4j
@Controller
public class UploadController {
	
	/** 存儲空間名稱 */
	private static final String BUCKETNAME = "bucket-private";

	private static final String ROOT_DIR = "root";
	
	@Value("${spring.cloud.alicloud.access-key}")
	private String accessKeyId;
	
	@Value("${spring.cloud.alicloud.secret-key}")
	private String sceretKey;
	
	@Value("${spring.cloud.alicloud.oss.endpoint}")
	private String endpoint;
	
	/**
	 * 上傳至OSS
	 * @param file
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping({ "/upload2OSS"})
	public ResponseVo<?> upload2OSS(@RequestParam(value = "file", required = false) MultipartFile file) throws Exception {
		
		if (null == file || file.isEmpty()) {
			throw new UploadFileNotFoundException(UploadResponseVo.Error.FILENOTFOUND);
		}
		
		try {
			SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd");
			String dir = fmt.format(new Date());
			
			AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME, ROOT_DIR + "/" + dir);
			String url = AliyunOssUtil.upload2OSS(file, item);
			if (!StringUtils.isEmpty(url)) {
				log.debug("上傳檔案至OSS後的url為:" + url);
				return ResultUtil.success("上傳成功", url);
			}
			
		} catch (Exception e) {
			log.error(String.format("UploadController.upload%s", e));
			throw e;
		}
		return ResultUtil.error("上傳失敗!");
	}
}
           
  1. 定義DownloadController
/**
 * 背景檔案下載下傳接口
 *
 */
@Slf4j
@Controller
public class DownloadController {
	
	/** 存儲空間名稱 */
	private static final String BUCKETNAME = "ak-web-private";
	
	@Value("${spring.cloud.alicloud.access-key}")
	private String accessKeyId;
	
	@Value("${spring.cloud.alicloud.secret-key}")
	private String sceretKey;
	
	@Value("${spring.cloud.alicloud.oss.endpoint}")
	private String endpoint;
	
	@Value("${spring.cloud.alicloud.oss.expiration}")
	private Integer expiration;
	
	@ResponseBody
	@PostMapping({ "/download"})
	public ResponseVo<?> downloadFromOSS(String key) throws Exception {
		
		try {
			
			AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME);
			String fileTmpUrl = AliyunOssUtil.downFromOSS(item, key, expiration);
			if (!StringUtils.isEmpty(fileTmpUrl)) {
				return ResultUtil.success("下載下傳成功", fileTmpUrl);
			}
			
		} catch (Exception e) {
			log.error(String.format("DownloadController.downloadFromOSS%s", e));
			throw e;
		}
		return ResultUtil.error("下載下傳失敗!");
	}
}
           

下載下傳接口優化為傳回重定向oss路徑

/**
 * 背景檔案下載下傳接口
 *
 */
@Slf4j
@Controller
public class DownloadController {
	
	/** 存儲空間名稱 */
	private static final String BUCKETNAME = "ak-web-private";
	
	@Value("${spring.cloud.alicloud.access-key}")
	private String accessKeyId;
	
	@Value("${spring.cloud.alicloud.secret-key}")
	private String sceretKey;
	
	@Value("${spring.cloud.alicloud.oss.endpoint}")
	private String endpoint;
	
	@Value("${spring.cloud.alicloud.oss.expiration}")
	private Integer expiration;
	
	@PostMapping({ "/download"})
	public String downloadFromOSS(String key) throws Exception {
		
		try {
			
			AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME);
			String fileTmpUrl = AliyunOssUtil.downFromOSS(item, key, expiration);
			if (!StringUtils.isEmpty(fileTmpUrl)) {
				return ResultUtil.success("下載下傳成功", fileTmpUrl);
			}
			return "redirect:" + fileTmpUrl;
		} catch (Exception e) {
			log.error(String.format("DownloadController.downloadFromOSS%s", e));
			throw e;
		}
	}
}
           

遇到的問題

The bucket you visit is not belong to you

在網上找到的解決方案有:

第一種解決辦法:url中主域名後面增加bucket目錄。

第二種解決辦法:在bucket管理内增加安全通路域名

但是我遇到的問題是由建立的OSS上傳路徑中攜帶了"//" 造成的~~

從OSS擷取臨時url是http協定的

檢視ClientConfiguration源碼,發現預設protocol是HTTP。

public class ClientConfiguration {
	... 
	protected Protocol protocol = Protocol.HTTP;
}
           

    但是因為用戶端有微信小程式,規定通路的資源必須為https協定的,是以需要将臨時url變更為https協定

解決方案:知道是配置的鍋,當然得從配置上下功夫喽,是以建立OSSClient時加入配置!

/**
* 擷取OSSClient
* @param item
* @return
*/
private OSS getOssClient(AliyunOssItem item) {
		
	ClientBuilderConfiguration config = new ClientBuilderConfiguration();
	config.setProtocol(Protocol.HTTPS);
	return new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey(), config);
}