Springboot实现阿里云对象存储OSS文件上传下载
前言
OSS作为阿里巴巴全集团数据存储的核心基础设施,多年支撑双十一业务高峰,历经高可用与高可靠的严苛考验。OSS的多重冗余架构设计,为数据持久存储提供可靠保障。同时,OSS基于高可用架构设计,消除单节故障,确保数据业务的持续性。
一、OSS是什么?
阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。您可以通过本文档提供的简单的REST接口,在任何时间、任何地点、任何互联网设备上进行上传和下载数据。基于OSS,您可以搭建出各种多媒体分享网站、网盘、个人和企业数据备份等基于大规模数据的服务。废话不多说,请允许我娓娓道来!
二、使用步骤
1.引入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.9.1</version>
</dependency>
2.AliyunOss工具类
package com.test.api.aliyun;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.*;
import com.test.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Slf4j
@Component
public class AliyunOss {
@Value("${aliyun.oss.appKey}")
private String appKey;
@Value("${aliyun.oss.appSecret}")
private String appSecret;
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.endpointInternal}")
private String endpointInternal;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.callbackUrl}")
private String callbackUrl;
private static final ConcurrentHashMap<String, String> uploadCache = new ConcurrentHashMap<>();
private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Integer.MAX_VALUE);
/**
* 上传升级包
* @param fileName
* @param fileType
* @param is
* @param callbackDataMap 回调地址
* @return
*/
public boolean uploadFile(String fileName, FileType fileType, InputStream is, Map<String, String> callbackDataMap) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, is);
if (fileType.getCallback() != null) {
Callback callback = new Callback();
callback.setCallbackUrl(callbackUrl + fileType.getCallback());
String messageId = callbackDataMap.get("messageId");
String sensitivity = callbackDataMap.get("sensitivity");
String deviceType = callbackDataMap.get("deviceType");
callback.setCallbackBody("{\\\"messageId\\\":${x:messageId},\\\"sensitivity\\\":${x:sensitivity},\\\"deviceType\\\":${x:deviceType}}");
callback.setCalbackBodyType(Callback.CalbackBodyType.JSON);
callback.addCallbackVar("x:messageId", messageId);
callback.addCallbackVar("x:sensitivity", sensitivity);
callback.addCallbackVar("x:deviceType", deviceType);
putObjectRequest.setCallback(callback);
}
ossClient.putObject(putObjectRequest);
ossClient.shutdown();
return true;
} catch (Exception e) {
log.error("上传文件异常:{}", e.getMessage(), e);
}
return false;
}
/**
* 上传文件
* @param fileName
* @param fileType
* @param is
* @return
*/
public boolean uploadFilePublic(String fileName, FileType fileType, InputStream is) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setObjectAcl(CannedAccessControlList.PublicRead);
ossClient.putObject(bucketName, objectName, is, metadata);
ossClient.shutdown();
return true;
} catch (Exception e) {
log.error("上传文件异常:{}", e.getMessage(), e);
}
return false;
}
/**
* 判断是否存在
* @param fileName
* @param fileType
* @return
*/
public boolean exists(String fileName, FileType fileType) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
ossClient.shutdown();
return exists;
} catch (Exception e) {
log.error("上传文件异常:{}", e.getMessage(), e);
}
return false;
}
/**
* 获取上传地址
* @param fileName
* @param fileType
* @param callbackDataMap
* @return
*/
public String getUploadUrl(String fileName, FileType fileType, Map<String, Object> callbackDataMap) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpoint, appKey, appSecret, clientBuilderConfiguration);
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
Date expiration = new Date(new Date().getTime() + 5 * 60 * 1000);
request.setExpiration(expiration);
request.setContentType("application/octet-stream");
// 生成PUT方式的签名URL。
if (callbackDataMap != null && fileType.getCallback() != null) {
Map<String, String> callback = new HashMap<>();
callback.put("callbackUrl", callbackUrl + fileType.getCallback());
callback.put("callbackBody", JsonUtil.toString(callbackDataMap));
callback.put("callbackBodyType", "application/json");
String callbackData = Base64.getEncoder().encodeToString(JsonUtil.toString(callback).getBytes(StandardCharsets.UTF_8));
request.addQueryParameter("callback", callbackData);
}
URL signedUrl = ossClient.generatePresignedUrl(request);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取上传地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取下载地址
* @param fileName
* @param fileType
* @return
*/
public String getDownloadUrl(String fileName, FileType fileType) {
try {
if (StringUtils.isEmpty(fileName)) {
return null;
}
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpoint, appKey, appSecret, clientBuilderConfiguration);
// 设置URL过期时间为1小时。
Date expiration = new Date(new Date().getTime() + 5 * 60 * 1000);
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
if (!exists) {
return null;
}
// 生成PUT方式的签名URL。
URL signedUrl = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取下载地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取下载地址
* @param fileName
* @param fileType
* @param time
* @return
*/
public String getDownloadUrl(String fileName, FileType fileType, int time) {
try {
if (StringUtils.isEmpty(fileName)) {
return null;
}
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
OSS ossClient = new OSSClientBuilder().build(endpoint, appKey, appSecret, clientBuilderConfiguration);
// 设置URL过期时间为1小时。
Date expiration = new Date(new Date().getTime() + time);
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
if (!exists) {
return null;
}
// 生成PUT方式的签名URL。
URL signedUrl = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取下载地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取下载地址
* @param fileName
* @param fileType
* @return
*/
public String getDownloadUrlForHttp(String fileName, FileType fileType) {
try {
if (StringUtils.isEmpty(fileName)) {
return null;
}
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
OSS ossClient = new OSSClientBuilder().build(endpoint, appKey, appSecret, clientBuilderConfiguration);
// 设置URL过期时间为1小时。
Date expiration = new Date(new Date().getTime() + 5 * 60 * 1000);
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
if (!exists) {
return null;
}
// 生成PUT方式的签名URL。
URL signedUrl = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取下载地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取上传地址
* @param fileName
* @param fileType
* @param callbackDataMap
* @return
*/
public String getUploadUrlForInternal(String fileName, FileType fileType, Map<String, Object> callbackDataMap) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
Date expiration = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
request.setExpiration(expiration);
request.setContentType("application/octet-stream");
// 生成PUT方式的签名URL。
if (callbackDataMap != null && fileType.getCallback() != null) {
Map<String, String> callback = new HashMap<>();
callback.put("callbackUrl", callbackUrl + fileType.getCallback());
callback.put("callbackBody", JsonUtil.toString(callbackDataMap));
callback.put("callbackBodyType", "application/json");
String callbackData = Base64.getEncoder().encodeToString(JsonUtil.toString(callback).getBytes(StandardCharsets.UTF_8));
request.addQueryParameter("callback", callbackData);
}
URL signedUrl = ossClient.generatePresignedUrl(request);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取上传地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取下载地址
* @param fileName
* @param fileType
* @return
*/
public String getDownloadForInternal(String fileName, FileType fileType) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
// 设置URL过期时间为1小时。
Date expiration = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
if (!exists) {
return null;
}
// 生成PUT方式的签名URL。
URL signedUrl = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
ossClient.shutdown();
return signedUrl.toString();
} catch (Exception e) {
log.error("获取下载地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 下载文件
* @param fileName
* @param fileType
* @return
*/
public ByteArrayOutputStream download(String fileName, FileType fileType) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String objectName = fileType.getType().concat("/").concat(fileName);
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V2);
clientBuilderConfiguration.setSupportCname(true);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
OSS ossClient = new OSSClientBuilder().build(endpointInternal, appKey, appSecret, clientBuilderConfiguration);
// 设置URL过期时间为1小时。
boolean exists = ossClient.doesObjectExist(bucketName, objectName);
if (!exists) {
return null;
}
OSSObject ossObject = ossClient.getObject(bucketName, objectName);
IOUtils.copy(ossObject.getObjectContent(), baos);
ossClient.shutdown();
return baos;
} catch (Throwable e) {
log.error("下载文件异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 获取绝对路径访问地址
* @param fileName
* @param fileType
* @return
*/
public String getPublicUrl(String fileName, FileType fileType) {
try {
return endpoint + "/" + fileType.type + "/" + fileName;
} catch (Exception e) {
log.error("获取访问地址异常:{}", e.getMessage(), e);
}
return null;
}
public enum FileType {
TEST("test/test", "测试", "oss/controller/test"),
;
private String type;
private String desc;
private String callback;
FileType(String type, String desc, String callback) {
this.type = type;
this.desc = desc;
this.callback = callback;
}
public String getType() {
return type;
}
public String getDesc() {
return desc;
}
public String getCallback() {
return callback;
}
}
}
3.上传下载测试
@Autowired
private AliyunOss aliyunOss;
/**
* 上传图片并下载返回预览地址
* @param file
* @return
* @throws IOException
*/
@PostMapping("/pic")
@ApiOperation(value = "上传图片", notes = "上传图片")
public Response<?> pic(MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
boolean isOk = aliyunOss.uploadFile(fileName, AliyunOss.FileType.TEST, file.getInputStream());
String downloadUrl = aliyunOss.getDownloadUrl(fileName, AliyunOss.FileType.TEST);
Map<String,Object> map = new HashMap<>();
map.put("downloadUrl",downloadUrl);
map.put("fileName",fileName);
return isOk ? Response.success(map) : Response.error("文件上传失败");
}
/**
* 上传视频并下载返回预览地址
* @param file
* @return
* @throws IOException
*/
@PostMapping("/video")
@ApiOperation(value = "上传视频", notes = "上传视频")
public Response<?> video(MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
boolean isOk = aliyunOss.uploadFileVideo(fileName, AliyunOss.FileType.TEST, file.getInputStream());
String downloadUrl = aliyunOss.getDownloadUrl(fileName, AliyunOss.FileType.TEST);
Map<String,Object> map = new HashMap<>();
map.put("downloadUrl",downloadUrl);
map.put("fileName",fileName);
return isOk ? Response.success(map) : Response.error("文件上传失败");
}
/**
* 获取上传地址
* @param picName
* @return
* @throws IOException
*/
String videoUploadUrl = aliyunOss.getUploadUrl(picName, AliyunOss.FileType.TEST);
/**
* 下载文件返回字节数组
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public byte[] downloadFile(String fileName, FileType fileType) {
try {
InputStream inputStream = downloadFileInputStream(fileName, fileType);
byte[] bytes = IOUtils.toByteArray(inputStream);
return bytes;
} catch (Exception e) {
log.error("下载文件异常:{}", e.getMessage(), e);
}
return null;
}