疯狂的狮子Li
2024-06-21 52598e5c5cd11e90792735b672adf0ecbe616ebe
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -14,11 +14,14 @@
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.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;
@@ -26,15 +29,11 @@
import software.amazon.awssdk.transfer.s3.model.*;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
/**
@@ -84,6 +83,9 @@
            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
            //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
            //创建AWS基于 CRT 的 S3 客户端
            this.client = S3AsyncClient.crtBuilder()
                .credentialsProvider(credentialsProvider)
@@ -92,15 +94,15 @@
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(10 * 1025 * 1024L)
                .checksumValidationEnabled(false)
                .forcePathStyle(isStyle)
                .build();
            //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
            this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
            // 检查是否连接到 MinIO,MinIO 使用 HTTPS 限制使用域名访问,需要启用路径样式访问
            // 创建 S3 配置对象
            S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
                // minio 使用https限制使用域名访问 需要此配置 站点填域名
                .pathStyleAccessEnabled(!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)).build();
                .pathStyleAccessEnabled(isStyle).build();
            // 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
            this.presigner = S3Presigner.builder()
@@ -160,13 +162,14 @@
    /**
     * 上传文件到 Amazon S3,并返回上传结果
     *
     * @param filePath  本地文件路径
     * @param key       在 Amazon S3 中的对象键
     * @param md5Digest 本地文件的 MD5 哈希值(可选)
     * @param filePath    本地文件路径
     * @param key         在 Amazon S3 中的对象键
     * @param md5Digest   本地文件的 MD5 哈希值(可选)
     * @param contentType 文件内容类型
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败,抛出自定义异常
     */
    public UploadResult upload(Path filePath, String key, String md5Digest) {
    public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
        try {
            // 构建上传请求对象
            FileUpload fileUpload = transferManager.uploadFile(
@@ -174,6 +177,8 @@
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .addTransferListener(LoggingTransferListener.create())
                    .source(filePath).build());
@@ -199,10 +204,11 @@
     * @param inputStream 要上传的输入流
     * @param key         在 Amazon S3 中的对象键
     * @param length      输入流的长度
     * @param contentType 文件内容类型
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败,抛出自定义异常
     */
    public UploadResult upload(InputStream inputStream, String key, Long length) {
    public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
        // 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
        if (!(inputStream instanceof ByteArrayInputStream)) {
            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
@@ -217,6 +223,8 @@
                    .putObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .build());
@@ -242,16 +250,13 @@
     * @throws OssException 如果下载失败,抛出自定义异常
     */
    public Path fileDownload(String path) {
        // 从路径中移除 URL 前缀
        String url = removeBaseUrl(path);
        // 构建临时文件路径 文件名必须是唯一不存在的,路径必须是存在的
        Path tempFilePath = Paths.get(extractFileName(url));
        // 构建临时文件
        Path tempFilePath = FileUtils.createTempFile().toPath();
        // 使用 S3TransferManager 下载文件
        FileDownload downloadFile = transferManager.downloadFile(
            x -> x.getObjectRequest(
                    y -> y.bucket(properties.getBucketName())
                        .key(url)
                        .key(removeBaseUrl(path))
                        .build())
                .addTransferListener(LoggingTransferListener.create())
                .destination(tempFilePath)
@@ -259,6 +264,37 @@
        // 等待文件下载操作完成
        downloadFile.completionFuture().join();
        return tempFilePath;
    }
    /**
     * 下载文件从 Amazon S3 到 输出流
     *
     * @param key 文件在 Amazon S3 中的对象键
     * @param out 输出流
     * @return 输出流中写入的字节数(长度)
     * @throws OssException 如果下载失败,抛出自定义异常
     */
    public long download(String key, OutputStream out) {
        try {
            // 构建下载请求
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
                // 文件对象
                .getObjectRequest(y -> y.bucket(properties.getBucketName())
                    .key(key)
                    .build())
                .addTransferListener(LoggingTransferListener.create())
                // 使用订阅转换器
                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
                .build();
            // 使用 S3TransferManager 下载文件
            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
            }
        } catch (Exception e) {
            throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
        }
    }
    /**
@@ -305,7 +341,7 @@
     * @throws OssException 如果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix) {
        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix));
    }
    /**
@@ -318,7 +354,7 @@
     * @throws OssException 如果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
        return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix));
    }
    /**
@@ -330,7 +366,7 @@
     * @throws OssException 如果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(File file, String suffix) {
        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix));
    }
    /**
@@ -449,16 +485,6 @@
     */
    public String removeBaseUrl(String path) {
        return path.replace(getUrl() + StringUtils.SLASH, "");
    }
    /**
     * 从文件路径中提取文件名
     *
     * @param path 文件路径
     * @return 提取的文件名或默认文件名
     */
    public String extractFileName(String path) {
        return FileUtils.getTmpDir() + StringUtils.SLASH + Paths.get(path).getFileName().toString();
    }
    /**