package com.monkeylessey.file.config;
|
|
import com.aliyun.oss.OSS;
|
import com.aliyun.oss.model.*;
|
import com.aliyuncs.DefaultAcsClient;
|
import com.aliyuncs.auth.sts.AssumeRoleRequest;
|
import com.aliyuncs.auth.sts.AssumeRoleResponse;
|
import com.aliyuncs.exceptions.ClientException;
|
import com.aliyuncs.http.MethodType;
|
import com.aliyuncs.profile.DefaultProfile;
|
import com.aliyuncs.profile.IClientProfile;
|
import com.monkeylessey.constant.RedisKeyPrefixConstants;
|
import com.monkeylessey.enums.ConstantEnum;
|
import com.monkeylessey.exception.UploadException;
|
import com.monkeylessey.file.domain.entity.FileInfo;
|
import com.monkeylessey.file.properties.OssProperties;
|
import com.monkeylessey.response.Result;
|
import com.monkeylessey.framework.utils.RedisUtil;
|
import lombok.RequiredArgsConstructor;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.stereotype.Component;
|
import org.springframework.util.StringUtils;
|
|
import javax.annotation.Nullable;
|
import java.io.File;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.net.URL;
|
import java.net.URLEncoder;
|
import java.util.Date;
|
import java.util.Objects;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* @author 29443
|
* @date 2022/4/23
|
*/
|
@Component
|
@RequiredArgsConstructor
|
public class OssTemplate {
|
|
private final OssProperties ossProperties;
|
private final RedisUtil redisUtil;
|
private final OSS ossClient;
|
|
private final static Logger log = LoggerFactory.getLogger(OssTemplate.class);
|
|
private final static String TMP_FILE_PREFIX = "/usr/local/tmpfile/";
|
|
/**
|
* STS获取令牌、AccessId、AccessKeySecret
|
*
|
* @return
|
*/
|
public void getSTSToken() {
|
String securityToken = "";
|
// regionId表示RAM的地域ID。以华东1(杭州)地域为例,regionID填写为cn-hangzhou。也可以保留默认值,默认值为空字符串("")。
|
String regionId = "cn-chengdu";
|
// 添加endpoint。适用于Java SDK 3.12.0及以上版本。
|
DefaultProfile.addEndpoint(regionId, "Sts", ossProperties.getStsEndpoint());
|
// 添加endpoint。适用于Java SDK 3.12.0以下版本。
|
// DefaultProfile.addEndpoint("",regionId, "Sts", endpoint);
|
// 构造default profile。
|
IClientProfile profile = DefaultProfile.getProfile(regionId,
|
ossProperties.getAccessKeyId(),
|
ossProperties.getAccessKeySecret());
|
// 构造client。
|
DefaultAcsClient client = new DefaultAcsClient(profile);
|
final AssumeRoleRequest request = new AssumeRoleRequest();
|
// 适用于Java SDK 3.12.0及以上版本。
|
request.setSysMethod(MethodType.POST);
|
// 适用于Java SDK 3.12.0以下版本。
|
//request.setMethod(MethodType.POST);
|
request.setRoleArn(ossProperties.getRoleArn());
|
request.setRoleSessionName(ossProperties.getRoleSessionName());
|
String policy = "{\n" +
|
" \"Version\": \"1\",\n" +
|
" \"Statement\": [\n" +
|
" {\n" +
|
" \"Effect\": \"Allow\",\n" +
|
" \"Action\": \"oss:PutObject\",\n" +
|
" \"Resource\": [\n" +
|
" \"acs:oss:*:*:xpstart-test/*\",\n" +
|
" \"acs:oss:*:*:xpstart-test/exampledir/*\"\n" +
|
" ]\n" +
|
" }\n" +
|
" ]\n" +
|
" }";
|
request.setPolicy(policy); // 如果policy为空,则用户将获得该角色下所有权限。
|
request.setDurationSeconds(ossProperties.getStsExpireTime()); // 设置临时访问凭证的有效时间为3600秒。
|
try {
|
final AssumeRoleResponse response = client.getAcsResponse(request);
|
System.out.println("Expiration: " + response.getCredentials().getExpiration());
|
System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());
|
System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());
|
System.out.println("Security Token: " + response.getCredentials().getSecurityToken());
|
System.out.println("RequestId: " + response.getRequestId());
|
// 获得的临时访问令牌
|
securityToken = response.getCredentials().getSecurityToken();
|
// RAM角色临时的AccessKeyId
|
String accessKeyId = response.getCredentials().getAccessKeyId();
|
// RAM角色临时的AccessKeySecret
|
String accessKeySecret = response.getCredentials().getAccessKeySecret();
|
|
redisUtil.saveForValueWithExpire(RedisKeyPrefixConstants.OSSTOKEN,
|
securityToken,
|
ossProperties.getStsExpireTime() - 100,
|
TimeUnit.SECONDS);
|
redisUtil.saveForValueWithExpire(RedisKeyPrefixConstants.OSSACCESSKEYID,
|
accessKeyId,
|
ossProperties.getStsExpireTime() - 100,
|
TimeUnit.SECONDS);
|
redisUtil.saveForValueWithExpire(RedisKeyPrefixConstants.OSSACCESSKEYSECRET,
|
accessKeySecret,
|
ossProperties.getStsExpireTime() - 100,
|
TimeUnit.SECONDS);
|
} catch (ClientException e) {
|
// todo 抛出获取sts临时访问令牌的异常
|
System.out.println("Failed:");
|
System.out.println("Error code: " + e.getErrCode());
|
System.out.println("Error message: " + e.getErrMsg());
|
System.out.println("RequestId: " + e.getRequestId());
|
}
|
}
|
|
/**
|
* 追加上传,第一次上传:metadata 可选
|
* 后续追加
|
* @see nextAppend()
|
* @param content
|
* @param fileInfo
|
* @param metadata
|
* @return
|
*/
|
public AppendObjectRequest firstAppend(InputStream content, FileInfo fileInfo, @Nullable ObjectMetadata metadata) {
|
if (Objects.isNull(content)) {
|
throw new UploadException("请传入要追加的内容");
|
}
|
if (Objects.isNull(metadata)) {
|
metadata = getDefaultMetadata(fileInfo);
|
}
|
// 构造追加上传请求
|
AppendObjectRequest request = getAppendObjectRequest(content, fileInfo, metadata);
|
// 设置文件的追加位置。
|
request.setPosition(0L);
|
AppendObjectResult appendObjectResult = ossClient.appendObject(request);
|
request.setPosition(appendObjectResult.getNextPosition());
|
log.info("下一次追加位置:【{}】", appendObjectResult.getNextPosition());
|
return request;
|
}
|
|
/**
|
* 后续追加上传方法
|
* @param request
|
* @return 返回的AppendObjectRequest对象已经设置好了:下一次追加位置
|
*/
|
public AppendObjectRequest nextAppend(AppendObjectRequest request) {
|
if (Objects.isNull(request)) {
|
throw new UploadException("请传入追加上传的请求对象");
|
}
|
if (Objects.isNull(request.getInputStream())) {
|
throw new UploadException("请在上传请求中设置追加内容");
|
}
|
AppendObjectResult appendObjectResult = ossClient.appendObject(request);
|
request.setPosition(appendObjectResult.getNextPosition());
|
log.info("下一次追加位置:【{}】", appendObjectResult.getNextPosition());
|
return request;
|
}
|
|
|
|
/**
|
* 构造默认的ObjectMetadata
|
* @param fileInfo
|
* @return ObjectMetadata,包含下载文件名、文件长度
|
*/
|
public ObjectMetadata getDefaultMetadata(FileInfo fileInfo) {
|
ObjectMetadata meta = new ObjectMetadata();
|
// 指定该Object被下载时的名称。
|
meta.setContentDisposition("attachment;filename=" + fileInfo.getOriginalFilename());
|
return meta;
|
// 设置文件内容长度
|
// meta.setContentLength(fileInfo.getSize());
|
// 指定上传的内容类型。
|
//meta.setContentType("text/plain");
|
// 指定该Object的网页缓存行为。
|
//meta.setCacheControl("no-cache");
|
// 指定该Object的内容编码格式。
|
//meta.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
|
// 该请求头用于检查消息内容是否与发送时一致。
|
//meta.setContentMD5("ohhnqLBJFiKkPSBO1eNaUA==");
|
// 指定过期时间。
|
//try {
|
// meta.setExpirationTime(DateUtil.parseRfc822Date("Wed, 08 Jul 2022 16:57:01 GMT"));
|
//} catch (ParseException e) {
|
// e.printStackTrace();
|
//}
|
// 指定服务器端加密方式。此处指定为OSS完全托管密钥进行加密(SSE-OSS)。
|
//meta.setServerSideEncryption(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
|
// 指定Object的访问权限。此处指定为私有访问权限。
|
//meta.setObjectAcl(CannedAccessControlList.Private);
|
// 指定Object的存储类型。
|
//meta.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
|
// 创建AppendObject时可以添加x-oss-meta-*,继续追加时不可以携带此参数。如果配置以x-oss-meta-*为前缀的参数,则该参数视为元数据。
|
//meta.setHeader("x-oss-meta-author", "Alice");
|
}
|
|
/**
|
* 构造追加上传请求
|
* @param content
|
* @param fileInfo
|
* @param metadata
|
* @return
|
*/
|
public AppendObjectRequest getAppendObjectRequest(InputStream content, FileInfo fileInfo, ObjectMetadata metadata) {
|
// 通过构造函数设置多个参数。
|
AppendObjectRequest appendObjectRequest = new AppendObjectRequest(ossProperties.getBucketName(), fileInfo.getFileKey(), content, metadata);
|
|
// 通过AppendObjectRequest设置单个参数。
|
|
// 设置Bucket名称。
|
//appendObjectRequest.setBucketName(bucketName);
|
// 设置Object名称。
|
//appendObjectRequest.setKey(objectName);
|
// 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为InputStream类型。
|
//appendObjectRequest.setInputStream(new ByteArrayInputStream(content1.getBytes()));
|
// 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为File类型。
|
//appendObjectRequest.setFile(new File("D:\\localpath\\examplefile.txt"));
|
// 指定文件的元信息,第一次追加时有效。
|
//appendObjectRequest.setMetadata(meta);
|
return appendObjectRequest;
|
}
|
|
|
/**
|
* 上传文件
|
*
|
* @param stream 文件输入流
|
* @param info 文件信息
|
*/
|
public void putObject(InputStream stream, FileInfo info) throws IOException {
|
// 设置对象元信息
|
ObjectMetadata objectMetadata = new ObjectMetadata();
|
objectMetadata.setContentLength(info.getSize());
|
objectMetadata.setContentType(info.getContentType());
|
// 阿里云的文件访问链接默认是浏览器直接下载,加上这个指定下载的文件名
|
// 如果想让链接是预览而不是下载,那么需要将目标bucket绑定一个域名。通过域名的形式访问即可,详情见<a>https://help.aliyun.com/document_detail/31836.html</a>
|
objectMetadata.setContentDisposition("attachment;filename=" + URLEncoder.encode(info.getOriginalFilename(), "UTF-8"));
|
|
ossClient.putObject(ossProperties.getBucketName(), info.getFileKey(), stream, objectMetadata);
|
}
|
|
/**
|
* 获取有时效的访问链接, 业务代码需要调用该方法刷新访问链接
|
*
|
* @param fileKey oss上的文件名
|
*/
|
public String getHasExpireUrl(String fileKey) {
|
|
// 公司阿里云oss都设置为私有,所以每次需要重新获取文件链接
|
// 使用redis做缓存,上传文件后缓存url。
|
String fileUrl = redisUtil.getValue(ConstantEnum.FILE_URL_REDIS_PREFIX.getValue() + fileKey, String.class);
|
if (StringUtils.hasText(fileUrl)) {
|
return fileUrl;
|
}
|
Date expiration = new Date(System.currentTimeMillis() + ossProperties.getUrlExpireTime() * 1000);
|
URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), fileKey, expiration);
|
fileUrl = url.toString();
|
redisUtil.saveForValueWithExpire(ConstantEnum.FILE_URL_REDIS_PREFIX.getValue() + fileKey, fileUrl, ossProperties.getUrlExpireTime() - 100, TimeUnit.SECONDS);
|
return fileUrl;
|
}
|
|
/**
|
* 获取没有时效的访问链接
|
*
|
* @param fileKey oss上的文件名
|
* <url>https://help.aliyun.com/document_detail/39607.htm?spm=a2c4g.11186623.0.0.16817f7aYiSCPO#concept-39607-zh</url>
|
*/
|
public String getNoExpireUrl(String fileKey) {
|
// 无过期时间的访问url, 按照阿里云的规则拼接即可
|
// 需要将bucket设置为公共读才行
|
String url = new StringBuilder().append("https://")
|
.append(ossProperties.getBucketName())
|
.append(".")
|
.append(ossProperties.getEndpoint())
|
.append("/")
|
.append(fileKey)
|
.toString();
|
return url;
|
}
|
|
/**
|
* 删除某个文件
|
*
|
* @param fileKey oss上的文件名
|
*/
|
public void removeObject(String fileKey) {
|
ossClient.deleteObject(ossProperties.getBucketName(), fileKey);
|
}
|
|
/**
|
* 获取某个文件
|
*
|
* @param fileKey oss上的文件名
|
* @return
|
*/
|
public InputStream getObject(String fileKey) {
|
OSSObject object = ossClient.getObject(ossProperties.getBucketName(), fileKey);
|
return object.getObjectContent();
|
}
|
|
/**
|
* 断点续传
|
* @param file
|
* @return
|
*/
|
public Result checkPointUpload(InputStream file, FileInfo fileInfo) throws Throwable {
|
String tmpFile = TMP_FILE_PREFIX + fileInfo.getFileKey();
|
// 将用户上传的文件缓存下来
|
this.saveTmpFile(file, tmpFile);
|
|
// 构造上传请求
|
UploadFileRequest uploadFileRequest = new UploadFileRequest(ossProperties.getBucketName(), fileInfo.getFileKey());
|
// 设置断点续传
|
uploadFileRequest.setEnableCheckpoint(Boolean.TRUE);
|
|
// 指定上传并发线程数,默认为一
|
uploadFileRequest.setTaskNum(5);
|
|
// 设置每个分片的大小:单位为字节,取值范围为100 KB~5 GB。默认值为100 KB。
|
uploadFileRequest.setPartSize(1 * 1024 * 1024);
|
|
// 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中,如果某一分片上传失败,再次上传时会根据文件中记录的点继续上传。上传完成后,该文件会被删除。
|
// 如果未设置该值,默认与待上传的本地文件同路径,名称为${uploadFile}.ucp。
|
uploadFileRequest.setCheckpointFile(fileInfo.getFileKey() + ".ucp");
|
|
// 设置要上传的文件
|
uploadFileRequest.setUploadFile(tmpFile);
|
|
// 设置回调
|
// uploadFileRequest.setCallback(new Callback());
|
|
// 上传
|
ossClient.uploadFile(uploadFileRequest);
|
|
return Result.ok();
|
}
|
|
/**
|
* 将用户上传的文件缓存下来
|
* @param in
|
* @param tmpFilePath 临时文件路径
|
* @throws IOException
|
*/
|
private void saveTmpFile(InputStream in, String tmpFilePath) throws IOException {
|
File tmp = new File(tmpFilePath);
|
if (! tmp.exists()) {
|
tmp.createNewFile();
|
}
|
FileOutputStream fileOutputStream = new FileOutputStream(tmp);
|
int len = -1;
|
byte[] b=new byte[1024];
|
while ((len = in.read(b)) != -1) {
|
fileOutputStream.write(b, 0, len);
|
}
|
in.close();
|
fileOutputStream.close();
|
}
|
|
}
|