| | |
| | | import org.dromara.common.oss.constant.OssConstant; |
| | | import org.dromara.common.oss.entity.UploadResult; |
| | | import org.dromara.common.oss.enumd.AccessPolicyType; |
| | | import org.dromara.common.oss.enumd.PolicyType; |
| | | import org.dromara.common.oss.exception.OssException; |
| | | import org.dromara.common.oss.properties.OssProperties; |
| | | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; |
| | | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; |
| | | import software.amazon.awssdk.core.ResponseInputStream; |
| | | import software.amazon.awssdk.core.async.AsyncRequestBody; |
| | | import software.amazon.awssdk.core.async.AsyncResponseTransformer; |
| | | import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; |
| | | import software.amazon.awssdk.regions.Region; |
| | | import software.amazon.awssdk.services.s3.S3AsyncClient; |
| | | import software.amazon.awssdk.services.s3.S3Configuration; |
| | | import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; |
| | | import software.amazon.awssdk.services.s3.model.GetObjectResponse; |
| | | import software.amazon.awssdk.services.s3.model.NoSuchBucketException; |
| | | import software.amazon.awssdk.services.s3.model.S3Exception; |
| | | import software.amazon.awssdk.services.s3.presigner.S3Presigner; |
| | | import software.amazon.awssdk.transfer.s3.S3TransferManager; |
| | | import software.amazon.awssdk.transfer.s3.model.*; |
| | |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.time.Duration; |
| | | import java.util.function.Consumer; |
| | | |
| | | /** |
| | | * S3 存储协议 所有兼容S3协议的云厂商均支持 |
| | |
| | | StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( |
| | | AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); |
| | | |
| | | //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问 |
| | | // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问 |
| | | boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE); |
| | | |
| | | //创建AWS基于 CRT 的 S3 客户端 |
| | | // 创建AWS基于 CRT 的 S3 客户端 |
| | | this.client = S3AsyncClient.crtBuilder() |
| | | .credentialsProvider(credentialsProvider) |
| | | .endpointOverride(URI.create(getEndpoint())) |
| | |
| | | .minimumPartSizeInBytes(10 * 1025 * 1024L) |
| | | .checksumValidationEnabled(false) |
| | | .forcePathStyle(isStyle) |
| | | .httpConfiguration(S3CrtHttpConfiguration.builder() |
| | | .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时 |
| | | .build()) |
| | | .build(); |
| | | |
| | | //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端 |
| | |
| | | .serviceConfiguration(config) |
| | | .build(); |
| | | |
| | | // 创建存储桶 |
| | | createBucket(); |
| | | } catch (Exception e) { |
| | | if (e instanceof OssException) { |
| | | throw e; |
| | | } |
| | | throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 同步创建存储桶 |
| | | * 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作 |
| | | * |
| | | * @throws OssException 当创建存储桶时发生异常时抛出 |
| | | */ |
| | | public void createBucket() { |
| | | String bucketName = properties.getBucketName(); |
| | | try { |
| | | // 尝试获取存储桶的信息 |
| | | client.headBucket( |
| | | x -> x.bucket(bucketName) |
| | | .build()) |
| | | .join(); |
| | | } catch (Exception ex) { |
| | | if (ex.getCause() instanceof NoSuchBucketException) { |
| | | try { |
| | | // 存储桶不存在,尝试创建存储桶 |
| | | client.createBucket( |
| | | x -> x.bucket(bucketName)) |
| | | .join(); |
| | | |
| | | // 设置存储桶的访问策略(Bucket Policy) |
| | | client.putBucketPolicy( |
| | | x -> x.bucket(bucketName) |
| | | .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType()))) |
| | | .join(); |
| | | } catch (S3Exception e) { |
| | | // 存储桶创建或策略设置失败 |
| | | throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); |
| | | } |
| | | } else { |
| | | throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]"); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | .key(key) |
| | | .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) |
| | | .contentType(contentType) |
| | | .acl(getAccessPolicy().getObjectCannedACL()) |
| | | // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, |
| | | // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) |
| | | //.acl(getAccessPolicy().getObjectCannedACL()) |
| | | .build()) |
| | | .addTransferListener(LoggingTransferListener.create()) |
| | | .source(filePath).build()); |
| | |
| | | } |
| | | try { |
| | | // 创建异步请求体(length如果为空会报错) |
| | | BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length); |
| | | BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder() |
| | | .contentLength(length) |
| | | .subscribeTimeout(Duration.ofSeconds(30)) |
| | | .build(); |
| | | |
| | | // 使用 transferManager 进行上传 |
| | | Upload upload = transferManager.upload( |
| | |
| | | y -> y.bucket(properties.getBucketName()) |
| | | .key(key) |
| | | .contentType(contentType) |
| | | .acl(getAccessPolicy().getObjectCannedACL()) |
| | | // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, |
| | | // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) |
| | | //.acl(getAccessPolicy().getObjectCannedACL()) |
| | | .build()) |
| | | .build()); |
| | | |
| | |
| | | * |
| | | * @param key 文件在 Amazon S3 中的对象键 |
| | | * @param out 输出流 |
| | | * @param consumer 自定义处理逻辑 |
| | | * @return 输出流中写入的字节数(长度) |
| | | * @throws OssException 如果下载失败,抛出自定义异常 |
| | | */ |
| | | public long download(String key, OutputStream out) { |
| | | public void download(String key, OutputStream out, Consumer<Long> consumer) { |
| | | try { |
| | | // 构建下载请求 |
| | | DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder() |
| | |
| | | Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest); |
| | | // 输出到流中 |
| | | try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream |
| | | return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread |
| | | if (consumer != null) { |
| | | consumer.accept(responseStream.response().contentLength()); |
| | | } |
| | | responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread |
| | | } |
| | | } catch (Exception e) { |
| | | throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); |
| | |
| | | /** |
| | | * 获取私有URL链接 |
| | | * |
| | | * @param objectKey 对象KEY |
| | | * @param second 授权时间 |
| | | * @param objectKey 对象KEY |
| | | * @param expiredTime 链接授权到期时间 |
| | | */ |
| | | public String getPrivateUrl(String objectKey, Integer second) { |
| | | public String getPrivateUrl(String objectKey, Duration expiredTime) { |
| | | // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL |
| | | URL url = presigner.presignGetObject( |
| | | x -> x.signatureDuration(Duration.ofSeconds(second)) |
| | | x -> x.signatureDuration(expiredTime) |
| | | .getObjectRequest( |
| | | y -> y.bucket(properties.getBucketName()) |
| | | .key(objectKey) |
| | |
| | | * @return UploadResult 包含上传后的文件信息 |
| | | * @throws OssException 如果上传失败,抛出自定义异常 |
| | | */ |
| | | public UploadResult uploadSuffix(byte[] data, String suffix) { |
| | | return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix)); |
| | | public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { |
| | | return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return UploadResult 包含上传后的文件信息 |
| | | * @throws OssException 如果上传失败,抛出自定义异常 |
| | | */ |
| | | public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) { |
| | | return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix)); |
| | | public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) { |
| | | return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public AccessPolicyType getAccessPolicy() { |
| | | return AccessPolicyType.getByType(properties.getAccessPolicy()); |
| | | } |
| | | |
| | | /** |
| | | * 生成 AWS S3 存储桶访问策略 |
| | | * |
| | | * @param bucketName 存储桶 |
| | | * @param policyType 桶策略类型 |
| | | * @return 符合 AWS S3 存储桶访问策略格式的字符串 |
| | | */ |
| | | private static String getPolicy(String bucketName, PolicyType policyType) { |
| | | String policy = switch (policyType) { |
| | | case WRITE -> """ |
| | | { |
| | | "Version": "2012-10-17", |
| | | "Statement": [] |
| | | } |
| | | """; |
| | | case READ_WRITE -> """ |
| | | { |
| | | "Version": "2012-10-17", |
| | | "Statement": [ |
| | | { |
| | | "Effect": "Allow", |
| | | "Principal": "*", |
| | | "Action": [ |
| | | "s3:GetBucketLocation", |
| | | "s3:ListBucket", |
| | | "s3:ListBucketMultipartUploads" |
| | | ], |
| | | "Resource": "arn:aws:s3:::bucketName" |
| | | }, |
| | | { |
| | | "Effect": "Allow", |
| | | "Principal": "*", |
| | | "Action": [ |
| | | "s3:AbortMultipartUpload", |
| | | "s3:DeleteObject", |
| | | "s3:GetObject", |
| | | "s3:ListMultipartUploadParts", |
| | | "s3:PutObject" |
| | | ], |
| | | "Resource": "arn:aws:s3:::bucketName/*" |
| | | } |
| | | ] |
| | | } |
| | | """; |
| | | case READ -> """ |
| | | { |
| | | "Version": "2012-10-17", |
| | | "Statement": [ |
| | | { |
| | | "Effect": "Allow", |
| | | "Principal": "*", |
| | | "Action": ["s3:GetBucketLocation"], |
| | | "Resource": "arn:aws:s3:::bucketName" |
| | | }, |
| | | { |
| | | "Effect": "Deny", |
| | | "Principal": "*", |
| | | "Action": ["s3:ListBucket"], |
| | | "Resource": "arn:aws:s3:::bucketName" |
| | | }, |
| | | { |
| | | "Effect": "Allow", |
| | | "Principal": "*", |
| | | "Action": "s3:GetObject", |
| | | "Resource": "arn:aws:s3:::bucketName/*" |
| | | } |
| | | ] |
| | | } |
| | | """; |
| | | }; |
| | | return policy.replaceAll("bucketName", bucketName); |
| | | } |
| | | |
| | | } |